Playwright は 2026 年現在、Web スクレイピングに使える最も強力な汎用ブラウザ自動化ライブラリです。1 つの Python API で Chromium・Firefox・WebKit を操作でき、ロケーターが自動で待機するためタイミング起因でスクリプトが壊れなくなり、page.route を使えば重いアセットをブロックしたり、そもそも HTML をパースする代わりにサイト自身の JSON レスポンスを読み取ったりできます。本ガイドでは pip install からサンドボックスサイトに対して動くスクレイパーまでを作り、続いてパワー機能 — リソースのブロック、レスポンスのインターセプト、並列コンテキスト — を扱い、最後に多くのチュートリアルが省く正直な話で締めくくります。ヘッドレス Playwright はどれほど検出されやすいのか、そして大規模に動かすと実際いくらかかるのか、です。
2026 年にスクレイピングで Playwright を選ぶ理由
必要なページがデータを JavaScript でレンダリングしているなら、requests と BeautifulSoup の組み合わせでは決してそれを見られません。これらのツールはサーバーが送る生の HTML を読むのであって、ブラウザがその後組み立てるものは読まないからです。静的ページには、そのスタックが今でも正しいデフォルトです(その場合は Python での Web スクレイピングガイドと BeautifulSoup チュートリアルから始めてください)。それ以外のすべてにはヘッドレスブラウザが必要で、Playwright は次の 4 つの具体的な理由からデフォルトの選択肢になりました。
- 自動待機するロケーター。
Locatorに対するすべてのアクション — クリック、テキスト取得、属性取得 — は、要素がアタッチされ、表示され、安定するまで待ってから実行され、タイムアウトまでリトライします。古い Selenium スクリプトのあちこちに散らばるtime.sleep(3)は、単純に消えます。 - 1 つの API、3 つのエンジン。
p.chromium・p.firefox・p.webkitは同じインターフェースを共有します。サイトが Chrome のエンジンで異なる挙動をするなら、Firefox への切り替えは一語の変更です。 - ブラウザコンテキスト。 コンテキストは 1 つのブラウザプロセス内の分離されたシークレットモード風セッションで、Cookie もストレージも独立しています。作成も破棄も安価で、タスクごとにブラウザを起動せずに Playwright が並列化を実現する仕組みです。
- ネットワークインターセプト。
page.routeはブラウザとネットワークの間に入るため、画像やフォントのリクエストを中断したり、サイト自身のフロントエンドが取得する JSON をキャプチャしたりできます。ページ上で最もクリーンなデータソースであることも珍しくありません。
Selenium との正直な比較: Selenium にはより大きなレガシーエコシステムと多くの言語バインディングがあり、Selenium Grid は分散テストで実績があります。しかし Playwright の自動待機、組み込みのネットワークインターセプト、コンテキストモデルは、スクレイパーのコードをより短く、より壊れにくくします。Selenium ではネットワークレベルの処理に明示的な WebDriverWait 条件や selenium-wire のようなプロキシツールが必要です。完全な比較は Selenium での Web スクレイピングにまとめました。新規のスクレイピングプロジェクトなら、Playwright のほうが良いスタートです。
requests + BeautifulSoup との比較: ブラウザは 1 ページあたり、素の HTTP リクエストよりおよそ 2 桁高コストです。Playwright は必要だから使うもの(JavaScript レンダリング、ユーザー操作、セッションフロー)であって、輝いて見えるから使うものではありません。
セットアップと最初のスクレイピング
コマンドは 2 つ。1 つ目がライブラリをインストールし、2 つ目がブラウザバイナリをダウンロードします。他のエンジンが必要と分かっていない限り、Chromium だけをインストールしましょう。
pip install playwright
playwright install chromium
練習用に作られたサンドボックス books.toscrape.com に対する、完全で実行可能なスクレイパーがこちらです。
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://books.toscrape.com/")
books = []
for card in page.locator("article.product_pod").all():
books.append({
"title": card.locator("h3 a").get_attribute("title"),
"price": card.locator(".price_color").inner_text(),
"in_stock": "In stock" in card.locator(".availability").inner_text(),
})
print(f"scraped {len(books)} books")
print(books[0])
browser.close()
実行すると、タイトル・価格・在庫状況を持つ 20 個の dict が得られます。このスクリプトにないものに注目してください。wait も、sleep も、リトライループもありません。page.goto はページの読み込みを待ち、各ロケーターのアクションは対象の要素を待ちます。これが自動待機の仕事です。
初日から知っておくべき公式ドキュメントの注意点が 1 つあります。sync API はスレッドセーフではありません。スレッドごとに 1 つの sync_playwright() インスタンスを使うか、並行処理には async API を使ってください(詳しくは後述)。
ロケーターの正しい使い方
page.locator() は CSS(または XPath、自動判別)を受け取りますが、Playwright のドキュメントはユーザー視点のロケーターを推奨しています。スクレイパーにとってこの助言は割に合います。ロールと可視テキストは、CSS クラスの連鎖を粉々にするリニューアルを生き延びるからです。
# CSS — fine for stable, semantic markup
page.locator("article.product_pod")
# Role-based — survives class renames
page.get_by_role("link", name="next").click()
# Text-based — good for labels and buttons
page.get_by_text("Add to basket")
ロケーターはデフォルトでstrictです。ロケーターが 2 つの要素にマッチした状態でアクションを実行すると、Playwright は黙って最初の要素を選ぶのではなくエラーを投げます。本当に多数の中の 1 つが欲しいときは、.filter(has_text="...")、.first、.nth(i) で絞り込みます。
スクレイピングの定番パターンは、繰り返しのコンテナを特定し、.all() を呼び、各アイテムを基準にフィールドを抽出すること — まさに最初のスクリプトがやったことです。これにページネーションを足せば、本物のクローラーになります。
books = []
while True:
for card in page.locator("article.product_pod").all():
books.append({
"title": card.locator("h3 a").get_attribute("title"),
"price": card.locator(".price_color").inner_text(),
})
next_link = page.locator("li.next a")
if next_link.count() == 0:
break
next_link.click()
print(f"{len(books)} books across all pages")
count() は「これは存在するか?」を調べる慣用的な方法です。存在しない要素へのアクションのようにタイムアウトを待つのではなく、即座に結果を返します。
スクレイパーのためのパワー機能
page.route で画像・フォント・メディアをブロックする
スクレイパーはページを見る必要がないので、レンダリングにコストを払うのはやめましょう。ナビゲーションの前にルートハンドラーを登録し、不要なリクエストタイプを中断します。
BLOCKED_TYPES = {"image", "font", "media"}
def block_heavy(route):
if route.request.resource_type in BLOCKED_TYPES:
route.abort()
else:
route.continue_()
page.route("**/*", block_heavy)
page.goto("https://books.toscrape.com/")
画像の多いページでは、これだけで転送バイト数が半分以下になり、ナビゲーションのたびに数秒を削れます。Scrapfly のベンチマークでは、典型的なサイトで帯域の節約は 2〜5 倍とされています。外科的なケースには URL パターンでのブロック(page.route("**/*.png", lambda r: r.abort()))も使えます。注意点は 2 つ。スタイルシートをブロックすると可視性に依存するセレクターが壊れることがあり、一部のアンチボット保護サイトでは解析スクリプトのブロックがかえって怪しさを増すことがあります。
DOM をスクレイピングする代わりに JSON レスポンスを取得する
モダンなサイトは、バックグラウンドの XHR/fetch 呼び出しでデータを読み込み、クライアント側でレンダリングすることがよくあります。つまり、欲しい構造化データはすでに JSON として回線上に存在します。あとから DOM をパースするのは、劣化コピーをスクレイピングしているようなものです。レスポンスを直接キャプチャしましょう。
with page.expect_response("**/api/search*") as resp_info:
page.get_by_role("button", name="Search").click()
data = resp_info.value.json() # the site's own structured payload
セッション全体にわたって受動的にキャプチャするなら、代わりにリスナーをアタッチします。
captured = []
def on_response(response):
if response.request.resource_type in ("xhr", "fetch") and "/api/" in response.url:
captured.append(response.json())
page.on("response", on_response)
page.goto("https://example.com/catalog")
これが機能するとき、DOM スクレイピングより厳密に優れています。型のあるフィールド、セレクターのメンテナンス不要、そしてページが決して表示しない追加フィールドが手に入ることも多いのです。ロケーターを 1 つでも書く前に、対象サイトで DevTools の Network タブを開いてください。ロケーターは不要かもしれません。
ブラウザコンテキストで並列スクレイピング
URL ごとにブラウザを起動してはいけません。1 つのブラウザを起動して多数のコンテキストを開きます。それぞれが、ごく一部のコストで済む分離されたセッションです。
import asyncio
from playwright.async_api import async_playwright
async def scrape_one(browser, sem, url):
async with sem:
context = await browser.new_context()
page = await context.new_page()
await page.goto(url)
title = await page.locator("h1").inner_text()
await context.close()
return {"url": url, "title": title}
async def main(urls):
sem = asyncio.Semaphore(4) # 3–5 concurrent pages per machine is realistic
async with async_playwright() as p:
browser = await p.chromium.launch()
results = await asyncio.gather(*(scrape_one(browser, sem, u) for u in urls))
await browser.close()
return results
セマフォが重要です。開いているページ 1 枚ごとに CPU と数百 MB のメモリを消費し、無制限の並行処理は対象サイトを叩きすぎてレート制限を招きます。グローバルだけでなく、ドメイン単位でスロットリングしましょう。
sync と async のどちらを使うか
スクリプト、単発ジョブ、cron で動かすものには sync API を使いましょう。読みやすく、デバッグも簡単です。1 プロセス内で複数ページを並行処理したいときや、スクレイパーが非同期アプリの中で動くときは async API に手を伸ばします。2 つの API はメソッド単位で互いに対応しているため、後からのアップグレードは機械的な作業であり、書き直しではありません。
よくある失敗と直し方
| 失敗 | 考えられる原因 | 対処 |
|---|---|---|
ロケーター待ちの TimeoutError | セレクターが永遠にマッチしない: CSS の誤り、iframe 内のコンテンツ、または DevTools で見たものとマークアップが異なる | headless=False で再実行して観察する。get_by_role/get_by_text を優先し、iframe には page.frame_locator() を使う |
wait_until="networkidle" 付きの goto がハングまたはタイムアウトする | ロングポーリング、WebSocket、解析ツールが接続を開いたままにし、ネットワークがアイドルにならない | 実サイトで networkidle を使わない。本当に必要なロケーターやレスポンスを待つ |
| headed では動くが headless で失敗する | ヘッドレスモードは異なるビューポート/フィンガープリントを報告し、遅延読み込みのコンテンツが画面外でトリガーされない | コンテキストに明示的な viewport と現実的な user_agent を設定し、mouse.wheel でスクロールして遅延読み込みを発火させる |
| Strict mode violation: locator resolved to N elements | セレクターが複数のノードにマッチした状態でアクションを実行した | .filter()、.first、.nth() で絞り込む。あるいはセレクターを直す。曖昧さはたいていセレクターが間違っているサイン |
| ブラウザにはデータがあるのにスクレイピング結果にない | ロード後の遅延 XHR でデータが届いている | その API 呼び出しに expect_response を使うか、それを描画する特定のロケーターを待つ |
Playwright は検出されるのか? はい — 正直な実態
素の状態のヘッドレス Playwright は検出可能で、これを省くチュートリアルはあなたを罠にはめています。手がかりはよく文書化されています。navigator.webdriver は true になり、Playwright が依存する CDP(Chrome DevTools Protocol)接続は検出スクリプトが探るランタイムの痕跡を残し、ヘッドレス Chromium のブラウザフィンガープリント — フォント、WebGL レンダラー文字列、欠落したプラグインリスト、デフォルトのビューポート — は実ユーザーのブラウザと測定可能なレベルで異なります。誰もが Playwright を使うからこそ、アンチボットベンダーは Playwright を名指しでテストしています。
パッチは存在します。playwright-stealth(puppeteer-stealth の移植)は明らかな JavaScript プロパティを書き換え、パッチ済みビルドはさらに踏み込みます。しかし、これはあなたが不利な側に立つ軍拡競争です。検出ベンダーは継続的にアップデートを出し、あなたが当てる修正の一つひとつが、彼らがすでに収集しているシグネチャです。保護されたターゲットでは CAPTCHA、静かに混入するゴミデータ、IP ブロックを覚悟してください。エスカレーションの階段はボットをブロックするサイトのスクレイピングで扱っています。
そしてコストの問題があります。これは検出されなくても効いてきます。ブラウザページ 1 枚には実際の CPU スライスと数百 MB の RAM が必要で、素の HTTP リクエストを毎分数千件さばけるマシンでも、並行して動かせる Playwright のページは数十枚です。保護されたサイト向けにレジデンシャルプロキシを足せば、ページ単価はさらに悪化します。Playwright は素晴らしいツールで、機能するサイトに対しては Python でこれほど快適なものはありません。しかし「もっとブラウザを動かす」は、スケールに対する高くつく答えです。
ブラウザを動かすのをやめるべきとき
保護された大量のターゲットに対する現実的な一手は、ブラウザ・プロキシ・アンブロックの運用を誰かに任せ、普通の HTTP API 経由で構造化 JSON を受け取ることです。それが Crawlora の Web スクレイピング APIのやっていることです。プラットフォームごとにドキュメント化されたエンドポイント、正規化されたフィールド、維持すべきパーサーもフィンガープリントもありません。Amazon 検索はプレーンなリクエスト 1 回です。
import requests
resp = requests.get(
"https://api.crawlora.net/api/v1/amazon/search",
params={"k": "wireless earbuds"},
headers={"x-api-key": "YOUR_API_KEY"},
)
for item in resp.json()["data"][:3]:
print(item["title"], item["price"], item["rating"])
課金は pay-on-success — 成功した 2xx レスポンスにのみ課金されます — で、無料枠は毎月 2,000 クレジット、カード不要。ブラウザコードを書き始める前に、エンドポイントが対象をカバーしているか試すには十分です。クレジットとエンドポイントの対応は料金ページを、ライブのレスポンスはプレイグラウンドでご覧ください。
スタック全体のためのまともな判断ルールはこちらです。
- 静的 HTML で JavaScript レンダリング不要 — ブラウザではなく requests + BeautifulSoup を使う。
- JavaScript でレンダリングされるページで、保護がないか軽い — Playwright を使い、アセットブロックとレスポンスインターセプトを併用する。
- データがバックグラウンドの API 呼び出しで届く — JSON をインターセプトする。ロケーターは一切不要かもしれない。
- 保護されたプラットフォーム(EC、検索、ソーシャル)を大量に扱う — 構造化スクレイピング API のほうが、ステルスパッチとプロキシプールの維持に勝る。
- 混在ワークロード — 現実のプロジェクトの多くは、ロングテールに Playwright を、手強い大量ターゲットに API を使う。
保護されたサイトのためのブラウザファームはもう不要
ドキュメント化されたエンドポイント、正規化された JSON、マネージドなプロキシとアンブロック。成功時のみ課金、毎月 2,000 クレジット無料、カード不要。
関連記事
- Python での Web スクレイピング完全ガイド — 柱となる記事: requests・BeautifulSoup・ブラウザの使い分け。
- Selenium での Web スクレイピング — スクレイパー向けの Playwright vs Selenium 完全比較。
- BeautifulSoup チュートリアル — ブラウザが不要なときの HTML パース。
- ボットをブロックするサイトのスクレイピング — 検出エスカレーションの実態と、各段階での選択肢。
よくある質問
WebスクレイピングにはSeleniumよりPlaywrightのほうが良いですか?
新規のスクレイピングプロジェクトなら、たいていはい。Playwrightのロケーターは自動で待機・リトライし、ネットワークインターセプトが組み込みで、ブラウザコンテキストのおかげで並列セッションが安価です。これらはすべて、Seleniumでは明示的なwaitやselenium-wireのような追加ツールが必要になる部分です。一方Seleniumには、より大きなレガシーエコシステムと、分散テスト向けのSelenium Gridがあります。
PlaywrightはWebサイトに検出されますか?
はい。ヘッドレスのPlaywrightはnavigator.webdriverを露出し、検出スクリプトが探るCDPランタイムの痕跡を残し、ヘッドレスChromiumのフィンガープリント(フォント、WebGL、プラグイン、ビューポート)は実ブラウザと異なります。playwright-stealthは明らかなリークを塞ぎますが、Playwrightを名指しでテストするアンチボットベンダーとの終わりのない軍拡競争です。
Playwrightは無料ですか?
はい。PlaywrightはMicrosoft製のオープンソースライブラリで、商用利用も無料、Pythonを第一級でサポートしています。実際のコストは計算資源です。ブラウザページ1枚ごとにCPUと数百MBのRAMが必要で、保護されたサイトではさらにプロキシとアンブロックの費用がかかります。
スクレイピングにはPlaywrightのsync APIとasync APIのどちらを使うべきですか?
スクリプト、単発ジョブ、cronタスクにはsync APIを使いましょう。読みやすくデバッグも簡単です。1プロセス内で複数ページを並行処理したいとき(セマフォでマシンあたり3〜5ページに制限)や、非同期アプリケーションの中で動かすときはasync APIを使います。両APIは互いに対応しているため、後からの移行は機械的な作業です。
Playwrightのスクレイピングを速くするには?
レバーは3つ。page.routeで画像・フォント・メディアをブロックする(ページ容量が半減することも多い)、DOMをパースする代わりにサイト自身のXHR/JSONレスポンスをインターセプトする、そしてURLごとにブラウザを起動するのではなく、1つのブラウザプロセス内で複数のブラウザコンテキストを使って並列化する、です。
Playwrightでwait_until='networkidle'がタイムアウトするのはなぜですか?
ロングポーリング、WebSocket、チャットウィジェット、ストリーミング解析のあるページではネットワークがアイドルにならないため、networkidleはナビゲーションのたびにタイムアウトを使い切ります。Playwrightのドキュメントも非推奨としています。代わりに、必要な特定のロケーターやAPIレスポンスを待ちましょう。
PlaywrightはJavaScriptの多いWebサイトをスクレイピングできますか?
はい。それこそがrequests + BeautifulSoupに対する最大の強みです。Playwrightは実物のブラウザエンジン(Chromium、Firefox、WebKit)を動かしてページのJavaScriptを実行し、ロケーターは動的に描画されるコンテンツを自動で待ちます。page.expect_responseを使えば、背後のJSON API呼び出しを直接取得することもできます。
