Crawl4AI 反爬进阶指南:Stealth 模式与 Undetected 浏览器的原理与实践
本文围绕 Crawl4AI 的文档 Undetected Browser Mode,系统讲解项目中两套反机器人检测能力:基于 playwright-stealth 的 Stealth 模式和基于 Patchright 的 Undetected 浏览器模式。读完后,你将掌握两者的适用场景对比、完整可运行的配置代码,并能顺着 browser_adapter.py 等源码理解适配器模式的底层实现,为受保护的站点选择最小必要的规避方案。
两种反爬能力的定位
Crawl4AI 提供了两个层次的防检测特性,用于访问启用了机器人检测的网站:
- Stealth 模式:通过 playwright-stealth 修改浏览器指纹与行为,适合应对基础检测;
- Undetected Browser Mode:一种高级浏览器适配器,通过更深层次的浏览器补丁(Patchright)应对更复杂的检测服务。
两者的能力边界可以按下表对照(引自文档原文):
| Feature | 常规浏览器 | Stealth 模式 | Undetected 浏览器 |
|---|---|---|---|
| WebDriver 检测 | 不通过 | 通过 | 通过 |
| Navigator 属性检测 | 不通过 | 通过 | 通过 |
| 插件模拟 | 不通过 | 通过 | 通过 |
| CDP 检测 | 不通过 | 部分通过 | 通过 |
| 深层浏览器补丁 | 无 | 无 | 有 |
| 性能影响 | 无 | 极小 | 中等 |
| 配置复杂度 | 无 | 无 | 极小 |
从源码结构看,这条"能力分层"在 async_crawler_strategy.py 中有一条清晰的调用链:AsyncPlaywrightCrawlerStrategy 根据传入的 browser_adapter 判断是否使用 Undetected 模式,并把结果透传给 BrowserManager:
# crawl4ai/async_crawler_strategy.py
self.adapter = browser_adapter or PlaywrightAdapter()
...
self.browser_manager = BrowserManager(
browser_config=self.browser_config,
logger=self.logger,
use_undetected=isinstance(self.adapter, UndetectedAdapter)
)
也就是说,是否走 Patchright 浏览器,完全由你传入的适配器类型决定,这就是文档所说的"Drop-in Replacement"的实现基础。
何时选择哪种方案
文档给出的选型建议:
常规浏览器 + Stealth 模式适用于:
- 站点只做基础检测(检查
navigator.webdriver、插件等); - 需要良好性能且只需应对基础保护;
- 站点检查常见的自动化指标。
Undetected 浏览器适用于:
- 站点使用复杂的机器人检测服务(如 Cloudflare、DataDome 等);
- 仅靠 Stealth 模式不够;
- 愿意用一定性能换取更好的规避效果。
最佳实践——渐进式增强(Progressive Enhancement):
- 从"常规浏览器 + Stealth 模式"开始;
- 若被拦截,切换到 Undetected 浏览器;
- 若仍被拦截,组合 Undetected 浏览器 + Stealth 模式。
这里有一点值得注意:Stealth 模式在配置层是独立于适配器的。BrowserConfig 中的 enable_stealth 默认为 False,其文档字符串明确说明它"不能与 use_undetected 浏览器模式共用";而 browser_manager.py 中创建 Stealth 适配器的条件正是 self.config.enable_stealth and not self.use_undetected。结合 browser_adapter.py 中 StealthAdapter 的实现(playwright_stealth.Stealth().apply_stealth_async(page)),可以推断官方推荐的"最强组合"实际由两层机制叠加:Patchright 底层补丁 + StealthAdapter 在页面创建时注入的 stealth 脚本。
Stealth 模式:一个开关即可启用
Stealth 模式是与常规浏览器和 Undetected 浏览器都兼容的简单防检测方案:
from crawl4ai import AsyncWebCrawler, BrowserConfig
# 用常规浏览器启用 stealth 模式
browser_config = BrowserConfig(
enable_stealth=True, # 简单标志位即可启用
headless=False # 有头模式更不容易被检测
)
async with AsyncWebCrawler(config=browser_config) as crawler:
result = await crawler.arun("https://example.com")
Stealth 模式做了些什么:
- 移除
navigator.webdriver标志; - 修改浏览器指纹;
- 模拟真实的插件行为;
- 调整 navigator 属性;
- 修复常见的自动化泄漏。
在源码层面,enable_stealth=True 之后,BrowserManager 会在 初始化时创建 StealthAdapter,并在页面创建时通过 _apply_stealth_to_page 调用 Stealth().apply_stealth_async(page) 注入补丁(见 browser_manager.py#L1469-L1480)。另外 browser_adapter.py#L159-L176 显示,如果环境中没有安装 playwright_stealth 包,适配器会静默降级为普通 Playwright 行为——生产使用前建议确认该依赖已安装。
同时,enable_stealth 有一个硬性约束:BrowserConfig 构造函数 中,当 browser_mode='builtin' 时直接抛出 ValueError("Stealth 模式需要独立的浏览器实例"),使用内置托管浏览器的场景需改用 dedicated 模式。
Undetected 浏览器模式:基于 Patchright 的深度补丁
对于 Stealth 模式无法绕过的复杂检测,应使用 Undetected 浏览器适配器。
核心特性
- Drop-in Replacement:与常规浏览器模式使用相同 API;
- Enhanced Stealth:内置补丁,规避常见检测手段;
- Browser Adapter 模式:可在常规与 Undetected 模式之间无缝切换;
- 自动安装:
crawl4ai-setup会安装所有必要的浏览器依赖。
Quick Start
import asyncio
from crawl4ai import (
AsyncWebCrawler,
BrowserConfig,
CrawlerRunConfig,
UndetectedAdapter
)
from crawl4ai.async_crawler_strategy import AsyncPlaywrightCrawlerStrategy
async def main():
# 创建 undetected 适配器
undetected_adapter = UndetectedAdapter()
# 创建浏览器配置
browser_config = BrowserConfig(
headless=False, # headless 模式更容易被检测
verbose=True,
)
# 用 undetected 适配器创建爬虫策略
crawler_strategy = AsyncPlaywrightCrawlerStrategy(
browser_config=browser_config,
browser_adapter=undetected_adapter
)
# 用自定义策略创建爬虫
async with AsyncWebCrawler(
crawler_strategy=crawler_strategy,
config=browser_config
) as crawler:
# 你的爬取代码在这里
result = await crawler.arun(
url="https://example.com",
config=CrawlerRunConfig()
)
print(result.markdown[:500])
asyncio.run(main())
这段代码在仓库中有一份可直接运行的对照示例:undetectability/undetected_basic_test.py,它先跑常规模式再跑 Undetected 模式,并对比 result.success、result.status_code 与 result.markdown.raw_markdown 长度,适合用作升级前后的 A/B 验证脚本。
源码视角:UndetectedAdapter 到底做了什么
UndetectedAdapter 与 PlaywrightAdapter 的关键差异有三处:
- 导入源不同。
get_imports()返回的是patchright.async_api中的Page、Error、TimeoutError,而 PlaywrightAdapter 返回playwright.async_api的同名类型; - 执行上下文隔离。
evaluate()默认以isolated_context=True调用page.evaluate,把 Crawl4AI 自身的脚本与页面环境隔离,只有涉及注入的控制变量(__console、__captured、__error、window.__)时才使用非隔离上下文; - 控制台捕获改为 JS 注入。由于 Patchright 场景下不便依赖事件监听,
setup_console_capture通过page.add_init_script注入一段脚本,把console.log/info/warn/error/debug包装后写入window.__capturedConsole,并额外监听error与unhandledrejection事件写入window.__capturedErrors;retrieve_console_messages再把这些数据取回并做毫秒到秒的时间戳换算。
相应地,browser_manager.py 的 start() 中,use_undetected=True 时 Playwright 实例本身就会从 patchright.async_api.async_playwright 导入,确保整个浏览器生命周期都跑在 Patchright 之上。
组合两种能力
为获得最大规避效果,可将 Stealth 模式与 Undetected 浏览器叠加:
from crawl4ai import AsyncWebCrawler, BrowserConfig, UndetectedAdapter
from crawl4ai.async_crawler_strategy import AsyncPlaywrightCrawlerStrategy
# 启用 stealth 模式的浏览器配置
browser_config = BrowserConfig(
enable_stealth=True, # 启用 stealth 模式
headless=False
)
# 创建 undetected 适配器
adapter = UndetectedAdapter()
# 组合两种能力
strategy = AsyncPlaywrightCrawlerStrategy(
browser_config=browser_config,
browser_adapter=adapter
)
async with AsyncWebCrawler(
crawler_strategy=strategy,
config=browser_config
) as crawler:
result = await crawler.arun("https://protected-site.com")
注意:如前所述,
BrowserConfig文档字符串声明enable_stealth不应与 undetected 模式共用,而BrowserManager在 undetected 场景下也会跳过 StealthAdapter 的创建。上述"组合"写法在代码层面不会报错,但 stealth 脚本是否实际生效取决于当前版本的实现细节;从源码结构看,此时真正的规避主力是 Patchright 补丁。升级版本后建议用检测测试站(下文 Example 1)验证实际效果,而不是盲目依赖组合。
完整示例
示例 1:Stealth 模式检测验证
import asyncio
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig
async def test_stealth_mode():
# 简单的 stealth 模式配置
browser_config = BrowserConfig(
enable_stealth=True,
headless=False
)
async with AsyncWebCrawler(config=browser_config) as crawler:
result = await crawler.arun(
url="https://bot.sannysoft.com",
config=CrawlerRunConfig(screenshot=True)
)
if result.success:
print("✓ 成功访问机器人检测测试站")
# 保存截图以验证检测项结果
if result.screenshot:
import base64
with open("stealth_test.png", "wb") as f:
f.write(base64.b64decode(result.screenshot))
print("✓ 截图已保存 - 检查绿色(通过)的检测项")
asyncio.run(test_stealth_mode())
用 SannySoft 之类的检测站截图验证,是判断"当前配置是否已被识破"最直观的手段。
示例 2:Undetected 浏览器模式
import asyncio
from crawl4ai import (
AsyncWebCrawler,
BrowserConfig,
CrawlerRunConfig,
UndetectedAdapter
)
from crawl4ai.async_crawler_strategy import AsyncPlaywrightCrawlerStrategy
async def main():
# 创建浏览器配置
browser_config = BrowserConfig(
headless=False,
verbose=True,
)
# 创建 undetected 适配器
undetected_adapter = UndetectedAdapter()
# 用 undetected 适配器创建爬虫策略
crawler_strategy = AsyncPlaywrightCrawlerStrategy(
browser_config=browser_config,
browser_adapter=undetected_adapter
)
# 用自定义策略创建爬虫
async with AsyncWebCrawler(
crawler_strategy=crawler_strategy,
config=browser_config
) as crawler:
# 配置爬取
crawler_config = CrawlerRunConfig(
markdown_generator=DefaultMarkdownGenerator(
content_filter=PruningContentFilter()
),
capture_console_messages=True, # 测试适配器的控制台捕获
)
# 在一个通常会检测机器人的站点上测试
print("Testing undetected adapter...")
result: CrawlResult = await crawler.arun(
url="https://www.helloworld.org",
config=crawler_config
)
print(f"Status: {result.status_code}")
print(f"Success: {result.success}")
print(f"Console messages captured: {len(result.console_messages or [])}")
print(f"Markdown content (first 500 chars):\n{result.markdown.raw_markdown[:500]}")
if __name__ == "__main__":
asyncio.run(main())
这里 capture_console_messages=True 恰好能验证前文提到的 JS 注入式控制台捕获是否正常工作:result.console_messages 的长度应当反映 UndetectedAdapter.retrieve_console_messages 取回的数据。
Browser Adapter 模式详解
Undetected 浏览器支持是用适配器模式实现的,允许在不同的浏览器实现之间无缝切换:
# 常规浏览器适配器(默认)
from crawl4ai import PlaywrightAdapter
regular_adapter = PlaywrightAdapter()
# Undetected 浏览器适配器
from crawl4ai import UndetectedAdapter
undetected_adapter = UndetectedAdapter()
三个适配器均在 crawl4ai/init.py 中导出(BrowserAdapter、PlaywrightAdapter、UndetectedAdapter)。抽象基类 BrowserAdapter 定义了所有浏览器特有操作的统一接口,适配器需要处理:
- JavaScript 执行(
evaluate); - 控制台消息捕获(
setup_console_capture/retrieve_console_messages); - 错误处理(
setup_error_capture); - 浏览器特定的优化(
get_imports决定类型与异常从哪个库导入)。
对比两个具体实现可以看清设计意图:PlaywrightAdapter 的 retrieve_console_messages 直接返回空列表(消息已通过 page.on("console") 事件实时收集),而 UndetectedAdapter 则必须主动拉取页面内注入的 window.__capturedConsole——两种策略的差异被完全封装在适配器内部,上层策略代码无需感知。
最佳实践
- 避免 Headless 模式:headless 下更容易被检测
browser_config = BrowserConfig(headless=False) - 使用合理的延迟:不要过快翻页。当前源码中
CrawlerRunConfig的delay_before_return_html默认为 0.1 秒、simulate_user默认为False(见 async_configs.py),可显式调大以模拟真实节奏:crawler_config = CrawlerRunConfig( delay_before_return_html=2.0, # 额外延迟 simulate_user=True, # 模拟用户交互 ) - 轮换 User Agent:
BrowserConfig的user_agent默认是一个固定的 Chrome 116 字符串,可自行定制,或使用user_agent_mode="random"让内置的 user_agent_generator 生成随机 UA;注意配置构造函数还会基于 UA 自动推导sec-ch-ua请求头(async_configs.py#L908-L909),保持两者一致能减少指纹矛盾。 - 优雅处理失败:部分站点仍会检测并拦截
if not result.success: print(f"Crawl failed: {result.error_message}")
进阶:渐进式检测处理
async def crawl_with_progressive_evasion(url):
# 第 1 步:先试常规浏览器 + stealth
browser_config = BrowserConfig(
enable_stealth=True,
headless=False
)
async with AsyncWebCrawler(config=browser_config) as crawler:
result = await crawler.arun(url)
if result.success and "Access Denied" not in result.html:
return result
# 第 2 步:若被拦截,尝试 undetected 浏览器
print("Regular + stealth blocked, trying undetected browser...")
adapter = UndetectedAdapter()
strategy = AsyncPlaywrightCrawlerStrategy(
browser_config=browser_config,
browser_adapter=adapter
)
async with AsyncWebCrawler(
crawler_strategy=strategy,
config=browser_config
) as crawler:
result = await crawler.arun(url)
return result
安装
Undetected 浏览器的依赖会在运行安装命令时自动装好:
crawl4ai-setup
该命令同时安装常规与 Undetected 两种模式所需的浏览器。从 install.py 的实现看,它在 Playwright 安装之后,会额外执行 patchright install --with-deps --force chromium;若任一步骤失败,会提示你手动运行 python -m patchright install --with-deps。因此在手动部署(如容器)场景中,确认 Patchright 浏览器二进制已安装是排查"Browser Not Found"的第一步。
局限性与注意事项
- 性能:由于额外的补丁,比常规模式略慢;
- Headless 检测:部分站点仍可能识别 headless 模式;
- 资源占用:可能比常规模式消耗更多资源;
- 不是 100% 保证:高级反机器人服务在持续演化,没有一劳永逸的方案。
故障排查
浏览器未找到
运行安装命令:
crawl4ai-setup
仍被检测
尝试叠加其他功能:
crawler_config = CrawlerRunConfig(
simulate_user=True, # 增加用户行为模拟
wait_time=5.0, # 更长等待(如当前版本无此参数,改用 delay_before_return_html / mean_delay)
)
参数核对说明:在当前仓库的
CrawlerRunConfig源码中,已确认存在的延迟/模拟相关参数为delay_before_return_html、mean_delay、max_range、simulate_user、wait_for_images(见 async_configs.py);文档示例中的magic与wait_time请以你所用版本实际签名为准。
性能问题
若性能不佳,可只对受保护站点启用 Undetected 模式:
# 仅对受保护站点使用选择性 undetected 模式
if is_protected_site(url):
adapter = UndetectedAdapter()
else:
adapter = PlaywrightAdapter() # 默认适配器
未来计划
说明:Crawl4AI 未来版本可能会默认启用 Stealth 模式与 Undetected 浏览器,以提供更好的开箱成功率。目前用户应在需要时显式启用这些功能。
总结
Crawl4AI 提供了灵活的反机器人方案:
- 从简单开始:多数站点用常规浏览器 + Stealth 模式即可;
- 必要时升级:面对复杂防护时切换到 Undetected 浏览器;
- 组合拳应对最强防护:两者叠加使用。
同时请记住:
- 始终尊重 robots.txt 与网站服务条款;
- 使用合理的延迟,避免压垮服务器;
- 权衡每种方案的性能代价;
- 渐进式测试,找到满足需求的最低规避级别。
延伸阅读
- Advanced Features - 全部高级特性概览
- Proxy & Security - 配合反机器人特性使用代理
- Session Management - 跨请求保持会话
- Identity Based Crawling - 额外的反检测策略
- Anti-Bot Detection & Fallback - 检测到拦截时的自动重试与代理升级
- Undetected 基础测试脚本 - 常规与 Undetected 模式的对照验证
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 StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00