farukbagci/anti-bot-field-guide
GitHub: farukbagci/anti-bot-field-guide
通过分析 HTTP 响应特征来识别具体是哪个 anti-bot 产品拦截了你的请求,并提供针对性诊断建议的实地指南。
Stars: 0 | Forks: 0
# 反机器人字段指南
**你被拦截了。是什么拦截了你,你该怎么做?**
[](https://github.com/farukbagci/anti-bot-field-guide/actions/workflows/ci.yml)


🇨🇳 [简体中文 README](README.tr.md)
这是一份诊断实地指南,针对你在公共网络上遇到的 anti-bot 产品:如何将它们与你已经得到的响应区分开来,每个产品到底检查了什么,以及如何区分临时验证和永久封禁。
这不是一个绕过限制的代码库。大多数花在“通过”拦截上的时间都花在了错误的问题上——人们在登录墙处频繁更换代理,给 proof-of-work 挑战添加 sleep,或者因为一个实际上是格式错误请求导致的 `403` 而重写客户端。首先要进行识别。
```
python detect.py https://example.com # fetch it and say what answered
python detect.py --from-json response.json # or replay a response you logged
```
无额外依赖。仅使用标准库,要求 Python 3.9+。
## 60秒内识别你的拦截者
在修改你的客户端任何内容之前,请按照以下顺序进行:
1. **保存整个响应。** 状态码、每一个响应头、完整的响应体。如果你只保存了状态码,你就丢弃了诊断依据。
2. **查看 `Set-Cookie` 的名称。** 不是值——而是名称。`__cf_bm`、`datadome`、`_abck`、`_px3`、`visid_incap_*` 和 `aws-waf-token`,每一个都能一眼认出其供应商。
3. **扫描响应头以寻找供应商特征。** `cf-ray`、`x-iinfo`、`x-kpsdk-ct`、`x-amzn-waf-action`、`server: AkamaiGHost`。
4. **查看 `` 和响应体的前 500 个字节。** "Just a moment"、"Access Denied ... Reference #"、"Incapsula incident ID",或者包含 `captcha-delivery.com` URL 的 JSON 数据块。
5. **确认这是否真的是一个 anti-bot 产品。** 带有 `Retry-After` 的 `429`、`401`、重定向到 `/login` 以及 `451` 都不是机器人检测。详见[下文](#this-is-not-an-anti-bot-problem)。
`detect.py` 会为你执行第 2 到 4 步,并打印出匹配到的内容。
## 识别矩阵
每个供应商占一行。Cookie 的**名称**很关键,值则无关紧要。这里的所有内容都可以从单个响应中观察到。
| 供应商 | 响应头 | Cookies | 典型状态码 | Body 标记 | 实际检查的内容 |
|---|---|---|---|---|---|
| **Cloudflare** | `cf-ray`, `cf-cache-status`, `cf-mitigated: challenge`, `server: cloudflare` | `__cf_bm`, `cf_clearance`, `__cflb` | 403, 503 (旧版 JS challenge), 429 (速率规则);偶尔为 200 | `Just a moment...`, `cdn-cgi/challenge-platform`, `window._cf_chl_opt`, `Attention Required! \| Cloudflare`, `Error 1020` | TLS + HTTP/2 指纹、Header 顺序、带 proof-of-work 的 JS challenge、单次请求的 bot score |
| **DataDome** | `x-datadome` (并非在所有部署中都存在) | `datadome` | 默认 403,**因站点而异** | 包含 `captcha-delivery.com` URL 的 JSON body | TLS 指纹、Header 顺序和大小写、设备指纹 JS payload、行为信号、IP 信誉 |
| **PerimeterX / HUMAN** | — (无可靠的公开 Header) | `_px3`, `_pxvid`, `pxcts`, `_pxhd`, `_pxCaptcha` | 403 | `_pxAppId`, 包含 `"appId":"PX…"` 的 JSON, `jsClientSrc`, `px-cloud.net`, `Access to this page has been denied` | JS sensor payload、行为生物特征(指针和按键时间)、TLS 指纹、IP 信誉 |
| **Akamai Bot Manager** | `server: AkamaiGHost`, `x-akamai-request-id` | `_abck`, `bm_sz`, `ak_bmsc`, `bm_sv`, `bm_mi` | 403;同时也包含 **200 并带有降级内容** | `Access Denied`, `Reference #18.…`, `You don't have permission to access` | 通过混淆 JS 提交的 Sensor payload、TLS + HTTP/2 指纹、Header 顺序、请求节奏 |
| **Imperva / Incapsula** | `x-iinfo`, `x-cdn: Incapsula` | `visid_incap_*`, `incap_ses_*`, `nlbi_*`, `reese84` | 403;带有 JS 插页式页面的 200 | `Incapsula incident ID`, `_Incapsula_Resource` | 产生已签名 token cookie 的 JS challenge、TLS 指纹、IP 信誉、请求模式 |
| **Kasada** | `x-kpsdk-ct`, `x-kpsdk-cd`, `x-kpsdk-v`, `x-kpsdk-r` | — (状态存在于 Header 中) | 带有简短或空 body 的 **429**;也可能是 403 | `kpsdk`, 以 `149e9513-01fa-4fb0-aad4-566afd725d1b` 开头的脚本路径 | 在混淆 VM 中执行的 proof-of-work、浏览器 API 探测、TLS 指纹 |
| **AWS WAF** | `x-amzn-waf-action`;如果由 CloudFront 代理则有 `x-amz-cf-id` / `server: CloudFront` | `aws-waf-token` | Block 操作为 403;Challenge / CAPTCHA 为 202 或 405 —— **因配置而异** | body 中的 `awswaf.com` 主机,`aws-waf-token` | 规则集匹配:IP 集合、基于速率的规则、托管规则组;针对 Challenge 和 CAPTCHA 操作还有 JS challenge |
| **Queue-it** | 指向 `*.queue-it.net` 的 `location:` | `QueueITAccepted-*`, `Queue-it*` | 302,随后在排队页面返回 200 | `queue-it.net`, `You are now in line`, `waiting room` | **与你的客户端无关。** 这是一个基于已发放 token 的 FIFO 队列 |
包含各项信号独立权重的完整加权数据位于 [SIGNATURES.md](SIGNATURES.md) 中。
## 供应商详情
### Cloudflare
**流程原理。** 一个请求到达后,根据站点的配置,它会被打分,然后根据结果被放行、发起 challenge 或拦截。Managed Challenge 会从 `/cdn-cgi/challenge-platform/` 提供一个插页式页面,运行 JS,执行一个小型的 proof-of-work,探测浏览器 API,成功后设置 `cf_clearance`。`__cf_bm` 是一个独立、生命周期较短的 bot-management cookie,在普通流量中也会出现——看到它并不意味着你被 challenge 了。
**常见的拦截原因。** 几乎总是 TLS 指纹。一个在其 `User-Agent` 中声称是 Chrome 的客户端,如果执行的 TLS 握手显然不是 Chrome 特征,这就是最容易被捕捉并采取行动的信号。这就是为什么一个有效的 `cf_clearance` cookie 在普通的 HTTP 库中仍然可能返回 `403`:cookie 没问题,是握手特征出了问题。
**临时还是永久?** 查看响应体中的数字错误代码。`1020` 是某人编写的防火墙规则——从该 IP 或该指纹重试也无法清除。`1015` 是速率限制规则,退避一段时间确实可以清除。`1010` 是浏览器签名封禁。没有错误代码的 "Just a moment" 插页式页面是一个 challenge,在设计上是临时的。
**理智的做法。** 在做任何更改之前,先确定你属于这三种情况中的哪一种。对于你被允许收集的数据遇到真正的 challenge,可行的模式是在真实浏览器中通过一次,然后保持会话*和* TLS 指纹的一致性——[cloudflare-bypass-toolkit](https://github.com/farukbagci/cloudflare-bypass-toolkit) 是我对此的实现。如果是 `1020`,没什么好解决的;联系运营者或停止访问。
### DataDome
**流程原理。** 一个 JS payload 收集设备和行为信号并提交它们;判定结果包含在 `datadome` cookie 中。被拒绝的请求会收到一个包含指向验证码主机的 `url` 字段的 JSON 响应体,而不是 HTML 页面。
**常见的拦截原因。** 请求速率,以及内部不一致的 Header 集合——一个没有 `Accept-Language` 的 Chrome `User-Agent`、以浏览器不会发送的顺序排列的 Header,或者与所声称的导航类型不匹配的 `Sec-Fetch-*` Header。
**临时还是永久?** DataDome 的双向反应都很快。如果插页式页面仅在 N 次请求后出现并在暂停后消失,那你遇到的是基于速率的评分。如果它在干净的 IP 发出第一次请求时就出现,触发因素就是你的客户端指纹,而不是你的行为,等待也无济于事。
**理智的做法。** 首先降低并发;这是免费的,也是最常见的原因。确保你发送的 Header 是你所声称的浏览器会发送的。如果两者都不起作用,那就是这个产品在发挥作用——申请 API 访问权限吧。
### PerimeterX / HUMAN
**流程原理。** 一个 sensor 脚本收集指针移动、按键时间和设备属性,将其提交,并接收 `_px3`。没有有效 `_px3` 的请求将根据其他可用信息进行评分。
**常见的拦截原因。** 行为权重很高。一个从不移动指针、从不滚动,并以完全均匀的间隔请求页面的客户端,无论 Header 如何,看起来都不像人类会话。
**临时还是永久?** 拦截响应通常是携带 `appId`、`vid` 和 `uuid` 的 JSON。这些是按会话计算的值,而不是按请求产生的噪声:如果在你的重试中 `vid` 保持不变,说明你被识别为同一个客户端,重试是徒劳的。
**理智的做法。** 记录 `appId`、`vid` 和 `uuid`——如果你联系网站运营者,这正是他们会要求提供的信息。没有任何 Header 配置可以解决行为判定。对于大批量访问,申请 API 权限才是现实可行的途径。
### Akamai Bot Manager
**流程原理。** 混淆的 JS 收集 sensor payload 并将其 POST 到特定站点的路径;结果反映在 `_abck` 中。`bm_sz` 和 `ak_bmsc` 在流程早期设置,并且存在于普通流量中。
**常见的拦截原因。** HTTP/2 指纹和 Header 顺序,其影响远大于任何单一的 Header 值。Akamai 也是与**软拦截**最相关的供应商:返回带有真实 HTML 的 `200` 响应,但悄悄缺失了你请求的结果,或者故意减慢响应速度。
**临时还是永久?** 这是唯一一个“我被拦截了吗?”确实模棱两可的情况,所以要测量而不是猜测。在同一个 URL 上,比较你的响应体长度和已知的页面内标记与浏览器会话的响应。如果 `200` 响应的长度实际上比浏览器的短,或者缺失了浏览器总是能获取到的标记,这就是一个软拦截,而不是成功。
**理智的做法。** 在*每一个*响应上记录 body 长度和内容标记,以便让软拦截表现为异常,而不是表现为悄无声息地缺失数据行。Akamai 的拦截页面带有 `Reference #`——请一字不差地记录下来。
### Imperva (Incapsula)
**流程原理。** 两代产品共存。经典版 Incapsula 设置 `visid_incap_` 和 `incap_ses__` 并从 `/_Incapsula_Resource?...` 提供脚本。高级 Bot Protection(前身为 Distil 生产线)使用生成 `reese84` token cookie 的 JS 插页式页面。你可能会在同一主机上遇到其中一种或同时遇到两种。
**常见的拦截原因。** IP 信誉在这里占有很大比重——无论你的客户端是什么样的,数据中心 IP 范围都会被严厉扣分。重复的、没有会话连续性的完全相同请求是第二个常见原因。
**临时还是永久?** 拦截页面上的 `Incapsula incident ID` 映射到运营者一侧的特定规则。如果在干净的 IP 上首次请求就出现插页式页面,这指向你的客户端指纹;如果在持续流量后出现,则指向你的行为。
**理智的做法。** 捕获 incident id——它是使支持请求具有可操作性的唯一信息,而且运营者确实会对此做出回应。保持会话而不是发起独立的请求。
### Kasada
**流程原理。** 重度混淆的脚本在 bytecode VM 内部运行 proof-of-work,并探测浏览器 API。结果通过 `x-kpsdk-*`请求头传输,而不是通过 Cookie。
**常见的拦截原因。** 没有运行 proof-of-work。这里没有部分得分:没有有效的 `x-kpsdk-*` Header,你发送的其他任何内容都无关紧要。
**临时还是永久?** **Kasada 的拒绝通常以 HTTP 429 的形式出现,body 简短或为空。** 这是本指南中最容易被误诊的响应。没有 `Retry-After` 且没有速率限制 Header 的 `429` 不一定是速率限制——在添加 sleep 并无休止地等待之前,请检查同一站点的任何端点是否存在 `x-kpsdk-*` Header。
**理智的做法。** 尽早识别它,因为对于这个供应商来说,“再努力尝试一下”最有可能白白浪费几天时间。检查项是执行的浏览器工作;坦诚的选项是使用真实的浏览器引擎、申请访问权限或停止访问。
### AWS WAF
**流程原理。** 每个请求都会对一个规则集进行评估。规则可以是 IP 集合、基于速率的规则或 AWS 托管规则组,操作包括允许、阻止、计数、Challenge 或 CAPTCHA。Challenge 和 CAPTCHA 操作会提供一个插页式页面并设置 `aws-waf-token`。
**常见的拦截原因。** 通常是比指纹识别简单得多的东西:IP 信誉列表、基于速率的规则,或者是对明显不是浏览器的 `User-Agent` 做出反应的托管规则组(常见的托管规则会明确针对默认库的 UA 进行拦截)。
**临时还是永久?** 基于速率的规则会在滑动窗口内进行评估,并在窗口结束后自然清除。IP 集合阻止则不会。如果退避几分钟没有任何改变,但更换不同的网络立即生效,说明你在一个被拦截的列表中,而不是超出了限制。
**理智的做法。** 在假设发生了任何复杂情况之前,先尝试一个诚实、具有描述性的 `User-Agent` 和较低的访问速率。请注意,单凭 CloudFront 的 Header 根本不能说明是否启用了 WAF——许多网站由 CloudFront 代理,但根本没有使用 WAF。
### Queue-it
**流程原理。** 一个 `302` 将你重定向到 `https://.queue-it.net/?c=…&e=…&t=`,你会收到一个 token,然后排队等待。释放后,你将被重定向回原处,并携带 `QueueITAccepted-*` Cookie。
**常见的拦截原因。** 没有任何原因。你没有被拦截。这是针对流量高峰(如售票、注册窗口)的并发上限——它出现在本指南中,仅仅是因为它太容易被误读为机器人检测。
**临时还是永久?** 按照设计,完全属于临时情况。
**理智的做法。** 不要盲目重试:新的请求会生成新的 token,并将你置于队尾。要么等待并保留已发放的 cookie,要么将工作安排在等待室旨在管理的高峰期之外进行。
## 决策树
```
flowchart TD
A["Unexpected response"] --> B{"Status code"}
B -->|"401 / 402 / 407 / 451"| N1["Not anti-bot: auth, payment, proxy auth or legal block"]
B -->|"3xx"| R{"Where does Location point"}
B -->|"429"| Q{"Retry-After present"}
B -->|"403 / 503 / 202 / 405"| V["Check headers and Set-Cookie names against the matrix"]
B -->|"200"| S{"Does the body look like real content"}
R -->|"a queue-it.net host"| N2["Queue-it waiting room: wait, keep the cookie, do not retry"]
R -->|"a login or auth path"| N3["Login wall, not bot detection"]
R -->|"somewhere else"| N4["Ordinary redirect: follow it and re-evaluate"]
Q -->|"yes"| N5["Ordinary rate limiting: honour the header"]
Q -->|"no"| K{"Any x-kpsdk headers on this site"}
K -->|"yes"| K1["Kasada, not a rate limit"]
K -->|"no"| N6["Still most likely a rate limit: back off and confirm it clears"]
V --> W{"Vendor markers found"}
W -->|"no"| U["Compare with the same request from a browser before changing anything"]
W -->|"yes"| X{"Challenge marker or block marker"}
X -->|"challenge or interstitial"| C1["Transient. Passable in a real browser. Keep session and TLS consistent afterwards"]
X -->|"block, rule id or incident id"| C2["A rule someone wrote. Retrying will not clear it. Log the id, contact the operator or stop"]
S -->|"yes"| Y["Not blocked"]
S -->|"no, shorter than a browser gets"| Z["Soft block or degraded response. Verify against a content marker, never against the status code"]
S -->|"no, it is an interstitial"| C1
```
## 这不是 anti-bot 问题
很大一部分“我被拦截了”的情况最终证明属于以下情况之一。每一种从远处看都像是机器人检测。
| 症状 | 实际上属于 | 如何确认 |
|---|---|---|
| `429` | 普通的速率限制 | 存在 `Retry-After` 或 `x-ratelimit-*` Header,且退避一段时间后即可清除。注意上文提到的 Kasada 例外情况。 |
| 一个网络返回 `403`,另一个返回 `200` | 地理封锁或 IP 信誉 | 同样的请求,不同的网络,没有其他更改 |
| `302` 重定向到 `/login`,或者在期望内容的地方返回 `200` 的登录页面 | 登录墙 | 读取 `Location` Header 和页面标题 |
| 仅在某些 URL 上出现 `400` / `404` | 格式错误的请求 | 百分号编码、未编码的空格、过时的路径模板。与浏览器的请求逐字节进行比较 |
| 只有你的客户端返回 `403`,`curl` 从不返回 `403` | Header 缺失或矛盾 | 最常见的是缺失 `Accept-Language`,或者 `Sec-Fetch-*` Header 与声称的导航类型不匹配 |
| 在浏览器中正常工作,从代码请求失败,且 Header 完全一致 | HTTP/2 与 HTTP/1.1 的区别 | 浏览器协商使用 HTTP/2;许多库默认使用 HTTP/1.1,并且服务器可以看到协议版本。不同的帧顺序本身就是一种指纹 |
| 正常工作了一个小时,然后所有的请求都变成 `403` | 会话过期 | Cookie 会过期。`cf_clearance` 和 `__cf_bm` 的生命周期很短;当你的出口 IP 发生变化时,绑定到单个 IP 的会话就会失效 |
| `451` | 法律或地域限制 | 客户端层面无法解决 |
| 返回 `200`,但 body 为空或被截断 | 瞬时的源站错误,或软拦截 | 对照已知正常的响应比较 body 长度。**永远不要将简短的 `200` 视为“没有更多结果”** |
最后一行情况造成的代价最昂贵。一个在空页面上停止的分页循环会很容易地提前结束一次爬取,并且报告成功。
## 你应该记录的信号
在你认为异常的每一个响应上——理想情况下也包括正常响应的样本——请捕获以下这些信息。存储是很便宜的;而你无法重现的诊断依据则不然。
| 字段 | 原因 |
|---|---|
| `status` | 很明显,但其本身不足以说明问题 |
| `cf-ray` / `x-akamai-request-id` / `x-amzn-requestid` | 运营者自己的请求 ID。这是支持团队会要求提供的信息 |
| `Set-Cookie` **名称**(绝不记录值) | 无需进一步工作即可识别供应商 |
| `server`, `x-cdn`, `x-iinfo`, `x-kpsdk-*`, `x-amzn-waf-action`, `cf-mitigated` | 供应商特征标识 |
| `retry-after`, `x-ratelimit-*` | 区分速率限制和机器人检测 |
| `content-type` | 在你期望 HTML 的地方收到 JSON 数据块,其本身就是一个特征 |
| `content-length` 和实际 body 长度 | 捕获软拦截和截断的唯一方法 |
| `` | 挑战页面成本最低的指纹 |
| Body 的前 ~500 个字节 | 足以在事后识别出供应商,且体积小易于保存 |
| 供应商 incident id (`Incapsula incident ID`, Akamai `Reference #`, PX `uuid`) | 使支持请求具有可操作性 |
| 请求时间戳和耗时 | 拦截前的缓慢响应本身就是一个信号 |
| 使用的出口 IP 或代理 | 否则你无法将拦截与特定网络关联起来 |
| 协商的是 HTTP/1.1 还是 HTTP/2 | 排除掉一整类的误导 |
将它们作为每个响应一行的 JSON 格式进行记录。`detect(status, headers, body)` 稍后无需进行网络调用即可读取该记录——这就是保持其纯粹性的意义所在。CLI 直接读取相同的格式:
```
python detect.py --from-json response.json
```
```
{"status": 403,
"url": "https://example.com/search",
"headers": {"server": "cloudflare", "cf-ray": "0000000000000000-XXX",
"set-cookie": ["__cf_bm=PLACEHOLDER; Path=/"]},
"body": "Just a moment... …"}
```
每个字段都是可选的。一条只有 `status` 的记录仍然可以生成报告。
## detect.py
```
$ python detect.py https://example.com
URL https://example.com/
Status 403
Body 4812 bytes
Title Just a moment...
Vendor Cloudflare (confidence: high, score 26)
State challenge
Matched signals
- header cf-ray: 0000000000000000-XXX
- header cf-mitigated: challenge
- header server: cloudflare
- cookie __cf_bm
- body contains "cdn-cgi/challenge-platform"
- body contains "just a moment..."
- status 403
Next step
Separate 'challenge' from 'firewall rule' before doing anything else. ...
```
选项:
| 标志 | 效果 |
|---|---|
| `--from-json FILE` | 读取已保存的响应而不是发起请求——一个包含 `status`、`headers`、`body`、`url` 的 JSON 对象。`-` 读取 stdin。完全不使用网络 |
| `--json` | 机器可读的输出 |
| `--verbose` | 同时打印每一个响应头——在捕获新签名时使用此选项 |
| `--timeout N` | 秒数,默认值为 15 |
| `--user-agent UA` | 覆盖 User-Agent。将诚实的 UA 与浏览器的 UA 进行比较本身就是一种诊断 |
| `--no-redirect` | 在第一个响应处停止,以便能够看到进入等待室的 `302` |
默认的 `User-Agent` 如实标识了该工具。这是故意的:这是一个诊断工具,真实的请求能告诉你运营者的规则会对真实的客户端做什么。
退出代码:`0` 成功运行,`1` 请求失败,`2` 用法错误。
### 作为库使用
`detect(status, headers, body, url=None)` 是纯粹的——没有 I/O 操作,不修改全局变量——因此你可以将其应用于来自任何 HTTP 客户端的响应,或者应用于你几周前捕获的日志。
```
from detect import detect
result = detect(response.status_code, response.headers, response.text)
if result["state"] == "block":
log.warning("hard block by %s: %s", result["vendor"], result["signals"])
elif result["state"] == "challenge":
...
```
`headers` 接受 dict、列表 dict、`(name, value)` 对列表,或 `http.client.HTTPMessage`。名称匹配不区分大小写,并会读取所有重复的 `Set-Cookie` Header。`body` 接受 `str` 或 `bytes`。`status` 接受 `int` 或 JSON 日志返回的字符串。
同样导出的有:`fetch(url, timeout=15.0, user_agent=..., follow_redirects=True)` → `(status, headers, body, urls)`,`load_record(path_or_dash)`,`format_report(result, url=None, headers=None, verbose=False)`,`normalize_headers(headers)`,`FetchError`,以及 `STATE_*` 常量。
返回的字段:
| 字段 | 含义 |
|---|---|
| `vendor` | 得分最高的供应商名称,或 `None`。结合 `confidence` 一起解读——这里的 `low` 意味着“只有一个微弱的标记”,而不是“已确认” |
| `confidence` | `high` / `medium` / `low` / `none`,源自累加的信号权重 |
| `score` | 匹配到的信号的累加权重 |
| `state` | `clear` / `challenge` / `block` / `queue` / `unknown` —— 描述**此响应**,而不是站点。只有置信度为 `medium` 或以上的供应商才能设置它 |
| `signals` | 匹配内容的人类可读列表 |
| `candidates` | 匹配到任何内容的每一个供应商,按得分排序 |
| `next_step` | 如何应对。一旦实际上确认了供应商,内容就是特定的;而在证据微弱时,则提供通用建议 |
| `hints` | 适合该响应的非 anti-bot 解释,并在最佳匹配微弱时给出警告 |
| `title`, `body_length`, `status` | 值得记录 |
`vendor` / `state` 的拆分很重要:一个站点可能位于 Cloudflare 之后,但仍然会交给你一个完全正常的页面。“存在 Cloudflare”和“Cloudflare 正在拦截我”是不同的调查结果,而混淆它们经常会让人们误入歧途。
添加一个供应商只需要在模块级别的 `VENDORS` 列表中添加一个条目——不需要更改其他代码。详见 [CONTRIBUTING.md](CONTRIBUTING.md)。
## 范围和意图
本代码库用于在你被允许收集的数据上进行**识别和理解**。了解你面前的是什么,能让你做出明智的决定:请求 API 访问权限、降低访问速率、修复格式错误的请求,或者停止访问。上面的大多数条目最终都以“联系运营者”或“减速”结束,因为这确实是当前情况所需要的。
以下是你自己的责任,而不是本代码库的:
- **`robots.txt` 和站点的服务条款。** 阅读它们。你有义务遵守。
- **访问数据的权限。** 在技术上能够抓取某些内容并不代表获得了授权。
- **速率和负载。** 封禁通常是站点在告诉你,你的访问让它付出了成本。
这里故意不提供的内容,也不接受作为贡献提交的内容:
- CAPTCHA 解决服务集成
- 凭据处理、撞库或账户接管技术
- 付费墙或访问控制绕过
- 针对任何供应商的逐步规避方案
这些产品的存在主要是为了阻止欺诈、撞库和库存滥用。一份帮助你识别并做出适当响应的指南对大家都有用;而一份用于击败它们的“操作手册”不仅没有用,也不是本项目的主旨。
## 客观局限性
- **本指南只做识别;不负责解决。** 对于大多数供应商来说,不存在配置上的捷径,坦率地承认这一点比假装有招更有用。
- **签名会漂移。** Cookie 名称、脚本路径和状态码都会改变,其中很多都是针对单个客户的配置。表中任何标记为“因配置而异”的内容确实会因配置而异。请根据你自己的抓包进行验证。
- **没有签名并不能证明什么。** 供应商可能存在且保持沉默。`detect.py` 只报告它匹配到了什么,从不说“这个站点没有受到保护”。
- **低置信度的匹配并不是一种识别。** 单个微弱的标记会被列为候选项并被标记为微弱。它从不设置响应状态,也绝不提供特定于供应商的建议——普通页面文案中的“waiting room”并不代表是一个队列,而“just a moment”这几个词也不代表是 Cloudflare 挑战。
- **单个响应只是一个狭窄的视角。** 有些产品只会在第二次请求、POST 请求、或者 XHR(而不是文档加载)中显露身份。
- **这里不包含行为或 TLS。** 提到 JA3/JA4 和 HTTP/2 指纹是因为它们是这些产品检查的内容,但是测量你自己的指纹需要标准库脚本之外的工具。
- **深度参差不齐。** 有些部分来自持续的生产实践,有些则来自极少的接触。在我不太确定的地方,措辞都会如实表达——“因配置而异”、“通常”、“实地观察”。欢迎并会合并修正意见。
## 相关工具
为了一个 pipeline 构建的五个代码库,它们依然可以作为一个整体被解读。本指南告诉你你面临的是什么,`cloudflare-bypass-toolkit` 处理最常见的应对方案,`scraper-skeleton` 执行任务,`proxy-pool` 提供地址,而 `jsonl-qc` 决定交付的结果是否合格。
| 代码库 | 功能 |
|---|---|
| [cloudflare-bypass-toolkit](https://github.com/farukbagci/cloudflare-bypass-toolkit) | 如果应对方案是针对 Cloudflare:用真实的浏览器通过一次挑战,然后在没有浏览器的情况下重放 |
| [scraper-skeleton](https://github.com/farukbagci/scraper-skeleton) | 编排层:有限的并发、重试、崩溃后恢复、计数对账 |
| [proxy-pool](https://github.com/farukbagci/proxy-pool) | 为运行器提供地址,并针对被禁用的地址提供每次失败后的冷却机制 |
| [jsonl-qc](https://github.com/farukbagci/jsonl-qc) | 验证输出的结果:schema、填充率、重复项、基线漂移——告诉你如何捕获那 27% 被静默丢失的数据 |
## 贡献
添加一个供应商只需在 `VENDORS` 中添加一个 dict 和一个测试。这是主要的贡献途径,并在 [CONTRIBUTING.md](CONTRIBUTING.md) 中有逐步文档说明。同样欢迎对现有签名进行修正——准确性是本代码库的全部价值所在。
```
python -m pytest tests/ -q
```
## 许可证
MIT —— 详见 [LICENSE](LICENSE)。
标签:Python, WAF识别, Web诊断, 反爬虫, 文档结构分析, 无后门, 网络请求分析, 逆向工具