Playwright 是微軟開發的開源瀏覽器自動化框架,用 Python 寫爬蟲時,它能完整模擬真實瀏覽器(Chromium、Firefox、WebKit),輕鬆取得 JavaScript 動態渲染的內容,並內建自動等待機制大幅降低「元素還沒載入就失敗」的困擾。本文從安裝到實戰一步步帶你寫出第一個 Playwright 爬蟲。
為什麼爬蟲要選 Playwright?
傳統爬蟲用 requests 抓 HTML,但遇到以下情況就會卡關:
- 網頁內容由 JavaScript 動態渲染(SPA、Vue、React 網站)
- 需要點擊、滾動、填表單才能觸發內容載入
- 網站會偵測「非瀏覽器」的請求並擋掉
Playwright 直接開一個真的瀏覽器去跑,這些問題迎刃而解。跟 Selenium 相比,它最大的優勢是不用另外下載驅動程式(不會遇到瀏覽器一更新驅動就失效的問題),而且自動等待機制讓腳本穩定很多。
| 比較項目 | requests | Selenium | Playwright |
|---|---|---|---|
| JS 動態渲染 | ❌ 拿不到 | ✅ | ✅ |
| 自動等待元素 | ❌ 手動 sleep | ⚠️ WebDriverWait | ✅ 內建 |
| 驅動程式管理 | 不需要 | ❌ 要自己下載 | ✅ 自動 |
| 反偵測難度 | 低 | 中 | 中高(可搭配 stealth) |
| 跨瀏覽器 | 不適用 | ✅ | ✅ Chromium/Firefox/WebKit |
安裝與初始化
pip install playwright
playwright install chromium # 下載 Chromium 瀏覽器(爬蟲最常用)
💡 不需要像 Selenium 那樣手動下載 chromedriver,
playwright install一次搞定,瀏覽器更新也不用擔心驅動失效。
核心架構:Browser、Context、Page
Playwright 是三層架構,理解它才能寫出乾淨的爬蟲:
graph TD
B[Browser 瀏覽器實例] --> C1[Context 1 登入態環境]
B --> C2[Context 2 匿名環境]
C1 --> P1[Page 頁面操作<br/>DOM / Network]
C2 --> P2[Page 頁面操作<br/>DOM / Network]- Browser:一個獨立的瀏覽器進程
- Browser Context:獨立的沙盒環境,各有自己的 Cookie、localStorage —— 可以同時處理登入態與匿名態,互不干擾
- Page:實際執行網頁操作、讀取 DOM 的標籤頁
第一個 Playwright 爬蟲
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# 啟動 Chromium(headless=False 可開視窗觀察)
browser = p.chromium.launch(headless=True)
context = browser.new_context(
viewport={"width": 1280, "height": 800}, # 模擬桌面視窗
locale="zh-TW",
)
page = context.new_page()
page.goto("https://example.com")
print(page.title()) # 取得頁面標題
browser.close()
Python 版同時支援同步與非同步(async)API,一般爬蟲用同步就夠了。
定位器(Locator):選元素的正確姿勢
Playwright 的定位器有「延遲求值」特性——定義時不會立刻查詢 DOM,而是執行操作時才解析,因此對動態內容特別友善。官方建議依循這個決策樹挑選定位方式:
get_by_role()— 按 ARIA 角色與名稱(按鈕、連結)get_by_label()— 表單欄位對應的 Label 文字get_by_placeholder()— 輸入框的提示文字get_by_text()— 畫面上的可見文字get_by_test_id()— 有data-testid屬性時- CSS / XPath — 最後手段,互動元素盡量避免
page.get_by_role("button", name="提交").click()
page.get_by_placeholder("請輸入帳號").fill("my_user")
page.get_by_text("登入成功") # 文字定位
page.locator("div.card > a.link") # CSS(不得已才用)
進階技巧:鏈式定位與過濾,適合處理重複結構(卡片、表格列):
# 鏈式定位:先鎖定卡片,再找裡面的按鈕
page.locator("div.card").get_by_role("button", name="詳情")
# 過濾:只找含特定文字的列
page.locator("tr").filter(has_text="待付款")
# 取第 N 個 / 第一個 / 最後一個
page.locator("li.item").first
page.locator("li.item").nth(2)
⚠️ Playwright 預設嚴格模式:如果定位器同時命中多個元素,操作時會直接報「Strict Mode Violation」。這是好事——它提醒你定位器寫得不夠精準,用鏈式、filter 或
.first解決。
等待機制:不要再寫 time.sleep()
Playwright 最大的賣點就是自動等待:執行 click()、fill() 等動作前,它會自動確認元素「已附加、可見、穩定(無動畫)、已啟用、沒被覆蓋」,全部通過才動手。所以絕大多數情況你不需要自己加等待。
需要等待特定條件時,用這些:
# 等某個元素出現或消失
page.wait_for_selector("div.result-item", state="visible")
# 等 URL 改變(適合表單提交後)
page.wait_for_url("**/dashboard")
# Web-first 斷言(會自動重試直到條件滿足)
from playwright.sync_api import expect
expect(page.get_by_text("儲存成功")).to_be_visible()
三個常見陷阱:
| 錯誤寫法 | 問題 | 正確做法 |
|---|---|---|
time.sleep(5) | 阻塞 Playwright 內部事件迴圈,狀態不同步 | page.wait_for_timeout(ms)(僅除錯用) |
wait_until="networkidle" | 現代網站有 analytics/WebSocket 背景請求,永遠等不到 | 明確等待目標 selector 出現 |
| 固定等待 5 秒「確保載入」 | 又慢又不可靠 | 等「明確的應用程式訊號」(元素/URL/API 回應) |
動態載入與無限滾動實戰
很多網站用「滾動觸發懶載入」或「按按鈕載入更多」。對策有三種,優先順序從高到低:
1. 攔截 API 回應(最穩定高效)
很多動態網站的資料來自後端 JSON API,直接攔截比解析 DOM 更快更穩:
with page.expect_response(lambda r: "/api/get_data" in r.url and r.status == 200) as resp:
page.get_by_role("button", name="載入更多").click()
json_data = resp.value.json() # 直接拿到結構化資料
2. 模擬滾動觸發載入
for _ in range(5):
page.mouse.wheel(0, 1500) # 向下滾 1500 像素
page.wait_for_timeout(800) # 給渲染一點時間(除錯用途可接受)
3. 等待目標元素出現
page.locator(".quote").first.wait_for(state="visible", timeout=15000)
完整範例:爬取 JS 渲染網站(已實測可跑)
quotes.toscrape.com/js 是官方提供的練習站,名言內容透過 JavaScript 動態渲染,非常適合驗證 Playwright 功力。以下範例我實測過,直接複製就能跑:
"""Playwright 爬蟲教學範例 — quotes.toscrape.com (JS 動態渲染版)"""
import csv
from playwright.sync_api import sync_playwright
BASE_URL = "https://quotes.toscrape.com/js/"
def main():
results = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
viewport={"width": 1280, "height": 800},
locale="zh-TW",
)
page = context.new_page()
page.goto(BASE_URL)
# 自動等待:等第一張卡片出現(Playwright 會自動重試直到可見)
page.locator(".quote").first.wait_for(state="visible", timeout=15000)
# 抓取第一頁所有名言
quotes = page.locator(".quote").all()
for q in quotes:
text = q.locator(".text").text_content().strip()
author = q.locator(".author").text_content().strip()
results.append({"text": text, "author": author})
print(f"「{text[:30]}...」 — {author}")
browser.close()
# 輸出 CSV(標準庫即可,不必裝 pandas)
with open("quotes.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["text", "author"])
writer.writeheader()
writer.writerows(results)
print(f"共儲存 {len(results)} 筆到 quotes.csv")
if __name__ == "__main__":
main()
執行結果(實測輸出):
「The world as we have created it is a pr...」 — Albert Einstein
「It is our choices, Harry, that show wha...」 — J.K. Rowling
「There are only two ways to live your li...」 — Albert Einstein
共儲存 10 筆到 quotes.csv
反偵測實戰:被 Cloudflare 擋怎麼辦?
愈來愈多網站(包括不少台灣電商、租屋平台)使用 Cloudflare 等防護。Playwright 預設會暴露一些特徵(例如 navigator.webdriver = true),會被快速識別為機器人。基本對策:
1. 偽裝環境參數
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
locale="zh-TW",
timezone_id="Asia/Taipei",
extra_http_headers={"Accept-Language": "zh-TW,zh;q=0.9,en;q=0.8"},
)
2. 使用 playwright-stealth 隱藏自動化特徵
pip install playwright-stealth
from playwright_stealth import stealth_sync
page = context.new_page()
stealth_sync(page) # 把 navigator.webdriver 等特徵偽裝成真實瀏覽器
3. 其他實用技巧
- 持久化 Context:用
launch_persistent_context(user_data_dir="./profile")保留 Cookie 與登入態,避免每次都是「全新機器人」 - Proxy 輪換:需要大量抓取時,在
launch(proxy={...})設定代理 - 模擬真人行為:
press_sequentially("關鍵字", delay=100)逐字輸入、隨機滾動間隔
⚠️ 誠實提醒:沒有「銀彈」能保證通過所有反爬蟲系統,Cloudflare Turnstile 這類 CAPTCHA 還是需要真人或付費解決服務。爬蟲務必遵守 robots.txt 與網站使用條款,控制請求頻率,做個有禮貌的爬蟲。
總結與下一步
Playwright 是目前 Python 爬蟲的首選工具,重點記住四件事:用語義化定位器(get_by_role 優先)、相信自動等待(少寫 sleep)、優先攔截 API 而非解析 DOM、被擋時先偽裝環境再談 stealth。
如果覺得手寫爬蟲太累,也可以看看 AI 時代的新玩法:用 LLM 直接從網頁提取結構化資料(LLM Scraper),或讓 AI 瀏覽代理幫你完成整個流程(browser-use)。但理解 Playwright 的底層原理,永遠是進階的基礎。
延伸閱讀
- LLM Scraper:使用 LLM 從網頁中提取結構化資料
- AI 瀏覽器自動化:代理式網頁控制的開源生態系
- Browser Use:開源 AI 代理框架,用於網頁瀏覽器控制
- Whisper 語音轉文字完整教學:OpenAI 免費開源工具,中文逐字稿與字幕一次搞定
- Python 自動化第一步:腳本編寫與 CLI 工具(第 41 章)
