🍆

スクレイピング用途でPlaywrightを使うときに知っておきたいこと

に公開

2024年4月2日: 全文を書き直しました。

AIを使えば簡単になっているスクレイピング。日常的に1万ページ以上スクレイピングしていくには、ブロック回避やコスト面を考えると、特に個人開発者が本格的にAIを取り入れるにはまだまだ厳しいでしょう。本記事では AI を介さない低コストなスクレイピングだけを取り扱っています。コスト重視でスクレイピングをされる方にぜひ読んで頂きたいです。

本記事は、僕が実践した試行錯誤の末のスクレイピング知識をまとめています。自分で試行錯誤した結果ですので、他の記事では見たことない内容も含んでいます。非常にボリュームが増えており、情報が探しにくいので、Zennの目次機能をご活用ください。今後も本記事に情報の加筆・修正を行っていきます。

更新情報
スクレイピングに関する注意
ログインが必要なページのスクレイピングについて

やめとけ

Playwrightでスクレイピングするのが最適かを判断する

スクレイピングには以下の2つの種類があります。

  • A. ブラウザを使わないスクレイピング(非常に軽量:node-fetch + Cheerio)
  • B. ブラウザを使うスクレイピング(重い:Playwright)

可能であれば(A)の手段でできないかを検討しましょう。

▼ (A)の手法は以下の記事をご覧ください。ブラウザで処理しない分、本当に軽くなるので非力なマシンでも処理ができます。
https://zenn.dev/masa5714/scraps/c209697f0a88f0

