
SeleniumじゃなくてPlaywrightを使ってみたいんだけど、使いやすいのかな?

Playwrightの情報は少なめですが処理速度も速くておすすめです。
これまで動的なサイトのスクレイピングといえばSelenium一択でしたが、ここ最近ではMicrosoftが提供するPlaywrightが注目されています。
項目 | Playwrightがおすすめな人 | Seleniumがおすすめな人 |
---|---|---|
要約 | これから始める人、モダンで高速な開発をしたい人 | 既存の知見や豊富な情報を活かしたい人 |
処理速度 | 速い | 重い |
情報量 | 少ない | 豊富 |
詳細 | 新規プロジェクトで、速度と安定性を重視し、最新のデバッグ機能(動画録画など)を活用したい場合に最適です。 | 巨大なコミュニティと膨大な日本語情報が魅力。既存のプロジェクトやフレームワークとの連携を重視する場合に堅実な選択肢です。 |
本記事は、スクレイピングを推奨・助長する目的で執筆しておりませんが、教育目的としてPlaywrightを使ってスクレイピング処理する流れを基本から実践までカバーしています。
- PythonでのPlaywright導入から、基本的なWebスクレイピング操作まで
- JavaScript駆動サイトを確実に攻略するPlaywrightの強力な機能
- コード自動生成ツール「Codegen」など、開発効率を飛躍的に向上させる応用テクニック
- これからのWebデータ収集におけるPlaywrightの重要性とあなたのスキルアップ
Seleniumよりも処理が軽くて速いツールを使ってみたい人やSeleniumでは満足できない人は、ぜひ本記事を参考にしてください。
細かい前置きはいいから早く実践に行きたいという方は以下のリンクから実践のフェーズに移動できます。
>>Playwrightのスクレイピング実践へ移動する
Webスクレイピングの常識を覆すPlaywrightの威力

Playwrightってなんで人気が出てきているの?

Seleniumの弱点をほぼカバーする後発ツールだから人気になっているんですよ。
- モダンなWeb環境に最適化
- 主要ブラウザやモバイル対応による安定性
- SeleniumからPlaywrightへ移行する理由
Playwrightを端的に表現するとSelenium(セレニウム)で実現できなかったスクレイピング処理を解決してくれる後発ツールです。
それぞれの内容について見ていきましょう。
Playwrightが解決するJavaScript駆動サイト(動的サイト)の難しさ
動的サイトは、RequestやScrapyといった従来のスクレイピングツールでは必要なデータが取得できないという「難しさ」があり、解決策としてSeleniumが選ばれていましたがそこに「自動待機」機能を備えたPlaywrightが登場し、新たな選択肢として注目されています。
これまでSeleniumを使用した場合、time.sleep()
を使用して明示的にコンテンツの読み込みを待たなくてはなりませんでしたが、Playwrightの場合、特定の要素が表示されたり、読み込まれたりするまでライブラリが自動的に待機します。

JavaScriptで後から表示されるデータ、Playwrightでもちゃんと取れるのですか?

Playwrightの自動待機機能なら、JavaScriptで遅延表示される要素も確実に捕捉できます
このようにPlaywrightは、JavaScriptによって動的に生成されるコンテンツを確実に捉えるため、ユーザー側が複雑な待機処理を手動で記述する必要が減り、スクレイピングの安定性が飛躍的に向上します。
主要ブラウザやモバイル対応による安定性
PlaywrightとSeleinumの大きな違いの一つとして、単一API対応というブラウザごとにコードを書き分けなくても共通の一つのコード記述で操作できる点にあります。
項目 | PlayWright | Selenium |
---|---|---|
主要ブラウザ | Chromium Firefox Safari | Chromium Firefox Safari |
モバイル対応 | 標準機能として内蔵し、iPhoneやPixelなどのデバイスを非常に高い精度でエミュレート可能。 | 機能はあるが限定的なエミュレートは可能だがPlaywrightほど手軽で高精度ではない。 |
特徴 | エンジンベースでの対応。ほぼ全てのモダンブラウザ環境を再現できるが、レガシーブラウザーは非対応。 | ブラウザ製品ごとに対応。WebDriverを介して操作するためレガシーブラウザーまで対応。 |
互換性 | ほぼ発生しない | 頻繁に発生しうる |
Playwrightを使えば、上記の表の通りに主要ブラウザだけでなく、モバイルへの対応も高精度なエミュレートができ、ブラウザ間の挙動の違いに悩まされることが少なくなりますよ。
レガシーブラウザーの対応を除けば、Playwright自身のライブラリとブラウザエンジンをセットで管理しているため、バージョン不整合によるトラブルが極めて少なく安定したスクレイピング環境を構築できます。

