Python爬虫进阶:Playwright请求拦截(Request Interception)与动态代理IP实战
·
前言
大家好,在日常的爬虫开发和自动化抓取中,我们经常会遇到一些让人头疼的场景。比如目标网站加载了大量无关的图片和视频拖慢了抓取速度,或者通过检测请求头和前端特征来封禁我们的机器。今天,我们就来深入探讨一下 Playwright 的一项高级杀手锏——请求拦截(Request Interception),并结合爬虫代理,实现高效、高并发、防屏蔽的工业级爬虫。
一、 为什么我们需要请求拦截?
在实际的爬虫抓取场景中,我们经常会面临以下痛点:
- 资源浪费:目标网站请求了额外的静态资源(如图片、CSS、大字体文件),严重拖慢了页面加载速度,浪费了宝贵的带宽。
- 认证与状态管理:登录态 token 藏在请求头里,需要在每次请求发送前动态注入。
- 代理动态切换:使用代理 IP 抓取时,需要考虑代理认证信息如何正确附加,以及如何强制隧道代理切换 IP。
- 绕过前端反爬:前端包含大量探针(如 navigator.webdriver),如果不在请求层面或加载前拦截,极易被识别。
这些问题的核心诉求就是:在请求发出去之前或响应回来之前,能够拦截并修改它。
二、 Playwright 请求拦截的核心机制
Playwright 实现请求拦截主要依赖两个核心对象:
- RouteRoute 对象:代表一个待处理的请求,它赋予了我们控制请求后续走向的权力,常见的方法有放行 continue()、伪造响应 fulfill() 以及直接中断 abort()。
- RequestRequest 对象:代表已发出或正在发出的请求,我们可以从中读取关键的上下文信息,例如通过 url()、headers()、method() 获取请求详情。
当页面触发网络请求时,只要我们通过 page.route(url, handler) 注册了对应 URL 模式的拦截器,Playwright 就会把 Route 对象交给我们处理。请注意,在处理函数中,必须调用 continue()、fulfill() 或 abort() 三者之一来结束拦截,否则请求会一直挂起continue()fulfill()abort()。
三、 实战:结合爬虫代理实现请求拦截与特征抹除
下面是一套综合实战代码。我们将演示如何配置全局隧道代理、如何通过拦截器丢弃无用静态资源、如何动态切换代理 IP,以及如何抹除自动化工具特征。
import asyncio
import uuid
import os
from playwright.async_api import async_playwright, Route, Request
# 亿牛云爬虫代理配置信息 (请替换为您自己的专属信息)
PROXY_HOST = "t.16yun.cn"
PROXY_PORT = "31111"
PROXY_USER = "your_username"
PROXY_PASS = "your_password"
async def mock_static_handler(route: Route, request: Request):
"""
静态资源拦截器:提高页面加载速度
"""
url = request.url.lower()
# 拦截图片资源并中止请求,极大提升抓取效率
if url.endswith(".png") or url.endswith(".jpg"):
await route.abort() #
# 字体文件直接跳过,减少加载时间
elif url.endswith(".woff2"):
await route.abort() #
else:
# 非目标请求,继续正常流程
await route.continue_() #
async def controlled_ip_handler(route: Route, request: Request):
"""
代理 IP 动态切换拦截器:通过附加 Proxy-Tunnel 头部实现
"""
# 每次请求生成新的 UUID 作为 tunnel 值,触发亿牛云强制分配新出口 IP
tunnel_id = str(uuid.uuid4()) #
# 动态注入自定义请求头,并将原请求头合并
await route.continue_(headers={
**request.headers,
"Proxy-Tunnel": tunnel_id, #
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" #
})
async def main():
async with async_playwright() as p:
# 1. 启动浏览器并配置全局代理
# 注意:proxy.server 格式必须是 http://host:port,Playwright不支持https前缀的代理
browser = await p.chromium.launch(
headless=True,
proxy={
"server": f"http://{PROXY_HOST}:{PROXY_PORT}", #
"username": PROXY_USER, #
"password": PROXY_PASS, #
}
)
# 忽略HTTPS证书错误,这在国内服务器代理环境中非常实用
context = await browser.new_context(ignore_https_errors=True) #
page = await context.new_page()
# 2. 全局注入:页面加载前执行,清除 webdriver 标识,绕过常规反爬
await context.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
window.webdriver = undefined;
delete window.CallSelenium;
""") #
# 3. 注册请求拦截器
# 注意:page.route() 注册的拦截器按顺序匹配,第一个匹配的拦截器处理后,后续不再调用
# 拦截所有API请求,实现动态代理IP切换
await page.route("**/api/**", controlled_ip_handler)
# 拦截所有其他请求,过滤无用静态资源
await page.route("**/*", mock_static_handler)
try:
# 4. 执行抓取动作 (以httpbin为例验证IP和Header)
print("正在访问目标网站...")
await page.goto("https://httpbin.org/ip")
# 等待网络空闲
await page.wait_for_load_state("networkidle") #
content = await page.content()
print("抓取成功,页面返回的IP信息:", content[:500])
except Exception as e:
print(f"抓取发生异常: {e}")
finally:
await browser.close()
if __name__ == "__main__":
asyncio.run(main())
四、 避坑与注意事项
在实际开发中,关于 Request Interception 有几个关键细节需要大家注意:
- 拦截器的链式冲突:上面代码提到了,Playwright 的拦截器是匹配即止的。如果你的一个请求同时命中了静态资源匹配和 API 匹配,只有先注册/先命中的会被执行。如果需要链式处理,建议在一个 handler 里整合逻辑。
- 异常捕获:如果拦截器抛出异常且未被捕获,Playwright 会默认放行该请求。在工业级爬虫里,建议给 Handler 加上 try/except,异常时主动 await route.abort(“failed”) 防止真实 IP 或特征泄露。
- 代理排查:如果抓取时发现 IP 还是本机,或者报错连不上,快速检查下配置格式是否带有 http:// 前缀,或者直接用 curl -x “http://username:password@t.16yun.cn:31111” -k https://httpbin.org/ip 测试一下代理服务的连通性。
总结
掌握 Playwright 的请求拦截,就像是给爬虫装上了“透视眼”和“手术刀”。结合高质量的代理IP,我们不仅能在网络层做到无缝伪装,还能大幅优化抓取性能,降低带宽成本。
如果你在爬虫架构设计或 Playwright 部署上有什么问题,欢迎在评论区留言交流。我们下期技术干货再见!
更多推荐

所有评论(0)