Puppeteer.JS を触ってみた
以前 Nightmare.JS を触ってみた という記事を書きましたが、他にもこんな感じのライブラリないかなと探していたところ見つけました
Puppeteer.js – a high-level API to control headless Chrome or Chromium
Google さんが作った Chrome か Chromium 用のライブラリです。基本は Nightmare.js と変わらないですね。
ただ DevTool Protocol を使っているのでおそらく何かいいことがあるのでしょう。それはドキュメントを読んで見てください
では前回と同様に ANA のサイトから3ヶ月後の羽田 – 小松間の航空券の金額を見てみましょう
const puppeteer = require('puppeteer');
const main = async () => {
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth(); // 1月 なら 0
const date = today.getDate();
const browser = await puppeteer.launch({
headless: false, // ここを true にすると Window が新たに開きません
});
const page = await browser.newPage();
await page.setViewport({ width: 1024, height: 768 });
await page.goto('https://www.ana.co.jp/');
await page.click('.select-dep'); // 出発地の空港クリック
await page.waitFor(500);
await page.waitFor('#depApo_ticket_modal');
await page.click('#depApo_ticket_modal .modal-list-cat li[data-val="3"]'); // 関東・甲信越
await page.waitFor(500);
// await page.click('#depApo_ticket_modal .reslut-box li[data-val="HND"]'); // 羽田
await page.evaluate(() => document.querySelector('#depApo_ticket_modal .reslut-box li[data-val="HND"]').click());
await page.waitFor(500);
await page.waitFor('#arrApo_ticket_modal');
await page.click('#arrApo_ticket_modal .modal-list-cat li[data-val="4"]'); // 北陸・東海
await page.waitFor(500);
await page.click('#arrApo_ticket_modal .reslut-box li[data-val="KMQ"]'); // 小松
await page.waitFor(500);
await page.waitFor('#calid1');
await page.click('.calendar-nav .next a'); // 翌月
await page.click(`#calid1 td[onclick="cal1.chgSet(${year}, ${month + 3}, ${date})"] a`); // 3ヶ月後の今日
await page.click(`#calid1 td[onclick="cal1.chgSet(${year}, ${month + 3}, ${date + 2})"] a`); // 3ヶ月 + 2日後の今日
await page.click('#module-dom .btn-search'); // 検索
await page.waitFor('#availabilityResultRecommendFare');
await page.screenshot(`./${year}-${month + 4}-${date}__${year}-${month + 4}-${date}__HND__KMQ.png`);
const table = await page.evaluate(() => {
// ここのコードは実際にブラウザで実行されるコードです
const innerTextTrim = element => element.innerText.trim().replace(/\r?\n/g, '');
const table = [[]];
const headerThList = document.querySelectorAll('#availabilityResultRecommendFare thead tr th');
for (let i = 0; i < headerThList.length; i += 1) {
if (headerThList[i].getAttribute('class') === 'availabilityResultTime') {
table[0][i] = '結果';
} else if (headerThList[i].getAttribute('class') === 'availabilityPremiumLabel') {
table[0][i] = 'プレミアムクラス';
} else {
table[0][i] = innerTextTrim(headerThList[i]);
}
}
const trList = document.querySelectorAll('#availabilityResultRecommendFare tbody tr');
for (let j = 0; j < trList.length; j += 1) {
const tr = trList[j];
const flightTime = innerTextTrim(tr.querySelector('th p.availabilityResultFlightTime'));
const flightDetailSpanList = tr.querySelectorAll('th p.availabilityResultFlightDetail span');
const body = [
flightTime + ' ' + innerTextTrim(flightDetailSpanList[0]) + ' ' + innerTextTrim(flightDetailSpanList[1]),
];
const bodyTdList = tr.querySelectorAll('td');
for (let k = 0; k < bodyTdList.length; k += 1) {
const fee = innerTextTrim(bodyTdList[k]);
const yenIndex = fee.indexOf('円');
body.push(yenIndex !== -1 ? fee.substring(0, yenIndex + 1) : fee);
}
table.push(body);
}
return table;
});
table.forEach(row => console.log(row.join('\t')));
await page.close();
await browser.close();
};
main();
一部そのままでは動かない部分があったので変更していますが、それ以外はほぼ同じです。Puppeteer.js 用の関数に置き換えたぐらいがやったことでしょうか。
Puppeteer.js のほうがはやい!
main 関数を呼び出す部分を下記のようにして実行時間を計測してみました
const s = new Date().getTime();
main()
.then(() => {
const e = new Date().getTime();
console.log(`${e - s}ms`);
})
.catch(e => console.error(e));
(ms) | Nightmare.JS | Puppeteer.JS |
1回目 | 15,150 | 12,795 |
2回目 | 15,150 | 12,706 |
3回目 | 12,754 | 13,247 |
4回目 | 13,254 | 17,152 |
5回目 | 14,577 | 12,709 |
平均 | 14,177 | 13,722 |
最小 | 12,754 | 12,706 |
最大 | 19,689 | 17,152 |
5回分の計測するのに Nightmare.JS は5回タイムアウトしています。5回分の計測するのに Puppeteer.JS は2回タイムアウトしています。