バージョン地獄に苦しまなくていいのが神ってます。
SeleniumからPlaywrightへ移行する理由
Seleniumは長年、Webブラウザ自動化ツールの定番でしたが、最近のWebサイトはJavaScriptを多用した複雑な構成が多く、データ収集が不安定になったり、タイムアウトエラーが頻発したりする場面が増えています。
一方、Playwrightは現在のモダンなWeb環境に合わせて開発されたツールだから、Seleniumが実装していないテスト実行の動画自動録画・詳細な操作ログなどデバッグ機能が非常に豊富。
特にCodegenという非常に強力な開発支援ツールがあるため、開発者ツールを開いて要素を…としなくても直感的に要素の取得などができ、爆速でコードの骨格が作れます。

Seleniumは作り込んだサイトだとデータ収集が不安定になりがち。

Playwrightは現在のWeb環境に最適化されているため、安定してデータを取得できます。
Playwrightは、Seleniumの後発ツールとしてJavaScriptがふんだんに使われた動的サイトの特性を考慮して設計されているため、情報量を除けばこれからWebスクレイピングを始めるならPlaywrightがおすすめです。
Playwrightで始めるスクレイピングの基本

Playwrightって難しいのかな?

Seleniumよりも簡単に導入~運用できますよ。
- Playwrightの環境構築
- 使用できるコマンドの確認
- サンプルコードの確認
- よくあるトラブルシューティング
Playwright導入~運用の流れをそれぞれ見ていきましょう。
Python環境へのPlaywright導入手順(ローカル環境)

Pythonの環境構築っていつも複雑なんだよね。

