Chrome DevTools Protocolに ExperimentalだがPage.setDownloadBehavior というのがあったので、これを呼んでファイルをダウンロードしてみた。
今回は公式のDevToolsのNode API、Puppeteerを使うが、 setDownloadBehaviorを送るAPIはまだなく、直接clientを取ってsendするので他のライブラリでもやることは変わらないと思う。 Puppeteerのインストールの際にChromiumも入る。setDownloadBehaviorは現行Chromeの60では対応していないようだが、62が入ったのでなんとかなりそう。
$ yarn add puppeteer
$ find . -name "*chrome*"
./node_modules/puppeteer/.local-chromium/mac-497674/chrome-mac
./node_modules/puppeteer/.local-chromium/mac-497674/chrome-mac/Chromium.app/Contents/Versions/62.0.3198.0/Chromium Framework.framework/Versions/A/Resources/chrome_100_percent.pak
./node_modules/puppeteer/.local-chromium/mac-497674/chrome-mac/Chromium.app/Contents/Versions/62.0.3198.0/Chromium Framework.framework/Versions/A/Resources/chrome_200_percent.pak
ちなみに、このChromeをLambda上で実行しようとすると失敗する。
Lambda上でPuppeteer/Headless Chromeを動かすStarter Kitを作った
ChromeでChromeをダウンロードしてみる。
const puppeteer = require('puppeteer'),
fs = require('fs');
const headless = true,
downloadPath = './Download';
(async () => {
const browser = await puppeteer.launch({headless: headless});
const page = await browser.newPage();
await page._client.send(
'Page.setDownloadBehavior',
{behavior : 'allow', downloadPath: downloadPath}
);
await page.goto('https://www.google.co.jp/chrome/browser/desktop/index.html', {waitUntil: 'networkidle'});
await page.click('a.download-button'); /* Chromeをダウンロード */
await page.click('button#eula-accept'); /* 利用規約に同意してインストール */
await waitDownloadComplete(downloadPath)
.catch((err) => console.error(err));
console.log('finished');
browser.close();
})();
ファイルがダウンロードできたかどうかは.crdownloadのありなしで判定している。
const waitDownloadComplete = async (path, waitTimeSpanMs = 1000, timeoutMs = 60 * 1000) => {
return new Promise((resolve, reject) => {
const wait = (waitTimeSpanMs, totalWaitTimeMs) => setTimeout(
() => isDownloadComplete(path).then(
(completed) => {
if (completed) {
resolve();
} else {
const nextTotalTime = totalWaitTimeMs + waitTimeSpanMs;
if (nextTotalTime >= timeoutMs) {
reject('timeout');
}
const nextSpan = Math.min(
waitTimeSpanMs,
timeoutMs - nextTotalTime
);
wait(nextSpan, nextTotalTime);
}
}
).catch(
(err) => { reject(err); }
),
waitTimeSpanMs
);
wait(waitTimeSpanMs, 0);
});
}
const isDownloadComplete = async (path) => {
return new Promise((resolve, reject) => {
fs.readdir(path, (err, files) => {
if (err) {
reject(err);
} else {
if (files.length === 0) {
resolve(false);
return;
}
for(let file of files){
// .crdownloadがあればダウンロード中のものがある
if (/.*\.crdownload$/.test(file)) {
resolve(false);
return;
}
}
resolve(true);
}
});
});
}
Headlessだと何もでてこないのでうまくいったか良くわからないが、 指定したパスを見にいったらちゃんと保存されていた。
機能的には近い立ち位置のNightmareJSの方も、 v1のPhantomJS、現行v2のElectronを経て v3ではHeadless Chromeになるかもしれない。 速いし、ウィンドウがないのでxvfb(X virtual framebuffer)も必要ないし良さそうなんだが、 現在のChrome DevTools ProtocolではNightmareの既存APIをサポートできなかったり、 Puppeteerとの住み分けはどうするのって話になっているみたいだ。
現状Nightmare自体にダウンロード機能は含まれていないが、 Electronのwill-downloadイベントを ハンドリングする nightmare-download-managerや nightmare-inline-download といったライブラリがある。