(A)が使えるかどうか判断する方法は、

  • JavaScript処理前の生HTMLからデータ取得ができる。
  • Next.jsで作られたサイトならSSRされているものならOK。(propsデータを取得解析する形で読み取れます。Pages Router時代のものですが、おおよそこのような形で取得可能です。
  • 動的生成されたCookie情報等なしでリクエストが通るもの。(唯一回避する手段として、初回だけPlaywrightでCookie情報を生成して、そのCookie情報を使ってリクエストするという方法があります。)

意外と(A)でイケるケースが多いのでぜひ検討してみてください。

  • 判断すら面倒くさい、回避に関する調査が面倒くさい。
  • リクエストのformdataを改ざんしてレスポンスを受け取りたい。
  • ブラウザ処理が必要なSPAや非同期系の処理が含まれているサイト。

であれば(B)の手法、つまり本記事を参考にする価値があります。

0. 【超重要】スクレイピングにはプロキシが必須だぞ

一般的にスクレイピングは 迷惑行為 とされています。同じサイトに何度も何度もリクエストを投げていくワケですから、サイトから見ればお客様ではありません。攻撃とみなされてブロックすることでサイトの可用性を高めようとします。サイト運営者目線で見れば当たり前の対応です。

スクレイピングする側(スクレイパー)目線では、何度実行しても正常にデータ取得できるようにスクレイピングのコードを育てていきます。その中でどうしても避けられないのが「IPアドレス」の問題です。IPアドレス単位でブロックされることがあります。ブロックされるとアクセスしてもサイトが表示されなくなります。継続的なスクレイピングを実施するには困ってしまいます。

そこで出てくるのが 「プロキシ」 です。

0-1. プロキシとは "捨てIPアドレス" である

プロキシの役割をイメージして頂くため、ざっくりと下記に示してみます。プロキシを刺した場合、データの流れは下記の通りになります。

このように、データ送受信の間にプロキシが入るため、相手サイトにはIPアドレスを知られることはありません。つまり 相手サイト目線ではプロキシのIPアドレスしかブロックしようがない という訳ですね。


プロキシを使うと、IPアドレスを借りてリクエストを投げることができます。例えば、あなたのIPアドレスが 222.222.222.222 だとして、プロキシを刺してリクエストすると、相手サイトからは 111.111.111.111 としてアクセスしているように見えるようになります。(222.222.222.222であると知る術がありません。)

あなたのリクエストが不正だと思われた場合、ブロック処理をするのですが、相手サイトとしては 111.111.111.111 がアクセスしてきたとしか検知できません。つまり、あなたのガチIPの 222.222.222.222 がブロックされることはあり得ません。

これからも分かるように、自宅のIPアドレスをそのままスクレイピングで使用すると、普段の利用ができなくなって非常に不便になります。その対策としても ブロックされても問題ない "捨てIPアドレス" として「プロキシ」を活用 するのです。

捨てIPアドレスがブロックされても、また別の捨てIPアドレスを使う...のように変装を繰り返していくのがスクレイピングにおけるプロキシ事情です。

※ブロックというのは、あなたが想像する以上に積極的に行われています。数百リクエストを実施する場合は遭遇する確率が高いです。

0-2. どうやってプロキシを手に入れるのか?

プロキシ(捨てIPアドレス)の入手方法をご紹介します。

「プロキシ 有料」などで検索すれば様々なサービスが出てきます。お好きなものをお選び頂ければ問題ありません。注意点として、日本のサイトをスクレイピングするならば、 日本リージョン(所在)のプロキシ があるかを確認しましょう。

理由は単純で、サイバーセキュリティまたはEU法の観点から海外IPアドレスのアクセスを拒否しているサイトも存在するからです。海外からのアクセスを受け付けているサイトであれば、海外リージョンのプロキシでもOKです。その他にも、IPアドレスから地域を特定して表示切替しているケースもある ので、その辺りは事前に確認しておきましょう。(確認方法 → 少量のプロキシを用意して、それらを用いて実際にアクセスできるかを確認する。)

0-3. 僕が実際に使ってるプロキシサービスはコレだけ!

自分で選ぶのは不安があるかと思いますので、僕が実際に使っているプロキシサービスをご紹介します。現役で使っており、他に利用しているサービスはガチで一切ありません。

▼ これを使っています ▼
https://www.webshare.io/?referral_code=pvyuamiwwexo
※アフィリエイトリンクになっています。

強みは 価格と安定性 です。非常に安い上にかなり安定していて使い勝手もとても気に入っています。金額の一例をご紹介します。

1,000個の捨てIPアドレスを契約しても月額27ドル弱で済んでしまいます。初めてプロキシに触れる方には高く感じるかもしれませんが、この価格は本当に安いです。ぜひ他のプロキシサービスと比較してみてください。(圧倒的すぎて)その安さに驚くと思います。

安くて安定したプロキシを色々と探してきましたが、これほど安くて高品質なプロキシを他に見たことはありません。初めての方こそ、まずは「WebShare」をお試し頂く価値はあるかと思います。速度の劣化もそれほど無く、かなり安定していこの価格。まさに個人開発者に優しいプロキシだと思います。

※ちなみに、プロキシサービスではプロキシが大量に自動で作られて管理されているため、動かないゴミみたいなプロキシが混じっていることがあります。使えないプロキシが無いか、node-fetchなどの軽量な手法で事前にプロキシを通してリクエストを投げて選別しておくと更に快適に利用できます。全てが完璧に動くプロキシではありません。 プロキシリストとはそういうものである と理解しておく必要があります。

0-3. WebShareのプロキシリストを使った実装例

Playwrightは実行までに色々と記述をしなければならず気軽にスクレイピングを開始するにはちょっと面倒くさいです。なので僕は実行の際に自動的にプロキシリストからランダムで選択される仕組みを実装して使っています。(下記に畳んであります。)

なお、プロキシはWebShareのIP認証機能でパスしているのでPlaywright上では認証処理を含めていません。

実装例を見る

▼プロキシをランダムで取得する関数

utils/functions/proxy.ts
import fs from "fs/promises";
import path from "path";

export const getRandomTheProxy = async () => {
  const proxy = await proxyList();
  const proxyArray = proxy.trim().split(/\n/);
  return proxyArray[Math.floor(Math.random() * proxyArray.length)];
};

const proxyList = (): Promise<string> => {
  // ここでプロキシリストを取得しています
  const fileProxyListPath = path.resolve(__dirname, "./../proxy/proxy-list.txt");
  return new Promise(async (resolve) => {
    const result = await fs.readFile(fileProxyListPath, "utf-8");
    resolve(result);
  });
};

▼Playwrightをスクレイピング用に調整した関数

utils/functions/browser.ts
import "dotenv/config";
import { getRandomTheProxy } from "@utils/functions/proxy";
import { chromium, Browser, BrowserContext, Page, Response } from "playwright";

export const chrome = (
  headless = false
): Promise<{
  browser: Browser;
  context: BrowserContext;
  page: Page;
  waitForResponseResource: (resource: string) => Promise<Response>;
}> => {
  return new Promise(async (resolve) => {
    const proxyURL = await getRandomTheProxy();

    // 帯域幅の節約のため、Webフォントと画像を無効化してます
    const browser = await chromium.launch({
      headless: headless,
      args: ["--blink-settings=imagesEnabled=false", "--disable-remote-fonts"],
      proxy: {
        server: `http://${proxyURL}`,
      },
    });
    const context = await browser.newContext();
    const page = await context.newPage();

    // 指定したリソースのレスポンスが返ってくるまで待機する
    // 非同期が含まれるサイトのスクレイピングをする際に超便利!
    function waitForResponseResource(resource: string) {
      return page.waitForResponse(
        (response) => {
          if (response.url() === resource && response.status() === 200) {
            return true;
          }
          return false;
        },
        { timeout: 60 * 1000 }
      );
    }

    resolve({
      browser,
      context,
      page,
      waitForResponseResource,
    });
  });
};

これらの関数を使ってスクレイピングをするには?

index.ts
import { chrome } from "@utils/functions/browser";

(async () => {
  // await chrome(true) とするとヘッドレスで実行できます
  const { page, browser } = await chrome();

  // ブラウザを閉じる
  await browser.close();
})();

pagebrowser も通常のPlaywrightと同じものです。ただ単に使いやすいようにラップしただけです。かなり少ない記述でプロキシを適用したり、画像やWebフォントを無効にした状態でのスクレイピングを開始できるので、この書き方オススメです!

よろしければご活用ください。

0-4. プロキシの帯域幅の節約を意識しよう!

有料プロキシを使うときには「帯域幅」を強く意識しておくことをオススメします。
帯域幅を節約するだけで金銭的なコストを抑えられます。

帯域幅とはデータ通信量のようなもので送受信できるデータ量には制限が設けられています。足りないならば課金されば解決するのですが、帯域幅を月250GBから月1,000GBにアップするだけで価格が2倍以上に跳ね上がることもあります。(僕が使ってるプランでは実際にそうでした。)

可能な限り月250GBで済むように帯域幅を節約した方がお得です。対策もシンプルで結果的にスクレイピングのパフォーマンスも上がるので以下を取り入れてみてください。

  • Webフォントを無効にする
  • 画像を無効にする
  • CSSを無効にする
  • 不要なJSファイルだけリクエストを破棄する

たったこれをするだけで90%ほど削減できるので絶対やりましょう!(スクレイピングできるページ量が単純計算で10倍ぐらいになります。)

上記画像は実際に削減した例です。(下が削減前、上が削減後)
元々のデータ通信量が3.6MBでしたが 279KB まで大幅に削減できています。本当はもっと下げたいところですが、最近のWebサイトはJSがバンドルされていることが多く、削減には限界があります。こればかりは仕方がありません。

※削減できるということは相手サイトへの負荷を下げられるということです。スクレイピングにおけるマナー的な技術でもあると僕は考えています。


◆ 画像の非表示とWebフォントの無効化

const browser = await chromium.launch({
  args: [
    "--blink-settings=imagesEnabled=false",
    "--disable-remote-fonts"
  ],
})

と指定するだけです。他にも様々なオプションがあります。(下記参照)
https://peter.sh/experiments/chromium-command-line-switches/

※Webフォントに関しては無効化されないこともありますので、 .woff ファイルを狙い撃ちしてリクエスト破棄する手法を用いることがあります。(下記リンク参照)

この手法を使えば、狙ったJSファイルやその他メディアファイルのリクエストだけを破棄することができます。めちゃくちゃ便利なので絶対に覚えておきたい技術です。

https://zenn.dev/masa5714/articles/d798c4fa5f8b08


◆ 帯域幅節約を考慮しつつ、画像を収集するには?

画像ファイルは帯域幅を圧迫するため、先で紹介した手法を用いると画像の収集ができなくなってしまいますよね。そこで、帯域幅を節約しつつも画像を収集する方法を考えてみました。

※画像収集する機会が僕にはありませんので、自分ならこうするというアイデアです。

  1. プロキシを刺しながらPlaywrightで画像URLを収集してリスト化する。
  2. そのリストをVPS等に渡し、VPSから画像だけをダウンロードする。

一般的にブロックされるのはクローリング作業のときで、画像データには判定処理が含まれていません。つまり、ブロックされる機会があるクローリング作業だけプロキシを刺しておけば十分だと考えます。VPSも捨てIPアドレスとして考えて、VPSから画像をダウンロードします。(DigitalOcenやLinodeなどのVPSを使えば、好きなタイミングで作成と破棄ができます。作成の度に新しいIPアドレスが発行されます。無駄なお金を払うことなく、なおかつIPアドレスを替えられるので非常に便利なVPSです。)

※ただし、画像取得にも認証処理が挟まれている強いサイトではまた別の手段を考える必要があるかと思います。

0-5. プロキシの切り替えをする場合はCookie等を削除しよう

Seleniumなどに接続してスクレイピングする場合に気をつけたいのが Cookie や LocalStorage などのブラウザ保持データの存在です。

プロキシを使えば IPアドレスによる "同じ人判定" を回避することができます。これだけでは完全ではなく、 ブラウザが保持するデータでも "同じ人判定" ができてしまう点にも留意しておきましょう。プロキシを刺していても判別できてしまいます。

これは僕の経験によるもので、実際にGAFAMのいずれかのサイトで、ブラウザをそのままにプロキシだけを切り替えてスクレイピングしていたところ、プロキシリストの数千個のIPアドレスが一斉にブロックされたことがあります。その後、CookieやLocalStorageを削除する処理を追加したことで大幅に改善しました。

このことから Cookie や LocalStorage の情報を元に "芋づる式に" 同一人物と疑われるIPアドレスを特定するような対策方法が存在するのだろうと推察しました。

プロキシの無駄死にをさせないためにも注意しておきましょう!

1. Playwrightのスクレイピング処理

スクレイピングでよく使うものをピックアップして掲載しています。

※初期設定的な部分は下記のスクラップに書いてたので本記事では省略します。

https://zenn.dev/masa5714/scraps/cac4c2cd6a0ad0

1-0. 処理の基本

import { chromium } from "playwright";

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();
})();