Playwrightは、わずか2つのコマンドで導入が完了するので安心してください!
# ターミナルまたはコマンドプロンプトで実行
pip install playwright
# Chromium, Firefox, WebKitの3つまとめてインストール
playwright install
Playwrightは、Pythonのパッケージ管理システムであるpip
を使ってたったの2ステップで終わるので、簡単に環境構築が完了します。
特定のブラウザだけインストールしたいときは、以下のコマンドを使用してください。
# Chromiumだけをインストール
playwright install chromium
# Firefoxだけをインストール
playwright install firefox
# WebKit (Safariのエンジン) だけをインストール
playwright install webkit
スマホをエミュレートする方法
#====================================================================
# iPhone 13 Proとしてアクセスする例
#====================================================================
import asyncio
from playwright.async_api import async_playwright, expect
async def main():
async with async_playwright() as p:
# p.devicesリストからエミュレートしたいデバイスを選ぶ
iphone_13_pro = p.devices["iPhone 13 Pro"]
browser = await p.webkit.launch(headless=False)
# デバイス情報をコンテキストに設定
context = await browser.new_context(**iphone_13_pro)
page = await context.new_page()
# この時点で、このpageはiPhone 13 Proとして振る舞う
await page.goto("https://www.yahoo.co.jp")
print(f"User Agent: {await page.evaluate('navigator.userAgent')}")
print(f"Viewport Size: {page.viewport_size}")
# スクリーンショットを撮るとiPhoneの画面サイズで保存される
await page.screenshot(path="screenshot_iphone.png")
await browser.close()
if __name__ == "__main__":
asyncio.run(main())
#====================================================================
# エミュレート可能なデバイス一覧の確認方法
# 'iPhone 13', 'Pixel 5', 'Galaxy S9+', 'iPad Pro 11'などが表示されます。
#====================================================================
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# p.devices辞書のキー(デバイス名)をすべて表示
print("===== 利用可能なデバイス一覧 =====")
for device_name in p.devices:
print(device_name)
次の項目では、Playwrightで使用できるコマンドについて見ていきましょう。
Playwrightで使用できるおもなコマンドと記述例
PythonのPlaywrightで使用できる主要なコマンド(メソッド)について、以下の表にまとめました。
分類 | メソッド名 / 機能 | Python記述例 | 効果・目的 |
---|---|---|---|
起動・終了 | launch() | browser = await p.chromium.launch(headless=False) | ブラウザ(Chromium, Firefox, WebKit)を起動する。 |
起動・終了 | new_context() | context = await browser.new_context() | 新しいブラウザコンテキスト(独立したセッション)を作成する。 |
起動・終了 | new_page() | page = await context.new_page() | 新しいページ(タブ)を開く。 |
ページ操作 | goto() | await page.goto(“https://example.com”) | 指定したURLに移動する。 |
要素の選択 | locator() | button = page.locator(“button.btn-primary”) | 要素を特定するためのロケータオブジェクトを作成する。 |
要素の選択 | getByRole() getByText()など | login_button = page.get_by_role(“button”, name=”ログイン”) element = page.get_by_text(“ようこそ”) | ユーザーの視点に近い方法で要素を特定する(役割、表示テキストなど)。 |
アクション | click() | await page.get_by_role(“button”).click() | 要素をクリックする。 |
アクション | fill() | await page.get_by_label(“名前”).fill(“山田 太郎”) | 入力フィールドにテキストを入力する。input()より推奨。 |
アクション | screenshot() | await page.screenshot(path=”screenshot.png”, full_page=True) | ページのスクリーンショットを撮る。 |
待機処理 | 自動待機 (Auto-Waiting) | (メソッドに内蔵) | click, fill, textContentなど多くのアクションが、対象要素が適切な状態になるまで自動で待機する。 |
待機処理 | wait_for_selector() | await page.wait_for_selector(“#dynamic-content”) | 指定したセレクタの要素が出現するまで待機する。 |
待機処理 | wait_for_load_state() | await page.wait_for_load_state(“networkidle”) | ネットワーク通信が落ち着くまで待機する。(”domcontentloaded”なども指定可) |
ネットワーク | route() | await page.route(“**/images/*.jpg”, lambda route: route.abort()) | 特定のネットワークリクエストを傍受して処理(中断、改変など)する。 |
ネットワーク | wait_for_response() | async with page.expect_response(“**/api/data”) as response_info: await page.get_by_text(“更新”).click() | 特定のアクション(クリックなど)によって発生するAPIレスポンスを待機する。 |
デバッグ | codegen | playwright codegen https://example.com | ブラウザ操作を記録し、自動でコードを生成する。 |
デバッグ | tracing | await context.tracing.start(screenshots=True, snapshots=True) await context.tracing.stop(path=”trace.zip”) | 実行した全操作の詳細な記録(動画、DOMスナップショット、コンソールログ等)を1つのファイルに保存する。 |
Playwrightは強力な非同期処理が強みなのでawaitasync
といった記述が目立ちますが、Seleniumで使用される一般的な同期処理コードも使えます。
サンプルコードについては次の項目で見ていきましょう。
Playwrightでスクレイピングするサンプルコード
PlaywrightはモダンなブラウザーツールですがSeleniumと比較して、以下の特徴を持っています。
- 非同期処理
- 強力な待機機能
- 高度なデバッグ・ネットワーク機能
以下にyahooにアクセスしてクリックするなどの基本的な動作を盛り込んだサンプルコードは以下のとおりです。
from playwright.sync_api import sync_playwright
import time
def run():
with sync_playwright() as p:
# 1. ブラウザを起動 (Chromiumを使用)
# headless=Falseにすると、ブラウザの動きを実際に見ることができます。
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# 2. URLにアクセス
print("Yahoo! JAPANにアクセスします...")
page.goto("https://www.yahoo.co.jp/")
# 3. 任意の場所をクリック (「ニュース」のリンクをクリック)
# get_by_roleを使うと、人間が見つけるのと同じように要素を探せて安定します。
print("「ニュース」のリンクをクリックします...")
page.get_by_role("link", name="ニュース").click()
# ページ遷移を待つ (通常は自動で待機しますが、念のため)
page.wait_for_load_state("domcontentloaded")
print(f"現在のページタイトル: {page.title()}")
# 4. テキストを入力 (検索バーに「Playwright」と入力)
# placeholderテキストを指定して検索バーを特定
print("検索バーに「Playwright」と入力します...")
search_bar = page.get_by_placeholder("トピックス、キーワードで検索")
search_bar.fill("Playwright")
# 見やすいように少し待機
time.sleep(5)
# Enterキーを押して検索を実行
search_bar.press("Enter")
page.wait_for_load_state("domcontentloaded")
print("検索を実行しました。")
# 5. ページ最下部までスクロール
print("ページの最下部までスクロールします...")
# JavaScriptを直接実行してスクロールさせる確実な方法
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
# スクロールしたことが分かるように少し待機
time.sleep(5)
# 6. 結果のスクリーンショットを撮る
screenshot_path = "result_screenshot_sync.png"
page.screenshot(path=screenshot_path, full_page=True)
print(f"'{screenshot_path}' にスクリーンショットを保存しました。")
# 7. ブラウザを閉じる
browser.close()
# スクリプトを実行
run()
Playwright公式ドキュメントでは非同期(Async) APIを中心に紹介しているのは、パフォーマンスと拡張性の高さを重視しているためです。しかし、個人の学習や小規模なツールであれば、可読性の高い同期処理でも問題ありません。
Python Playwrightのトラブルシューティング例
Playwrightは非常に安定したツールですが、実際の多様な操作を含むWebスクレイピングの過程で予期せぬエラーに遭遇することがあります。

Playwrightはどんなエラーに遭遇しやすいの?

Playwrightでは、同期的処理を非同期処理で実装すると読み込みエラーが頻発しています。
特にWebサイトの構造変更や読み込みのタイミングによるエラーは頻繁に発生しがちです。
発生しがちなトラブル | 原因の例 | 解決策の例 |
---|---|---|
要素が見つからない | セレクターの記述ミス ページが完全に読み込まれていない 要素が動的に生成されている iframe(アイフレーム)内部の要素を探している | 正しいCSSセレクターやXPathの確認page.wait_for_selector() で要素の出現を待機frame_locator() を使用してiframe内の要素を指定 |
タイムアウトエラー | ページのロードが遅い 要素の出現が遅延している ネットワーク接続の問題 | page.set_default_timeout() でタイムアウト時間を延長page.wait_for_load_state('networkidle') でネットワークアイドル状態を待機各アクションに適切なタイムアウト値を設定 |
ブラウザの起動に失敗 | Playwrightのブラウザバイナリが正しくインストールされていない システムの依存関係の欠如 | playwright install を再実行パッケージインストールの確認 |
予期せぬ動作 | Webサイトの変更 不適切な自動待機 | 定期的なスクリプトのテストとメンテナンスpage.pause() でステップ実行し特定page.screenshot() でエラー時の画面を保存 |
Playwrightは公式ドキュメントが非常に充実しており、問題解決の糸口が見つからない場合は、まず公式ドキュメントを参照することをおすすめします。
参考:Playwrightの公式ドキュメント
- 例外処理の未実装
よくエラーが出ている部分を細分化し、例外処理を実装する - 同期的処理の実装
同期的処理を想定している非同期処理を同期処理に修正する - 長時間実行によるメモリリーク(解放漏れ)
並列処理やバックグラウンドブラウザをこまめに閉じる
エラーの数だけ技術は上達するので、トライアンドエラーを恐れずにPlaywrightをガシガシ使い込んでいきましょう。
Playwrightを使ってPython公式サイトをスクレイピングしてみる

実際にPlaywrightを使ってスクレイピングしてみましょう。

待ってました。
- サイト名:Python公式サイト
- サイトURL:https://www.python.org/
- 利用規約:https://www.python.org/about/legal/
- robots.txt:https://www.python.org/robots.txt
Seleniumのときと同様にPythonの公式サイトをサンプルとして使わせていただき、実際にスクレイピングをしてみましょう。
参考:【実践】SeleniumでPythonスクレイピング!サンプルコードあり
- robots.txt・利用規約を確認する
- 取得する要素を確認する
- スクリプトを書き、検証する
各スクレイピング手順について、それぞれ見ていきましょう。
実践手順1. robots.txt・利用規約を確認する
まずは、スクレイピングが可能かどうか利用規約とrobots.txtを確認します。
# Directions for robots. See this URL:
# http://www.robotstxt.org/robotstxt.html
# for a description of the file format.
User-agent: HTTrack
User-agent: puf
User-agent: MSIECrawler
Disallow: /
# The Krugle web crawler (though based on Nutch) is OK.
User-agent: Krugle
Allow: /
Disallow: /~guido/orlijn/
Disallow: /webstats/
# No one should be crawling us with Nutch.
User-agent: Nutch
Disallow: /
# Hide old versions of the documentation and various large sets of files.
User-agent: *
Disallow: /~guido/orlijn/
Disallow: /webstats/
以上のrobots.txtと利用規約から以下のことが読み取れます。
- /~guido/orlijn/ と /webstats/ はスクレイピング禁止
- 以下の名前を持つクローラーはすべてのページへアクセス拒否
HTTrack, puf, MSIECrawler, Nutch - 利用規約に明示的なスクレイピング禁止は記載されていない
特定のページを除けば、常識の範囲内でスクレイピングができることが確認できます。
実践手順2. 取得する要素を確認する
今回はPythonでPlaywrightを使用してスクレイピングするということなので、Pythonの公式サイトから投稿されている記事のタイトルと投稿日を取得していく流れは、以下のとおりです。
- Python公式サイトにアクセスする
- Newsのナビメニューをクリックする
- 新着記事一覧からmoreをクリックする
- 記事一覧の投稿日と記事タイトルを取得する
- CSVに保存してダウンロードする
要素を確認する際に注意しておきたいのがPCとスマホやモバイルなどの端末による見え方やページ遷移先が異なる場合があります。(今回はその典型例)



いちいち開発者ツールを閉じるの面倒くさい。

PCとモバイルで取得要素の名称自体が変わることもあるので、面倒でも開発者ツールを閉じましょう。
ローカル環境の場合、Codegenを使えば開発者ツールを開閉したりせずに要素の確認からコードの生成までおこなえます。
実践手順3. Pythonスクリプトを書き、検証する
今回は、ウェブスクレイピングをすることが目的であるため、開発環境に関しては言及しません。したがって、誰でもほぼ同じ動作環境が再現できるGoogle Colaboratory(通称:Colab)でコードの実装を行います。
Python公式にアクセスして新着ニュースを取得するコードは下記の通り。
# ==============================================================================
# セクション1: 環境構築
# ==============================================================================
import logging
import os
import sys
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
IS_COLAB = "google.colab" in sys.modules
try:
if IS_COLAB:
logging.info(">>> Google Colab環境を検出しました。Playwrightの環境構築を開始します。")
get_ipython().system('pip install playwright pandas beautifulsoup4 lxml -q')
get_ipython().system('playwright install chromium')
logging.info(">>> 環境構築が正常に完了しました。")
else:
logging.info(">>> ローカル環境とみなし、環境構築をスキップします。")
except Exception as e:
logging.error(f"--- 環境構築中にエラーが発生しました: {e} ---")
raise SystemExit("環境構築に失敗したため、処理を中断します。")
# ==============================================================================
# セクション2: ライブラリのインポート
# ==============================================================================
import time
import asyncio
import pandas as pd
from bs4 import BeautifulSoup
from datetime import datetime
from playwright.async_api import async_playwright, TimeoutError as PlaywrightTimeoutError
if IS_COLAB:
from google.colab import files
# ==============================================================================
# セクション3: メイン処理
# ==============================================================================
async def main():
async with async_playwright() as p:
browser = None
page = None
try:
browser = await p.chromium.launch(headless=True, args=["--no-sandbox", "--disable-dev-shm-usage"])
context = await browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36',
viewport={'width': 1920, 'height': 1080}
)
page = await context.new_page()
logging.info("Playwrightのセットアップが完了しました。")
await page.goto("https://www.python.org/", timeout=60000)
logging.info(f"'{await page.title()}' にアクセスしました。")
await page.locator("#news > a").click(force=True)
await page.wait_for_url("**/blogs/")
logging.info(f"ニュース一覧ページ ({page.url}) に遷移しました。")
await page.locator('xpath=//*[@id="content"]/div/section/div/div[1]/div/p/a').click()
await page.wait_for_url(lambda url: "blog.python.org" in url or "pythoninsider.blogspot.com" in url)
logging.info(f"ブログサイト ({page.url}) に正常に遷移しました。")
is_mobile_site = 'pythoninsider.blogspot.com' in page.url
if is_mobile_site:
logging.info(">>> モバイル版ブログサイトのスクレイピングを開始します。")
else:
logging.info(">>> PC版ブログサイトのスクレイピングを開始します。")
news_data = []
MAX_PAGES = 10
for i in range(MAX_PAGES):
logging.info(f"--- ページ {i + 1} のデータを取得します ---")
await page.wait_for_selector(".blog-posts")
html = await page.content()
soup = BeautifulSoup(html, 'lxml')
page_articles_found = 0
def format_date(date_str):
try:
date_obj = datetime.strptime(date_str, "%A, %B %d, %Y")
return date_obj.strftime("%Y-%m-%d")
except ValueError:
return date_str
post_containers = soup.select(".blog-posts > div.date-outer")
for container in post_containers:
date_tag = container.select_one("h2.date-header span")
date_str = date_tag.get_text(strip=True) if date_tag else "日付不明"
formatted_date = format_date(date_str)
articles = container.select("div.post-outer")
for article in articles:
title_tag = article.select_one("h3.post-title a")
if title_tag:
title = title_tag.get_text(strip=True)
if title and not any(d['タイトル'] == title for d in news_data):
# logging.info(f" >> 発見: [投稿日: {formatted_date}] [タイトル: {title}]")
news_data.append({'投稿日': formatted_date, 'タイトル': title})
page_articles_found += 1
logging.info(f"このページから新たに {page_articles_found} 件の記事を取得しました。")
if page_articles_found == 0 and i > 0:
logging.info("新たな記事がなかったため、最終ページと判断し、処理を終了します。")
break
if i < MAX_PAGES - 1:
try:
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)')
await page.wait_for_timeout(1000)
next_button_locator_str = "#blog-pager-older-link" if is_mobile_site else "#Blog1_blog-pager-older-link"
next_button = page.locator(next_button_locator_str)
if await next_button.count() > 0:
await next_button.click()
logging.info(f"次のページ({i + 2}ページ目)へ遷移します...")
await page.wait_for_load_state('domcontentloaded')
else:
logging.info("「Older Posts」ボタンがDOM内に見つかりませんでした。ここで取得を終了します。")
break
except PlaywrightTimeoutError:
logging.info("「Older Posts」ボタンの操作でタイムアウトしました。ここで取得を終了します。")
break
logging.info(f"合計 {len(news_data)} 件のニュースを取得しました。")
if news_data:
df = pd.DataFrame(news_data, columns=['投稿日', 'タイトル'])
csv_filename = 'python_blog_news_playwright.csv'
df.to_csv(csv_filename, index=False, encoding='utf-8-sig')
logging.info(f"取得したデータを'{csv_filename}'に保存しました。")
if IS_COLAB:
files.download(csv_filename)
else:
logging.warning("取得できたニュースがなかったため、CSVファイルは作成されませんでした。")
except Exception as e:
logging.error(f"処理中に予期せぬエラーが発生しました: {e}", exc_info=True)
if page:
error_filename = 'error_screenshot_playwright.png'
await page.screenshot(path=error_filename)
if IS_COLAB:
files.download(error_filename)
finally:
logging.info("処理が終了しました。ブラウザは自動的に閉じられます。")
# asyncioを使ってmain関数を実行
import nest_asyncio
nest_asyncio.apply()
asyncio.run(main())

