如果你做浏览器采集做得久了,大概率经历过跟我一样的阶段:从纯请求库 → Puppeteer → 对抗反爬 → 疯狂踩坑 → 架构越来越难维护。
我一直把 Puppeteer 当成主力工具,但这两年采集任务变得越来越复杂,光靠 Chromium + 几个 stealth 插件,真的撑不住了。
于是,我开始把整个框架搬到 Playwright 上。没想到的是,它于我不仅是“替代品”,而是一个彻底重新定义框架结构的契机。
下面我就把整套迁移过程拆开讲,主要从几个方面展开:
- 迁移后整个系统是什么形态?
- 各模块都换了什么?解决了 Puppeteer 时代哪些痛点?
- 关键代码怎么写?
- 采集任务从开始到结束是怎样跑完一整圈的?
整篇文章适合你正在评估“要不要从 Puppeteer 跳出来”。
一、框架长什么样?
1)旧框架(Puppeteer时代)大概是这样:
- 一个单一的 Chromium 引擎
- 浏览器实例多了就开始卡、崩、互相抢资源
- 反检测要靠各种插件和 hack
- Cookie、UA、Headers 每次都得手动 patch
- 浏览器池形同虚设,稳定性靠玄学
你想扩展?你得对它“发明”东西。
2)迁移到 Playwright 后的框架变成这样:
- 可以随时切 Chromium / Firefox / WebKit
- context 隔离特别干净,每个任务像住单独房间
- 反检测能力比 Puppeteer 天生更稳
- 代理、同步/异步、网络控制都更顺手
- 多浏览器池真的能跑起来而不是“理论支持”
一句话:
以前是把各种功能“拼”到 Puppeteer 上;现在是 Playwright 把这些能力“送”给我。
二、模块拆解:每一块为什么要重写?
下面按照模块逐个拆开讲,说说“为何要换、换了之后有什么变化”。
1. 浏览器调度:终于不用靠玄学分配实例了
Puppeteer 的浏览器实例管理,对高并发永远是灾难:
- 要么内存爆炸
- 要么上下文串数据
- 要么任务互相抢页面
切到 Playwright 最大的感受是:
它的 browser → context → page 层级,天然就是为多实例环境设计的。
上下文隔离得非常干净,登录状态、Cookie、UA 互不影响,我可以放心开几十个上百个任务。
2. 代理模块:从“能用”变成“顺滑”
Puppeteer 配代理要各种配置,Playwright 则非常直觉。
我把爬虫代理塞进去只要这样:
# ====爬虫代理配置(示例)====
proxy={
"server": f"http://{PROXY_HOST}:{PROXY_PORT}",
"username": USERNAME,
"password": PASSWORD
}
没有任何额外补丁。
你会突然意识到,过去那些奇怪的配置很可能不应该由你来搞。
3. 反检测模块:终于不再和 webdriver 打游击
Puppeteer 时代我靠:
- 修改 navigator.webdriver
- 注入各种混淆脚本
- 使用 stealth 插件
- patch 浏览器源码
现在 Playwright 隐蔽性自带 buff,我只需要:
context.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
""")
可控性大大增强。
4. 采集执行层:任务隔离彻底变得清爽
以前每个任务之间“多少都会有点关系”,要么共享 session,要么 UA 不一致,要么 cookies 串掉。
Playwright 直接给你一个:
“每个任务有自己的小世界(context)”
这对采集框架来说太关键了。
三、核心代码拆解
下面是一个可以直接运行的示例模板,我自己在框架里就是由它扩展而来。
import asyncio
from playwright.async_api import async_playwright
# ==== 爬虫代理配置 参考16YUN====
PROXY_HOST = "proxy.16yun.cn" # 代理域名
PROXY_PORT = "3100" # 代理端口
USERNAME = "your_username" # 代理用户名
PASSWORD = "your_password" # 代理密码
TARGET_URL = "https://www.example.com"
async def run():
async with async_playwright() as p:
# 1. 启动浏览器(挂上代理)
browser = await p.chromium.launch(
headless=True,
proxy={
"server": f"http://{PROXY_HOST}:{PROXY_PORT}",
"username": USERNAME,
"password": PASSWORD
}
)
# 2. 创建 context(隔离 UA + Cookie)
context = await browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0 Safari/537.36",
extra_http_headers={
"Accept-Language": "zh-CN,zh;q=0.9"}
)
# 注入基础反检测脚本(隐藏 webdriver)
await context.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
""")
# 3. 打开页面
page = await context.new_page()
await page.goto(TARGET_URL, timeout=30000)
print("页面标题:", await page.title())
print("HTML 大小:", len(await page.content()))
await browser.close()
asyncio.run(run())
代码核心要点:
Playwright 的 proxy 是真正的“一步到位”。
context = 小型隔离世界,不会串号。
反检测脚本注入优雅很多,不用 plugin 乱 patch。
四、任务执行全过程
如果让我来描述采集任务在框架里的完整生命周期,那大概是这样:
- 任务从任务管理器流进来。
它被标记好采集目标、规则、优先级。 - 浏览器调度器挑出一个空闲浏览器实例。
不会乱分配,因为 Playwright 的实例独立性很好。 - 代理模块接管网络出口,塞入爬虫代理账号和密码。
这一步也不再绕远路。 - 新 context 被创建出来。
它有自己独立的 Cookie、UA、Headers,和其他任务毫无关系。 - 反检测模块注入脚本,让浏览器看起来更“像真人”。
- 采集执行层开始跑任务。
页面加载、元素等待、数据提取……
全部在这个 context 里完成。 - 解析模块把内容结构化,存储模块将数据丢进数据库或 MQ。
- 任务结束,context 被销毁,房间被清空。
你会明显感觉到:
Playwright 让“过程”变得自然,不再需要你一直打补丁。
总结:为什么最终我还是“搬家”了?
说到底,切换 Playwright 的理由其实很现实:
✔ 稳定性比 Puppeteer 强太多
✔ 并发隔离是真正可控的
✔ 反检测能力不再靠民间补丁
✔ 代理注入简单到不能再简单
✔ 三内核(Chromium、Firefox、WebKit)任意用
✔ 架构变得真正可扩展
迁移框架本身不难,难的是你什么时候真正下决心。不夸张地说,Playwright 对我这个采集框架的升级贡献巨大。