このように書くことでスクレイピングを開始できます。
ただ、毎度これらを書くのは面倒なので、「0-3. WebShareのプロキシリストを使った実装例」 で紹介した書き方がオススメです。

1-1. ページ遷移する

await page.goto("https://www.reirieofficial.com/");

指定したURLにアクセスできます。

1-2. 要素の取得

const element = page.locator("#hoge");

使用感としては JSの document.querySelectorAll() とほぼ同じだとイメージするのが分かりやすいかと思います。対象箇所が複数あるなら全部取得されます。

1-3. 複数要素のループ処理(要素ごとに処理する)

const elements = await page.locator(".article");
const count = await elements.count();
for (let i = 0; i < count; i++) {
  const text = await elements.nth(i).innerText();
  console.log(text);
}

この例では要素ごとにテキストを取得しています。 .nth() で一つずつ処理要素を切り替えています。

1-4. テキストを取得する

const element = page.locator("#hoge");
const text = await element.innerText();

.innerText() でテキストを取得できます。または、 .textContent() でも可能です。
テキスト取得時には .trim() しておくことをオススメします。

1-5. 要素の有無を判定する

const element = page.locator("#hoge");

if (await element.count() > 0) {
  console.log("要素がありました!");
} else {
  console.log("要素がありませんでした...。");
}

.count() で要素を数えて 1つ以上 あるかで判定できます。
await 忘れがちなので注意!

1-6. テキストボックスにテキストを入力する

const inputElement = await page.locator("#input-hoge").fill("こんにちは");

.fill() でテキストを入力できます。

ラジオボタンやセレクトボックスについては下記をご覧ください。

https://playwright.dev/docs/input

1-7. マウス操作

カーソルを移動(実際にはカーソルは表示されない)
await page.mouse.move(100, 100);

x方向に100px、y方向に100pxの位置に移動します。

左クリックをする
await page.mouse.click(100, 100)

x方向に100px、y方向に100pxの位置でクリックします

マウス座標を計測する際には下記のブックマークレットが便利でした!

https://gist.github.com/darrenscerri/9019209

※要素をクリックする場合は別の方法が用意されています。基本的には下記を多用していきます。

await page.locator("#button").click();

1-8. ページスクロール

await page.mouse.wheel(0, 200);

マウスホイール操作(スクロールできる)もできます。
x方向に0px、y方向に200px ページを進めます。

なお、通常のマウスホイールと同じような仕様になっているので、 overflow: scroll などになっている要素があれば、その要素までマウスカーソルを移動させる必要があります。

1-9. キーボード操作

Enterキーを押す
await page.keyboard.press("Enter");

Enterキーを押したときと同じ動きをします。
フォームでEnterで送信する場合は事前に await page.locator("#hoge").fill("こんにちは") などでinput要素などを選択状態にしておきましょう。

1-10. クリック先の別ウィンドウをスクレイピングするには?

const [popup] = await Promise.all([
  page.waitForEvent("popup"),
  await page.locator("#button").click(), // ここでポップアップが開く
]);

// 別ウィンドウで開いたページのロード完了を待機
await popup.waitForLoadState();

// 別ウィンドウの中の要素のテキストを取得
await popup.locator("#hoge").innerText();

// 別ウィンドウで開いたものだけを閉じる
await popup.close();

page ではなくこのように popup を使ってスクレイピングしていく形になります。

1-11. JSによるページ遷移を待機する

通常、 await page.locator("#link").click() でページ遷移したら、ページ表示されるまで自動的に待機してくれます。しかし、JSによるページ遷移は待ってくれません。(例:location.hrefなどによる遷移)

そんなときは .waitForURL() を使います。

https://playwright.dev/docs/api/class-page#page-wait-for-url

もしくは特定のリソースのレスポンスが返ってくるまで待機するのも良いでしょう。

0-3. WebShareのプロキシリストを使った実装例」 で紹介した関数に waitForResponseResource() というものを含めてあります。

await waitForResponseResource("https://example.jp/assets/app.js") と指定すれば、 https://example.jp/assets/app.js のレスポンスが返ってくるまで待機してくれるようになります。DOM上に変化の表れない非同期通信などに活用すると便利だと思います。

1-12. SPAやNext.jsなどの動的なフロントエンドで必要な「要素が出現するまで待機する」に対応するには?

動的に要素が出現する場合、出現を待機しなければなりません。それを解決するには expect() を使います。(『この要素絶対あるから待っとけ!今は無くても待っとけよ。絶対出てくるから。だから監視しとけ。』と伝えるための記述です。)

import { expect } from "@playwright/test";

const inputElement = page.locator("#dynamic-element .input");
await expect(inputElement).toBeVisible({ timeout: 0 });

待機処理は4行目で行われます。( inputElement が画面に表示されるまで待機する )
expect() が絡む to 系の処理はデフォルト 5000ms でタイムアウトとなってしまいます。5秒では足りないケースが大半なので、 timeout: 0 と指定することで、タイムアウトを無効化しています。

