Scrapling StealthyFetcher 实战:绕过 Cloudflare 验证与浏览器指纹检测的完整指南
本文以 Scrapling 官方文档 docs/fetching/stealthy.md 为主体,系统讲解 StealthyFetcher 类的定位、全部参数、Cloudflare 自动求解、浏览器自动化与 Session 池化用法,并结合 scrapling/engines/_browsers/_stealth.py、scrapling/engines/_browsers/_base.py 等源码,还原每个反检测参数(WebRTC、Canvas、WebGL)在底层如何转化为浏览器启动标志,以及 Cloudflare 求解器内部真实的检测与点击逻辑。读完后你应能独立完成高防护网站的抓取、配置可复用的隐身会话,并理解 Scrapling 在 patchright 之上做了哪些自动化的隐身处理。
StealthyFetcher 的定位与前置知识
StealthyFetcher 是 Scrapling 提供的隐身(stealth)抓取器。它与 DynamicFetcher 类非常相似——相同的浏览器(Chromium/Google Chrome)、相同的自动化方式、同样基于 Playwright 的 API 体系。核心区别在于:StealthyFetcher 提供高级反机器人防护绕过能力,其中大部分在底层自动完成,其余部分由你显式开启。
正如 DynamicFetcher 一样,你需要了解 Playwright 的 Page API 才能对页面做自动化操作,这一点在 docs/fetching/dynamic.md 中有详细铺垫。官方文档列出的前置条件如下:
- 已阅读过 DynamicFetcher 页面,因为本类建立在它之上,本文档不重复那些内容;
- 已阅读过 Fetchers 基础,理解什么是 Response 对象,以及应该选择哪个 fetcher;
- 已阅读过 元素查询,理解如何从 Selector / Response 对象中查找/提取元素;
- 已阅读过 主类,知道 Response 类从 Selector 类继承了哪些属性和方法。
基本用法
所有 fetcher 共用唯一的导入方式:
from scrapling.fetchers import StealthyFetcher
解析器(parser)的通用配置方式见 docs/fetching/choosing.md(Fetcher.configure(...) 或 selector_config 参数)。
fetch 方法的异步版本即 async_fetch,这一点所有 fetcher 通用。
从源码看(scrapling/fetchers/stealth_chrome.py#L62-L63),StealthyFetcher.fetch 的实现极为精简:先把 selector_config 与全局解析器配置合并(并兼容旧参数名 custom_config),然后创建一个 StealthySession 上下文管理器并调用其 fetch。也就是说,单次 fetch 本质上是一次性的隐身会话,真正的抓取逻辑全部落在 StealthySession 上:
# scrapling/fetchers/stealth_chrome.py(节选)
with StealthySession(**kwargs) as engine:
return engine.fetch(url)
它到底做了哪些隐身处理
StealthyFetcher 是 DynamicFetcher 的隐身版本,官方文档列出的能力包括:
- 自动绕过所有类型的 Cloudflare Turnstile / Interstitial 验证;
- 绕过 CDP runtime 泄漏与 WebRTC 泄漏;
- 隔离 JS 执行、移除大量 Playwright 指纹、阻止通过某些机器人常见行为进行的检测;
- 生成 canvas 噪声,防止通过 canvas 进行指纹识别;
- 自动 patch 已知方法以检测 headless 模式运行,并提供选项对抗时区不匹配攻击;
- 以及其他反防护选项。
这些描述在源码中都有对应证据。当前版本(0.3.13 之后)底层引擎已从 Camoufox 替换为 patchright(scrapling/engines/_browsers/_stealth.py#L8-L9 直接 from patchright.sync_api import sync_playwright),默认启用其隔离执行上下文;文档特别提示:Stealthy 模式默认使用 Patchright 的隔离执行上下文,如果你的 page_action 需要读取 init_script 写到 window 上的全局变量,需从 action 中调用 page.evaluate(..., isolated_context=False)。
会话初始化时,StealthySessionMixin.__validate__ 会为上下文预置一组"看起来像真实浏览器"的选项(scrapling/engines/_browsers/_base.py#L508-L519):
{
"is_mobile": False,
"has_touch": False,
"service_workers": "allow",
"ignore_https_errors": True,
"screen": {"width": 1920, "height": 1080},
"viewport": {"width": 1920, "height": 1080},
"permissions": ["geolocation", "notifications"],
}
随后 __generate_stealth_options(scrapling/engines/_browsers/_base.py#L522-L542)在未使用 cdp_url 时追加 DEFAULT_ARGS + STEALTH_ARGS 启动标志,并按开关追加:
| 参数 | 追加的浏览器标志 |
|---|---|
block_webrtc=True |
--webrtc-ip-handling-policy=disable_non_proxied_udp、--force-webrtc-ip-handling-policy(确保策略强制执行) |
allow_webgl=False |
--disable-webgl、--disable-webgl-image-chromium、--disable-webgl2 |
hide_canvas=True |
--fingerprinting-canvas-image-data-noise |
这解释了文档参数表中 block_webrtc(强制 WebRTC 走代理、防止本地 IP 泄漏)、hide_canvas(canvas 随机噪声)与 allow_webgl 的实际落地方式——它们都是 Chromium 启动级开关,而非页面脚本注入。
完整参数列表
Scrapling 为这个 fetcher 及其会话类提供了大量选项。官方文档给出的完整参数表如下(其中 Optional 列中 ❌ 表示必填、✔️ 表示可选):
| 参数 | 说明 | 可选 |
|---|---|---|
url |
目标 url | ❌ |
headless |
传 True 以 headless/隐藏模式运行浏览器(默认),False 为 headful/可见模式 |
✔️ |
disable_resources |
丢弃不必要的资源请求以提升速度。被丢弃的请求类型为 font、image、media、beacon、object、imageset、texttrack、websocket、csp_report、stylesheet |
✔️ |
cookies |
为下一个请求设置 cookies | ✔️ |
useragent |
传入要使用的 useragent 字符串。否则 fetcher 会生成并使用同浏览器同版本的真实 Useragent | ✔️ |
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 |
Scrapling 将等待 wait_selector 给定的选择器达到该状态。默认状态为 attached |
✔️ |
google_search |
默认启用,Scrapling 会设置一个 Google referer 头 | ✔️ |
extra_headers |
要添加到请求的额外头字典。若同时使用,google_search 设置的 referer 优先于这里设置的 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、local storage 等)。默认创建临时目录。仅在会话类中生效 | ✔️ |
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。
这种"会话默认 + 单请求覆盖"的合并逻辑在 scrapling/engines/_browsers/_validators.py#L178-L215 的 validate_fetch 函数中实现:先用会话 _config 中的值填充 _fetch_params 各字段,再把本次请求显式传入的覆盖项做 msgspec 校验后更新回去。
文档给出的 5 条重要备注(原文继承):
- 基本上与 DynamicFetcher 类的参数相同,但额外多了:
solve_cloudflare、block_webrtc、hide_canvas、allow_webgl四个参数。capture_xhr参数与DynamicFetcher共享。这一点可以从类型定义印证:StealthSession就是PlaywrightSession加上这四个字段(scrapling/engines/_browsers/_types.py#L117-L121)。 disable_resources选项在作者的测试中让部分网站的请求快了约 25%,并能节省代理用量,但需谨慎,它可能导致某些网站永远加载不完。google_search参数对所有请求默认启用,将 referer 设为https://www.google.com/。与extra_headers一起使用时,其 referer 优先级更高。源码中该逻辑位于 scrapling/engines/_browsers/_stealth.py#L212-L215:仅当extra_headers未显式包含referer键时才注入 Google referer。- 如果你未设置 user agent 且启用了 headless 模式,fetcher 会为相同浏览器版本生成一个真实 user agent 并使用;如果你未设置 user agent 且未启用 headless 模式,fetcher 将使用浏览器默认 user agent(在最新版本中与标准浏览器一致)。
init_script注册在浏览器 context 上,因此在页面创建时运行。Stealthy 模式默认使用 Patchright 的隔离执行上下文;如果你的page_action需要读取脚本放到window上的全局变量,请从 action 中调用page.evaluate(..., isolated_context=False)。
此外,StealthConfig(scrapling/engines/_browsers/_validators.py#L144-L155)还有一个文档未明示的自动行为:开启 solve_cloudflare 且 timeout 小于 60,000 ms 时,超时会被自动提升到 60,000 ms——这与文档"使用 Cloudflare 求解器时超时应至少 60 秒"的建议相互印证。
实战示例
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)
- 交互式挑战(点击验证框)
- 隐形挑战(自动后台验证)
甚至可以解决内嵌了验证码的自定义页面(embedded 场景)。
重要说明:
- 对于使用自定义实现的网站,有时你需要用
wait_selector确保 Scrapling 在解决验证码后等待真实网站内容加载完成。部分网站是"边缘案例",而求解器正尽可能保持通用。 - 使用 Cloudflare 求解器时,超时应至少 60 秒,以留出充足的挑战求解时间。
- 该功能可与代理及其他隐身选项无缝配合。
从源码看,求解入口在 fetch 流程中(scrapling/engines/_browsers/_stealth.py#L253-L256):先 _wait_for_page_stability(load/domcontentloaded/networkidle 三级等待,见 _base.py#L142-L147),然后调用 self._cloudflare_solver(page),求解完成后再做一次页面稳定性等待,随后才轮到 page_action 与 wait_selector。
浏览器自动化
这里就是你的 Playwright Page API 知识发挥作用的地方。你传入的函数接收 Playwright API 的 page 对象,执行期望的操作后 fetcher 继续流程。
该函数在等待 network_idle(如果启用)之后、等待 wait_selector 参数之前立即执行,因此它可用于自动化之外的目的——你可以任意修改页面。
下面的示例使用页面的 mouse events 以滚轮方式滚动页面,然后移动鼠标:
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.goto 之前执行(scrapling/engines/_browsers/_stealth.py#L240-L247),page_action 在导航与稳定性等待之后执行;两者抛出的异常都会被捕获并记录日志,不会中断整个抓取流程。
等待条件
# 等待某个选择器
page = StealthyFetcher.fetch(
'https://quotes.toscrape.com/js-delayed/',
wait_selector='.quote',
wait_selector_state='visible'
)
这是 fetcher 在返回响应前执行的最后一次等待(如果启用)。你向 wait_selector 传入一个 CSS 选择器,fetcher 将等待 wait_selector_state 中传入的状态达成。如果不传状态,默认是 attached,即等待元素出现在 DOM 中。
之后,如果 load_dom 启用(默认),fetcher 会再检查所有 JavaScript 文件是否已加载并执行(domcontentloaded 状态),否则继续等待。如果你启用了 network_idle,fetcher 会再次等待 network_idle 达成。
可等待的状态(对应 Playwright 的 page.wait_for_selector 语义):
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
为了在相同配置下发起多个请求时保持浏览器打开,使用 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, # 60 秒,用于 Cloudflare 挑战
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 创建轮转的浏览器标签页池(rotating pool of Browser tabs)。不再是所有请求都用一个标签页,而是设置同时可显示的标签页数量上限。每个请求到来时,库会关闭所有已完成任务的标签页,并检查当前标签页数量是否低于最大允许页数/标签页数,然后:
- 如果还在允许范围内,fetcher 会为你创建一个新标签页,之后一切照常。
- 否则,它会以亚秒级间隔持续检查 60 秒,看是否允许创建新标签页,然后抛出
TimeoutError。当抓取的目标网站无响应时可能发生这种情况。
这套逻辑允许同一个浏览器内同时抓取多个 URL,节省大量资源,而且速度极快。在 0.3 和 0.3.1 版本中,该池曾复用已完成的标签页以节省更多资源/时间,但该逻辑被证明有缺陷,因为几乎不可能让页面/标签页免于被上一次请求的配置"污染"。从源码看,60 秒上限对应 _max_wait_for_page = 60(scrapling/engines/_browsers/_base.py#L227),标签页生命周期由 PagePool 与 _page_generator 上下文管理器管理(scrapling/engines/_browsers/_base.py#L182-L215)。
会话的收益
- 浏览器复用:复用同一浏览器实例,后续请求快得多。
- Cookie 持久化:像普通浏览器一样自动处理 cookie 与会话状态。
- 一致的指纹:所有请求共享同一浏览器指纹。
- 内存效率:相比每次 fetch 都启动新浏览器,资源利用更好。
从源码结构看,普通模式(未配置 proxy_rotator 且未指定 cdp_url)下,start() 使用 launch_persistent_context 启动持久化 context 并合并浏览器选项、context 选项与 user_data_dir(scrapling/engines/_browsers/_stealth.py#L90-L93);这正是"会话级 cookie/本地存储持久化"的实现基础。
Cloudflare 求解器内部机制(源码级)
solve_cloudflare 的核心是 _cloudflare_solver 与 _detect_cloudflare 两个方法。
挑战类型检测(scrapling/engines/_browsers/_base.py#L544-L577):静态方法 _detect_cloudflare 在页面 HTML 中查找 cType: 'non-interactive'、cType: 'managed'、cType: 'interactive' 三种标记;若都不匹配,再用 Selector 检查页面中是否存在 script[src*="challenges.cloudflare.com/turnstile/v"],有则判定为 embedded(内嵌式 Turnstile)。测试用例 tests/fetchers/sync/test_stealth_session.py#L72-L93 对这三类检测与"普通页面返回 None"都做了断言。
求解流程(scrapling/engines/_browsers/_stealth.py#L107-L182):
- 先等待 5 秒内的
networkidle,然后检测挑战类型;未检测到则记录 "No Cloudflare challenge found." 并直接返回。 non-interactive(隐形)类型:只要页面标题仍是<title>Just a moment...</title>就每秒检查一次,直到等待页消失。- 交互式/托管类型:定位 Cloudflare 挑战 iframe——URL 匹配模块级常量
__CF_PATTERN__(正则^https?://challenges\.cloudflare\.com/cdn-cgi/challenge-platform/.*,见 _stealth.py#L19);若找不到 iframe,回退到#cf_turnstile div, #cf-turnstile div, .turnstile>div>div(embedded 场景)或.main-content p+div>div>div选择器定位验证框。 - 取验证框的 bounding box 后,在固定随机偏移处模拟点击:
captcha_x, captcha_y = outer_box["x"] + randint(26, 28), outer_box["y"] + randint(25, 27),并以delay=randint(100, 200)的鼠标点击执行,模拟真实用户而非毫秒级精确点击。 - 点击后再次
_wait_for_page_stability;若 "Just a moment..." 标题仍在,则以递归方式重新求解(return self._cloudflare_solver(page))。
重试、代理与请求流程(源码级)
StealthySession.fetch 的完整流程(scrapling/engines/_browsers/_stealth.py#L184-L300)值得完整理解,因为它串起了多个参数的实际语义:
- 代理解析:先
kwargs.pop("proxy")取出单请求静态代理;若会话配置了proxy_rotator且未传静态代理,则调用proxy_rotator.get_proxy()轮换取代理,否则使用静态代理。 - Google referer 注入:
google_search为真且extra_headers中未显式包含referer键时,注入https://www.google.com/作为page.goto的 referer。 - 重试循环:
for attempt in range(self._config.retries)(默认 3 次)。每次通过_page_generator获取标签页(使用代理时为该代理创建全新 context,用完即关,避免配置污染)。 - 响应捕获:通过
page.on("response", ...)注册处理器,记录主框架的导航 document 响应;若配置了capture_xhr,还会把匹配正则的xhr/fetch类型响应收进列表,最终挂到response.captured_xhr(逻辑见 scrapling/engines/_browsers/_base.py#L149-L180)。 - 执行顺序:
page_setup(导航前)→page.goto(url, referer=referer)→_wait_for_page_stability(load+ 可选domcontentloaded+ 可选networkidle)→ 可选_cloudflare_solver+ 再次稳定性等待 → 可选page_action→ 可选wait_selector(locator.first.wait_for(state=...))+ 稳定性等待 →page.wait_for_timeout(params.wait)→ResponseFactory.from_playwright_response(...)返回Response对象(meta中记录本次使用的代理)。 - 失败处理:捕获异常后
page_info.mark_error(),若还有剩余次数,区分代理错误(is_proxy_error)与普通错误打印不同日志,等待retry_delay(默认 1 秒)后重试;最后一次失败则抛出异常。
这一链路也解释了文档备注中"会话中所有参数可全局设置、部分参数可按请求覆盖"的原因:_fetch_params(scrapling/engines/_browsers/_validators.py#L158-L175)只包含可覆盖字段(google_search、timeout、wait、page_action、page_setup、extra_headers、disable_resources、wait_selector、wait_selector_state、network_idle、load_dom、blocked_domains、solve_cloudflare、selector_config),与文档列出的可覆盖清单一致。
使用 Camoufox 作为引擎
0.3.13 版本之前,这个 fetcher 使用 Camoufox 的定制版作为引擎,之后因诸多原因被 patchright 取代。如果你确认 Camoufox 在你的设备上稳定、没有严重的内存问题、并且希望继续使用,完全可以。
首先,如果尚未安装 Camoufox 库、浏览器与 Firefox 系统依赖,需要安装:
pip install camoufox
playwright install-deps firefox
camoufox fetch
然后继承 StealthySession 并重写 start:
from scrapling.fetchers import StealthySession
from playwright.sync_api import sync_playwright
from camoufox.utils import launch_options as generate_launch_options
class StealthySession(StealthySession):
def start(self):
"""Create a browser for this instance and context."""
if not self.playwright:
self.playwright = sync_playwright().start()
# 在这里配置 camoufox 运行选项
launch_options = generate_launch_options(**{"headless": True, "user_data_dir": ''})
# 示例,部分内容是 0.3.13 之前我们一直在做的
launch_options = generate_launch_options(**{
"geoip": False,
"proxy": self._config.proxy,
"headless": self._config.headless,
"humanize": True if self._config.solve_cloudflare else False, # 针对 Cloudflare 建议启用 humanize,其余看你需求
"i_know_what_im_doing": True, # 使用自定义用户配置时关闭警告
"allow_webgl": self._config.allow_webgl,
"block_webrtc": self._config.block_webrtc,
"os": None,
"user_data_dir": self._config.user_data_dir,
"firefox_user_prefs": {
# 这就是 `enable_cache` 内部做的事,所以改由这里来做
"browser.sessionhistory.max_entries": 10,
"browser.sessionhistory.max_total_viewers": -1,
"browser.cache.memory.enable": True,
"browser.cache.disk_cache_ssl": True,
"browser.cache.disk.smart_size.enabled": True,
},
# 等等……
})
self.context = self.playwright.firefox.launch_persistent_context(**launch_options)
else:
raise RuntimeError("Session has been already started")
之后即可像以前一样正常使用,甚至包括求解 Cloudflare 挑战:
with StealthySession(solve_cloudflare=True, headless=True) as session:
page = session.fetch('https://sergiodemo.com/security/challenge/legacy-challenge')
if page.css('#page-not-found-404'):
print('Cloudflare challenge solved successfully!')
同一套逻辑适用于 AsyncStealthySession,只有少量差异(start 变为 async def,使用 async_playwright 与 await):
from scrapling.fetchers import AsyncStealthySession
from playwright.async_api import async_playwright
from camoufox.utils import launch_options as generate_launch_options
class AsyncStealthySession(AsyncStealthySession):
async def start(self):
"""Create a browser for this instance and context."""
if not self.playwright:
self.playwright = await async_playwright().start()
# 在这里配置 camoufox 运行选项
launch_options = generate_launch_options(**{"headless": True, "user_data_dir": ''})
# 或按上面示例设置启动选项
self.context = await self.playwright.firefox.launch_persistent_context(**launch_options)
else:
raise RuntimeError("Session has been already started")
async with AsyncStealthySession(solve_cloudflare=True, headless=True) as session:
page = await session.fetch('https://sergiodemo.com/security/challenge/legacy-challenge')
if page.css('#page-not-found-404'):
print('Cloudflare challenge solved successfully!')
什么时候该用 StealthyFetcher
官方文档给出的适用场景清单(When to Use):
- 需要绕过反机器人防护(bypassing anti-bot protection)
- 需要可靠的浏览器指纹(reliable browser fingerprint)
- 需要完整的 JavaScript 支持(full JavaScript support)
- 希望自动化的隐身特性(automatic stealth features)
- 需要浏览器自动化(browser automation)
- 正在处理 Cloudflare 防护(Cloudflare protection)
在 docs/fetching/choosing.md 的对比表中,StealthyFetcher 与 DynamicFetcher 同速(🐇🐇🐇)、同内存等级,但隐身与反机器人选项为最高档(⭐⭐⭐⭐⭐),最适合"动态加载网站 + 小型自动化 + 复杂防护"的组合。若你的目标只是无防护的动态页面,可先评估更轻量的 DynamicFetcher(docs/fetching/dynamic.md)或纯 HTTP 的 Fetcher。
延伸阅读
- 完整异步测试参考:tests/fetchers/async/test_stealth.py、tests/fetchers/sync/test_stealth_session.py
- 官方 Agent 技能示例(隐身会话用法):agent-skill/Scrapling-Skill/examples/03_stealthy_session.py
- 参数类型契约:
StealthSession/StealthFetchParams定义于 scrapling/engines/_browsers/_types.py - 选择 fetcher 的决策依据:docs/fetching/choosing.md
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