1. 项目概述:为什么是Playwright?
如果你还在用Selenium或者Requests+BeautifulSoup的组合,吭哧吭哧地对付那些加载个价格都要等半天、点个按钮才能出数据的现代网站,那感觉就像拿着螺丝刀去修一台精密的智能手机——不是不行,是太费劲了。现代Web应用的反爬机制,早就不是简单的检查User-Agent那么简单了。它们会检测你的浏览器指纹、监控你的鼠标轨迹、分析你的请求频率,甚至能识别出你是不是在用自动化工具。这时候,一个能“以假乱真”的浏览器自动化工具,就成了破局的关键。
Playwright,这个由微软开源和维护的浏览器自动化库,就是为此而生的。它不像Selenium那样历史包袱重,也不像Pyppeteer那样生态单一。Playwright从设计之初就瞄准了现代Web开发的痛点:跨浏览器(Chromium, Firefox, WebKit原生支持)、自动等待、强大的网络拦截和修改能力,以及对移动端和单页应用(SPA)的完美支持。用它来写爬虫,你不再是和HTML源码斗智斗勇,而是在模拟一个真实用户的操作,网站反什么,你就演什么。这篇文章,我会带你从零开始,用Playwright打造一个能搞定主流动态反爬的智能爬虫,并附上可以直接抄作业的完整实战代码。无论你是爬虫新手想避开初学时的那些坑,还是老手在寻找更高效的解决方案,这里都有你想要的干货。
2. 核心武器库:Playwright的五大破防利器
在动手写代码之前,我们必须先理解Playwright凭什么能成为“反爬克星”。它的能力不是简单的“打开网页-点击元素”,而是一套组合拳。
2.1 跨浏览器原生支持与上下文隔离
Playwright最直观的优势是开箱即用的跨浏览器支持。你不需要为Firefox或Safari(WebKit)单独安装驱动,一个 playwright install 命令就全搞定了。这不仅仅是方便,更深层的意义在于“多样性”。有些反爬策略可能只针对Chrome(Chromium)的某些特征进行检测,这时切换到Firefox上下文可能就轻松绕过。
更重要的是 BrowserContext (浏览器上下文)的概念。你可以把它理解为一个独立的、隔离的浏览器会话。每个上下文都有独立的cookie、本地存储和缓存。在爬虫实践中,这意味着:
- 会话隔离 :你可以为每个任务或每个代理IP创建一个独立的Context,确保任务之间不会互相污染Cookie,也方便管理不同的登录状态。
- 指纹模拟 :在每个Context里,你可以定制化地设置User-Agent、视口大小、地理位置、语言偏好等,从而生成不同的浏览器指纹,降低被批量识别的风险。
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# 启动浏览器
browser = p.chromium.launch(headless=True) # 生产环境建议无头模式
# 创建两个完全隔离的上下文,模拟两个不同的用户环境
context1 = browser.new_context(
user_agent=\’Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 …\’,
viewport={\’width\’: 1920, \’height\’: 1080},
locale=\’zh-CN\’
)
context2 = browser.new_context(
user_agent=\’Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 …\’,
viewport={\’width\’: 1440, \’height\’: 900},
locale=\’en-US\’
)
# 两个Page互不干扰
page1 = context1.new_page()
page2 = context2.new_page()
2.2 智能自动等待:告别 time.sleep 的蛮荒时代
用过Selenium的人肯定对 WebDriverWait 和遍地开花的 time.sleep 深恶痛绝。等待时间短了元素还没加载出来,长了又极度影响效率。Playwright的等待是“智能”且“内置”的。
它的核心方法是 wait_for_selector 、 wait_for_function 以及导航时的 wait_until 选项。但更强大的是,像 click() , fill() 这类操作本身就会自动等待元素可交互(enabled and visible)。比如 page.click(\’button#submit\’) ,Playwright会一直等到这个按钮出现在DOM中、可见、且没有被禁用,才会去点击它。这几乎杜绝了因元素未就绪而导致的交互失败。
在爬虫中,我们最关心的是数据何时加载完毕。 page.goto(url, wait_until=\’networkidle\’) 这个参数是神器。 networkidle 会等待页面上几乎没有网络请求活动时(通常约500ms内无新请求)才认为导航完成,这对于等待AJAX数据加载完毕再抓取至关重要。当然,你也可以根据情况使用 domcontentloaded (DOM加载完成)或 load (所有资源加载完成)。
实操心得 :不是所有场景都适合 networkidle 。有些网站会有后台心跳包或WebSocket持续连接,导致 networkidle 永远等不到。这时更靠谱的策略是 wait_for_selector ,直接等待你关心的那个数据容器元素出现。例如: page.wait_for_selector(\’.product-list\’, state=\’visible\’, timeout=10000) 。
2.3 网络请求拦截与修改:直击数据源头
这是Playwright相比传统爬虫工具的降维打击能力。很多动态数据并非直接渲染在初始HTML里,而是通过XHR或Fetch请求获取的JSON。与其费力地去解析页面、模拟点击,不如直接“截获”这些API请求。
page.route() 方法允许你拦截、修改或模拟(mock)任何网络请求。你可以:
# 拦截所有图片请求并阻止加载,加速页面渲染
async def abort_images(route):
if route.request.resource_type == \”image\”:
await route.abort()
else:
await route.continue_()
await page.route(\”**/*\”, abort_images)
# 拦截特定API请求,并直接返回模拟数据
def handle_api(route):
if \”/api/data\” in route.request.url:
# 构建一个模拟的JSON响应
mock_response = {
\”status\”: \”success\”,
\”data\”: [{\”id\”: 1, \”name\”: \”Mock Item\”}]
}
route.fulfill(
status=200,
content_type=\”application/json\”,
body=json.dumps(mock_response)
)
else:
route.continue_()
page.route(\”**/api/*\”, handle_api)
2.4 丰富的元素定位器:更稳定,更语义化
Playwright提供了多种定位元素的方式,远比Selenium的 find_element_by_xxx 丰富和稳定。特别是它推崇使用面向用户的、语义化的定位器,这些定位器基于可访问性属性,比脆弱的CSS选择器或XPath更不容易因前端代码微调而失效。
- page.get_by_