実際には 90000ms (1分半)ぐらいにしておき、あり得ないほど待機が発生したらタイムアウトでエラー扱いにした方が便利ではあります。

ちなみに、ブラウザ表示の状態に関わらずDOMに出現すれば良い場合

.toBeVisible() だとブラウザ上に表示された場合のみに検知することになります。これでは不便なシーンもあることでしょう。表示状態に関係なく、ブラウザにDOM追加されたとき(開発者ツール等でHTMLを見れる状態)が検知されれば十分な場合は .toBeAttached() を使用すると非常に便利です。

反対にDOMから消えるのを待機したいなら .toBeAttached({ attached: false }) とすればOKです。

▼詳しくはこちら

https://playwright.dev/docs/test-assertions

1-13. スマホのエミュレーションを有効化する

Chromeの開発者ツールにあるスマホデバイスのエミュレーション機能を有効にすることもできます。

import { devices } from "playwright";

await browser.newContext({
  ...devices["iPhone 14 Pro Max"],
});

使用可能なデバイスは こちらのページ をご覧ください。このページは常に更新されているので、お使いのPlaywrightバージョンによっては組み込まれていないこともあります。

また、スマホエミュレーションを有効にしていると、 .click() が使えなくなります。代わりに await page.touchscreen.tap(x, y) を使うことになります。 .locator()await page.locator('.hoge').tap() という形になります。ご注意ください。

1-14. スクリーンショットを撮る

await page.screenshot({
  path: "filename.png"
})

たったこれだけでスクリーンショットを撮影できます。この場合は filename.png というファイル名の画像が出力されます。

スマホのエミュレーションを有効にしている場合は、スマホデバイスのdpi基準となるので注意してください。例えば iPhone 14 Pro Maxは表示上は430pxですが、スクリーンショットになると1200px超のサイズになってしまいます。

解像度を気にしない場合は

await page.screenshot({
  path: "filename.png",
  scale: "css"
})

scalecss とすれば、表示されているそのままのサイズで撮影できます。タップ座標を指定する場合などに引っかかる可能性があるので注意しておきましょう。

また、要素だけに絞ってスクリーンショットを撮影することができます。要素だけを撮影したい場合に活用できます。

const theElement = await page.locator("#hoge");

await theElement.screenshot({
  path: "the-element.png",
  scale: "css"
});

2. 知っておくとスクレイピングの質が上がる知識

ここでは知っておけばスクレイピングの質が上がることをお伝えしていきます。僕がスクレイピングのときに大事にしていることを掲載しています。

2-0. 本格稼働時には await page.waitForTimeout() を極力使わないようにしよう!

要素の出現や処理完了を待機するとき、await page.waitForTimeout() を使いたくなるかもしれません。しかし、これは可能な限り使わない方がいいです。

なぜなら必ずしも指定した時間内に処理が終わるとは限らないため、正常に処理できずに失敗する原因になります。Playwrightには、 waitFor 系のメソッドが多く用意されています。それぞれの用途に合わせて適切なものを使いましょう。

どうしても解決できない場合は、サイト処理の癖を把握して、

  • ◯◯◯という要素が出ている頃にはこの処理は終わっているな?ということは、◯◯◯という要素の出現を監視すればいいんじゃないか?
  • レスポンスリソースが返ってきている頃には処理が終わっているな?このレスポンスリソースさえ待機していればいいんじゃないか?

など、間接的に解決できる要素が無いか調査してみましょう。
必ず代替の手段が発見できるはずです。

それでも解決できない場合は、 await page.waitForTimeout() を使っちゃってください。その際は継続的なスクレイピングが実現できるようにリトライ処理を追加するなど工夫が必要になります。

2-1. 必ずオリジナルデータを残しながらスクレイピングしよう!(データの加工は後から行おう)

ゴミデータを残したくないという考えで、スクレイピングを実行しながら同時にデータを加工したくなるかもしれません。しかし、これは極力やめましょう。必ずスクレイピングが終わってからデータの加工を行いましょう。

理由はすごく単純で「オリジナルデータが残らないため再度加工ができなくなってしまう」からです。後から別の形式にデータ加工をしたくなってもオリジナルデータが残っていなければ、改めてスクレイピングし直さなければなりません。完全に時間の無駄ですよね。

オリジナルデータが残っていれば何度も何度も無限に加工チャレンジできますし、もしかすると取得したデータの中に想定外のデータ形式が見つかるきっかけになるかもしれません。オリジナルデータを残しておくことは想像以上の価値があります。

※ただし .trim() などのオリジナルデータを傷つけない程度の加工であればやっておきましょう。

※オリジナルデータを残しつつ、同時に加工処理するならば話は別です。この場合はスクレイピング中にデータ処理を行っても問題ありません。

2-2. 【これ便利】要素の取得は事前にChromeの開発者ツールのConsoleタブで確認しておこう!

スクレイピングあるあるで「要素が取得できてなかっただけの単純なミス」というのがあります。このような初歩的なミスを減らすだけで大幅に作業効率を改善できます。

その対策は非常に簡単なもので、

document.querySelectorAll(".hoge");

Google ChromeのConsoleタブでこれを実行し、要素が正しく取得できるかを確認しておくだけです。ちょっとした手間で無駄なミスを減らせるので絶対にやりましょう!

page.locator(".hoge") をコードでいきなり書くとセレクタ記述ミスが起きたり、そもそも要素の取得ができなかったり、想定外に多くの要素が取得されてしまうことも珍しくありません。思い込みでコードを書くのは避けて、 document.querySelectorAll(".hoge"); の実行を積極的に行っておくことをオススメします。

2-3. 予想外の動きをしたらひたすら throw new Error() しておこう!

スクレイピングは予期せぬエラーが当たり前に発生します。
一発で完璧なスクレイピングのコードを書くのはほぼ不可能なので諦めてください。

想定してない動きや要素数を検出したら全て throw new Error("XXXでエラーが発生") とスローしておきましょう。そのときは処理が止まってしまいますが、コードを修正して既知の問題にしておけば次のスクレイピングでは動いてくれることでしょう。

スクレイピングのコードは "コードを育てる" という感覚を持って書いていきましょう。それが結果的に継続的なスクレイピングが可能な強いコードに育っていきます。

2-4. パフォーマンスを犠牲にしてでも書き込みは都度行った方がいい!

パフォーマンスの観点からデータをまとめてファイルやDBに保存したくなるかもしれません。しかし、これはやめておいた方がいいです。スクレイピングにおいては都度書き込む形をオススメします。

