Scrapling StealthyFetcher 实战指南:绕过 Cloudflare 与反爬指纹检测的隐身浏览器抓取
StealthyFetcher 是 Scrapling 提供的隐身浏览器抓取器,基于 Chromium + Patchright 构建,自动处理绝大多数反机器人检测:Cloudflare Turnstile/Interstitial 挑战、CDP 运行时泄漏、WebRTC 本地 IP 泄漏、Canvas 指纹等。读完本文,你将掌握 StealthyFetcher 的完整参数体系、Cloudflare 自动求解机制、页面自动化与等待条件控制,以及 StealthySession/AsyncStealthySession 会话复用与标签页池的原理,并能直接写出可运行的抓取代码。
基本用法与定位
StealthyFetcher 是与 DynamicFetcher 同源的隐身浏览器抓取器,共享同一套浏览器自动化模型,页面交互基于 Playwright 的 Page API。与 DynamicFetcher 的区别在于:自 0.3.13 版本起,原 DynamicFetcher 上的 stealth 选项被整体迁入 StealthyFetcher,并新增了 solve_cloudflare、block_webrtc、hide_canvas、allow_webgl 四个专属参数。所有 Fetcher 的导入方式统一:
from scrapling.fetchers import StealthyFetcher
解析选项(parser 配置)的说明见 Fetcher 选择指南。异步版本的 fetch 方法名为 async_fetch。
StealthyFetcher 会自动完成以下隐身工作:
- 自动绕过所有类型的 Cloudflare Turnstile/Interstitial 挑战;
- 绕过 CDP 运行时泄漏和 WebRTC 泄漏;
- 隔离 JS 执行、移除大量 Playwright 指纹、阻断基于典型机器人行为的检测;
- 生成 Canvas 噪声,防止通过 Canvas 进行指纹识别;
- 自动 patch 检测 headless 模式的已知方法,并提供选项对抗时区不匹配攻击;
- 其他反保护选项。
从源码结构看,这套能力建立在 Patchright(Playwright 的隐身补丁版)之上而非原生 Playwright:_stealth.py 直接 from patchright.sync_api import sync_playwright / from patchright.async_api import async_playwright,而 DynamicFetcher 使用的是普通 Playwright。
完整参数列表
StealthyFetcher 与其会话类提供大量可配置项,完整列表如下:
| 参数 | 说明 | 可选 |
|---|---|---|
| url | 目标 URL | 否 |
| headless | 传 True 以 headless/隐藏模式运行浏览器(默认),False 为 headful/可见模式。 |
是 |
| disable_resources | 丢弃不必要的资源请求以提速。被丢弃的类型有 font、image、media、beacon、object、imageset、texttrack、websocket、csp_report、stylesheet。 |
是 |
| cookies | 为下一次请求设置 cookies。 | 是 |
| useragent | 传入要使用的 User-Agent 字符串。否则 fetcher 会生成并使用同浏览器、同版本的真实 User-Agent。 | 是 |
| network_idle | 等待页面直到至少 500 ms 内没有任何网络连接。 | 是 |
| load_dom | 默认启用,等待页面所有 JavaScript 完全加载并执行(等待 domcontentloaded 状态)。 |
是 |
| timeout | 页面内所有操作和等待使用的超时(毫秒)。默认 30,000 ms(30 秒)。 | 是 |
| wait | 所有步骤完成后、关闭页面并返回 Response 对象前等待的时间(毫秒)。 |
是 |
| page_action | 用于自动化。传入一个接收 page 对象的函数,在导航后执行所需自动化。 |
是 |
| page_setup | 接收 page 对象、在导航前执行的函数。用于注册事件监听器或必须在页面加载前设置的路由。 |
是 |
| wait_selector | 等待某个特定 CSS 选择器达到特定状态。 | 是 |
| init_script | 一个 JavaScript 文件的绝对路径,在本会话中每个页面创建时执行。 | 是 |
| wait_selector_state | 等待 wait_selector 给定的选择器满足指定状态。默认状态为 attached。 |
是 |
| google_search | 默认启用,Scrapling 会设置 Google referer 请求头。 | 是 |
| extra_headers | 为请求添加的额外请求头字典。若与 google_search 一起使用,google_search 设置的 referer 优先。 |
是 |
| proxy | 请求使用的代理。可以是字符串,也可以是仅含 'server'、'username'、'password' 键的字典。 | 是 |
| real_chrome | 如果设备上安装了 Chrome 浏览器,启用后 Fetcher 将启动并使用你的浏览器实例。 | 是 |
| locale | 指定用户语言区域,如 en-GB、de-DE。会同时影响 navigator.language、Accept-Language 请求头以及数字和日期格式化规则。默认系统语言区域。 |
是 |
| timezone_id | 修改浏览器时区。默认系统时区。 | 是 |
| cdp_url | 不启动新浏览器实例,而是连接该 CDP URL,通过 CDP 控制真实浏览器。 | 是 |
| user_data_dir | User Data Directory 路径,存储 cookies 和本地存储等浏览器会话数据。默认为临时目录。仅对会话类生效 | 是 |
| extra_flags | 传递给浏览器启动的额外浏览器标志列表。 | 是 |
| solve_cloudflare | 启用后,fetcher 会在返回响应前求解所有类型的 Cloudflare Turnstile/Interstitial 挑战。 | 是 |
| block_webrtc | 强制 WebRTC 遵守代理设置,防止本地 IP 地址泄漏。 | 是 |
| hide_canvas | 为 Canvas 操作添加随机噪声,防止指纹识别。 | 是 |
| allow_webgl | 默认启用。禁用后完全关闭 WebGL 和 WebGL 2.0 支持。不建议禁用 WebGL,因为许多 WAF 现在会检查 WebGL 是否启用。 | 是 |
| additional_args | 作为额外设置传递给 Playwright context 的参数,优先级高于 Scrapling 的设置。 | 是 |
| selector_config | 创建最终 Selector/Response 类时使用的自定义解析参数字典。 |
是 |
| blocked_domains | 要阻止请求的域名集合。子域名同样匹配(例如 "example.com" 也会阻止 "sub.example.com")。 |
是 |
| block_ads | 屏蔽约 3,500 个已知广告/追踪域名。可与 blocked_domains 组合使用。 |
是 |
| dns_over_https | 通过 Cloudflare 的 DNS-over-HTTPS 路由 DNS 查询,使用代理时防止 DNS 泄漏。 | 是 |
| proxy_rotator | 用于自动轮换代理的 ProxyRotator 实例。不能与 proxy 同时使用。 |
是 |
| retries | 请求失败时的重试次数。默认 3。 | 是 |
| retry_delay | 重试尝试之间的等待秒数。默认 1。 | 是 |
| capture_xhr | 传入正则 URL 模式字符串,抓取页面加载期间匹配的 XHR/fetch 请求。捕获的响应可通过 response.captured_xhr 获取。默认 None(禁用)。 |
是 |
| executable_path | 自定义浏览器可执行文件的绝对路径,替代内置 Chromium。适用于非标准安装或自定义浏览器构建。 | 是 |
在会话类中,以上所有参数都可作为整个会话的全局设置;同时,部分参数(google_search、timeout、wait、page_action、page_setup、extra_headers、disable_resources、wait_selector、wait_selector_state、network_idle、load_dom、solve_cloudflare、blocked_domains、proxy、selector_config)可以在每次请求时单独覆盖,即这些是"标签页级别"可配置的参数。
注意事项:
- 参数基本上与 DynamicFetcher 相同,只是多了
solve_cloudflare、block_webrtc、hide_canvas、allow_webgl这几个参数; disable_resources在测试中使某些网站的请求快了约 25%,还能节省代理流量,但需谨慎使用——它可能导致某些网站永远无法完成加载;google_search默认对所有请求启用,将 referer 设为https://www.google.com/。若与extra_headers同时使用,其 referer 优先;- 如果未设置 user agent 且启用了 headless 模式,fetcher 会生成同浏览器版本的真实 user agent 并使用;如果未设置 user agent 且未启用 headless 模式,则使用浏览器默认 user agent(最新版本的浏览器中与普通浏览器一致);
init_script注册在浏览器 context 上,因此页面创建时就会执行。Stealthy 模式默认使用 Patchright 的隔离执行上下文;如果你的page_action需要读取脚本放到window上的全局变量,请在 action 中调用page.evaluate(..., isolated_context=False)。
参数背后的底层实现
从源码可以印证这些隐身参数的实际作用机制。StealthySessionMixin.__generate_stealth_options 中:
block_webrtc=True会追加启动参数--webrtc-ip-handling-policy=disable_non_proxied_udp与--force-webrtc-ip-handling-policy,强制 WebRTC 走代理,防止本地 IP 从 UDP 信道泄漏;allow_webgl=False会追加--disable-webgl、--disable-webgl-image-chromium、--disable-webgl2;hide_canvas=True会追加--fingerprinting-canvas-image-data-noise,由浏览器内核对 Canvas 图像数据注入噪声。
这些标志仅在未使用 cdp_url 时生效(连接外部浏览器时无意义)。此外 会话初始化 还固定了 1920x1080 的屏幕/视口、关闭 is_mobile/has_touch、允许 Service Worker、忽略 HTTPS 错误并授予地理位置与通知权限——这些都是让指纹"看起来像真人桌面浏览器"的默认基线。
StealthyFetcher.fetch() 本身只是一个薄封装:stealth_chrome.py 中它合并 selector_config 后 with StealthySession(**kwargs) as engine: return engine.fetch(url),即每次 fetch 调用都会创建一个临时会话上下文,用完即关;async_fetch 同理包装 AsyncStealthySession。导出入口见 fetchers/__init__.py 的惰性导入定义。
示例
Cloudflare 与隐身选项
# 自动 Cloudflare 求解器
page = StealthyFetcher.fetch('https://nopecha.com/demo/cloudflare', solve_cloudflare=True)
# 与其他隐身选项组合
page = StealthyFetcher.fetch(
'https://protected-site.com',
solve_cloudflare=True,
block_webrtc=True,
real_chrome=True,
hide_canvas=True,
google_search=True,
proxy='http://username:password@host:port', # 也可以是仅含 'server'、'username'、'password' 键的字典
)
solve_cloudflare 参数启用对所有类型 Cloudflare Turnstile/Interstitial 挑战的自动检测与求解:
- JavaScript 挑战(managed)
- 交互式挑战(点击验证框)
- 无感挑战(后台自动验证)
甚至能求解内嵌 captcha 的自定义页面。
重要提示:
- 对于使用自定义实现的网站,你可能需要用
wait_selector确保 Scrapling 在求解 captcha 后等待真实网站内容加载完成。有些网站是真正的边缘案例,因为求解器要尽可能通用; - 使用 Cloudflare 求解器时,超时应至少设置为 60 秒,给足挑战求解时间;
- 该功能可与代理及其他隐身选项无缝配合。
求解器的源码级工作流
solve_cloudflare=True 时,实际执行流程在 _cloudflare_solver(同步版,异步版在 L382-L457)中:
- 检测挑战类型:_detect_cloudflare 在页面内容中匹配
cType: 'non-interactive'、cType: 'managed'、cType: 'interactive'三种 Turnstile 标记;若都未命中,则用 CSS 选择器script[src*="challenges.cloudflare.com/turnstile/v"]判断是否为embedded(内嵌式)挑战。测试用例 test_stealth_session.py 逐一验证了这四种类型的识别结果; - 无感挑战:只要页面标题还是
Just a moment...,就每秒轮询一次并等待 load 状态,直到挑战页消失; - 交互/托管/内嵌挑战:先通过 URL 正则
__CF_PATTERN__(匹配challenges.cloudflare.com/cdn-cgi/challenge-platform/...)定位挑战 iframe,取 iframe 元素的bounding_box();找不到 iframe 时回退到页面内的 box 选择器(#cf_turnstile div, #cf-turnstile div, .turnstile>div>div或.main-content p+div>div>div)。随后在验证框坐标上叠加randint(26, 28)像素的随机偏移模拟人类点击(page.mouse.click(..., delay=randint(100, 200))),点击后等待网络空闲并轮询至多 10 秒确认Just a moment...标题消失;若仍在,则递归重试求解。正则匹配边界由 test_cf_pattern_regex 测试覆盖。
在 fetch 主流程中(fetch 方法),Cloudflare 求解发生在 page.goto 与首次 _wait_for_page_stability 之后、page_action 之前;求解完成后还会再执行一次 _wait_for_page_stability(page, load_dom, network_idle) 确保 captcha 之后页面完全加载——这解释了为什么"求解后建议配合 wait_selector"。
浏览器自动化
这里正是你 Playwright Page API 知识发挥作用的地方。你传入的函数接收 Playwright API 的 page 对象,执行期望的操作,然后 fetcher 继续。
该函数在 network_idle 等待(如果启用)结束后、wait_selector 参数等待之前执行,因此也可用于自动化之外的目的——你可以随意修改页面。下面的例子使用了页面的鼠标事件,用鼠标滚轮滚动页面,然后移动鼠标:
from playwright.sync_api import Page
def scroll_page(page: Page):
page.mouse.wheel(10, 0)
page.mouse.move(100, 400)
page.mouse.up()
page = StealthyFetcher.fetch('https://example.com', page_action=scroll_page)
如果使用异步 fetch 版本,函数也必须是异步的:
from playwright.async_api import Page
async def scroll_page(page: Page):
await page.mouse.wheel(10, 0)
await page.mouse.move(100, 400)
await page.mouse.up()
page = await StealthyFetcher.async_fetch('https://example.com', page_action=scroll_page)
需要注意 page_setup 与 page_action 的时机差异:page_setup 在 page.goto 之前执行,适合注册 page.route 或事件监听;两者在源码中均被 try/except 包裹,抛出的异常会被记录日志而不中断抓取流程。
等待条件
# 等待选择器
page = StealthyFetcher.fetch(
'https://example.com',
wait_selector='h1',
wait_selector_state='visible'
)
这是 fetcher 返回响应前执行的最后一次等待(如果启用)。向 wait_selector 传入 CSS 选择器,fetcher 会等待 wait_selector_state 指定的状态被满足。如果不传状态,默认为 attached,即等待元素出现在 DOM 中。
之后,如果 load_dom 启用(默认),fetcher 会再检查一次所有 JS 是否已加载并执行(domcontentloaded 状态),否则继续等待。如果启用了 network_idle,fetcher 会再次等待 network_idle 满足。
可等待的状态包括以下四种:
attached:等待元素出现在 DOM 中;detached:等待元素不存在于 DOM 中;visible:等待元素具有非空的包围盒且没有visibility:hidden。没有内容或display:none的元素包围盒为空,不视为可见;hidden:等待元素从 DOM 分离、或包围盒为空、或visibility:hidden。与visible选项相反。
真实场景示例(Amazon)
仅供教育目的;这个示例由 AI 生成,也说明了通过 AI 使用 Scrapling 有多简单:
def scrape_amazon_product(url):
# 使用 StealthyFetcher 绕过保护
page = StealthyFetcher.fetch(url)
# 提取商品详情
return {
'title': page.css('#productTitle::text').get().clean(),
'price': page.css('.a-price .a-offscreen::text').get(),
'rating': page.css('[data-feature-name="averageCustomerReviews"] .a-popover-trigger .a-color-base::text').get(),
'reviews_count': page.css('#acrCustomerReviewText::text').re_first(r'[\d,]+'),
'features': [
li.get().clean() for li in page.css('#feature-bullets li span::text')
],
'availability': page.css('#availability')[0].get_all_text(strip=True),
'images': [
img.attrib['src'] for img in page.css('#altImages img')
]
}
会话管理
如果要让浏览器保持打开、用相同配置发起多个请求,使用 StealthySession/AsyncStealthySession 类。它们可以接受 fetch 函数的全部参数,从而为整个会话指定配置:
from scrapling.fetchers import StealthySession
# 以默认配置创建会话
with StealthySession(
headless=True,
real_chrome=True,
block_webrtc=True,
solve_cloudflare=True
) as session:
# 用同一个浏览器实例发起多个请求
page1 = session.fetch('https://example1.com')
page2 = session.fetch('https://example2.com')
page3 = session.fetch('https://nopecha.com/demo/cloudflare')
# 所有请求复用同一浏览器实例上的同一个标签页
异步会话用法
import asyncio
from scrapling.fetchers import AsyncStealthySession
async def scrape_multiple_sites():
async with AsyncStealthySession(
real_chrome=True,
block_webrtc=True,
solve_cloudflare=True,
timeout=60000, # Cloudflare 挑战需要 60 秒
max_pages=3
) as session:
# 使用共享浏览器配置发起异步请求
pages = await asyncio.gather(
session.fetch('https://site1.com'),
session.fetch('https://site2.com'),
session.fetch('https://protected-site.com')
)
return pages
你可能注意到了 max_pages 参数。这是一个新参数,让 fetcher 能够创建轮换的浏览器标签页池。不再所有请求共用一个标签页,而是限制同时打开的最大页面数。每次请求时,库会关闭所有已完成任务的标签页,并检查当前标签页数是否低于上限,然后:
- 如果在允许范围内,fetcher 会为你创建新标签页,一切照常;
- 否则,它会每亚秒级检查一次是否允许创建新标签页,持续 60 秒,然后抛出
TimeoutError。这种情况可能发生在目标网站无响应时。
这套逻辑允许在同一个浏览器中同时抓取多个 URL,节省大量资源,而且速度很快。在 0.3 和 0.3.1 版本中,标签页池曾复用已完成的标签页以进一步节省资源/时间,但该逻辑被证明有缺陷——几乎不可能保护页面/标签页不被上一个请求的配置污染。
会话默认使用持久化 context:start 方法 中,非 CDP 场景下调用 launch_persistent_context 并指定 user_data_dir(默认为临时目录),这就是"会话间 cookie 自动保持"的机制来源;而 cdp_url 或 proxy_rotator 场景则分别走 connect_over_cdp 与 chromium.launch。
会话的收益
- 浏览器复用:复用同一浏览器实例,后续请求快得多;
- Cookie 持久化:像普通浏览器一样自动处理 cookie 与会话状态;
- 一致的指纹:所有请求共享同一浏览器指纹;
- 内存效率:相比每次 fetch 都启动新浏览器,资源占用更低。
重试与代理细节
从 fetch 主循环 可以确认几个文档未展开的默认行为:
- 每个请求在
retries(默认 3)次尝试内循环执行,失败后等待retry_delay(默认 1 秒)再重试;若是代理类错误(is_proxy_error判定),日志会明确标注是哪个代理失败; - 会话配置了
proxy_rotator时,每次尝试都会从轮换器取一个新代理;而请求级传入的静态proxy会覆盖轮换器与 session 代理,并为其单独创建浏览器 context; - 会话已关闭后调用
fetch会抛出RuntimeError("Context manager has been closed")。
何时使用 StealthyFetcher
在以下场景应使用 StealthyFetcher:
- 需要绕过反机器人保护;
- 需要可靠的浏览器指纹;
- 需要完整的 JavaScript 支持;
- 希望自动获得隐身能力;
- 需要浏览器自动化;
- 目标站使用 Cloudflare 保护。
相关文档与延伸阅读
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00