created: 2024-08-13T04:52:55.654Z

puppeteer で、スクレイピングでシンプルに GET で html を取得する処理

「JS や画像など、GET でとれた html 以外はいらない」という実装をした時のメモ。

全体ではだいたいこんな実装になった。

async get(url: string) {
  const browser = await this.getBrowser();
  const page = await browser.newPage();
  await page.setRequestInterception(true);
  page.on("request", (request) => {
    if (
      request.isNavigationRequest() &&
      request.resourceType() === "document" &&
      request.frame() == page.mainFrame()
    ) {
      return request.continue();
    } else {
      return request.abort();
    }
  });
  const result = await page.goto(url, { waitUntil: "domcontentloaded" });
  await page.close();
  await browser.close();
  return result;
}

無用なリクエストを取りやめるための実装

この部分。page.on("request") で abort する。

page.on("request", (request) => {
  if (...) {
    return request.continue();
  } else {
    return request.abort();
  }
});

余談だが、いろんなメディアで試してみると目当てのリソース以外にすごい量のリクエストが飛んでることがわかっておもしろい。

豆知識

base64 でエンコードされた src="data:..." みたいな img タグも page.on("request") をトリガーする。

request.isNavigationRequest()

NavigationRequest とは puppeteer 用語でページ遷移を発生させるリクエストのこと。 リダイレクトや、フォームの submit は isNavigationRequest=true

CSS や JS、画像を読み込むときは isNavigationRequest=false になる

request.resourceType() === "document"

これも img タグや favicon をリクエストをしないための設定。 document, script などのラベルは Chrome Dev Tool と一緒。

isNavigationRequest だけでまかなえてそうだが、isNavigationRequest の挙動が完全にわかってるわけではないのと、雰囲気が気に入ったので条件に入れた。

request.frame() == page.mainFrame()

ページ内の iframe などから発行されているリクエストを弾くための設定。

!!request.frame()?.parentFrame() というのも検討したけど、parentFrame の仕様がよくわからなくて、null とか空の配列とかややこしいとイヤなので見送った。

その他

page.setRequestInterception

ちゃんと見てないがこれを設定しないとエラーとなる。

Error: Request Interception is not enabled

close

後始末。

const result = await page.goto(url, { waitUntil: "domcontentloaded" });
// puppeteer は 1 年に 1 回くらい触るけど、毎年忘れていて毎年 jest が終了しない
await page.close();
await browser.close();

await page.goto とせずにpage.close を呼んでしまうとエラーになる。

Error: Navigating frame was detached

page.goto(url, { waitUntil: "domcontentloaded" })

知らなかったが、goto メソッドの戻り値は HTTPResponse オブジェクト。 シンプルに GET の結果が欲しい場合は DCL まで待って戻り値を使う。

401 だけ page.goto が例外を投げる

httpbin で 400 から 503 まで調べたが、ステータスコードが 401 のときだけ page.goto メソッドが例外を投げる。401 だと Chrome は basic 認証のフォームを出すので、そのへんで特別扱いになっているのかも。

Error: net::ERR_INVALID_AUTH_CREDENTIALS

なので 401 が返されない保証がないときは try-catch をかかないといけない。

参考

ザ・ロングラン 人生を走り出す日まで (Kindle Single)
[ad] ザ・ロングラン 人生を走り出す日まで (Kindle Single)
ミシュカ シュバリー, 村山 美雪 (Kindle版)