如果你玩过抓取,大概率都经历过这个阶段:
一开始写个 Playwright 脚本,点两下、滚一滚、打印个标题,觉得「真香」;
但当要跑几百个任务、几千个页面时,才发现这玩意开始卡、崩、被封、甚至浏览器不关干净。
这篇文章就来讲讲——Playwright 从“能跑”到“能撑”的那条进化路:
从单机脚本到分布式调度,我们到底要补上哪些坑。
整篇文章分成五个部分:
错误示例 → 正确姿势 → 原因解释 → 陷阱提示 → 模板推荐。
属于那种踩过坑的人写给后来人的实战笔记。
一、最常见的误区:单机脚本的「幻觉稳定」
很多人第一次接触 Playwright 的时候,写出来的代码大概是这样👇
from playwright.sync_api import sync_playwright
def scrape(url):
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url)
print(page.title())
browser.close()
scrape("https://www.example.com")
跑一下,输出正常。
于是信心满满地开始爬几十、几百个 URL。
然后,灾难就来了:
- IP 被封,返回 403 或直接空白页。
- 浏览器实例一堆 zombie process,CPU 直接打满。
- 任务出错就全盘崩溃,没有重试机制。
- 想看日志或重放失败页面?抱歉,没有任何监控。
这种“看似能跑”的脚本,其实就是个演示级 demo。
真要放到企业项目里,它几乎撑不过一天。
二、正确打开方式:Playwright + 代理 + 调度
要让 Playwright 稳定跑在生产环境里,必须把它“架构化”起来。
至少要具备这几个要素:
- 有代理池,防止封禁。
- 有任务队列,能重试、能分发。
- 有浏览器池,控制资源、避免内存炸裂。
- 有调度器,统一管理任务、监控执行情况。
下面这段代码,是一个相对正确的“工程级”写法。
用了 Playwright + 异步协程 + 爬虫代理IP,模拟了一个简单的任务队列采集系统。
实战代码:Playwright + 爬虫代理 + 异步采集
"""
Playwright 企业采集模板(百度百科版)
支持:代理IP、任务队列、异常控制、内容提取
"""
import asyncio
from playwright.async_api import async_playwright
# ==== 亿牛云爬虫代理配置(示例) ====
PROXY_HOST = "proxy.16yun.cn" # 代理域名
PROXY_PORT = "12345" # 代理端口
PROXY_USER = "16YUN" # 代理用户名
PROXY_PASS = "16IP" # 代理密码
# ==== 模拟任务队列 ====
# 可以替换为数据库、Redis 或 MQ 队列,这里简单用列表代替
BAIKE_URLS = [
"https://baike.baidu.com/item/人工智能",
"https://baike.baidu.com/item/Python",
"https://baike.baidu.com/item/量子计算",
"https://baike.baidu.com/item/云计算",
"https://baike.baidu.com/item/深度学习",
]
# ==== 页面采集逻辑 ====
async def fetch_baike_page(playwright, url):
# 代理配置(Playwright 需要完整代理URL)
proxy = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
# 启动浏览器(开启代理 + 无头模式)
browser = await playwright.chromium.launch(
headless=True,
proxy={"server": proxy}
)
try:
page = await browser.new_page()
# 设置 UA 和超时,防止被识别
await page.set_extra_http_headers({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
})
# 打开页面
await page.goto(url, timeout=30000)
# 等待主要内容加载完成
await page.wait_for_selector(".lemma-summary", timeout=10000)
# 提取标题与简介
title = await page.title()
summary = await page.locator(".lemma-summary").inner_text()
print(f"\n[✅] {url}")
print(f"标题:{title}")
print(f"简介:{summary[:100]}...") # 只显示前100字防止过长
except Exception as e:
print(f"[❌] 抓取失败:{url},错误原因:{e}")
finally:
await browser.close()
# ==== 主入口 ====
async def main():
async with async_playwright() as p:
# 并发执行多个任务
tasks = [fetch_baike_page(p, url) for url in BAIKE_URLS]
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())
这段代码其实已经具备了企业架构的雏形:
- 代理IP防护
- 异步并发采集
- 异常处理和资源释放
- 多任务同时执行
虽然它还不是真正的分布式架构,但已经能抗住中等规模的采集量。
三、为什么这种写法更靠谱?
说白了,就是让系统更“可控”。
以前那种单机脚本,是“凭运气跑”;
而这个版本是“按机制跑”。
- 代理 IP:用来切换出口,防止被网站识别。
- asyncio 并发:让 CPU 和网络 I/O 同时跑,性能比同步模式高一个数量级。
- 异常捕获:不怕某个任务崩掉,全局还能继续。
- 浏览器上下文隔离:每个任务一个 page,互不影响,减少干扰。
当这些基础都做完之后,你再往上接入 Redis 队列、Kafka 调度、Prometheus 监控,这个架构就自然能长成“企业级采集系统”。
四、那些容易忽略的坑
我见过太多采集项目,死在一些“微不足道”的细节上。
下面这几点真是血泪经验:
- 不要一次开太多浏览器。
Playwright 不是轻量线程,它是真浏览器。建议维护浏览器池(固定几个实例,复用 Page)。 - 不要频繁切代理。
很多新手以为“换得越勤越安全”,其实相反。代理握手频繁,反而容易连不上。一般每 5~10 次请求换一次就够。 - 要有日志。
出错的时候能回溯,是生产环境生存的底线。哪怕只是简单的 print,也比什么都没有强。 - 动态页面要等待。
别一打开就抓内容,很多网站前端数据是延迟加载的。page.wait_for_selector()是好朋友。 - 分布式节点要有心跳。
不然调度器会以为任务执行中,其实 worker 已经挂掉。
这些看似细碎的“工程卫生”,恰恰决定了采集系统能不能跑得稳。
五、从“能跑”到“能撑”的那一步
Playwright 本身非常强大,它的稳定性、协议兼容性、脚本语法都很现代。
但它只是底层引擎,真正让它强大的,是外围那套生态:
代理、队列、调度、日志、监控。
换句话说,Playwright 是发动机,但你还得有底盘、油路、仪表盘,才能跑出长距离。
在企业项目中,我们通常会这么搭:
- 上层:调度系统(Kubernetes / Airflow / Celery)
- 中层:任务队列(Redis / Kafka)
- 底层:浏览器池 + 代理池
- 数据层:结果入库(MongoDB / ClickHouse / Elasticsearch)
这样的组合,就能让采集系统具备:
高并发、自动恢复、抗封禁、可追踪、可复现。
六、最后的感想
我一直觉得 Playwright 是个“上限很高”的工具。
你可以用它写个 20 行的小脚本,也可以撑起一个分布式采集平台。
差别就在于:你是把它当工具,还是当架构核心。
真正的企业级采集,不在于能不能抓到数据,而在于——
能不能一直抓、稳定抓、合规抓。
所以别小看一行代理配置、一个异常捕获或一个任务重试逻辑。
这些看似琐碎的部分,决定了你的系统是“能跑一会儿”,还是“能撑很久”。