先述した通り、スクレイピングは失敗するシーンが当たり前に多いです。要因は様々(プロキシ、相手サーバー側の異常、IPブロック、サイト更新による構造の変化等)で、処理が止まってしまうと、適切な書き方をしていなければ書き込み前のデータを全て失ってしまいます。

ただでさえスクレイピングは運要素があるのに、数百ページを同時にスクレイピングできて初めて書き込むという更に運要素を追加するのは非現実的です。 "とにかく失敗する前提" で都度書き出しするようにしていきましょう!

3. CSVでデータを取り扱う

CSVはデータベースを使わずにスクレイピング処理マシン内に一時的にデータを溜めておくときに便利です。

3-1. CSVとしてデータを書き出す

https://www.npmjs.com/package/csv-writer

csv-writer というパッケージが使いやすくてオススメです。
若干使いにくさも感じていて、そのあたりをカバーする関数を書いておきます。

※下記折り畳んであります。

csv-writerを快適に使うための関数

▼ 新規ファイル作成

_functions.ts
import fs from "fs/promises";

export async function createNewFileCSV(outputFilePath: string, columnNames: string[]) {
  await fs.writeFile(outputFilePath, columnNames.join(",") + "\n", "utf-8");
}

▼ csv-writerのセットアップ

_functions.ts
export function createCsvWriter(outputFilePath: string, columnNames: string[], append = false) {
  const header: {
    id: string;
    title: string;
  }[] = [];

  columnNames.map((theColumnName) => {
    header.push({ id: theColumnName, title: theColumnName });
  });

  return createObjectCsvWriter({
    path: outputFilePath,
    header: header,
    append: append,
  });
}
使い方

CSVファイルを新規作成してデータをCSVで書き出す

import path from "path";
import { createNewFileCSV, createCsvWriter } from "./_functions";

// CSVの項目名を定義する
const csvColumnNames = ["prefecture", "city", "town"];
// 出力先を決定する
const outputFilePath = path.resolve(__dirname, "./sample.csv");

(async () => {
  // CSVファイルを新規作成する
  await createNewFileCSV(outputFilePath, csvColumnNames);

  // 今回書き込むデータ
  const results = [
    {
      prefecture: "愛知県",
      city: "名古屋市中区",
      town: "上前津"
    }
  ]

  // csv-writerを準備する
  const csvWriter = createCsvWriter(outputFilePath, csvColumnNames, true);
  // csv-writerで書き込みを実行する
  await csvWriter.writeRecords(results);
})();

カラムの中にJSON形式でデータを格納したい場合は、 JSON.stringify() で文字列に変換することで実現可能です。

3-2. CSVデータをJSONとして呼び出す

CSVファイルを読み込んでJSONとして使う方法も紹介します。

https://www.npmjs.com/package/csvtojson

csvtojson というパッケージが使いやすいです。

import path from "path";
import csv from "csvtojson";

const inputFilePath = path.resolve(__dirname, "./sample.csv");

(async () => {
  const data: {
    prefecture: string;
    city: string;
    town: string;
  }[] = await csv({
    noheader: false, // カラム名の有無(false -> カラム名あり)
  }).fromFile(inputFilePath);

  // 1行ずつ処理していく
  for (let i = 0; i < data.length; i++) {
    console.log(data[i].town);
  }
})();

JSON.stringify() で格納されたデータは JSON.parse() でJSONに戻すことができます。

4. 知っておくと便利な知識

必須ではないが知っておくと便利な知識をまとめていきます。

4-1. Playwrightでもブラウザを表示せずにスクレイピングできる

Playwrightを使ったスクレイピングでもブラウザを表示せずにスクレイピングできます。表示せずに使えるブラウザを「ヘッドレスブラウザ」といいます。Google Chromeもヘッドレスモードに対応しています。ただ単に画面に表示されないだけであり、いつものブラウザとほとんど一緒です。JSもCSSも全て処理してくれます。本当にただ目に見えないだけです。

await chromium.launch({
  headless: true
});

headlessオプションを true にするだけでヘッドレスモードで実行してくれます。

ただし、スクレイピングのコードを書いていく開発段階では、動きが分からなくて非常にやりにくいので、ヘッドレスモードを無効(すなわちブラウザを表示させる状態)の方を使っていきましょう。

ほぼ完璧に動くコード(リトライ処理も用意できた状態)が書けてから有効化しましょう!

4-2. ヘッドレスモードでは要素の取得ができないなどの特有の問題が発生することがある

通常ブラウザでは問題なくスクレイピングできていたのに、ヘッドレスモードを有効にした途端に正しくスクレイピングができなくなるというケースも存在します。このケースは ユーザーエージェントの偽装 によって解決できる可能性が高いです。(憶測ではありますが、スクレイピング対策としてユーザーエージェントに含まれる HeadlessChrome などの文字列で判別している可能性があります。)

ユーザーエージェントを偽装する例

const context = await browser.newContext({
  userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
});

このように書くだけで反映されます。
メジャーなユーザーエージェントは下記ページを参考にすると良いでしょう。ランキング形式になっており、今多くの環境で使われてるものをチェックできます。(余談: アクセスするとCloudflareのCAPTCHAが走るようです。CloudflareのCAPTCHAのデザインって危険な雰囲気で一般ユーザーから悪い印象持たれそうで使いたくないですね。)

https://techblog.willshouse.com/2012/01/03/most-common-user-agents/

もしくはご自身のChromeのNetworkタブで確認しても良いでしょう。

4-3. Playwrightでのスクレイピング高速化のアイデア

PlaywrightでのスクレイピングはJSやCSSや画像などのデータが含まれるため、ページ表示に時間がかかってしまいます。これがスクレイピング速度を遅らせる原因となっています。

ページ表示速度を爆速にするための手段として、 .route() という機能があります。"阿部寛のホームページ" 級に軽くできます。

これで余計なリクエストを減らすことができるのでスクレイピング先の負担軽減となり得ます。スクレイピングする側のマナーとも言える技術かもしれません。

https://zenn.dev/masa5714/articles/d798c4fa5f8b08

https://zenn.dev/masa5714/articles/c8973bd32b5b30

4-4. デスクトップ通知が来るようにしておくと何かと便利!

VPSを用意するほどでも無い規模(3日以内で終わるようなもの)なら、デスクトップ通知が来るようにしておくとすごく便利です。スクレイピング失敗しているのに止まっていることに気づかなかったというロスを減らすことができます。

例えば例外エラーを検出したら通知が来たり、処理が完了したら通知が来るようにするなど...。

node-notifier を使えば数行で導入できてしまいます。

import notifier from "node-notifier";

