Puppeteer ってソラで書けますか? ぼくは書けないので pptr.dev にアクセスしてコピペしてます。
Puppeetteer でファイルをダウンロードする方法はわかりやすい API としては提供されておらず、Stackoverflow を毎回見てる。これも古い方法が出回ったままだったりするので令和4年現在での最新版と思われる方法を書いておく。
例として、https://motemen.github.io/beautiful-graph-maker/ から画像をダウンロードしてみる。これは JavaScript で画像を生成してるのでブラウザのインスタンスが必要なやつだ。
Chrome DevTools Protocol 経由でダウンロードする
Chrome DevTools Protocol ってのは Chrome その他のブラウザをプログラムから操作・計測・デバッグ等々するためのプロトコルらしい。Pptr からこれにアクセスする API があるので、こちらを経由する。
const cdpSession = await page.target().createCDPSession();
CDP 経由で Browser.setDownloadBehavior を呼ぶ。世間的には Page.setDownloadBehavior を呼ぶ、というサンプルが多いようだけど、これは deprecated らしいので今はこちら。
await cdpSession.send("Browser.setDownloadBehavior", { behavior: "allow", downloadPath, eventsEnabled: true, });
ダウンロード完了を知りたいので eventsEnabled
を true にしておく。behavior
については後述。
イベントが有効になったので Browser.downloadProgress イベントをリスンする。これでダウンロード完了のタイミングがわかる。
const downloaded = new Promise<void>((resolve, reject) => { cdpSession.on( "Browser.downloadProgress", (params: { state: "inProgress" | "completed" | "canceled" }) => { if (params.state == "completed") { resolve(); } else if (params.state == "canceled") { reject("download cancelled"); } } ); });
あとは Promise の使い方という感じで、適当にタイムアウトを設定しつつダウンロードする。ちなみにダウンロードされた正確なファイル名は分からないので、ダウンロード先のディレクトリは毎回新しいものを作るようにするのがよさそう。
Browser.setDownloadBehavior に behavior: "allowAndName"
を指定するとファイル名が Browser.downloadProgress で取得される guid になるようだけど、この場合拡張子が消えるのに注意。Browser.downloadWillBegin イベントで suggestedFilename
を取得しておくといいだろう。
await button.click(); // これでダウンロード開始 await Promise.race([ downloaded, new Promise<boolean>((_resolve, reject) => { setTimeout(() => { reject("download timed out"); }, DOWNLOAD_TIMEOUT); }), ]);
Playwright だと
ちなみに Puppeteer と同様にブラウザをプログラムから扱う microsoft/playwright というライブラリはつづりが分かりやすいだけでなく、ダウンロード用の API も提供してるらしい。これだけでいけた。
const [download] = await Promise.all([ page.waitForEvent("download"), button.click(), ]); // { // path: '/var/folders/hm/0xt2zy.../279506cd-00e1-43d5-ad4f-f72909ba706c', // suggestedFilename: 'graph.png' // } console.log({ path: await download.path(), suggestedFilename: download.suggestedFilename(), });
path は guid 的なものになるようなので、suggestedFilename から拡張子を取得する。
今回使ったコードの全体は Gist にある: https://gist.github.com/motemen/ce4ca5d4134b31a11725f3461a09c136