前言

大家好,在日常的爬虫开发和自动化抓取中,我们经常会遇到一些让人头疼的场景。比如目标网站加载了大量无关的图片和视频拖慢了抓取速度,或者通过检测请求头和前端特征来封禁我们的机器。今天,我们就来深入探讨一下 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 有几个关键细节需要大家注意:

  1. 拦截器的链式冲突:上面代码提到了,Playwright 的拦截器是匹配即止的。如果你的一个请求同时命中了静态资源匹配和 API 匹配,只有先注册/先命中的会被执行。如果需要链式处理,建议在一个 handler 里整合逻辑。
  2. 异常捕获:如果拦截器抛出异常且未被捕获,Playwright 会默认放行该请求。在工业级爬虫里,建议给 Handler 加上 try/except,异常时主动 await route.abort(“failed”) 防止真实 IP 或特征泄露。
  3. 代理排查:如果抓取时发现 IP 还是本机,或者报错连不上,快速检查下配置格式是否带有 http:// 前缀,或者直接用 curl -x “http://username:password@t.16yun.cn:31111” -k https://httpbin.org/ip 测试一下代理服务的连通性。

总结

掌握 Playwright 的请求拦截,就像是给爬虫装上了“透视眼”和“手术刀”。结合高质量的代理IP,我们不仅能在网络层做到无缝伪装,还能大幅优化抓取性能,降低带宽成本。

如果你在爬虫架构设计或 Playwright 部署上有什么问题,欢迎在评论区留言交流。我们下期技术干货再见!

更多推荐