awaitasync
の使い方が重要です。
参考までに以下のリンクにGoogle Colabのサンプルコードを置いておくので参考にしてください。
参考:Google Colab
おまけ:Playwrightでスクレイピング処理を実用する
おまけ編では以下のスクレイピング処理を実施します。
- 任意のキーワードでYahooの検索結果にアクセスする
- 1ページ目から5ページ目までの順位を取得する
- 以下の要素を取得する
記事タイトル・記事URL・記事のディスクリプションPAA・関連キーワード - CSVに保存しダウンロードする
最初の構成ではYahooのトップページから検索キーワードを入力して検索ボタンをクリックする流れでしたが、ボット回避がうまくいかずうまく処理できなかったため、検索結果に直接アクセスする方法に変更しました。
# ==============================================================================
# セクション1: 環境構築
# ==============================================================================
import logging
import os
import sys
# 既存のログハンドラをクリア
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
IS_COLAB = "google.colab" in sys.modules
try:
if IS_COLAB:
logging.info(">>> Google Colab環境を検出しました。Playwrightの環境構築を開始します。")
get_ipython().system('apt-get update -qq && apt-get install -y fonts-ipafont-gothic -qq')
get_ipython().system('pip install playwright pandas -q')
get_ipython().system('playwright install chromium')
logging.info(">>> 環境構築が正常に完了しました。")
else:
logging.info(">>> ローカル環境とみなし、環境構築をスキップします。")
except Exception as e:
logging.error(f"--- 環境構築中にエラーが発生しました: {e} ---")
raise SystemExit("環境構築に失敗したため、処理を中断します。")
# ==============================================================================
# セクション2: ライブラリのインポート
# ==============================================================================
import asyncio
import pandas as pd
from playwright.async_api import async_playwright
from urllib.parse import quote_plus
if IS_COLAB:
from google.colab import files
# ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
# ★ 設定項目 ★
SEARCH_KEYWORD = "ここに検索したいキーワード"
MAX_PAGES_TO_SCRAPE = 5 # 取得したいページ数
# ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
# ==============================================================================
# セクション3: メイン処理
# ==============================================================================
async def main():
async with async_playwright() as p:
browser = None
page = None
try:
browser_args = ["--no-sandbox", "--disable-dev-shm-usage", "--disable-blink-features=AutomationControlled"]
browser = await p.chromium.launch(headless=True, args=browser_args)
context = await browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
viewport={'width': 1920, 'height': 1080},
locale='ja-JP'
)
page = await context.new_page()
logging.info("Playwrightのセットアップが完了しました。")
encoded_keyword = quote_plus(SEARCH_KEYWORD)
start_url = f"https://search.yahoo.co.jp/search?p={encoded_keyword}"
logging.info(f"検索結果ページに直接アクセスします: {start_url}")
await page.goto(start_url, wait_until="domcontentloaded", timeout=60000)
logging.info("ポップアップや同意画面が表示されていないか確認し、処理します...")
try:
later_button = page.locator('button:has-text("後で")')
if await later_button.is_visible(timeout=5000):
await later_button.click()
logging.info("「アドレスバーを設定」ポップアップの「後で」ボタンをクリックしました。")
await later_button.wait_for(state="hidden", timeout=5000)
logging.info("「アドレスバーを設定」ポップアップが閉じられたことを確認しました。")
agree_button = page.locator('button:has-text("同意して閉じる")')
if await agree_button.is_visible(timeout=3000):
await agree_button.click()
logging.info("クッキー同意ボタンをクリックしました。")
await agree_button.wait_for(state="hidden", timeout=5000)
logging.info("クッキー同意画面が閉じられたことを確認しました。")
except Exception as e:
logging.warning(f"ポップアップ処理中に軽微なエラー(またはポップアップ無)が発生しましたが、処理を続行します: {e}")
all_search_results = []
for page_num in range(1, MAX_PAGES_TO_SCRAPE + 1):
logging.info(f"--- {page_num} ページ目のデータを取得します ---")
await page.wait_for_selector("#contents", timeout=30000)
# page.title()が不安定な場合があるため、エラーが発生しても処理を続行する
try:
page_title = await page.title()
logging.info(f"検索結果ページ '{page_title}' の読み込み完了。")
except Exception as e:
logging.warning(f"ページのタイトル取得に失敗しましたが、処理を続行します: {e}")
await page.wait_for_timeout(1000) # 動的コンテンツの読み込みを少し待つ
result_items = await page.locator("div.sw-Card.Algo").all()
# このページに検索結果がない場合はループを抜ける
if not result_items:
logging.info("このページに検索結果が見つかりませんでした。取得を終了します。")
break
logging.info(f"ページ内の検索結果(div.sw-Card.Algo)を{len(result_items)}件見つけました。")
rank_offset = len(all_search_results)
for i, item in enumerate(result_items, 1):
rank = rank_offset + i
link_loc = item.locator("a:has(h3)").first
title_loc = link_loc.locator("h3")
desc_loc = item.locator("p.sw-Card__summary")
if await title_loc.count() == 0:
continue
title = await title_loc.inner_text()
url = await link_loc.get_attribute("href")
description = await desc_loc.inner_text() if await desc_loc.count() > 0 else ""
logging.info(f" [順位 {rank}] {title}")
all_search_results.append({"順位": rank, "タイトル": title, "URL": url, "ディスクリプション": description})
logging.info(f"--- {page_num} ページ目から {len(result_items)} 件の記事を取得しました ---")
# 次のページへ遷移するか、ループを終了するかを判断
if page_num < MAX_PAGES_TO_SCRAPE:
next_button = page.locator('.Pagenation__next a:has-text("次へ")')
if await next_button.count() > 0:
logging.info("「次へ」ボタンをクリックして、次のページへ遷移します...")
await next_button.click()
await page.wait_for_load_state("domcontentloaded", timeout=60000)
else:
# 「次へ」ボタンがない場合は最終ページなのでループを抜ける
logging.info("「次へ」ボタンが見つかりませんでした。最終ページと判断し、取得を終了します。")
break
else:
logging.info(f"指定された {MAX_PAGES_TO_SCRAPE} ページの取得が完了したため、ループを終了します。")
logging.info(f"--- 合計 {len(all_search_results)} 件の検索結果を取得しました ---")
related_keywords = []
paa_questions = []
try:
logging.info("--- 関連キーワードとPAAの取得を試みます ---")
# 関連検索ワードのセレクタ
related_keywords_loc = page.locator('div.Unit--south li.SouthUnitItem a')
paa_loc = page.locator('div.AnswerRelatedQuestions .sw-Accordion__title')
if await related_keywords_loc.count() > 0:
related_keywords = [await loc.inner_text() for loc in await related_keywords_loc.all()]
logging.info(f"関連キーワード: {related_keywords}")
else:
logging.warning("関連キーワードのセクションが見つかりませんでした。")
if await paa_loc.count() > 0:
paa_questions = await paa_loc.all_inner_texts()
logging.info(f"PAA: {paa_questions}")
else:
logging.warning("PAAのセクションが見つかりませんでした。")
except Exception as e:
logging.warning(f"関連情報(キーワード/PAA)の取得中に軽微なエラーが発生しました: {e}")
if all_search_results:
df_results = pd.DataFrame(all_search_results)
filename_results = f"yahoo_search_results_{SEARCH_KEYWORD.replace(' ', '_')}.csv"
df_results.to_csv(filename_results, index=False, encoding='utf-8-sig')
logging.info(f"検索結果を'{filename_results}'に保存しました。")
if IS_COLAB: files.download(filename_results)
if related_keywords:
df_related = pd.DataFrame(related_keywords, columns=["関連キーワード"])
filename_related = f"yahoo_related_keywords_{SEARCH_KEYWORD.replace(' ', '_')}.csv"
df_related.to_csv(filename_related, index=False, encoding='utf-8-sig')
logging.info(f"関連キーワードを'{filename_related}'に保存しました。")
if IS_COLAB: files.download(filename_related)
if paa_questions:
df_paa = pd.DataFrame(paa_questions, columns=["他の人はこちらも質問"])
filename_paa = f"yahoo_paa_{SEARCH_KEYWORD.replace(' ', '_')}.csv"
df_paa.to_csv(filename_paa, index=False, encoding='utf-8-sig')
logging.info(f"PAAを'{filename_paa}'に保存しました。")
if IS_COLAB: files.download(filename_paa)
except Exception as e:
logging.error(f"処理中に予期せぬエラーが発生しました: {e}", exc_info=True)
if page:
error_filename = f"error_screenshot_{SEARCH_KEYWORD.replace(' ', '_')}.png"
await page.screenshot(path=error_filename, full_page=True)
logging.info(f"エラー発生時のスクリーンショットを '{error_filename}' として保存しました。")
if IS_COLAB:
try:
files.download(error_filename)
html_filename = f"error_page_{SEARCH_KEYWORD.replace(' ', '_')}.html"
with open(html_filename, 'w', encoding='utf-8') as f:
f.write(await page.content())
logging.info(f"エラー発生時のHTMLを '{html_filename}' として保存しました。")
files.download(html_filename)
except Exception as download_error:
logging.error(f"エラーファイルのダウンロード中に問題が発生しました: {download_error}")
finally:
if browser: await browser.close()
logging.info("処理が終了しました。")
# asyncioを使ってmain関数を実行
import nest_asyncio
nest_asyncio.apply()
asyncio.run(main())