notifier.notify({
  title: "エラー - Playwright",
  message: "hogeを検出しました。",
  sound: true, // OS標準の通知音を鳴らしてくれる
  wait: false, // 通知の確認を待たずに終了する
});

すごく簡単に導入できるのでぜひ!
僕がよく使うスタイルとして、仮想デスクトップで裏側でスクレイピングを走らせておいて、普通にパソコンを使っています。スクレイピングで何か問題が起きれば通知で教えてくれるので、スクレイピングしている感覚を忘れて日常生活を送れます!

4-5. スクレイピング処理はClass化しておいた方が便利!

主にJSを書く人はクラス化するシーン少ないのではないでしょうか。
スクレイピングの処理を書くときはクラス化すると簡潔になったり、コードを自然と整理するようになったり、細かな処理にも名前を付けることになるのでオススメです!

単なる関数だけで実装しようとするとかなり複雑になってしまいます。様々なパターンに柔軟に対応するためにもクラス化が想像以上に便利です。クラスを書き慣れていない方は、TypeScriptを使うとすごく楽になります。(最初はTypeScriptを覚えるまでが辛いですが、使えるようになると無駄なことに脳みそを使わずにコードを書けて快適になります!これはマジです。)

4-6. VPSでスクレイピング処理をするならLinodeやDigitalOceanがオススメ

外部のコンピュータでスクレイピングを実行するとき、FaaSまたはVPSを検討することでしょう。しかし、FaaSは実行時間などの様々な制約があるためスクレイピングには向きません。(スクレイピング用途では実践してないので勝手な思い込みであれば申し訳ないです。が、FaaSは await 処理などが含まれるとCPUやメモリ使用時間が増えてかなり莫大なコストがかかります。)

となると、VPSを使うことになるのですが、海外事業者のサービス(Linoe / DigitalOcean / Vultr)の方がスクレイピングに適しています。起動中の時間だけ課金される仕組みになっており、必要なときだけ立ち上げ、不要になったら破棄するということが可能なため、VPSのコストを大幅に削減できます。Webサーバーとは異なり、スクレイピングはスポット的に動かす用途が主だと思いますので、そういう意味でスクレイピングに適したVPSだと言えます。

先で挙げた中で DigitalOcean には日本リージョンのVPSがありません。一番近くてシンガポールになります。物理的な距離があると通信に遅延が発生するため、極力日本リージョンのあるVPSを選択すべきです。そのため、LinodeやVultrを選んでおく方が安牌でしょう。とはいえ、DigitalOceanはダッシュボードが洗練されているので、使いやすさだけ見ればDigitalOceanも捨てがたいところではあります。

1台のVPSを契約するだけでも複数の処理をさせることができます。Dockerでコンテナを立てて、そのコンテナ内で処理をすればソースコードの改修もほぼ不要で並列処理が実現できます。メモリ1GBであってもnode-fetchによるスクレイピングであれば複数動かせますが、Playwrightでは複数稼働は難しいとお考え頂いた方がよろしいかと思います。

この辺りは色々と試して安定して動くようにバランスを調整してみましょう!

4-7. Playwrightだけで解決しなくてもOK!可能ならば node-fetch でスクレイピングできる方法を探した方が軽い、早い、安い!

スクレイピングは何もPlaywrightだけで解決する必要はありません。どちらかと言うと、可能であれば node-fetch + Cheerio を用いたスクレイピングができないかを検討することをオススメします。Playwrightは処理が重い上にページ表示まで時間がかかりすぎてしまいます。

その問題を解決するのが node-fetch + Cheerio を用いたスクレイピングです。安価で非力なマシンでも高速に動いてくれます。ただし、node-fetch + Cheerio でのスクレイピングの際に発生する特有の問題もあります。

それが「リクエストの際にCookieが必要だが、そのCookie情報はJavaScriptで動的に生成される」というケースです。この場合は Playwright でしか解決できないと思い込みがちですが、実は node-fetch + Cheerio でスクレイピングできるアイデアが存在します。

とても単純ですが、意外と忘れがちな解決策です。

(await page.context().cookies()).map((theCookie) => `${theCookie.name}=${theCookie.value};`).join(" ")

上記のように書けばPlaywrightのCookieをnode-fetchで使える形に変換できます。掛け合わせてスクレイピングできないかを検討するようにしてみると最高なスクレイピング環境が手に入ります!

https://zenn.dev/masa5714/scraps/c209697f0a88f0

4-8. サイトマップを探すことでスクレイピングを効率化できる

スクレイピングしたいページを収集するために、記事一覧から情報を取得することになるとお考えの方も多いことでしょう。もちろんその方法でも全く問題ないのですが、他にも手段が残されているのでご紹介します。その手段が「サイトマップから攻める」です。

サイトマップはGoogleなどのクローラーのクローリング効率を高めるために提供される情報です。SEO対策をしっかりしているサイトでは、ほぼ確実に用意してあります。サイトマップを見つけることで、サイト全体のリンクリストを取得することができます。

しかし、サイトマップの設置場所は表に出てこないので、探す必要があります。探す際に役に立つのが「robots.txt」です。robots.txtはおおよそ /robots.txt に設置されています。例えば、 https://example.com/ というURLがトップページの場合には https://example.com/robots.txt に設置されているというわけですね。

robots.txt にはサイトマップのURLが書かれていることがあります。(書かれていないこともある。)こうしてサイトマップの場所を探し当てることで、スクレイピングの効率化ができます。

4-9. CAPTCHA対策

CAPTCHAが実装されてるサイトのスクレイピングは不可能です。
というか、スクレイピングやスパム対策として作られたものなので、これを突破しようとするのは不適切です。(スクレイピングが適切かどうかは置いといて...。)

中にはCAPTCHAを突破するためのサービス(2Captcha)が提供されていますが、これを使うのはスクレイパーとして、なんか許せないです。こんなサービスを使うぐらいなら諦めてください。

ただし、人力と機械のハイブリッドなスクレイピングであれば個人的に良しとしています。(都合良すぎ!!)

ということで、CAPTCHAが出現したときに自分がCAPTCHA突破するのを待機する処理を紹介します。

const waitForCAPTCHA = (page) => {
  return new Promise(async (resolve) => {
    const counter = setInterval(async function () {
      const path = await page.url();
      if (path.match(/google\.com\/sorry\/index/)) {
        console.log("停止中");
      } else {
        console.log("続行します");
        clearInterval(counter);
        resolve("");
      }
    }, 500);
  });
};

// 使う
(async () => {
  // ...中略 const page
  await page.goto(`【CAPTCHAが発生しうるページへのアクセス】`);
  await waitForCAPTCHA(page);

  console.log("CAPTCHAを突破したので再開する");
})();

