- 技術 💻
Puppeteerとは・使い方|Playwrightとの違いとエラー原文
結論
Puppeteer は、Node.js から Chrome(と Firefox)を自動で操作するライブラリです。npm install puppeteer で Chrome ごと入り、スクリーンショット・PDF・フォーム入力・データの取り出しが数行で書けます。Playwright との違いは「自動で待つか」「対応ブラウザ」「テストランナーの有無」です。
この記事でわかること
- インストールから起動・撮影・入力・待機・要素取得・通信監視までの実行結果
- Playwright との違い表と、同じ処理の対応コード(両方を動かして比較)
- 起動失敗・タイムアウト・セレクター不一致などエラー原文 12 種と直し方
Puppeteer は、スクリプトから Chrome を「糸で操る(puppeteer=人形遣い)」道具です。スクリプトと Chrome は、DevTools Protocol(Chrome の開発者ツールが使う通信の仕組み)で WebSocket 越しにつながります。
flowchart TD
S[Node.js のスクリプト] --> P[puppeteer<br>ライブラリ]
P -->|DevTools Protocol<br>WebSocket| C[Chrome<br>ヘッドレス]
C --> W[自作のページ<br>127.0.0.1:45760]
C -->|結果| O[PNG・PDF・JSON]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| puppeteer とは | Node.js から Chrome を操作する Google 製のライブラリ | Puppeteerとは |
| puppeteer 使い方(最小) | launch → newPage → goto → 操作 → close の5段 | Puppeteerの使い方 |
| インストール | npm install puppeteer。Chrome も一緒に入る |
インストール |
| Chrome をダウンロードできない | 手元の Chrome を executablePath で指す |
インストール |
| スクリーンショット・PDF | page.screenshot()・page.pdf() |
スクリーンショットとPDF |
| 要素が出るまで待つ | waitForSelector か Locator。$() は待たない |
待機の書き方 |
| puppeteer playwright 違い | Playwright は自動待機・3エンジン・ランナー同梱 | Playwrightとの違い |
| TimeoutError が出る | セレクターの誤りか、要素が出ない。原文で見分ける | よくあるエラー |
確認環境:Ubuntu 24.04.5 LTS、Node.js 22.23.4・npm 10.9.9 で、/dev/shm の作業フォルダーに Puppeteer 25.16.0(2026年10月22日公開、同梱の Chrome は 156.0.8078.47)を npm で入れました。この環境は Chrome のダウンロード先(storage.googleapis.com)への接続が許可されていないため、同梱版の取得は 403 で止まり、手元にある Chrome for Testing 153.0.8010.12 を executablePath に指定して動かしています。操作した相手は、Node.js の http モジュールで 127.0.0.1:45760 に立てた自作のページ(トップ・商品一覧・ログイン・ダイアログ・遅い応答)だけです。比較の Playwright は playwright-core 1.63.0 を同じ Chrome で動かしました。root で実行したため --no-sandbox を付けています(理由と注意は本文)。インストールの設定・ヘッドレスモード・Locator・トラブルシューティングは Puppeteer の公式ドキュメント(pptr.dev)、Playwright との対応は Playwright 公式の「Migrating from Puppeteer」で確かめました。図は mermaid で描画を確かめています(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Puppeteerとは・使い方|Playwrightとの違いとエラー原文」
Puppeteerとは(できること・仕組み)
この節では、Puppeteer が何をするライブラリで、どんな用途に向くのかが分かります。
Puppeteer は、Chrome または Firefox を制御する高レベルの API を提供する JavaScript ライブラリです(公式)。 Google の Chrome DevTools チームが作っており、ブラウザとは DevTools Protocol か WebDriver BiDi で通信します。既定では画面を出さないヘッドレスモードで動きます。
1撮る
ページ全体・要素だけのスクリーンショット、A4 の PDF。レポートや帳票の自動生成に使う。
2操作する
入力・クリック・選択・ダイアログの応答。ログインが要る画面の定期チェックや E2E テスト。
3取り出す
JavaScript で描画されたあとの DOM から値を取る。静的 HTML の解析では取れないデータ。
4通信を見る・変える
リクエストと応答の記録、画像の遮断、API の応答の差し替え、オフラインの再現。
向かないのは、JavaScript の無いページからテキストを集めるだけの用途です。ブラウザを起動する分だけ重く、HTML の解析で済みます。その使い分けは、関連記事「Python BeautifulSoupスクレイピング|コピペ用コードとメソッド一覧」で扱っています。
Puppeteerのインストール(npm)とChromeの保存先
この節では、npm で入れたときに何が起きるか、Chrome を取れなかったときの対処、puppeteer と puppeteer-core の違いが分かります。
npm install puppeteer を実行すると、ライブラリと一緒に、その版に合う Chrome(Chrome for Testing)が ~/.cache/puppeteer にダウンロードされます。 手元では、ライブラリの部分は 3 秒・27 パッケージ・40MB で入りました。
$ npm install puppeteer
added 27 packages, and audited 28 packages in 3s
$ npm ls --depth=0
`-- puppeteer@25.16.0
$ du -sh node_modules
40M node_modules
Chrome のダウンロードが止まったとき
社内のネットワークや CI では、ダウンロード先に出られないことがあります。手元の環境でも、インストール時のダウンロードは静かに失敗し、手動で入れ直すと次の原文が出ました。
$ npx puppeteer browsers install chrome
Error: All providers failed for chrome 156.0.8078.47:
- DefaultProvider: getaddrinfo ENOTFOUND storage.googleapis.com
# プロキシ経由(proxy-agent を入れた後)
Error: All providers failed for chrome 156.0.8078.47:
- DefaultProvider: Download failed: server returned code 403. URL: https://storage.googleapis.com/chrome-for-testing-public/156.0.8078.47/linux64/chrome-linux64.zip
この状態でスクリプトを動かすと「Could not find Chrome」になります。対処は 2 つで、手元ではどちらも確かめました。
手元の Chrome を executablePath で指す
puppeteer.launch({ executablePath: '/path/to/chrome' })。環境変数 PUPPETEER_EXECUTABLE_PATH でも同じ(公式の Configuration)。手元では Chrome for Testing 153 を指して、Puppeteer 25.16.0(想定は 156)でもすべての例が動いた。
ダウンロードを最初から省く
環境変数 PUPPETEER_SKIP_DOWNLOAD=1 を付けて npm install するか、puppeteer-core を入れる。保存先を変えるなら PUPPETEER_CACHE_DIR。
出られる環境なら手動で入れ直す
npx puppeteer browsers install chrome。公式は、パッケージマネージャーがインストールスクリプトを止めたときの対処としてこのコマンドを挙げている。
| 設定 | 環境変数 | 意味 |
|---|---|---|
executablePath |
PUPPETEER_EXECUTABLE_PATH |
使う Chrome の実行ファイル |
cacheDirectory |
PUPPETEER_CACHE_DIR |
Chrome の保存先(既定 ~/.cache/puppeteer) |
skipDownload |
PUPPETEER_SKIP_DOWNLOAD |
インストール時のダウンロードを省く |
defaultBrowser |
PUPPETEER_BROWSER |
chrome か firefox(既定 chrome) |
temporaryDirectory |
PUPPETEER_TMP_DIR |
一時ファイルの場所 |
設定は .puppeteerrc.cjs や package.json にも書けます(公式)。環境変数が設定ファイルより優先され、puppeteer-core ではどちらも無視されます。
puppeteer
入れると Chrome も一緒に入る
版の組み合わせを考えなくてよい
まず動かしたい・手元の開発
puppeteer-core
ライブラリだけ。Chrome は自分で用意
executablePath か connect() が必須
Docker・サーバーレス・既存の Chrome
Puppeteerの使い方:最小のコードと起動
この節では、起動から終了までの 5 段と、最初に出やすい起動エラーが分かります。
流れは launch(起動)→ newPage(タブ)→ goto(開く)→ 操作 → close(終了)です。 最後の browser.close() を忘れると Chrome のプロセスが残ります。
// hello.mjs(Node.js 22、ES モジュール)
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: true, // 既定。画面を出さない
executablePath: process.env.CHROME_PATH, // 同梱の Chrome を取れないときだけ指定
});
const page = await browser.newPage();
await page.setViewport({ width: 1000, height: 600 });
const res = await page.goto('http://127.0.0.1:45760/', { waitUntil: 'networkidle0' });
console.log(res.status(), await page.title());
await browser.close();
root で動かすコンテナーでは、最初の実行で次のように止まりました。
Error: Failed to launch the browser process: Code: 1
stderr:
[2352:2352:1029/184608.057229:ERROR:content/browser/zygote_host/zygote_host_impl_linux.cc:102] Running as root without --no-sandbox is not supported. See https://crbug.com/638180.
TROUBLESHOOTING: https://pptr.dev/troubleshooting
Chrome は root ではサンドボックス付きで起動しません。公式のトラブルシューティングは、サンドボックス無しでの実行は強く非推奨で、信頼できるページだけを開く場合に限り args: ['--no-sandbox'] を使えるとし、推奨は root ではない利用者(公式の Dockerfile では pptruser)で動かすことです。この記事は自作のページしか開かないため --no-sandbox を付けて進めました。付け直した結果です。
browser.version(): Chrome/153.0.8010.12
puppeteer version: 25.16.0
launch: 715 ms
status: 200 title: Puppeteer 練習サイト
total: 2399 ms
| launch のオプション | 意味 |
|---|---|
headless: true / 'shell' / false |
新ヘッドレス(既定)/chrome-headless-shell/画面あり |
executablePath |
使う Chrome |
args: ['--no-sandbox'] |
サンドボックスを切る。信頼できるページ限定 |
slowMo: 100 |
操作ごとに 100ms 待つ(目で追うとき) |
defaultViewport |
既定は 800×600。null で窓の大きさに合わせる |
userDataDir |
プロファイルの保存先。ログイン状態を残す |
スクリーンショットとPDFの撮り方(実行結果)
この節では、ページ全体・要素だけ・PDF の撮り方と、できたファイルの大きさが分かります。
await page.screenshot({ path: 'top.png' }); // 表示領域(1000×600)
await page.screenshot({ path: 'top-full.png', fullPage: true }); // ページ全体
const lead = await page.$('#lead');
await lead.screenshot({ path: 'lead.png' }); // 要素だけ
await page.pdf({ path: 'top.pdf', format: 'A4', printBackground: true });
top.png 25271 bytes → 1000×600
top-full.png 25271 bytes → 1000×600(ページが短いので同じ)
lead.png 7520 bytes → 720×24(段落の大きさ)
top.pdf 62858 bytes → 1 ページ・595.9×841.9pt(A4)
撮った PNG には、見出し・リンク・日本語の段落がそのまま描かれていました。日本語が□になるときは OS に日本語フォントが無いのが原因で、Docker では fonts-noto-cjk などを入れます。
PDF はヘッダー・フッターも付けられます。displayHeaderFooter: true と headerTemplate・footerTemplate(HTML。class="pageNumber" と totalPages がページ番号に置き換わる)を渡し、margin を空けると表示されました。
await page.pdf({
path: 'items.pdf', format: 'A4', displayHeaderFooter: true,
margin: { top: '20mm', bottom: '20mm' },
headerTemplate: '<span style="display:block;font-size:8px;width:100%;text-align:center;">Puppeteer 練習サイト</span>',
footerTemplate: '<span style="display:block;font-size:8px;width:100%;text-align:center;"><span class="pageNumber"></span>/<span class="totalPages"></span></span>',
});
screenshotのtype: 'jpeg', quality: 80で JPEG に、clip: {x, y, width, height}で範囲を切り出せます。- PDF の幅は CSS の印刷用メディア(
@media print)が効きます。画面と同じ見た目で出したいときはpage.emulateMediaType('screen')を先に呼びます。
フォーム入力・クリック・ログイン(type・click・Locator)
この節では、入力・チェック・選択・送信と、遷移を待つ書き方が分かります。
入力は page.type、クリックは page.click、遷移は waitForNavigation をクリックと同時に待ちます。 クリックの後に書くと、遷移が終わった後に待ち始めてタイムアウトすることがあります。
await page.goto(BASE + '/login');
await page.type('#user', 'alice', { delay: 20 }); // 1文字 20ms で打つ
await page.type('#pass', 'correct-horse');
await page.click('#remember'); // チェックボックス
await page.select('#role', 'editor'); // select
await Promise.all([page.waitForNavigation(), page.click('#submit')]);
console.log(page.url(), await page.$eval('#welcome', (el) => el.textContent));
console.log((await browser.cookies()).map((c) => `${c.name}=${c.value} httpOnly=${c.httpOnly}`));
wrong pass -> http://127.0.0.1:45760/login | ユーザー名かパスワードが違います
form values: { user: 'alice', pass: 'correct-horse', remember: 'on', role: 'editor' }
ok -> http://127.0.0.1:45760/dashboard?u=alice | ようこそ、alice さん
cookies: [ 'sid=abc123 httpOnly=true' ]
間違ったパスワードでは自作サーバーが 401 の画面を返し、正しい値では 303 でダッシュボードへ移り、HttpOnly の Cookie が保持されていました。
Locator API(公式の推奨)
公式が推奨する page.locator() は、操作の前に「要素がある・見えている・有効・位置が安定」を自動で確かめ、満たすまで待ってから動きます(公式)。page.click(selector) などの以前からの API は互換のために残っています。
await page.locator('#user').fill('alice');
await page.locator('#pass').fill('correct-horse');
await Promise.all([page.waitForNavigation(), page.locator('#submit').click()]);
console.log(await page.locator('#who').map((el) => el.textContent).wait()); // alice
ページ遷移そのものは Locator でも自動では待たないため、waitForNavigation は残しています。
confirm・alert のダイアログ
ダイアログが出るとページは止まります。page.on('dialog') で受け取り、accept() か dismiss() を呼びます。
dialog: confirm 削除しますか?
after dialog: OK
待機の書き方(waitForSelector・waitForFunction・waitForResponse)
この節では、「取れない・空になる」の原因である待ち方の違いが、実測の時間つきで分かります。
page.$()・$$()・$eval() は待ちません。 JavaScript が後から作る要素は、waitForSelector か Locator で出るまで待ってから取ります。自作ページの「ボタンを押すと 1.5 秒後に出る要素」と「API を取ってから描く表」で試した結果です。
待たずに $("#later"): null
waitForSelector: 1.5秒後に出た要素 1510ms
goto 直後の行数: 0
waitForFunction 後の行数: 4 941ms
waitForResponse: 200 4 件 860ms
await page.click('#show');
await page.waitForSelector('#later'); // 要素が出るまで(既定 30 秒)
await page.waitForFunction(() => document.querySelectorAll('tr.item').length >= 4); // 条件が真になるまで
const [resp] = await Promise.all([ // API の応答そのものを待つ
page.waitForResponse((r) => r.url().endsWith('/api/items') && r.status() === 200),
page.reload(),
]);
| 待つもの | API | 向く場面 |
|---|---|---|
| 要素が現れる・消える | waitForSelector(sel, {visible, hidden}) |
遅れて描かれる要素 |
| 任意の条件 | waitForFunction(fn) |
件数・文字列・変数の状態 |
| ページ遷移 | waitForNavigation({waitUntil}) |
送信・リンクのクリック |
| 特定の応答 | waitForResponse(fn)・waitForRequest |
API を呼び終えたことの確認 |
| 通信が静まる | waitForNetworkIdle()・goto の networkidle0 |
何を待つか決めにくいとき |
| 固定の秒数 | new Promise(r => setTimeout(r, 1000)) |
最後の手段。waitForTimeout は削除済み |
waitUntilはload(既定)・domcontentloaded・networkidle0(接続が 0 本の状態が 500ms)・networkidle2(2 本以下が 500ms)から選びます。- 既定のタイムアウトは 30,000ms で、
page.setDefaultTimeout(ms)で変えられます(手元のgetDefaultTimeout()は 30000)。
要素の取得とデータの取り出し($・$$・evaluate・::-p-text)
この節では、DOM から値を取る 4 通りと、CSS 以外のセレクターの書き方が分かります。
値を取るだけなら $eval・$$eval・evaluate が簡潔です。 渡した関数はブラウザの中で実行され、戻り値は JSON にできる値だけが Node 側に返ります。
await page.$eval('#count', (el) => el.textContent); // 1 要素
await page.$$eval('tr.item .name', (els) => els.map((e) => e.textContent)); // 複数
await page.evaluate(() => [...document.querySelectorAll('tr.item')].map((tr) => ({
name: tr.querySelector('.name').textContent,
price: Number(tr.querySelector('.price').textContent.replace(/[^\d]/g, '')),
})));
const first = await page.$('tr.item'); // ElementHandle
await (await first.getProperty('textContent')).jsonValue();
$eval : 4件
$$eval : [ 'ノートPC', 'モニター', 'キーボード', 'マウス' ]
evaluate: [ { name: 'ノートPC', price: 128000 }, { name: 'モニター', price: 32000 }, { name: 'キーボード', price: 9800 }, { name: 'マウス', price: 4500 } ]
ElementHandle: 1ノートPC128,000円
Puppeteer のセレクターは CSS に加えて、公式の疑似要素でテキスト・XPath・アクセシブル名を指定できます(手元の結果)。
::-p-text(キーボード) → 3キーボード9,800円(その行)
::-p-xpath(//tr[@class="item"][2]/td[2]) → モニター
::-p-aria(ログイン) → http://127.0.0.1:45760/login(リンクの href)
evaluate の戻り値
DOM 要素を返すと {} になった(手元で確認)。文字列・数値・配列・オブジェクトにして返す。
JSON にできる値だけ
Node の変数
関数の中から外の変数は見えない。token is not defined になる。引数で渡す:evaluate((t) => …, token)。
別のプロセスで動く
ElementHandle
要素への参照。クリックや撮影に使う。使い終えたら dispose()。大量に持つとメモリを使う(公式)。
値だけなら $eval
Shadow DOM
>>>(任意の深さ)・>>>>(直下)で shadow root の中を探せる(公式)。
Web Components
ネットワーク監視・リクエストの遮断・応答のモック
この節では、ページが出す通信の記録、画像の遮断、API の差し替え、オフラインの再現が分かります。
page.on('request')・page.on('response') で通信をすべて受け取れます。 商品一覧ページを開いたときの記録です(size は応答本文のバイト数、ms は要求から応答まで)。
┌─────────┬────────┬────────────────┬──────────────┬────────┬──────────────────────────┬─────┬──────┐
│ (index) │ method │ url │ type │ status │ ctype │ ms │ size │
├─────────┼────────┼────────────────┼──────────────┼────────┼──────────────────────────┼─────┼──────┤
│ 0 │ 'GET' │ '/items' │ 'document' │ 200 │ 'text/html' │ 67 │ 591 │
│ 1 │ 'GET' │ '/style.css' │ 'stylesheet' │ 200 │ 'text/css' │ 22 │ 186 │
│ 2 │ 'GET' │ '/app.js' │ 'script' │ 200 │ 'application/javascript' │ 56 │ 550 │
│ 3 │ 'GET' │ '/favicon.ico' │ 'other' │ 404 │ 'text/html' │ 1 │ 365 │
│ 4 │ 'GET' │ '/api/items' │ 'fetch' │ 200 │ 'application/json' │ 809 │ 179 │
└─────────┴────────┴────────────────┴──────────────┴────────┴──────────────────────────┴─────┴──────┘
自作サーバーは /api/items を 800ms 遅らせて返しているので、809ms がそのまま出ています。page.on('console') も付けると、ブラウザのコンソールに出た Failed to load resource: the server responded with a status of 404 (Not Found)(favicon)が Node 側で読めました。
画像と CSS を止める・API を差し替える
await page.setRequestInterception(true);
page.on('request', (req) =>
['image', 'stylesheet'].includes(req.resourceType()) ? req.abort() : req.continue());
// API の応答を差し替える(モック)
page.on('request', (req) => req.url().endsWith('/api/items')
? req.respond({ status: 200, contentType: 'application/json',
body: JSON.stringify([{ id: 99, name: 'モック商品', price: 1 }]) })
: req.continue());
│ '/style.css' │ 'stylesheet' │ 'FAILED' │ 'net::ERR_FAILED' │
│ '/logo.png' │ 'image' │ 'FAILED' │ 'net::ERR_FAILED' │
│ '/' │ 'document' │ 200 │ 'text/html' │
mocked rows: [ 'モック商品' ]
offline goto: net::ERR_INTERNET_DISCONNECTED at http://127.0.0.1:45760/
遮断した通信は requestfailed で net::ERR_FAILED として届き、差し替えた API では表に「モック商品」だけが描かれました。最後の行は page.emulateNetworkConditions({ offline: true, … }) でオフラインにしたときの goto の失敗です。
インターセプトを切るとき
setRequestInterception(false) だけ呼ぶ。request の listener が残っていると、次の continue() で Error: Request Interception is not enabled! になったpage.removeAllListeners('request') で listener を外してから setRequestInterception(false)手元で出た原文そのまま
ヘッドレスモード・スマホの再現・速さの実測
この節では、3 つのヘッドレスモードの違いと、起動にかかる時間の実測が分かります。
Puppeteer 22 以降の既定 headless: true は通常の Chrome を画面なしで動かす「新ヘッドレス」です。 以前の既定だった旧ヘッドレスは chrome-headless-shell という別の実行ファイルになり、headless: 'shell' で使います。公式は、Chrome の全機能が要らない自動化では shell のほうが高性能だとしています。手元で同じ処理(起動→商品一覧→表が出るまで待つ→撮影→終了)を 5 回ずつ行った中央値です。
| 起動の仕方 | launch | 合計(API の 800ms 待ち込み) |
|---|---|---|
puppeteer headless: true(Chrome 153) |
354 ms | 1,502 ms |
puppeteer headless: 'shell'(chrome-headless-shell 153) |
146 ms | 1,242 ms |
| playwright-core 1.63.0(同じ Chrome 153) | 200 ms | 1,506 ms |
起動だけ見ると shell は半分以下でした。headless: false は画面が要るため、この環境では Missing X server to start the headful browser. Either set headless to true or use xvfb-run to run your Puppeteer script. で止まります。サーバーで画面ありを使うなら xvfb-run を使います。
スマホの再現は KnownDevices(手元で 153 機種)を page.emulate() に渡すだけです。
import puppeteer, { KnownDevices } from 'puppeteer';
await page.emulate(KnownDevices['iPhone 15']);
viewport: { width: 393, height: 659, deviceScaleFactor: 3, isMobile: true, hasTouch: true, isLandscape: false }
UA: Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1
PuppeteerとPlaywrightの違い(表と対応コード)
この節では、Puppeteer と Playwright の違いと、同じ処理を両方で書いたときの差が分かります。
いちばん大きな違いは「待ち方」です。 Puppeteer は waitForSelector などを自分で書き、Playwright は操作の前に自動で待ちます。対応ブラウザとテストランナーの有無も分かれます。
| Puppeteer 25.16 | Playwright 1.63 | |
|---|---|---|
| 開発元 | Google(Chrome DevTools チーム) | Microsoft |
| 言語 | JavaScript / TypeScript | JS/TS・Python・Java・.NET |
| ブラウザ | Chrome・Firefox(WebDriver BiDi) | Chromium・Firefox・WebKit(Safari 系) |
| 通信 | DevTools Protocol か WebDriver BiDi | 各エンジン用の独自の仕組み |
| 待機 | Locator は要素の状態を自動で待つ。遷移は waitForNavigation |
操作とアサーションが自動で待つ |
| テストランナー | なし(Jest・Mocha 等と組む) | Playwright Test を同梱(並列・トレース・codegen) |
| ブラウザの管理 | npm install で Chrome が入る | npx playwright install で別に入れる |
| 向く用途 | Chrome 前提の自動化・PDF・取得 | 複数ブラウザの E2E テスト |
同じログインを両方で書く
// Puppeteer
await page.type('#user', 'alice');
await page.type('#pass', 'correct-horse');
await Promise.all([page.waitForNavigation(), page.click('#submit')]);
await page.waitForSelector('tr.item');
const names = await page.$$eval('tr.item .name', (els) => els.map((e) => e.textContent));
// Playwright
await page.fill('#user', 'alice');
await page.fill('#pass', 'correct-horse');
await page.click('#submit'); // 遷移を自動で待つ
await page.locator('tr.item').first().waitFor();
const names = await page.locator('tr.item .name').allTextContents();
Playwright 側は手元で次のようになりました。click の後に待たずに url() を読んでもダッシュボードになっています。一方で allTextContents() は「今ある要素」を返すだけで待たないため、API の応答前に呼ぶと空でした。自動待機にも範囲があります。
url: http://127.0.0.1:45760/dashboard?u=alice | ようこそ、alice さん
rows (待たない): []
rows (waitFor 後): [ 'ノートPC', 'モニター', 'キーボード', 'マウス' ]
Playwright 公式の移行ガイドにある対応です。
| Puppeteer | Playwright |
|---|---|
puppeteer.launch() |
chromium.launch()(firefox・webkit も) |
page.setViewport() |
page.setViewportSize() |
page.type(sel, text) |
page.locator(sel).fill(text) |
page.select(sel, v) |
page.locator(sel).selectOption(v) |
page.waitForXPath() |
page.waitForSelector()(XPath も可) |
page.waitForNetworkIdle() |
page.waitForLoadState('networkidle') |
page.cookies()・setCookie() |
context.cookies()・addCookies() |
setRequestInterception+on('request') |
page.route() |
browser の createIncognitoBrowserContext() |
browser.newContext() |
Playwright のテストの書き方と CI での実行は、関連記事「Playwright入門|E2Eテストの書き方とCI実行まで実行結果つきで解説」で扱っています。
Selenium・WebDriver・静的スクレイピングとの使い分け(判断表)
この節では、ブラウザ自動化の道具をどう選ぶかが、用途ごとに分かります。
- 1JavaScript が無くても取れるページ → HTTP 取得+HTML 解析BeautifulSoup・cheerio。ブラウザを起動しない分だけ軽い
- 2Chrome だけでよい自動化・PDF・撮影 → Puppeteernpm 1 回で Chrome ごと入る。Node.js の中で完結
- 3複数ブラウザの E2E テスト → PlaywrightWebKit まで含む。ランナー・トレース・並列が同梱
- 4Python・Java のチーム、既存の WebDriver 資産 → SeleniumW3C WebDriver 標準。ドライバーの版合わせが要る
- 5AI エージェントにブラウザを使わせる → MCP サーバーChrome DevTools MCP(Puppeteer 製)・Playwright MCP
| Puppeteer | Playwright | Selenium | |
|---|---|---|---|
| 通信の標準 | DevTools Protocol/BiDi | 独自 | W3C WebDriver |
| ドライバーの用意 | 不要(同梱) | 不要(install コマンド) | Selenium Manager が自動取得 |
| 言語 | JS/TS | JS/TS・Python・Java・.NET | 多言語 |
| 学ぶ量 | 少ない | 中 | 中 |
WebDriver の仕組みと ChromeDriver の版違いのエラーは、関連記事「WebDriverとは|仕組み・ChromeDriverとバージョンエラーの直し方」で、Python から Selenium を使う方法は関連記事「Selenium Pythonの使い方|要素取得の全パターンと待機処理」で扱っています。
Puppeteerのよくあるエラー原文と直し方
この節では、手元で実際に出したエラーの原文と、それぞれの原因・直し方が分かります。
| 原文(先頭行) | 原因 | 直し方 |
|---|---|---|
Could not find Chrome (ver. 156.0.8078.47). |
Chrome が未ダウンロード・キャッシュの場所が違う | npx puppeteer browsers install chrome、または executablePath |
Browser was not found at the configured executablePath (…) |
指定したパスに実行ファイルが無い | パスを確かめる。Mac と Linux で場所が違う |
Running as root without --no-sandbox is not supported. |
root で起動した | root 以外で動かす。信頼できるページ限定で --no-sandbox |
Missing X server to start the headful browser. |
画面の無い環境で headless: false |
headless: true か xvfb-run |
TimeoutError: Waiting for selector `#leed` failed |
セレクターの誤り・要素が出ない | DevTools で document.querySelector を試す |
No element found for selector: #leed |
page.click の対象が無い |
同上。出るまで待つなら Locator |
TimeoutError: Navigation timeout of 2000 ms exceeded |
ページが遅い・waitUntil の条件が満たされない |
timeout を延ばす。domcontentloaded にする |
net::ERR_CONNECTION_REFUSED at http://… |
接続先が起動していない・ポート違い | URL とサーバーの状態を確かめる |
token is not defined |
evaluate の中から Node の変数を参照 |
第 2 引数以降で渡す |
Execution context was destroyed, most likely because of a navigation. |
evaluate の途中でページが遷移した |
遷移を待ってから evaluate |
Request Interception is not enabled! |
インターセプトを切った後に listener が残った | removeAllListeners('request') を先に |
Attempted to use detached Frame '…'. |
close() した後に page を使った |
順序を見直す。finally で close |
セレクターの誤りは TimeoutError として届くので、e instanceof TimeoutError(import { TimeoutError } from 'puppeteer')で分けて扱えます。手元では timeout: 2000 のとき 2,004ms で失敗しました。セレクターを確かめる手順は、関連記事「Chrome DevToolsの使い方|デバッグ手順と便利機能一覧」で扱っています。
使うときの決まり(利用規約・負荷・サンドボックス・片付け)
この節では、Puppeteer を他人のサイトやサーバーで使う前に確かめておくことが分かります。
- 1相手のサイトの利用規約と robots.txt を読む自動アクセスを禁じるサイトでは使わない。公開 API があればそちらを使う
- 2間隔を空け、並列を増やしすぎない1 ページ 1 ブラウザが基本。画像や CSS を遮断すると相手の負荷も減る
- 3--no-sandbox は信頼できるページ限定公式は強く非推奨。Docker では root 以外の利用者で動かす
- 4try/finally で必ず close する例外で close が飛ぶと Chrome が残る。Docker は --init でゾンビを回収(公式)
- 5認証情報をコードに書かない環境変数か秘密情報の管理機能から渡す。スクリーンショットにも写り込む
よくある質問
Q. Puppeteer とは何ですか? A. Node.js から Chrome や Firefox を自動で操作するライブラリです。Google の Chrome DevTools チームが作り、DevTools Protocol か WebDriver BiDi でブラウザと通信します。画面を出さないヘッドレスモードが既定です。
Q. 読み方は? A. 「パペティア」です。人形遣いという意味で、ブラウザを糸で操るところから付いています。
Q. 無料ですか? A. Apache License 2.0 のオープンソースで、無料で使えます。
Q. Python から使えますか? A. 公式は Node.js 用で、Python 向けの公式パッケージはありません。Python でブラウザを操作するなら、Playwright の公式パッケージか Selenium を使うのが確実です。
Q. npm install で Chrome が落ちてきません。
A. ネットワークの制限か、インストールスクリプトが止められています。npx puppeteer browsers install chrome を試し、出られない環境なら手元の Chrome を executablePath(または環境変数 PUPPETEER_EXECUTABLE_PATH)で指します。手元では Chrome 153 を指して Puppeteer 25.16 が動きました。
Q. Docker で動かすには?
A. 公式のトラブルシューティングにある依存ライブラリを入れ、root 以外の利用者で動かし、docker run --init でゾンビプロセスを回収します。日本語を撮るなら日本語フォント(fonts-noto-cjk など)も入れます。Alpine は公式に対応していません。
Q. 要素が取れず null や空の配列になります。
A. $()・$$eval() は待ちません。JavaScript が後から描く要素は waitForSelector・waitForFunction か Locator で出るまで待ってから取ります。手元では待たずに取ると null、1.5 秒待つと取れました。
Q. page.waitForTimeout が無いと言われます。
A. 以前の版にあった固定秒数の待機は削除されました。new Promise((r) => setTimeout(r, ms)) で代用できますが、要素や応答を待つ API に置き換えるほうが安定します。
Q. Playwright とどちらを選べばよいですか? A. Chrome だけでよい自動化・PDF・データ取得なら Puppeteer、Safari(WebKit)を含む複数ブラウザの E2E テストや、テストランナー・トレースまで欲しいなら Playwright が向きます。API は似ており、Playwright 公式に移行の対応表があります。
Q. ログインした状態を次回も使えますか?
A. userDataDir でプロファイルの保存先を固定すると Cookie が残ります。browser.cookies() で取り出して保存し、次回 browser.setCookie() で戻す方法もあります。
Q. headless: true と 'shell' はどちらを使えばよいですか?
A. 既定の true は通常の Chrome と同じ描画で、互換性が高い選択です。起動の速さを優先し、Chrome の全機能が要らない処理なら 'shell'(手元の起動は 146ms と半分以下)を試します。
まとめ
- Puppeteer は Node.js から Chrome・Firefox を操作するライブラリ。
npm install puppeteerで Chrome ごと入り、取れない環境ではexecutablePathで手元の Chrome を指せば動く(Chrome 153 と 25.16 で確認)。 - 流れは launch → newPage → goto → 操作 → close。root では
--no-sandboxが要るが、公式は強く非推奨で、信頼できるページ限定。 $()・$evalは待たない。遅れて出る要素はwaitForSelector・waitForFunction・waitForResponseか Locator で待つ。遷移はwaitForNavigationをクリックと同時に待つ。page.on('request'/'response')で通信を記録し、インターセプトで遮断・モック・オフラインを再現できる。切るときは listener を先に外す。- Playwright との違いは自動待機・対応ブラウザ・ランナーの有無。同じ Chrome での起動は shell 146ms・Puppeteer 354ms・Playwright 200ms だった。
次に読むとよい記事です。npm の基本は関連記事「npm コマンド 一覧|package.json 書き方と実行結果・npm 12対応」で扱っています。
参考・出典
確認日はいずれも 2026年10月29日です。
- Puppeteer「Puppeteer」(公式トップ):https://pptr.dev/
- Puppeteer「Configuration」:https://pptr.dev/guides/configuration
- Puppeteer「Configuration interface」:https://pptr.dev/api/puppeteer.configuration
- Puppeteer「Headless modes」:https://pptr.dev/guides/headless-modes
- Puppeteer「Page interactions」(Locators・Selectors):https://pptr.dev/guides/page-interactions
- Puppeteer「Troubleshooting」:https://pptr.dev/troubleshooting
- npm「puppeteer」:https://www.npmjs.com/package/puppeteer
- Playwright「Migrating from Puppeteer」:https://playwright.dev/docs/puppeteer
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Puppeteerとは・使い方|Playwrightとの違いとエラー原文」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。