Puppeteer は、Chrome DevTools Protocol または WebDriver BiDi 経由で Chrome(および Firefox)を操作する Google 製の Node.js ライブラリです。ページが JavaScript の実行後にしかデータを返さない場合、単純な fetch では空のシェルしか得られません — 実際のブラウザが必要です。本ガイドは 2026 年時点での実践的な進め方をたどります。インストール、JS サンドボックスのスクレイピング、重いアセットのブロック、JSON のインターセプト、検出の限界についての正直な話、そして自分で Chromium を動かすのをやめるべきタイミングです。
スクレイピングが初めての方は、静的 HTML 層向けの Python での Web スクレイピングから始めてください。クロスブラウザな Python のデフォルトについては Playwright での Web スクレイピングをご覧ください。本記事はすでに Puppeteer を使いたい Node.js チーム向けです。
なぜスクレイピングに Puppeteer なのか
| 状況 | 適切なツール |
|---|---|
| データが初期 HTML に含まれる | fetch + パーサー(cheerio) — ブラウザは不要 |
| データがページが呼び出す JSON API から届く | DevTools でその API を見つけたら直接叩く |
| データが JavaScript の後にしかレンダリングされない | Puppeteer(または Playwright) |
| 複数ステップの UI 操作(クリック、入力、スクロール) | Puppeteer(または Playwright) |
| スクリーンショット / PDF | Puppeteer |
スタックがすでに Node で、Chromium だけが必要で、薄い CDP ラッパーが欲しいなら、Puppeteer は自然な選択のままです。マルチブラウザ対応や新規プロジェクトでの自動待機の使い勝手では Playwright が優位です。この比較は本記事の後半で扱います。
セットアップと最初のスクレイピング
npm i puppeteer
# If install scripts are blocked by your package manager:
npx puppeteer browsers install
puppeteer は対応する Chrome ビルドをダウンロードします。システムの Chrome やリモートのブラウザサービスを使う場合は puppeteer-core を使ってください。
こちらは JavaScript で引用を描画するサンドボックス quotes.toscrape.com/js に対する完全なスクレイパーです(プレーンな HTTP では引用は 0 件になります)。
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto("https://quotes.toscrape.com/js/", {
waitUntil: "domcontentloaded",
});
await page.waitForSelector(".quote");
const quotes = await page.$$eval(".quote", (cards) =>
cards.map((card) => ({
text: card.querySelector(".text")?.textContent?.trim() ?? "",
author: card.querySelector(".author")?.textContent?.trim() ?? "",
tags: [...card.querySelectorAll(".tag")].map((t) => t.textContent.trim()),
})),
);
console.log(`scraped ${quotes.length} quotes`);
console.log(quotes[0]);
await browser.close();
あとで何時間も節約できる注意点です。
waitUntil: "domcontentloaded"は多くの場合これで十分です。networkidle0はアナリティクスや WebSocket のあるページでハングします。waitForSelector(またはpage.locator(...).wait())がsetTimeoutによる当て推量を置き換えます。- 明示的な viewport を設定すると、デフォルトの小さなヘッドレスサイズがモバイルレイアウトをトリガーしたりデスクトップ用セレクターを欠落させたりするのを防げます。
データをきれいに抽出する
ほとんどのスクレイパーは 3 つのパターンでカバーできます。
// 1) One field
const title = await page.$eval("h1", (el) => el.textContent.trim());
// 2) Many records ($$eval runs in the page context — fast)
const rows = await page.$$eval("article.product", (nodes) =>
nodes.map((n) => ({
name: n.querySelector("h2")?.textContent?.trim(),
price: n.querySelector(".price")?.textContent?.trim(),
})),
);
// 3) Locator API (Puppeteer 22+) — auto-retry style waits
await page.locator("button#load-more").click();
ループの中で「Next」をクリックしてページネーションするか、サイトが ?page=N の URL を公開していればそれをたどります。並行数は上限を決めましょう。開いているページ 1 枚 1 枚が実際のブラウザプロセスです。
スクレイパーのためのパワー機能
画像・フォント・メディアをブロックする
スクレイパーにピクセルは不要です。ナビゲーションの前に重いリソースタイプを中断します。
await page.setRequestInterception(true);
page.on("request", (req) => {
const type = req.resourceType();
if (type === "image" || type === "font" || type === "media") {
req.abort();
} else {
req.continue();
}
});
await page.goto("https://quotes.toscrape.com/js/");
画像の多いカタログサイトでは、帯域と CPU の大幅な節約が期待できます。注意点は 2 つ。スタイルシートをブロックすると可視性に依存するセレクターが壊れることがあり、一部のアンチボットサイトではアナリティクスの除去がかえって怪しさを増すことがあります。
DOM をスクレイピングする代わりに JSON をインターセプトする
DevTools の Network タブに、すでに構造化データを返す XHR が見えるなら、それをキャプチャします。
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
/** @type {any[]} */
const payloads = [];
page.on("response", async (res) => {
const url = res.url();
if (!url.includes("/api/") || res.request().resourceType() !== "xhr") return;
try {
payloads.push(await res.json());
} catch {
// not JSON
}
});
await page.goto("https://example.com/catalog", { waitUntil: "networkidle2" });
console.log(payloads[0]);
await browser.close();
これがうまくいくと CSS セレクターより優れています。型のあるフィールド、リニューアルによる破損の少なさ、そして UI が決して表示しない追加フィールドが手に入ることも多いです。ロケーターを 1 つでも書く前に、Network タブを確認しましょう。
N 個のブラウザを使わずに並列処理する
ブラウザは1 つだけ起動し、多数のページ(またはブラウザコンテキスト)を開いて、シンプルなプールでスロットリングします。
import puppeteer from "puppeteer";
async function mapPool(items, limit, worker) {
const ret = [];
let i = 0;
await Promise.all(
Array.from({ length: limit }, async () => {
while (i < items.length) {
const idx = i++;
ret[idx] = await worker(items[idx]);
}
}),
);
return ret;
}
const browser = await puppeteer.launch({ headless: true });
const urls = [
"https://quotes.toscrape.com/js/page/1/",
"https://quotes.toscrape.com/js/page/2/",
];
const results = await mapPool(urls, 3, async (url) => {
const page = await browser.newPage();
try {
await page.goto(url, { waitUntil: "domcontentloaded" });
await page.waitForSelector(".quote");
return page.$$eval(".quote .text", (els) => els.map((e) => e.textContent));
} finally {
await page.close();
}
});
await browser.close();
console.log(results.flat().length);
1 台のマシンあたり同時 3〜5 ページ程度が現実的な出発点です。無制限の並行処理はホストを OOM に追い込み、ターゲット側のレート制限も招きます。
よくある失敗と直し方
| 失敗 | 考えられる原因 | 対処 |
|---|---|---|
| セレクター待ちのタイムアウト | CSS の誤り、iframe 内のコンテンツ、または JS が一度も実行されていない | headless: false で headed 実行する。page.frames() / フレーム用ロケーターを使う。ネットワークレスポンスを待つ |
| スクレイプ結果が空、HTTP は 200 | SPA のシェルで、データは後から XHR で読み込まれる | ハイドレート後のノードに waitForSelector を使うか、JSON をインターセプトする |
| headed では動くが headless で失敗する | フィンガープリント/viewport の差異。画面外の遅延読み込みが発火しない | 現実的な viewport とユーザーエージェントを設定する。page.mouse.wheel でスクロールする |
インターセプト中に net::ERR_FAILED | 一部のリクエストで req.continue() を忘れている | リクエストハンドラーでは常に明示的に continue するか abort する |
| ターゲットが CAPTCHA / ソフト 403 を返す | データセンター IP + 自動化のシグナル | レジデンシャルプロキシが有効。ステルスプラグインも多少効く。大量なら管理型 API を使う |
Puppeteer は検出されるのか? はい
素の状態では、Puppeteer が操作するヘッドレス Chrome は簡単に見破られます。よくあるシグナルは次のとおりです。
navigator.webdriver === true- ヘッドレスの UA / プラグインの欠落 / 奇妙な WebGL レンダラー文字列
- 検出スクリプトが探る CDP のランタイム痕跡
- フィンガープリントに加わるデータセンター IP のレピュテーション
よくある DIY の対処は puppeteer-extra と puppeteer-extra-plugin-stealth です。
import puppeteer from "puppeteer-extra";
import StealthPlugin from "puppeteer-extra-plugin-stealth";
puppeteer.use(StealthPlugin());
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto("https://example.com");
await browser.close();
ステルスは単純なチェックには依然として有効です。しかし Cloudflare、DataDome、PerimeterX などに対しては、2026 年時点であなたが不利な側に立つ軍拡競争です。行動スコアリング、TLS フィンガープリンティング、既知の自動化スタックに対する継続的なラボテストが行われています。どのブラウザにもレジデンシャルプロキシを組み合わせつつ、手強いターゲットでは CAPTCHA や静かなゴミデータを覚悟してください。エスカレーションの階段はボットをブロックするサイトのスクレイピングにまとめています。
Puppeteer vs Playwright vs Selenium
| Puppeteer | Playwright | Selenium 4 | |
|---|---|---|---|
| 主言語 | Node.js(公式) | Python、Node、Java、.NET | 多数(WebDriver) |
| ブラウザ | Chrome/Firefox(CDP/BiDi) | Chromium、Firefox、WebKit | ドライバー経由で主要ブラウザ全般 |
| 自動待機 | ロケーターは改善中だが、まだ手動が多い | 一級のロケーター | 明示的な WebDriverWait |
| ネットワーク制御 | Request Interception + CDP | page.route が組み込み | 歴史的にアドオンが必要 |
| ステルスのエコシステム | 成熟(puppeteer-extra) | 移植版 / patchright | undetected-chromedriver など |
| 最適な用途 | Node + Chromium のスクレイパー | 新規のマルチブラウザスクレイパー | レガシーな Grid / 多言語 QA |
Node で Chrome ファーストにすでに投資しているならPuppeteerを選びます。WebKit/Firefox や Python が必要になるかもしれない新規のスクレイパーにはPlaywrightを選びます。Grid や既存の WebDriver スイートが中心ならSeleniumを選びます。どれも検出から自由ではなく、フリート規模ではどれもコストがかさみます。
ブラウザを動かすのをやめるべきとき
ブラウザページ 1 枚には実際の CPU スライスと数百 MB の RAM が必要です。毎分数千件のプレーンな HTTP リクエストをさばけるホストでも、同時に動かせる Puppeteer のページは数十枚程度かもしれません。レジデンシャルプロキシと CAPTCHA 解決を足せば、単価の経済性は崩れます。
きれいな HTML や Markdown さえあればよい公開ページには、Crawlora の Web スクレイピング API(POST /api/v1/web/scrape)が取得を代行し(render: "auto" が必要と判断すればヘッドレスブラウザにエスカレーションもします)、それをすべて 1 つの API キーの裏側でこなします。
curl -X POST "https://api.crawlora.net/api/v1/web/scrape" \
-H "x-api-key: $CRAWLORA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "formats": ["markdown", "html"], "render": "auto"}'
const res = await fetch("https://api.crawlora.net/api/v1/web/scrape", {
method: "POST",
headers: {
"x-api-key": process.env.CRAWLORA_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com",
formats: ["markdown", "html"],
render: "auto",
}),
});
const { data } = await res.json();
console.log(data.markdown?.slice(0, 400));
console.log("fetch method:", data.scrape?.method); // "http" | "browser" | ...
Crawlora がすでに正規化しているプラットフォーム(Amazon、Reddit、Google SERP など)では、汎用ブラウザよりプラットフォーム専用エンドポイントを優先してください。セレクターを維持する代わりに型付き JSON が手に入ります。無料枠は毎月 2,000 クレジット、カード不要で、課金は pay-on-success です。詳しくは料金ページ、任意の URL は Free Web Scraper で、あるいはプレイグラウンドを開いて試してください。
- 静的 HTML — Puppeteer ではなく fetch + cheerio(または Python の requests + BeautifulSoup)を使う。
- JavaScript でレンダリングされ、保護が軽い — アセットブロックとレスポンスインターセプトを併用した Puppeteer。
- データがすでに XHR の JSON として届く — それをインターセプトする。DOM は不要。
- 大量の手強い WAF — 管理型の /web/scrape やプラットフォーム API がステルスファームに勝る。
- 混在ワークロード — ロングテールの UI フローには Puppeteer を、大量ターゲットには API を使う。
保護された公開ページのために Chromium ファームは不要
ドキュメント化されたエンドポイント、マネージドなレンダリングとプロキシ、成功時のみ課金。毎月 2,000 クレジット無料、カード不要。
関連記事
- Playwright での Web スクレイピング — Python によるマルチブラウザ版の対応記事
- Selenium での Web スクレイピング — WebDriver の道筋と今でも有効な場面
- ボットをブロックするサイトのスクレイピング — 検出エスカレーションの階段
- Web スクレイピング向けプロキシ解説 — レジデンシャル vs データセンターの経済性
- 2026 年のベスト Web スクレイピング API — アクセス層を購入すべきタイミング
よくある質問
2026年、WebスクレイピングにPuppeteerは適していますか?
はい。JavaScriptのレンダリング、UIフローのクリック操作、スクリーンショットの取得が必要なNode.jsチームには向いています。静的HTMLには不向きなデフォルト(fetch + cheerioを使うべき)で、現代のアンチボットベンダーを相手にフリート規模で運用するとコストがかさみます。
スクレイピングにおいてPuppeteerはPlaywrightより優れていますか?
Chromeのみを使うNodeスタックでは、Puppeteerは薄く成熟したCDPラッパーで、ステルスプラグインのエコシステムも充実しています。マルチブラウザ対応、一級のオートウェイティング・ロケーター、一級のPythonサポートが必要な新規のスクレイパーには、通常Playwrightのほうが適しています。
Webサイトは Puppeteer を検出できますか?
できます。デフォルトのヘッドレスChromeはnavigator.webdriver、ヘッドレスのフィンガープリント、CDPの痕跡を露出します。puppeteer-extra-plugin-stealthは明らかな漏洩をパッチしますが、CloudflareやDataDomeなどのベンダーはTLSフィンガープリント、IPレピュテーション、挙動もスコアリングするため、大量アクセスではステルスだけでは不十分です。
JavaScriptでデータを読み込むページはどうスクレイピングしますか?
Puppeteerを起動してナビゲートし、ハイドレーション後に現れるセレクターを待つか(あるいはJSONを返すXHRをインターセプトする)、$$evalやロケーターAPIで抽出します。DevToolsでサイト自身のJSONが見えるなら、それをインターセプトするほうが好ましいです。DOMスクレイピングより安定します。
Puppeteerでnetworkidleがハングするのはなぜですか?
アナリティクスやWebSocket、ロングポーリングのあるページは完全にアイドルにならないため、networkidle0/2はタイムアウトを丸ごと消費することがあります。必要なコンテンツにはwaitUntil domcontentloadedとwaitForSelector(または特定のレスポンス待ち)を優先してください。
Puppeteerの代わりにスクレイピングAPIを使うべきタイミングは?
ターゲットが強固なWAFの背後にある、高い並行性が必要、あるいはブラウザのRAMやプロキシ運用のコストがデータの価値を上回る場合です。管理型のPOST /web/scrapeやプラットフォーム専用のJSONエンドポイントを使えば、フリート運用やステルスパッチ、CAPTCHA対応の手間から解放されます。
Puppeteerは無料ですか?
はい — Apache-2.0のオープンソースです。実際にかかるコストは、計算資源(ページごとに数百MBのRAM)、保護されたサイト向けのプロキシ、そしてセレクターとステルスパッチを維持するためのエンジニアリング時間です。
