Playwright 配合住宅代理采集动态网页:从配置到被封的排查

什么时候必须上浏览器

requests 拿到的是服务端返回的初始 HTML。如果页面内容由前端 JS 渲染,或者接口带签名参数需要浏览器环境才能生成,就必须用真实浏览器。Playwright 比 Selenium 更适合采集场景:启动快、原生支持请求拦截、等待机制更可靠。

基础配置

代理在 launchnew_context 阶段传入:

from playwright.sync_api import sync_playwright

PROXY = {
    "server": "http://gateway.example.com:8080",
    "username": "用户名",
    "password": "密码",
}

with sync_playwright() as pw:
    browser = pw.chromium.launch(proxy=PROXY, headless=True)
    context = browser.new_context(
        locale="zh-CN",
        timezone_id="Asia/Shanghai",
        viewport={"width": 1440, "height": 900},
    )
    page = context.new_page()
    page.goto("https://shop.example.com/item/123", wait_until="domcontentloaded")
    print(page.title())
    browser.close()

要点:localetimezone_id 一定要和目标业务地区一致。用国内时区去访问美国站点的价格页,是很常见的暴露点。

等待:不要用固定 sleep

固定 time.sleep(3) 要么浪费三秒,要么在慢的时候不够。用显式等待:

# 等某个元素出现,而不是等固定时间
page.goto(url, wait_until="domcontentloaded")

# 方式一:等选择器
page.wait_for_selector(".price-value", timeout=15000)

# 方式二:等接口返回(更适合数据在 XHR 里的页面)
with page.expect_response(lambda r: "/api/product/" in r.url) as info:
    page.wait_for_selector("#load-more")
payload = info.value.json()

方式二往往更高效:数据本来就在接口响应里,不必等 DOM 渲染完再解析。

资源拦截省流量

住宅代理按流量计费,浏览器默认会加载图片、字体、媒体和大量第三方脚本,这些几乎都是纯消耗:

BLOCK = {"image", "font", "media"}
BLOCK_HOSTS = ("google-analytics.com", "doubleclick.net", "hotjar.com")

def route_handler(route):
    req = route.request
    if req.resource_type in BLOCK:
        return route.abort()
    if any(h in req.url for h in BLOCK_HOSTS):
        return route.abort()
    return route.continue_()

context.route("**/*", route_handler)

注意不要拦 stylesheet。很多站点的关键内容依赖 CSS 选择器判断可见性,拦掉样式表可能导致元素被判定为不可见而拿不到数据。

用真实的浏览器上下文

new_context 默认是一个干净环境,缺少真实浏览器的很多细节。用持久化上下文能保留 Cookie 和本地存储,对需要登录态的任务很重要:

# 持久化上下文:目录里保留 cookie、localStorage、缓存
context = pw.chromium.launch_persistent_context(
    user_data_dir="./profile_shop",
    proxy=PROXY,
    locale="zh-CN",
    viewport={"width": 1440, "height": 900},
)

配合静态住宅 IP 使用时,一个账号对应一个 user_data_dir 加一个固定出口 IP,环境一致性最好。

被风控时,先分清是哪一层的问题

Playwright 场景下”抓不到数据”有三个完全不同的原因,处理方式差别很大:

现象大概率原因处理方向
页面返回验证码 / 挑战页出口 IP 被风控换住宅代理,降低单 IP 频率
页面正常但关键元素不存在浏览器指纹被识别处理自动化特征,改用持久化上下文
元素选择器超时,但手动浏览器正常等待策略不对改显式等待,等接口而非 DOM
请求直接 403代理未生效或 IP 被封先用 httpbin 确认出口 IP

第三行是纯工程问题,跟代理无关,但经常被误判成”代理质量差”。

关于自动化特征

Playwright 默认会暴露一些自动化痕迹,比如 navigator.webdriver。部分站点会据此拦截。处理方式是启动时关掉自动化开关:

browser = pw.chromium.launch(
    proxy=PROXY,
    args=[
        "--disable-blink-features=AutomationControlled",
        "--no-sandbox",
    ],
)

需要注意的是,这类处理只能解决基础检测。如果目标站点有成熟的设备指纹体系,仅靠参数调整不够,通常需要更完整的浏览器环境方案。先确认瓶颈是否真的在这一层——用同一个代理配 requests 请求同一页面,如果 requests 能拿到内容而 Playwright 不行,那才是指纹问题;如果两者都拿不到,问题在 IP。

一个稳定的采集骨架

把上面的要点合起来,一个可用的骨架大概是这样:

from playwright.sync_api import sync_playwright
import random

def scrape(urls, proxy):
    results = []
    with sync_playwright() as pw:
        browser = pw.chromium.launch(proxy=proxy, headless=True,
                                     args=["--disable-blink-features=AutomationControlled"])
        context = browser.new_context(locale="zh-CN", timezone_id="Asia/Shanghai")
        context.route("**/*", lambda r: r.abort()
                      if r.request.resource_type in {"image", "font", "media"}
                      else r.continue_())
        page = context.new_page()
        for url in urls:
            try:
                page.goto(url, wait_until="domcontentloaded", timeout=45000)
                page.wait_for_selector(".price-value", timeout=15000)
                results.append({"url": url, "price": page.inner_text(".price-value")})
            except Exception as e:
                results.append({"url": url, "error": str(e)})
            # 请求之间加随机间隔,避免形成规律流量
            page.wait_for_timeout(random.randint(800, 2500))
        browser.close()
    return results

小结

Playwright 和代理配合的关键是三点:时区与语言对齐目标地区、显式等待替代固定 sleep、拦截静态资源控制流量。被拦截时先做一次 requests 对照测试,用结果区分是 IP 层的问题还是浏览器层的问题,能省掉大量无效调参。

取舍结论

维度结论
前提条件已用 requests 对同一页面做过对照测试,确认瓶颈在浏览器层而不是 IP 层。
核心取舍拦截静态资源能省一半以上流量,但拦掉样式表可能让依赖可见性判断的元素取不到值。
不适用场景目标站点有成熟设备指纹体系时,仅靠启动参数处理不够,需要更完整的浏览器环境方案。

一句话:先做 requests 对照测试再调参,避免把 IP 问题误判成指纹问题。

在你的目标站点上验证这套方案

文中的做法都可以用试用额度直接跑通。用你自己的目标站点测一轮,成功率数据比任何评测都可靠。