Puppeteer を使ってファイルをダウンロードする際に、任意のパスと名前で保存したい。
残念ながら、 現時点ではシンプルな方法は提供されていないようだ。
以下の Issue で何年にもわたって議論されているものの、 「コレ!」 という解決方法は無さそう。
Question: How do I get puppeteer to download a file? · Issue #299 · puppeteer/puppeteer
しかし、 この Issue の #issuecomment-668087154 のコメントで、 なかなか泥臭い方法で実現するヒントが書かれていた。
これを参考にして、任意のパスと名前でダウンロードファイルを保存してみよう。
実行方法
あらかじめ、 puppeteer の npm パッケージをローカルにインストールしておく。
npm install puppeteer --save
その状態で、後述の .js ファイルを nodejs で実行すれば OK だ。
node puppeteer-download-with-specify-name.js
コードと解説
何をしているのかというと、 GitHub 上の puppeteer のソースコード ZIP ファイルをダウンロードする際に、 Chrome DevTools Protocol を直叩きして、 任意のパスとファイル名で保存している。
具体的なポイントは、主に 以下の 2点。
Page.setDownloadBehavior
メソッドで、 ファイルのダウンロードの許可とダウンロード先のディレクトリを指定Fetch.enable
メソッドとFetch.requestPaused
イベントで、 ファイルダウンロードのレスポンスにContent-Disposition
HTTP ヘッダーを無理やりねじ込む
Content-Disposition
HTTP ヘッダー のドキュメントに書かれている通り、 attachment
と filename
ディレクティブを指定することで、 ファイルが (ブラウザ内で表示されるのではなく)ダウンロードが必要であることと、 ダウンロード時のファイル名を指定することができる。
但し、 Page.setDownloadBehavior
メソッドは 実験的で且つ非推奨 なので、 将来にわたってサポートが続くかどうかはわからない点は、注意だ。
少なくとも、 Chromium 92.0.4512.0 (r884014) では問題なく動いている。
ちなみに、実行する Chromium はヘッドレスモードでもヘッドフルモードでもどちらでも意図通り動くはず。
この方法は Chrome DevTools Protocol に思いっきり依存しているので、 Selenium など他のブラウザ自動化ツールでは同一の方法が難しく (※)、 Puppeteer ならではの方法と言える。
※: Selenium 4.x のプレリリース版を使えば、 Chrome DevTools Protocol にアクセスできるようだが、 イベントハンドラを書くのが難しそう? ドキュメントがそろってないのでまだなんとも…
スクレイピング中にファイルをダウンロードする場合などでは、保存先のパスと名前を指定できたほうが良い気がするのだが……
今後の puppeteer や Chrome DevTools Protocol の更新でもっと簡単に実現できるようになることを期待しよう。
ピンバック: ブラウザ操作中の通信の内容をローカルファイルに自動で保存する | Aqua Ware つぶやきブログ