Playwright 网络爬取已成为需要从只在 JavaScript 运行后才组装内容的页面中提取数据的首选方案。Playwright 由 Microsoft 开发,驱动真实浏览器,像真人一样等待内容加载,并通过一致的 API 支持 Chromium、Firefox 和 WebKit。这种组合使其远比大多数爬虫最初使用的旧版自动化工具更加稳定可靠。
本指南是 Python 中 Playwright 网络爬取的实操教程,在 API 有差异的地方附有简短的 Node 示例。你将安装 Playwright 及其浏览器,启动无头 Chromium,导航至页面,等待合适的选择器,提取文本和属性,处理"加载更多"交互和翻页,截取屏幕截图,以及捕获 JSON 网络响应。最后我们将诚实地面对实际运维问题:Playwright 在大规模爬取时仍会被封锁,以及托管渲染轮换服务真正发挥作用的场景。
为何选择 Playwright 而非旧版自动化工具
如果你曾用 Selenium 或原始 Puppeteer 编写爬虫,在 Playwright 中第一个注意到的就是那些不稳定的 sleep() 调用消失了。这背后有几个设计决策功不可没。
- 自动等待。在 Playwright 点击、填写或读取某个元素之前,它会等待该元素附加到 DOM、可见、稳定且可操作。你无需在代码中随处添加任意延迟,由此产生的爬虫在页面加载较慢或有动画时可靠性大幅提升。
- 三种浏览器引擎,一套 API。同一个脚本可以在 Chromium、Firefox 或 WebKit 上运行。当某个网站在一种引擎下表现异常,你只需改一个词就能切换,无需重写驱动配置。
- 健壮的选择器。除 CSS 和 XPath 之外,Playwright 还提供惰性求值的定位器和文本选择器,在操作时重新查询 DOM,因此能够在重新渲染时仍保持有效,而缓存的元素句柄则会失效。
- 异步设计。该 API 围绕异步 I/O 构建,当你需要扩展规模时,可以在单个浏览器进程中自然地并行处理多个页面。
关于为何有时必须使用真实浏览器的背景知识,请参阅用于网络爬取的无头浏览器。如果你已有 Selenium 技术栈并希望进行并排比较,用 Selenium 和 BeautifulSoup 爬取动态内容涵盖了该方案。
前置条件
在编写代码之前你需要三样东西,每样都不需要太长时间。
Python 3.8 或更高版本。用 python --version 确认你的版本。Playwright 还提供一流的 Node.js 绑定供你选择 JavaScript;本指南中的概念一一对应,稍后会出现一个简短的 Node 示例。
熟悉选择器。你应该能够打开浏览器开发者工具,审查元素,并读出 CSS 选择器。一旦页面渲染完成,提取数据主要就是选择器练习。
有权限爬取的目标网站。使用条款允许爬取的站点,仅限公开数据,并遵守 robots.txt 及合理的速率限制。这里介绍的技术是通用的;你负责决定将它们指向哪里。
安装 Playwright 及其浏览器
创建一个虚拟环境以隔离依赖项,安装 Playwright 包,然后运行其安装程序以下载浏览器二进制文件。第二步是人们常常忘记的;仅有 pip 包并不包含浏览器。
python -m venv pw_env source pw_env/bin/activate pip install playwright playwright install chromium
在 Windows 上,用 pw_env\Scripts\activate 代替 source 那行来激活环境。playwright install chromium 命令会下载一个固定版本的 Chromium;不传参数则会获取所有三种引擎。如果你看到关于缺少可执行文件的错误,几乎总是意味着这个安装步骤被跳过了。
启动浏览器并打开页面
从最小的实用脚本开始:启动无头 Chromium,打开页面,导航至某个 URL,并读取标题。同步 API 使第一个示例保持可读性;当扩展规模重要时我们再切换到异步。
from playwright.sync_api import sync_playwright def main(): with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto("https://quotes.toscrape.com/js/") print(page.title()) browser.close() if __name__ == "__main__": main()
关于这里的几个选择:headless=True 以无可见窗口的方式运行,这正是无人值守任务所需要的;在开发时将其改为 False 可以看到浏览器的工作过程。所选 URL 是一个特意设计的 JavaScript 渲染演示页面:只有在脚本运行后,引用语才会出现,这正是纯 HTTP 请求返回空容器而 Playwright 大显身手的情况。
对于一次性之外的任何场景,请在 new_page() 之前用 browser.new_context() 创建一个浏览器上下文。上下文是具有自己的 cookie、存储和 user agent 的隔离会话,因此你可以运行多个独立页面而不会相互干扰状态。如上所示直接调用 new_page() 会使用默认上下文,对于单页面来说没问题。
等待选择器,然后提取文本和属性
这是 Playwright 网络爬取的核心。与其猜测页面需要多长时间,不如等待标志数据已就绪的具体元素,然后读取它。Playwright 的定位器会自动等待,因此单次调用即可完成等待和选择两个步骤。
from playwright.sync_api import sync_playwright def scrape_quotes(url): with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto(url) page.wait_for_selector("div.quote") results = [] for quote in page.query_selector_all("div.quote"): text = quote.query_selector("span.text").inner_text() author = quote.query_selector("small.author").inner_text() link = quote.query_selector("a").get_attribute("href") results.append({"text": text, "author": author, "link": link}) browser.close() return results
最关键的一行是 page.wait_for_selector("div.quote")。它会阻塞直到 DOM 中至少存在一个引用语元素,这意味着 JavaScript 已经运行且数据已就位。之后,query_selector_all 返回所有匹配的元素,inner_text() 和 get_attribute() 分别提取文本和属性。从锚点读取 href 展示了属性情况;读取引用语和作者展示了文本情况。代码中没有任何固定延迟。
处理"加载更多"点击和翻页
真实目标很少一次显示所有内容。两种模式涵盖了大多数情况:就地追加内容的"加载更多"按钮,以及交换页面的编号或"下一页"翻页。Playwright 两者都能处理,因为它可以点击然后等待结果。
对于就地"加载更多"按钮,在循环中点击它直到它消失,每次点击后等待新内容稳定。
def load_all(page): while True: button = page.query_selector("button.load-more") if not button or not button.is_visible(): break button.click() page.wait_for_load_state("networkidle")
对于经典翻页,跟随"下一页"链接直到它消失,同时逐页爬取数据。因为定位器在每次调用时都重新查询 DOM,所以你不必担心导航后的句柄过期问题。
def scrape_all_pages(page, url): page.goto(url) rows = [] while True: page.wait_for_selector("div.quote") for q in page.query_selector_all("div.quote span.text"): rows.append(q.inner_text()) next_link = page.query_selector("li.next a") if not next_link: break next_link.click() return rows
注意第一个代码片段中的 wait_for_load_state("networkidle"):它等待到短暂时间内没有进行中的网络请求,这是延迟加载内容已到达的良好信号。在触发后台请求的操作之后使用它。
截取屏幕截图
截图对于调试返回空结果的爬虫以及存档页面在捕获时的样子非常有用。Playwright 默认捕获可见视口,或者通过一个标志捕获完整的可滚动页面。
page.screenshot(path="page.png", full_page=True)
当某次运行没有返回数据时,在提取之前立即拍摄的整页截图通常能在几秒内告诉你原因:一个 cookie 弹窗、一个 CAPTCHA,或者一个封锁页面占据了内容应有的位置。
捕获网络和 JSON 响应
通常最干净的数据根本不在 HTML 中,而是在页面在后台调用的 JSON API 中。与其解析渲染后的标记,不如监听网络响应并直接获取该 JSON。这比爬取 DOM 更快、更不易出错,因为 API 的结构变化频率远低于布局。
captured = [] def on_response(response): if "/api/" in response.url and response.ok: try: captured.append(response.json()) except Exception: pass page.on("response", on_response) page.goto("https://example.com/listings") page.wait_for_load_state("networkidle")
page.on("response", ...) 钩子会对每个网络响应触发。按 URL 片段过滤可以隔离你关心的调用,response.json() 会帮你解析响应体。首先在开发者工具的 Network 标签页中找出哪个接口携带数据,然后在这里匹配它。如果某个网站大量使用这些 XHR 调用,请参阅如何用 Python 爬取 JavaScript 页面了解更多 API 优先方法。
Node.js 中的相同脚本
如果你的技术栈是 JavaScript,Node 绑定几乎与 Python 完全一致。方法名相同,一切都基于 promise,安装浏览器的方式也相同,使用 npx playwright install chromium。
const { chromium } = require("playwright"); (async () => { const browser = await chromium.launch({ headless: true }); const page = await browser.newPage(); await page.goto("https://quotes.toscrape.com/js/"); await page.waitForSelector("div.quote"); const texts = await page.$$eval("div.quote span.text", els => els.map(e => e.textContent)); console.log(texts); await browser.close(); })();
Python 中的 wait_for_selector 变成了 waitForSelector,$$eval 在页面中运行一个函数以一次性提取多个元素。选择你的团队已经维护的语言;爬取逻辑是相同的。
隐身现实:Playwright 仍会被封锁
这是大多数教程跳过的部分。驱动真实浏览器解决了渲染问题,但这并不会让你隐身。现代反爬虫系统关注的远不止 JavaScript 是否运行。它们对浏览器进行指纹识别,检查 TLS 和 HTTP/2 签名,对行为信号评分,并按 IP 进行速率限制。原始无头 Playwright 运行会留下痕迹,在任何真实规模下更大的问题是你的 IP:少数几个数据中心地址频繁访问同一主机会被很快标记。
你可以应对这些问题。人们会添加隐身插件,随机化 user agent 和视口,放慢请求速度,并接入代理池。每种方法都有帮助,每种方法也都带来维护负担。运行一组无头浏览器本身就是运维开销:它们消耗大量内存,会崩溃,需要固定浏览器版本,在多台机器上并行运行是真正的基础设施工作。在此之上还要维护一个健康的轮换代理池,坦率地说,这才是大部分工作所在。
关于保持不被封锁的深度攻略,请参阅如何在爬取网站时不被封锁。
当 Playwright 在规模扩大时开始遭遇封锁,Crawling API 会接手困难的部分。它在真实浏览器中渲染页面,并通过服务端轮换住宅 IP 路由请求,然后通过单次调用将完成的 HTML 或解析后的数据交给你,让你无需自己运行无头浏览器集群和代理池。对于真正需要驱动程序的重交互流程,你仍然可以在本地保留 Playwright。
托管 API 适用的场景,以及 Playwright 仍然胜出的场景
这不是 Playwright 与托管 API 之间的对立,而是知道哪种工具适合哪种任务。当你的瓶颈是封锁、CAPTCHA 或 IP 信誉时,当你需要爬取大量页面且不想自己运维浏览器和代理基础设施时,或者当你只是需要可靠地大量返回渲染后的 HTML 时,就应该使用 Crawling API。由于渲染和轮换在服务端进行,你只需发出一个简单请求并解析结果,无需看管任何集群。
当任务真正是交互式的时候,请在本地保留 Playwright:多步骤表单、你控制的登录账户后的认证流程、拖放、文件上传,或任何你需要精确编排一系列用户操作并观察结果的情况。两者可以很好地结合使用。许多团队在 Playwright 中进行原型开发并处理重交互流程,然后一旦封锁成为限制因素,就通过托管 API 路由大量抓取流量。如果你想要 IP 轮换作为直接可用的端点同时保留自己的浏览器,Smart AI Proxy 通过标准代理接口为你提供住宅轮换。
核心要点
- Playwright 解决了不稳定性。自动等待、一套 API 背后的三种浏览器引擎、惰性定位器和异步特性使其比旧版驱动程序在渲染页面时更可靠。
-
先等待,再提取。使用
wait_for_selector确认数据已渲染,然后用inner_text()读取文本,用get_attribute()读取属性。 -
原生点击和翻页。循环点击"加载更多"按钮直到它消失,或跟随"下一页"链接,在触发数据获取的操作后等待
networkidle。 -
尽可能获取 JSON。监听
page.on("response", ...)等待后台 API 调用,比解析 DOM 更快且更不易出错。 - 渲染不等于隐身。Playwright 在规模扩大时仍会被指纹识别和 IP 封锁;托管渲染加轮换 API 消除了集群和代理开销,而 Playwright 仍然是重交互本地流程的理想选择。
常见问题
Playwright 适合网络爬取吗?
适合。Playwright 驱动真实浏览器,因此能处理纯 HTTP 请求无法处理的 JavaScript 渲染页面。其自动等待消除了困扰旧版工具的大多数时序不稳定问题,它通过一套 API 支持 Chromium、Firefox 和 WebKit,其惰性定位器在重新渲染时仍能存活。对于重交互或客户端渲染的目标,它是目前最强的选项之一。
我应该将 Playwright 与 Python 还是 Node.js 一起使用?
两者都可以;API 在两种语言中几乎完全相同。方法名仅在大小写上有所不同(Python 中的 wait_for_selector 在 Node 中变为 waitForSelector),两者都通过单个命令安装浏览器。选择你的团队已经在维护的语言,让爬虫融入你现有的技术栈。
如何在 Playwright 中等待内容加载?
用 page.wait_for_selector("your.selector") 等待标志数据已就绪的具体元素,该方法会阻塞直到该元素存在。对于点击触发的后台数据获取,使用 page.wait_for_load_state("networkidle") 等待网络活动平息。避免固定延迟;Playwright 的自动等待和这些显式等待更可靠。
使用 Playwright 爬取时会被封锁吗?
会。运行真实浏览器解决了渲染问题,但不能解决检测问题。反爬虫系统对浏览器进行指纹识别,检查网络签名,并按 IP 进行速率限制,因此原始无头运行会被标记,数据中心 IP 在大量请求时会被封锁。放慢速度、随机化指纹和轮换住宅 IP 都有帮助;托管 Crawling API 将渲染和轮换结合在一起,让你无需自己维护该技术栈。
如何用 Playwright 捕获 API 或 JSON 数据?
用 page.on("response", ...) 附加一个处理器,通过 URL 片段过滤响应找到携带数据的接口,然后对其调用 response.json()。首先在浏览器开发者工具的 Network 标签页中识别正确的调用。读取底层 JSON 比解析渲染后的 HTML 更快,也更不易出错。
何时应该使用 Crawling API 而不是 Playwright?
当封锁、CAPTCHA 或 IP 信誉成为瓶颈时,或者当你需要爬取大量页面且不想运行浏览器和代理基础设施时,切换到 Crawling API。它在服务端渲染并轮换 IP,通过单次调用返回完成的 HTML。对于真正的交互式本地流程(如多步骤认证表单),保留 Playwright,并通过 API 路由大量抓取流量。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

