1. OpenClaw 操控浏览器被 Cloudflare 拦下,先别急着换工具
你写了一段 OpenClaw 脚本,本地跑得好好的,一访问目标站点就卡在「Checking your browser before accessing」或者直接返回 403、1020、cf-mitigated 这类响应头。页面没渲染出来,后续的点击、填表、截图全部失败。这不是 OpenClaw 本身坏了,而是目标站点前面的 Cloudflare 判定这次请求「不像正常浏览器」,把请求挡在了边缘节点。
OpenClaw 是一套浏览器自动化框架,能驱动真实浏览器内核完成导航、元素定位、表单提交、截图等操作,适合做数据采集、流程自动化、回归测试的人。它默认走的是本机网络出口,浏览器指纹、TLS 握手特征、请求头顺序都带着自动化痕迹。Cloudflare 的 Bot Management 会综合 IP 信誉、JA3/JA4 指纹、HTTP 头顺序、JS 挑战执行情况来判断,只要有一项对不上,就可能触发拦截。
我试过最直接的排查方式:把同一个 OpenClaw 请求分别指向两个不同的出口,一个被拦、一个通过,就能确认问题出在网络出口还是请求特征。这篇就按这个思路,给你一份可复制的 config.toml 骨架,加上 TaoToken 统一 Key/API 通道的接入步骤,最后用一次请求验证动作定位拦截来源。全程不涉及任何绕过手段,只做合规的通道配置与特征排查。
2. 为什么 Cloudflare 会盯上 OpenClaw 的请求
2.1 拦截的三种典型表现
第一种是 JS 挑战页,返回 503 加一段window._cf_chl_opt脚本,浏览器要执行几秒才放行。第二种是硬拦截,直接 403,响应体里带Attention Required! | Cloudflare。第三种是静默降级,返回 200 但内容是验证页,OpenClaw 以为加载成功,实际拿不到目标 DOM。
这三种背后对应不同的判定维度。JS 挑战说明 Cloudflare 怀疑你是机器人但愿意给机会;硬拦截说明 IP 信誉或指纹已经进了黑名单;静默降级最常见于请求头缺失Accept-Language、Sec-Fetch-*这类浏览器必带字段。
2.2 出口 IP 与请求特征,哪个才是元凶
很多人第一反应是「换个 IP 就好了」,但实际排查下来,请求特征的问题占比更高。Cloudflare 会检查 TLS 握手的 JA3 指纹,OpenClaw 如果用的是非浏览器栈发起的连接,指纹和 Chrome 差很远。另外 HTTP/2 的伪头顺序、User-Agent与sec-ch-ua是否匹配,都是硬指标。
判断方法很简单:用同一个 OpenClaw 配置,只改出口通道,看拦截是否消失。如果换了出口还是被拦,那就是请求特征问题,需要调 config.toml 里的浏览器参数;如果换了出口就通过,说明是原出口 IP 信誉问题。TaoToken 在这里的作用是提供一条稳定的统一 API 通道,让出口和请求特征都走可控路径,方便你对比定位。
3. TaoToken 前置:拿到统一 Key 与通道地址
TaoToken 是一个统一模型与 API 通道服务,把多家模型的调用收敛到一个 Key 和一套接口上,适合需要稳定出口、统一鉴权的自动化场景。对 OpenClaw 来说,它的价值在于:你不用为每个目标站点单独配代理,而是通过统一通道发起请求,出口特征一致、可复现,排查拦截时变量更少。
接入前你需要准备两样东西:一个 API Key,和通道的基础地址。Key 在控制台的 API Keys 页面创建,建议按项目命名,方便后续轮换。基础地址用https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为 base_url 使用。
创建 Key 的入口在这里:
控制台 API Keys:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=openclaw_cloudflare
如果你还没决定用哪个模型来驱动 OpenClaw 的页面理解或内容提取,可以先在模型对话里试跑一段提示词,确认输出格式符合预期再写进配置:
模型对话:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=openclaw_cloudflare
Key 拿到后不要硬编码进脚本,用环境变量注入。下面这行在 Linux/macOS 下设置,Windows 用set或$env::
export TAOTOKEN_API_KEY="sk-你的实际key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"4. 可复制的 config.toml 骨架与接入步骤
4.1 config.toml 完整骨架
OpenClaw 的配置文件通常放在项目根目录或~/.openclaw/config.toml。下面这份骨架把浏览器启动参数、请求头、通道地址都拆开写,方便你逐项对照排查。注意[browser]段里的参数是给真实浏览器内核用的,不是伪造,目的是让自动化浏览器保持和手动浏览器一致的默认行为。
# OpenClaw 浏览器自动化配置骨架 # 用途:配合 TaoToken 统一通道,排查 Cloudflare 拦截来源 [channel] # 统一 API 通道,不带查询参数 base_url = "https://taotoken.net/api" # 从环境变量读取,避免硬编码 api_key_env = "TAOTOKEN_API_KEY" # 请求超时,Cloudflare 挑战页可能较慢,给足时间 timeout_ms = 45000 # 失败重试次数,建议 2 次,避免触发风控 max_retries = 2 [browser] # 使用真实浏览器内核,不要用 headless 伪装 engine = "chromium" headless = false # 视口设为常见桌面尺寸,减少指纹异常 viewport_width = 1440 viewport_height = 900 # 语言必须和 Accept-Language 一致 locale = "zh-CN" # 时区与出口地区匹配 timezone = "Asia/Shanghai" [browser.headers] # 这些头浏览器会自动带,显式写出便于排查 accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8" accept_language = "zh-CN,zh;q=0.9,en;q=0.8" # sec-ch-ua 必须和 User-Agent 里的版本对应 sec_ch_ua = "\"Chromium\";v=\"124\", \"Google Chrome\";v=\"124\", \"Not-A.Brand\";v=\"99\"" sec_ch_ua_mobile = "?0" sec_ch_ua_platform = "\"Windows\"" # 不要手动改 User-Agent,让浏览器自己带 user_agent = "" [request] # 导航等待策略,networkidle 更适合挑战页 wait_until = "networkidle" # 挑战页检测关键词,命中则记录日志 challenge_markers = ["cf-chl", "_cf_chl_opt", "Checking your browser"] # 拦截状态码 block_status_codes = [403, 429, 503] [logging] level = "debug" # 记录每次请求的出口信息和响应头,排查用 log_request_headers = true log_response_headers = true4.2 逐项说明关键参数
headless = false这一项很多人会忽略。Cloudflare 对 headless 浏览器的检测很成熟,navigator.webdriver、window.chrome缺失、WebGL 渲染器异常都会暴露。用有头模式跑,拦截率会明显下降。如果你必须在服务器上跑,用虚拟显示而不是 headless。
sec_ch_ua和user_agent必须版本一致。如果你让浏览器自己带 UA,就不要手动写sec_ch_ua;如果手动写了,UA 里的 Chrome 版本号要对得上。版本错位是触发静默降级的常见原因。
wait_until = "networkidle"比domcontentloaded更适合挑战页,因为 JS 挑战需要等脚本执行完。但要注意,有些站点有长轮询,networkidle可能一直不触发,这时改成load加固定等待更稳。
4.3 把通道地址接进 OpenClaw 请求
OpenClaw 里发起页面请求时,把base_url指向 TaoToken 通道。下面是一段 Python 调用示例,展示如何读取配置并带上统一 Key:
import os import toml import requests # 读取配置 with open("config.toml", "r", encoding="utf-8") as f: cfg = toml.load(f) base_url = cfg["channel"]["base_url"] api_key = os.environ.get(cfg["channel"]["api_key_env"]) timeout = cfg["channel"]["timeout_ms"] / 1000 headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } # 通过统一通道发起请求,出口和鉴权都走 TaoToken resp = requests.post( f"{base_url}/v1/chat/completions", headers=headers, json={ "model": "claude-3-5-sonnet", "messages": [ {"role": "user", "content": "提取当前页面正文,返回纯文本"} ], }, timeout=timeout, ) print("状态码:", resp.status_code) print("响应头 cf-ray:", resp.headers.get("cf-ray")) print("响应头 cf-mitigated:", resp.headers.get("cf-mitigated"))这段代码的重点不是调用模型,而是观察响应头。cf-ray存在说明请求经过了 Cloudflare 边缘,cf-mitigated出现说明被拦截。把这两个头记下来,后面排查全靠它们。
5. 一次请求验证:定位是出口还是请求特征
5.1 验证动作设计
验证的核心是控制变量。准备两个出口:一个是你的本机直连,一个是 TaoToken 统一通道。用同一份 config.toml、同一个目标 URL、同一个 OpenClaw 脚本,各跑一次,对比响应头和状态码。
# 第一次:本机直连,记录结果 python openclaw_probe.py --target "https://目标站点.com" --channel local # 第二次:走 TaoToken 通道,记录结果 python openclaw_probe.py --target "https://目标站点.com" --channel taotokenopenclaw_probe.py里把每次请求的status_code、cf-ray、cf-mitigated、server四个字段写进日志。跑完后对比:
| 对比项 | 本机直连 | TaoToken 通道 | 结论 |
|---|---|---|---|
| 状态码 403 | 是 | 否 | 原出口 IP 信誉问题 |
| 状态码 403 | 是 | 是 | 请求特征问题,查 config.toml |
| cf-mitigated 存在 | 是 | 否 | 出口被标记,换通道解决 |
| 两者都 200 但内容为验证页 | 是 | 是 | 请求头缺失,补 sec-fetch-* |
5.2 成功结果长什么样
走 TaoToken 通道且配置正确时,你应该看到:状态码 200,cf-ray存在(说明经过了边缘节点),cf-mitigated不存在,响应体里是目标页面的真实 DOM,没有_cf_chl_opt脚本。OpenClaw 的页面加载回调里wait_until正常返回,元素定位能拿到内容。
如果状态码 200 但cf-mitigated是challenge,说明还在挑战流程里,需要检查wait_until是否给足了时间,以及浏览器是否真的执行了 JS。可以在 config.toml 里把timeout_ms调到 60000 再试。
5.3 验证通过后的收尾
验证通过后,把logging.level从debug调回info,避免日志过大。把max_retries保持 2 次,不要调高,重试太频繁反而会被 Cloudflare 加重标记。Key 用环境变量注入,不要写进 config.toml 提交到仓库。
如果你后续要做长期编码任务或 Agent 自动化,建议用 Coding Plan 来管理调用配额,避免单次 Key 被限流影响排查:
Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=openclaw_cloudflare
6. 本篇常见错排查
6.1 报错 1020:Access denied
这是 Cloudflare 的防火墙规则直接拒绝,通常和出口 IP 信誉有关。先按第 5 节的对比法确认是不是出口问题。如果是,走 TaoToken 通道后应该消失。如果走通道还在,检查 config.toml 里user_agent是否为空、sec_ch_ua是否和浏览器版本匹配。
6.2 报错 403 但响应体是验证页
状态码 403 加Attention Required!说明被硬拦。重点查accept_language和sec_fetch_*头。OpenClaw 默认可能不带Sec-Fetch-Site、Sec-Fetch-Mode,补上这三个:
[browser.headers] sec_fetch_site = "none" sec_fetch_mode = "navigate" sec_fetch_user = "?1" sec_fetch_dest = "document"6.3 挑战页一直循环不通过
wait_until = "networkidle"有时会在挑战页上一直等。改成load加显式等待挑战标记消失:
# 等待挑战标记消失,最多 30 秒 page.wait_for_function( "() => !document.querySelector('#cf-chl-container')", timeout=30000 )如果 30 秒还没消失,说明 JS 挑战没通过,检查浏览器是否禁用了 JS 或 Cookie。Cloudflare 挑战依赖 Cookie 写入,Cookie 被清会导致循环。
6.4 通道返回 401 或 403
这是 TaoToken 鉴权失败,不是 Cloudflare 拦截。检查TAOTOKEN_API_KEY环境变量是否设置、Key 是否过期、base_url是否写成了带路径的地址。正确写法是https://taotoken.net/api,不要加/v1后缀,路径由请求时拼接。
6.5 请求头顺序不对
Cloudflare 会检查 HTTP/2 伪头顺序。用 requests 库手动拼头时,顺序可能和浏览器不一致。这种情况建议直接用 OpenClaw 的浏览器内核发请求,而不是用 requests 模拟。浏览器内核的头顺序是原生的,不会出错。
排查时如果拿不准是通道问题还是配置问题,先看接入文档里的鉴权说明,再对照本文的 config.toml 骨架逐项核对:
接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=openclaw_cloudflare
最后提醒一句:Cloudflare 的判定是动态的,今天通过的配置明天可能因为站点规则更新而失效。把第 5 节的对比验证脚本留好,每次拦截复现时先跑一遍,比盲目改配置快得多。Key 轮换用控制台,别在脚本里写死,这是踩过坑之后最省事的习惯。