これは google.com/sorry/index が含まれるURLに到達した場合に処理が停止されるコードです。CAPTCHAを突破すると処理が再開されます。

4-10. スクレイピング対象の中身が更新されたかどうかを判定したいならハッシュ化しておくと便利!

過去にスクレイピングした状態から今回のスクレイピングでページ内容が更新されているか判定したいシーンがあるかと思います。その際は、記事の「ハッシュ化」を検討してみてください。

npm install crypto-js @types/crypto-js
import crypto from "crypto";

// (...省略)スクレイピングしてテキストを取得するまでして、
// const content にページの内容が入っていると仮定。

const hash = crypto.createHash("sha256").update(content).digest("hex");

このようにするとページの内容がハッシュ値に変換できます。ハッシュと聞くとデータサイズが増幅するようなイメージですが、仮に2000文字とか膨大な文字数でもハッシュ値になるので64文字ほどになります。もしも記事に1文字でも変化があればハッシュ値が変わるため、この値を比較すれば更新されているかどうかが判別できる、という仕組みです。

ストレージを圧迫せずに手軽に更新を検出できて便利です!

5. マニアックなスクレイピング知識

より踏み込んだスクレイピングを行いたい方に向けた知識です。色々考えて独自に出したスクレイピングのアイデアで、試行錯誤の末に辿り着いたものを放出しています。たぶん海外の情報サイトにも掲載されていない部分もあると思います。

5-1. HTMLに出力されないようなレスポンスデータをスクレイピングするには?

HTMLには出力されないデータをスクレイピングしたいケースもあることでしょう。
例えば、Spotifyの歌詞機能の行ごとの同期再生時間のデータなど。このようなデータはレスポンスデータに含まれていることが多いです。

Playwrightならレスポンスデータのスクレイピングも可能です。具体的な方法を下記記事にしましたのでご覧ください。

https://zenn.dev/masa5714/articles/f80cc8c291d86c

5-2. Canvasや複雑なHTMLで作られたサイトのスクレイピング方法

Flutterで作られたサイトが増えてきています。中には CanvasKit というものを使って出力しているサイトもあります。これらはかなり複雑なHTML構造になっており、HTMLタグからのスクレイピングが実質的に不可能なサイトです。HTMLタグが取得できなければクリックもテキスト取得もできません。

これは困った。

悩んで試行錯誤した結果、スクレイピングする方法を見つけました!取得したいデータが非同期で受け取る前提ではありますが、.route() からフェッチデータを盗み見するという方法です。また、非同期通信を発生させるためにクリックイベント発火が必要になりますが、これも「テンプレートマッチング」という手法を使って解決できました。今のところ、これが最適解だと考えています。(AIを使わない低コストなスクレイピングの場合。)

他に良い方法があればコメント欄で教えてください!

▼ Networkからデータを監視する
https://zenn.dev/masa5714/articles/639b1cfc246abe

▼テンプレートマッチング
https://zenn.dev/masa5714/articles/4aaf433262427e

https://github.com/microsoft/playwright-mcp

5-3. Android端末をPlaywrightで操作する

Android端末なら中古で3000円ぐらいでそれなりのスペックのものを購入できます。
これがスクレイピングマシンとして動いてくれたら最高じゃないですか?なんとPlaywrightにはAndroid端末のChromeを操作する機能も備わっています。

ADB経由でPlaywrightのコマンドを実行することができます。
母艦としてPCは必要ですが、重すぎるサイトを1台のパソコンでスクレイピングするのが厳しいとき、ページレンダリング部分をAndroid端末に担わせることができるので、PC側の負荷を下げつつ、大量スクレイピングが可能になります

https://zenn.dev/masa5714/articles/59f1c0e37ccc4c

2025年10月21日時点では、Mac mini M1をスクレイピングマシンとして採用した方がいいなという印象です。Mac mini M1の16GBが4万円台で購入できてしまいます。 Android端末を複数台使うとUSB接続が不安定になることが多く、対策のための余計なコードが増えて辛くなってしまいました。使わなくなった場合にも、リセールの観点からも非常にコスパの良い選択肢だと思います。

5-4. 非同期処理のPOSTデータを改ざんする

「このURLにPOSTすると簡単にデータ取得できそうだし、POSTデータを改ざんすれば一度に大量データを取得できそう!でもnode-fetchなどでリクエストすると、CORS回避できなくてリクエストが弾かれちゃう...。」

上記のようなシーンも出てくるかと思います。
PlaywrightならWebサイトを踏み台にすることでドメイン制約の突破を実現し、POSTデータを改ざんしてリクエストすること ができます。

例えば、Chromeのネットワークタブで解析したPOSTデータが以下のようになっていた場合、

{
  limit: 10
}

limit の値を 10000000 とかに改ざんできれば一度に大量データを取得できそうな気がします。これを変更できたらすごく楽ですよね。やってみましょう。

https://zenn.dev/masa5714/articles/f80cc8c291d86c

ほとんど上記の記事の内容ではありますが、

await page.route(/searchWords/, async (route) => {
  const response = await route.fetch({
    postData: {
      limit: 100000
    },
  });

  // ここに結果が入っている
  const text = await response.text();
  console.log(text);

  // 対象リソースのリクエストを実行
  route.continue();
});

上記のようにすれば、 searchWords という文字列が入ったURLを検出したときに、任意の処理を加えることができます。この例では、postDataを上書きする形で、本来の値を破棄して、 limit: 100000 という条件でPOSTするように改ざんされます。

この手法を使うと、相手サーバーが予期せぬ "攻撃" になってしまう可能性もありますので、十分に考慮した上で取り組むようにしましょう。極力、控えるべきではあります。

node-fetchなどの手軽なスクレイピングでは突破できない際に、サクッと突破できる手段として持っておくとデータ収集の幅が広がると思います。

6. ポエム

スクレイピングに関するポエムです。読んでも何も得られません。

6-1. 単発のスクレイピングは簡単だが、低コストで継続的なスクレイピングは難しい

スクレイピングは簡単とよく言われていますが、簡単なのは単発のスクレイピングだからです。わずか500ページぐらいのスクレイピングであれば大したことはありません。めちゃくちゃ簡単です。

これが数千ページから1万ページを超えるとブロック対策をする必要が出てきたり、プロキシの帯域幅に気を使う必要が出てくるなど、考慮すべき点が多くなってきます。無限にお金を出せるなら難しくありませんが、個人レベルで低コストかつ大規模に継続的なスクレイピングをしようとすると一気にハードルが上がります。

規模によって難易度が大きく変わることを理解しておきましょう!

