Puppeteerを使ってみる(Node.js)

Puppeteerは、Googleが開発したヘッドレスでブラウザ(Chrome または Chromium)を操作できるNode.jsのライブラリです

ノンヘッドレス(UIあり)でも動作可能です

ブラウザは「DevTools Protocol」経由で操作します

  • ページのスクリーンショットやPDFの作成
  • フォーム送信やキーボード入力などの自動化
  • Webスクレイピングなど
目次
  1. インストール
  2. 待ち時間
  3. スクリーンショット・PDFを取得する
  4. 自動入力
  5. 情報の取得

インストール

Puppeteerをインストール(npm i puppeteer)した場合、最新バージョンのChromiumも一緒にダウンロードされるのでファイルサイズ容量が大きくなります
インストール時のChromiumはOSに依存しているので、開発時と違うOSではPuppeteerの再インストールが必要です

npm i puppeteer

ブラウザーを起動するときに渡すことができる一般的なオプション
headless:ノンヘッドレスブラウザにする場合true
slowMo:指定されたミリ秒単位でPuppeteer操作を遅くします(デバッグのためなど)
devTools : パネルを自動的に開くかどうか(ヘッドレスではfalse)

const browser = await puppeteer.launch(
 { 
  headless: true,
  slowMo: 500,
  devTools: true
 }
)
import puppeteer from 'puppeteer';

(async () => {
  const browser = await puppeteer.launch( { 
  headless: false,
  slowMo: 500,
  });
  const page = await browser.newPage();
  await page.goto('https://example.com/');
 //3秒まつ
  await new Promise((r) => setTimeout(r, 3000))
  await browser.close();
})();

「puppeteer-core」はChromiumのダウンロードしません
既にPCにあるChromeまたはChromiumを使用する場合

npm i puppeteer-core
const browser = await puppeteer.launch({
 executablePath : 'インストール済みのChromeやChromiumのパス'
});

待ち時間

//指定ミリ秒待つ
await new Promise((r) => setTimeout(r, milliseconds))
//特定の要素が存在するようになるまで待つ
await page.waitForSelector(セレクタ);
// 特定の要素が表示されるまで待つ
await page.waitForSelector(セレクタ, {visible: true})
//新しいURLを開いた後、リロード後ページが読み込まれるのを待つ
await page.waitForNavigation()

タイムアウトのデフォルトは30秒です
上書きすることができて、0にするとタイムアウトは無しになります

page.setDefaultTimeout(タイムアウトミリ秒単位の最大時間)
page.setDefaultNavigationTimeout(タイムアウトミリ秒単位の最大時間)

スクリーンショット・PDFを取得する

https://example.comの
スクリーンショットを「example.png 」
PDFを「example.pdf」でファイルと同じ階層に保存します

import puppeteer from 'puppeteer';

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com/');
  await page.screenshot({ path: 'example.png' });
  await page.pdf({ path: 'example.pdf', format: 'a4' })
  await browser.close();
})();

デフォルトで800×600pxですが、viewportの設定(setViewport)で変更できます
また、page.screenshot()のオプションに、fullPage:trueを指定すると全体のスクリーンショットを取得できます

await page.setViewport({
  width: 640,
  height: 480
});

自動入力

type(セレクタ,テキスト,options):inputタグにテキストを入力する
select(セレクタ, optionのテキスト):セレクトボックスの選択
click(セレクタ,options) :クリックする*clickCountのデフォルトは1

import puppeteer from 'puppeteer';

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    slowMo: 50,
  });
  const page = await browser.newPage();
  await page.goto("https://devexpress.github.io/testcafe/example/"); 
  await page.type("#developer-name", "花子", { delay: 100 });
  await page.select('#preferred-interface', 'JavaScript API');
  await page.click("#tried-test-cafe", { clickCount: 1 });
  await browser.close();
})();

キーボードクラス

await page.keyboard.press('Enter');
await page.keyboard.type('Hello World!');
await page.keyboard.down('Shift');
await page.keyboard.up('Shift');

クリックでページ遷移した場合など全て終わったてから次に進める場合
Promise.allを使います

await Promise.all([
 page.waitForNavigation(), // ナビゲーションを待つ 
 page.click('#nextBtn') // クリック 
])

情報の取得

https://example.com/のページ

タイトルを取得する:page.title()
URLを取得する:page.url()
テキストを取得する場合など
page.$eval()はdocument.querySelectorを実行して、結果を第二引数の関数の引数に渡します
page.$$eval()はdocument.querySelectorAllを実行して、結果を関数の引数に渡します

import puppeteer from 'puppeteer';

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto("https://example.com/");
  const title = await page.title();
  const url = await page.url();
  const text = await page.$eval("h1", (el) => el.textContent);
  const pList = await page.$$eval("p", (arr) => arr.map((el)=> el.textContent));
    console.log(title); //Example Domain
    console.log(url);  //https://example.com/
    console.log(text);  //Example Domain
    console.log(pList);  //['This domain is...', ''More information...']
  await browser.close();
})();

page.$():document.querySelectorを実行
セレクターに一致する要素がない場合の戻り値null
page.$$():document.querySelectorAllを実行
セレクターに一致する要素がない場合の戻り値[]
*取得したDom要素をブラウザ(Dom API)と同じように操作できないので
page.evaluate()を使います
関数をブラウザに送りその結果をreturnで受け取りたり、下記コードのように第二引数にDOM要素(ElementHandle)を指定して関数の引数にすることができます

import puppeteer from 'puppeteer';
import fs from 'fs/promises';

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto("https://example.com/");
  const bodyHandle = await page.$("body");
  const html = await page.evaluate((body) => body.innerHTML, bodyHandle);
//html.txtファイルに出力
  await fs.writeFile('html.txt', html, 'utf8');
  await browser.close();
})();

page.evaluate()で関数を直接ブラウザに送る場合

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto("https://example.com/");
  const html = await page.evaluate(() => document.querySelector("body").innerHTML)
  await fs.writeFile("html.txt", html, "utf8");
  await browser.close();
})();

imgタグのsrc属性を取得して、その画像URLから画像ファイルをダウンロードします
画像ファイルはバイナリーデータです

import puppeteer from 'puppeteer';
import fs from 'fs/promises';

(async () => {
	const browser = await puppeteer.launch();
	const page = await browser.newPage();
	await page.goto('https://next-dogs.vercel.app/list');
	const arrayImage = await page.$$eval('img', arr => {
		return arr.map(el => el.src);
	});
	// 最初の画像のURLに移動
	const imageUrl = arrayImage[0];
	const resource = await page.goto(imageUrl);
  // 末尾を取得してファイル名に
  const fileName = imageUrl.split('/').pop();
  // バッファ取得(バイナリーデータ)
	const buffer = await resource.buffer();
	await fs.writeFile( fileName, buffer);
	await browser.close();
})();