用 Playwright 抓取必须由 JavaScript 渲染才显示内容的网页,并自动点击“下一页”翻页。开发时用本机 Chrome 调试,上线时只改一个环境变量,就切换到 Bright Data Browser API 的远程浏览器运行。
先看清成品
成品是
quotes.py:打开练习站 quotes.toscrape.com/js(内容由 JS 生成,直接请求 HTML 拿不到),等待渲染、提取名言、作者和标签,翻页后保存为quotes.json。本文代码已用本机 Chrome 和 Playwright 1.64.0 实际运行:抓取 3 页共 30 条、内容无重复,第一条作者是 Albert Einstein。本文环境没有 Bright Data 账号,连接远程 Browser API 的部分没有实际运行,连接地址格式按官方示例编写。
注意:采集前先读目标网站的服务条款和 robots.txt,只采集公开、非个人的数据,控制频率,不绕过登录和付费墙,不采集个人信息。本文示例使用专门供练习的网站 toscrape.com。用于商业用途前请咨询法律意见,并遵守所在地关于数据和个人信息保护的法律。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
| JS 渲染抓取 | 等元素出现后再提取 |
| 自动翻页 | 点击“Next”直到最后一页或页数上限 |
| 双模式 | 本机 Chrome 调试,Browser API 上线 |
| JSON 输出 | 中文不转义,方便查看 |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| Bright Data 账号 | 新账号每月有 5,000 免费额度 | 以官方免费额度页面为准,额度用完会停止,不会自动扣费 |
| Python | 3.10 或更高 | 建议使用虚拟环境 |
| Playwright | pip install playwright |
本文使用 1.64.0 |
| 本机浏览器 | 已安装 Chrome,或执行 playwright install chromium |
本地调试用 |
官方免费额度页面说明 Browser API 也使用每月免费额度,按流量计费(每 MB 5 个额度)。
第一步:为什么需要浏览器?
先确认页面确实需要渲染:
curl -s https://quotes.toscrape.com/js/ | grep -c 'class="quote"'
输出 0,说明 HTML 里没有内容,名言是浏览器执行 JS 后才生成的。这时 Web Unlocker 这类“拿 HTML”的方式不够用,需要真正的浏览器。
第二步:怎样准备 Browser API?
在控制台添加 Browser API 区域,Overview 里能看到用户名(形如 brd-customer-xxx-zone-yyy)和密码。官方示例的连接地址是:
wss://用户名:密码@brd.superproxy.io:9222
第三步:代码怎么写?
1. 项目结构
js-scraper/
├── quotes.py
└── quotes.json # 运行后生成
2. quotes.py
"""抓取需要 JavaScript 渲染的页面(练习站 quotes.toscrape.com/js),支持翻页。
配置 BRIGHTDATA_BROWSER_AUTH 时连接 Bright Data Browser API;否则用本机 Chrome 调试。
用法:python quotes.py --pages 3
"""
import argparse
import asyncio
import json
import os
from playwright.async_api import async_playwright
START = "https://quotes.toscrape.com/js/"
async def open_browser(p):
auth = os.environ.get("BRIGHTDATA_BROWSER_AUTH") # 形如 brd-customer-xxx-zone-yyy:密码
if auth:
return await p.chromium.connect_over_cdp(f"wss://{auth}@brd.superproxy.io:9222")
return await p.chromium.launch(channel=os.environ.get("LOCAL_CHANNEL", "chrome"), headless=True)
async def scrape(max_pages: int):
results = []
async with async_playwright() as p:
browser = await open_browser(p)
page = await browser.new_page()
await page.goto(START, timeout=120_000)
for n in range(1, max_pages + 1):
await page.wait_for_selector("div.quote") # 等 JS 把内容渲染出来
items = await page.eval_on_selector_all(
"div.quote",
"""els => els.map(e => ({
text: e.querySelector('.text').innerText,
author: e.querySelector('.author').innerText,
tags: [...e.querySelectorAll('.tag')].map(t => t.innerText)
}))""",
)
results.extend(items)
print(f"第 {n} 页:{len(items)} 条")
nxt = page.locator("li.next > a")
if n == max_pages or await nxt.count() == 0:
break
await nxt.click()
await page.wait_for_load_state("domcontentloaded")
await browser.close()
return results
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--pages", type=int, default=2)
ap.add_argument("--out", default="quotes.json")
a = ap.parse_args()
data = asyncio.run(scrape(a.pages))
with open(a.out, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"共 {len(data)} 条,已保存到 {a.out}")
if __name__ == "__main__":
main()
| 要点 | 说明 |
|---|---|
connect_over_cdp |
连接远程浏览器,官方示例同样使用这个方法 |
channel="chrome" |
本地直接用已安装的 Chrome |
wait_for_selector |
不要用固定等待时间,等元素出现最可靠 |
eval_on_selector_all |
在浏览器里一次性提取,减少往返 |
| 页数上限 | 防止意外翻页过多消耗额度 |
第四步:怎样运行?
1. 本机调试
python3 -m venv .venv && source .venv/bin/activate
pip install playwright
python quotes.py --pages 3
没有安装 Chrome 时,先执行 playwright install chromium,再用 LOCAL_CHANNEL=chromium python quotes.py。
本文实际输出:
第 1 页:10 条
第 2 页:10 条
第 3 页:10 条
共 30 条,已保存到 quotes.json
2. 切换到 Browser API
export BRIGHTDATA_BROWSER_AUTH='brd-customer-xxx-zone-yyy:你的密码'
python quotes.py --pages 3
代码其他部分不用改。
3. 检查结果
| 检查项 | 期望 |
|---|---|
quotes.json |
30 条,每条有 text、author、tags |
| 无重复 | 翻页成功时每页内容不同 |
| 中文环境 | 文件直接可读 |
出问题时该怎么调?
1. 报 Executable doesn't exist
本机没有 Chrome,Playwright 也没下载浏览器。执行 playwright install chromium 并设置 LOCAL_CHANNEL=chromium。
2. wait_for_selector 超时
| 原因 | 解决方法 |
|---|---|
| 选择器写错 | 用浏览器开发者工具确认 |
| 页面加载慢 | 加大超时,例如 timeout=60000 |
| 被拦截显示验证页 | 截图看看:await page.screenshot(path="debug.png") |
3. 远程连接失败
检查用户名密码是否是 Browser API 区域的(不是 Web Unlocker 区域),以及网络是否允许连接 9222 端口。
常见问题怎么处理?
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 每页数据相同 | 点了下一页但没等新内容 | 等待 URL 或元素变化 |
| 额度消耗快 | 加载了图片和视频 | 只抓需要的页面,控制页数 |
| 本地正常远程失败 | 远程会话有时长限制 | 拆成多个较短任务 |
| 输出为空列表 | 选择器不匹配 | 先在本地有界面模式(headless=False)观察 |
| 需要登录才能看 | 涉及账号 | 不要用于绕过登录,遵守网站条款 |
项目资源
| 用途 | 地址 |
|---|---|
| Browser API 介绍 | https://docs.brightdata.com/products/scraping-browser/introduction |
| Browser API 代码示例 | https://docs.brightdata.com/products/scraping-browser/code-examples |
| Browser API 错误码 | https://docs.brightdata.com/products/scraping-browser/error-codes |
| 免费额度 | https://docs.brightdata.com/general/account/billing-and-pricing/free-tier |
| Playwright Python | https://playwright.dev/python/docs/intro |
| 练习站 Quotes to Scrape(JS 版) | https://quotes.toscrape.com/js/ |
建议先在本机跑 python quotes.py --pages 1,确认能拿到 10 条,再加大页数。
本地完全跑通后,再设置 BRIGHTDATA_BROWSER_AUTH 用远程浏览器跑一页,对比结果一致后再正式使用。