7. データの管理と加工

スクレイピングをすると、データ管理に悩まされることになります。可能であればスクレイピングとデータ加工の工程は切り離せるように設計すべきでしょう。

何かの助けになるようなアイデアを共有していきます。

7-1. スクレイピング直後にCloud Storageへアップロードしてデータをすぐ手放すという手法(データ加工自動化)

https://zenn.dev/masa5714/articles/cf71d5d9f6d6ed

Cloud Storageにはトリガーという機能があります。アップロードイベントをトリガーにして、データ処理を実行するようなパイプラインを組んでおくと便利です。例えば、上記記事のように Cloud Storage にアップロードすると、 Cloud Run Functions にリクエストが飛び、Gemini API を叩いてデータ処理を完了させるなど。

この方法ならば、しこたまスクレイピングしてアップロードするだけで勝手にデータ加工まで済んでしまう夢のようなことが実現できます。

週次集計、月次集計などが必要であれば、Cloud Storageに入れたJSONファイルを BigQuery に取り込んでクエリをスケジューリングしておき、Cloud Storageに自動エクスポートさせて、格納をトリガーとして Cloud Run Functions を走らせて集計処理を完了するということもできます。

「トリガー」を活用したイベントドリブンなパイプラインの設計を考えてみると幸せになれますよ!

※これだけ豪華な仕様でもほぼ0円で済ませられます!やばすぎ!

もちろん、スクレイピングと同時にアップロードすることで時間が喰いすぎてしまうならば、スクレイピングしてローカルにファイルを出力するだけにしておく。ローカルのファイルをアップロードするだけのプログラムを動かしておく。という構成にしておくとアップロード時間によってスクレイピング効率が下がる問題は解決できるでしょう。

7-2. 大量データを処理するならば、連想配列で高速化を心がけよう!

スクレイピングした後はデータを加工することになるかと思いますが、闇雲に for文 だけで処理すると膨大な時間がかかってしまうことがあります。例えば、重複を排除するときなど。

重複を排除する処理では1つのデータ毎にfor文でデータを検索しがちです。そうではなく、連想配列を使うことでインデックスが効くので、連想配列の形式で実装できないかを考えてみてください。実装が少し手間にはなりますが、処理完了までの速度が段違いですので、ぜひともJavaScriptにおいても「インデックス」を意識してみてください!

extra. 付録

分類できないおまけ情報を掲載しています。

extra-1. Googleの検索結果のリンクが取得できるCSSセレクタ

Googleの検索結果ではclass名が変わってしまいます。継続的なスクレイピングをするならば、class名に頼らないCSSセレクタが必要です。

document.querySelectorAll('a[href^=http]:not(a[href*=google],a[class]):has(h1,h2,h3,h4,h5,h6):not(:is([jsslot],ul) a[href^=http]:not(a[href*=google],a[class]):has(h1,h2,h3,h4,h5,h6))')

2025年3月12日現在: HTML構造が変わってしまったようで今は使えません。気が向いたタイミングで更新します。

僕が作ったこのセレクタを使ってみてください。

開発者ツールのConsoleで上記を実行してみると、検索結果のリンクだけが取得できるかと思います。(スポンサー、ニュース、マップ、目次リンクや画像リンクなど余計なものが除外されている。下記画像のように 赤枠の箇所を除外 し、あくまでリンクリストだけが対象となる。)

我ながら力作のセレクタです。

extra-2. 正規表現が苦手な人に使って欲しい Regex101

スクレイピングでは正規表現をかなり使います。
でも正規表現難しくて苦手じゃないですか?僕は超苦手です。

そこで、

https://regex101.com/

がオススメです。
ブラウザ上で手軽にテストできるのでちゃんと動く正規表現が作りやすくなります。このツールのおかげで比較的楽に正規表現に向き合えています。

extra-3. 並列処理でスクレイピングしたい

単体のPlaywrightでは並列でスクレイピングは難しいです。
最も簡単な方法は Selenium Grid に接続するという手段です。

興味がある方は下記をご覧ください。

https://zenn.dev/masa5714/articles/01d8f89fff1b68

node-fetchの例ではありますが、並列処理(厳密には並列実行と言うらしい)するコードを下記に記載しています。リンクを辿っていくような処理であっても動的にプロセスを追加して並列処理することが可能なクラスも掲載しています。このクラスでは同時実行の上限数を制御できるようにしています。(例:100件のプロセスが登録されても5件ずつ処理する。)

https://zenn.dev/link/comments/c15edc97eb17c9


extra-4. Supabaseでスクレイピングデータを溜め込んで処理結果を配信する構成を考えてみました。

https://zenn.dev/masa5714/scraps/d788b115a59744

無料 で運用できる方法で、なおかつ負荷分散するアイデアをスクラップにまとめてみました。よろしければご覧ください。

extra-5. 【Windows向け】Playwrightで起動したChromiumのプロセスを強制終了する

滅多にありませんが、Playwirghtの管理下を離れてChromiumのプロセスが生き残ってしまうことがあります。これをNode.jsからプロセスを強制終了する方法を下記に記載します。なお、これは通常ブラウザのChromeはそのまま生き残らせて、PlaywirghtのChromiumだけを終了させてくれます。

Playwrightで browser.close() ができない状況に陥って機械的にプロセスを終了させたいときにご活用ください。

import { exec } from "child_process";

function killChromium() {
  return new Promise((resolve) => {
    exec(`wmic process where "ExecutablePath LIKE '%chromium%'" get ProcessId`, (error, stdout) => {
      const processes = stdout.match(/\d+/g);
      if (processes) {
        const pids = processes.map(Number);
        for (const pid of pids) {
          exec(`taskkill /F /PID ${pid}`);
        }
      }
      resolve(true);
    });
  });
}

調査中

まだ実践してなくて調査中の内容を掲載しています。
実践後に記事に追加します。

調査中-1. スマホアプリのAPIキーぶっこ抜き

スマホアプリのAPIキーを抜いてリクエストするという荒業を過去にやったことがある。必要になったら手法を確立するために調査したいが、現時点では不要なので調査してない。

ちなみに当時やったのは Charles 経由でリクエストを監視してAPIキーを見つけるというもの。ちゃんとできた記憶がある。

プロキシサービスは「WebShare」がオススメ

冒頭でもお伝えしましたが、最後に改めてリンク貼らせてください。(アフィリエイトリンク)

https://www.webshare.io/?referral_code=pvyuamiwwexo

金額の一例をご紹介します。

非常に安価で安定したスクレイピング用のプロキシをお探しの方は、WebShareを検討してみてください!

Discussion