ぜひ研究材料として活用してください。
参考までに以下のリンクにGoogle Colabのサンプルコードを置いておくので参考にしてください。
参考:Google Colab
よくある質問(FAQ)
まとめ:Playwrightでスクレイピングを効率化しよう
これまでにPython環境へのPlaywright導入から具体的な操作方法、サンプルコードまでを解説してきました。
- 主要ブラウザやモバイル対応で多様な環境を再現できるツール
- 非同期処理で効率的かつ高速にスクレイピングできる
- 自動待機機能やネットワーク操作など高機能
Playwrightは「速度」「安定性」「開発体験」の3つの点でSeleniumを大きく上回るモダンなツールです。

Selenium以外の選択肢があるのは良いですね。

モダン・高機能・高速と三拍子揃ったツールです。
項目 | Playwrightがおすすめな人 | Seleniumがおすすめな人 |
---|---|---|
要約 | これから始める人、モダンで高速な開発をしたい人 | 既存の知見や豊富な情報を活かしたい人 |
処理速度 | 速い | 重い |
情報量 | 少ない | 豊富 |
詳細 | 新規プロジェクトで、速度と安定性を重視し、最新のデバッグ機能(動画録画など)を活用したい場合に最適です。 | 巨大なコミュニティと膨大な日本語情報が魅力。既存のプロジェクトやフレームワークとの連携を重視する場合に堅実な選択肢です。 |
参考情報が豊富なSeleniumも使いやすいツールですが、Playwrightは後発ツールながらモダンなWebサイト(動的サイト)の自動化においては、PlaywrightがSeleniumよりもはるかに効率的で安定したスクレイピング成果が得られますよ。