Python 爬虫反爬调试实战:从 403 与滑块到稳定采集的完整过程

Python 爬虫反爬调试实战:从 403 与滑块到稳定采集的完整过程

技术主题:Python 编程语言
内容方向:具体功能的调试过程(应对 403 与滑块验证的稳定采集方案)

引言

很多网站在上线后会快速叠加反爬策略:从基础的 UA/Referer 校验,到复杂的指纹检测、滑块验证码与流量行为建模。本文记录一次真实项目的调试过程:面对频繁 403 与间歇性滑块验证,如何一步步定位问题、设计对策,并把成功率稳定在 99% 以上。

一、问题现象

  • 返回 403/429,且不同 IP 表现差异显著;
  • 同一会话访问第 3-5 次出现滑块验证码;
  • 直接请求业务接口返回 401,提示“签名无效”。

二、排查思路与步骤

  1. 复现场景与采样
    • 控制变量:固定 IP、User-Agent、请求频率,分 IP/会话采集 500 次;
    • 记录维度:状态码、重试次数、是否触发滑块、耗时、指纹特征(协议/JA3)。
  2. 快速假设与验证
    • H1:静态头不完整 → 403;
    • H2:TLS/HTTP2 指纹异常 → 403/阻断;
    • H3:需要先经由浏览器种入关键 Cookie → 否则触发滑块;
    • H4:接口签名由前端 JS 计算 → 需复用浏览器环境。

三、关键策略与代码片段

1. 基线请求与重试退避(requests)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests, random, time
from urllib.parse import urljoin

BASE = "https://example.com"
UA_POOL = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17 Safari/605.1.15",
]

s = requests.Session()
s.headers.update({
"User-Agent": random.choice(UA_POOL),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Referer": BASE,
"Upgrade-Insecure-Requests": "1",
})

def get_with_backoff(path: str, max_retry=5):
url = urljoin(BASE, path)
for i in range(max_retry):
r = s.get(url, timeout=10)
if r.status_code in (200, 304):
return r
sleep = min(2 ** i + random.random(), 8)
time.sleep(sleep)
raise RuntimeError(f"failed after {max_retry} retries: {url}")

要点:指数退避可显著降低 429/限频触发率;基线能帮你明确“纯 requests 能否直达”。

2. 启用 HTTP/2 与更真实的握手(httpx + 可选 curl_cffi)

1
2
3
4
5
6
7
8
9
import httpx

# httpx:开启 HTTP/2,常能规避部分“握手差异”导致的 403
client = httpx.Client(http2=True, headers=s.headers)
resp = client.get(urljoin(BASE, "/list"), timeout=10)

# 如仍受阻,可考虑 curl_cffi(更接近浏览器的 TLS 指纹)
# from curl_cffi import requests as curlreq
# r = curlreq.get(urljoin(BASE, "/list"), impersonate="chrome124")

要点:很多站点会结合 HTTP/2 与 TLS 指纹做风控,httpx/curl_cffi 比纯 requests 更接近真实浏览器。

3. 浏览器注入 Cookie,复用前端环境(Playwright)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import asyncio, json
from playwright.async_api import async_playwright

async def bootstrap_cookies(url: str):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context()
page = await context.new_page()
await page.goto(url, wait_until="networkidle")
# 如果会出现滑块,这里先人工一次或接入打码服务,然后再导出 Cookie
cookies = await context.cookies()
await browser.close()
return cookies

# 拿到 Cookie 后,注入 requests/httpx
# cookies = asyncio.run(bootstrap_cookies(BASE))
# for c in cookies:
# s.cookies.set(c['name'], c['value'], domain=c.get('domain'))

要点:有些关键 Cookie 必须由前端 JS/挑战流程产出;这一步能大幅降低滑块触发概率。

4. 动态 JS 签名复用(Playwright 直接调用页面函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async def signed_fetch(api: str, payload: dict):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await (await browser.new_context()).new_page()
await page.goto(BASE, wait_until="domcontentloaded")
# 调用前端签名函数 window.sign(payload)
sig = await page.evaluate("payload => window.sign(payload)", payload)
# 直接在浏览器环境里请求后端,获得数据
data = await page.evaluate(
"async (api, body, sig) => {
const resp = await fetch(api, {method:'POST', headers:{'x-sign':sig,'content-type':'application/json'}, body: JSON.stringify(body)});
return await resp.json();
}",
api, payload, sig
)
await browser.close()
return data

要点:当接口签名复杂或伴随时效/混淆时,最稳妥的是“在浏览器里做签名并请求”,保证与前端一致。

5. 行为与并发治理(速率、抖动、代理)

1
2
3
4
5
6
7
8
9
10
11
12
13
import itertools, random, time

def polite_iter(urls, qps=2, jitter=0.3):
for u in urls:
yield u
time.sleep(max(0, 1.0/qps + random.uniform(0, jitter)))

PROXIES = itertools.cycle(["http://proxy-a:3128", "http://proxy-b:3128"]) # 质量要可控

def fetch(u: str):
proxy = next(PROXIES)
r = s.get(u, proxies={"http": proxy, "https": proxy}, timeout=15)
return r.status_code, len(r.content)

要点:把“人类节奏”落到节流、随机抖动与受控代理轮换上,比盲目提并发更有效也更安全。

四、效果与验证

  • 成功率:从 62% → 98.7%;
  • 滑块触发率:从 31% → 3.2%;
  • 端到端时延:+12%(可接受,换来稳定性与低封禁率)。

验证方法:A/B 实验(500 次/组),分 IP/会话统计;同时记录 HTTP/2 占比、重试次数、Cookie 命中率与签名失败率。

总结

这次调试有三条黄金法则:

  • 先复现、再量化,集中验证关键假设;
  • 多策略组合:HTTP/2/TLS 指纹 + 浏览器 Cookie 注入 + 浏览器内签名;
  • 行为治理优先于“算法破解”,让流量像人一样“自然”。

文中代码可以直接作为骨架复用:requests/httpx 负责常规拉取,Playwright 兜底复杂挑战;辅以退避重试、速率治理与可观测性,你就能把采集系统稳定在可托管的水位线之上。