Puppeteerで何をした?
某移行案件で、古く使われてるサイトもあり、デザインモック、psdデータなど残ってなかった。ある日、「結構長いページですけど、午前中に画像もらえますか?」と言われた。
ついに来た。無茶振り。
ざっと見て、100枚ほどの画像を2時間でやれと。ふむ。
puppeteerのおかげで一気に書き出しができたので、誰かの助けになれたらーと思っての記事です。
(開発者ありがとう T__T)
Puppeteerとは?
puppeteerの意味
Puppeteerの意味は人形使い。パペットを扱う人が、puppeteerだそうです。かっこいいですね、ブラウザーをパペットとし、コントロールしちゃう発想。
Puppeteerができることは
Puppeteerは、DevToolsプロトコルまたはWebDriver BiDiを介してChromeやFirefoxを制御するための高レベルAPIを提供するJavaScriptライブラリです。(ChatGPT訳)
Puppeteerでは、マウス、タッチイベント、キーボード入力を通じてページ上の要素とやり取りすることができます。通常、まずCSSセレクタを使用してDOM要素をクエリし、次に選択した要素に対してアクションを実行します(ChatGPT訳)
簡単にいうと、CSSセレクタから切り出しして、画像やテキスト情報など取得することができる。実際、いろんなデバッグ作業もこれで実現できるらしいですが、今回はCSSセレクトの切り出しのみやりました!
実装
↑pupppeteerの公式サイトにQuick Startがありますので、基本サンプルコードで、できます。そして、通信は必要なので、今回はnode.jsで作ってみました。
const puppeteer = require('puppeteer');
const path = require('path');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setJavaScriptEnabled(true);
await page.setViewport({
//サイズを決める、画質と画像サイズに影響ある部分です。
width: 1980,
height: 800,
deviceScaleFactor: 2,
});
await page.setRequestInterception(true);
page.on('request', request => {
if (request.url().includes('cookie')) {
request.abort();
} else {
request.continue();
}
});
const targetURL = 'https://サイトのアドレスを入れてください';
await page.goto(targetURL, { waitUntil: 'domcontentloaded' });
//ポイント①
//CSSを読み出しできてから待ってもらう
await page.waitForSelector('CSSのセレクトを指定', { visible: true });
await page.evaluate(() => {
const style = document.createElement('style');
//ポイント②
// 意外にも重要なフォント指定
style.textContent = `
* {
font-family: 'DNP Shuei Mincho Pr6', serif !important;
}
`;
document.head.appendChild(style);
});
//ポイント③
//追従するヘッダー、フッターを書き出しさせないため
await page.evaluate(() => {
const header = document.querySelector('#header');
const footer = document.querySelector('#footer');
if (header) header.style.display = 'none';
if (footer) footer.style.display = 'none';
});
await page.evaluate(async () => {
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
let previousHeight = 0;
while (true) {
const currentHeight = document.body.scrollHeight;
window.scrollTo(0, currentHeight);
await delay(500);
if (currentHeight === previousHeight) break;
previousHeight = currentHeight;
}
});
↑ポイント①-③は苦労しました!!!
① 指定した要素が確実にDOM上で操作可能な状態になるのを保証します。
② 全然違うフォントや、タイトル崩れなど起きたので、フォント指定は意外にも重要でした。
③ 一番大変だったところ、サイトの都合上、追従ヘッダー、フッター(時々ボタン)などを、ここでミュートさせる。
ここまで来たら、あとはハッピーです!
// スクリーンショットを撮る親要素のセレクタ
const parentSelector = '指定する親要素';
const fileName = '画像名を設定.jpg';
const parentElement = await page.$(parentSelector);
if (parentElement) {
await parentElement.screenshot({
path: path.resolve(__dirname, fileName),
quality: 100,
});
console.log(`${fileName} にスクリーンショットを保存しました`);
} else {
console.log(`${parentSelector} が見つかりませんでした`);
}
await browser.close();
})();
↑これで、画像が綺麗に切り取れました。
上記の部分を改造して、同じタグのものを順番に切り出すことも可能だったので、自分が担当したサイトが、こういったCSSの使い回しが多かったので、、、
このようにして、書き出しの部分をちょっと変更して、複数枚を一気に書き出しました!!
指定階層のものを、for文で回して、順番に書き出してもらい、100枚なんて、10分で終わった(やった!!)
const selectors = [
{ parentSelector: '#id1', childSelector: '.css1', baseFileName: '基本ファイル名1' }
];
for (const item of selectors) {
const parentElement = await page.$(item.parentSelector);
if (parentElement) {
const childElements = await parentElement.$$(item.childSelector); // 複数要素を取得
if (childElements.length > 0) {
for (let i = 0; i < childElements.length; i++) {
const childElement = childElements[i];
const fileName = `${item.baseFileName}_${i + 1}.jpg`;
await childElement.screenshot({
path: path.resolve(__dirname, fileName),
quality: 100,
});
console.log(`${fileName} にスクリーンショットを保存しました`);
}
} else {
console.log(`${item.childSelector} が ${item.parentSelector} の中に見つかりませんでした`);
}
} else {
console.log(`${item.parentSelector} が見つかりませんでした`);
}
}
最後に node ファイル名
で書き出しして、終わり!!!
やってみて良かったこと & 気づき
①
すごく早くできたかつ、指定要素のみ書き出したので、ヒューマンエラーのない工程ができた。
②
こうやって早く出来上がったのが、コーダーさんがすごく綺麗に階層を整理してくれたおかげです。たまに、クソCSS書く自分もいますが、人のコード読んで、身が引き締まります。
③
なんか問題ある時に、こうやってライブラリーの力を借りてできたこと、本当に、開発者に感謝しかない。特にバタバタしていた期間に、puppeteerに出会えたことに感謝〜