关于 certainlyscraped
certainlyscraped 是面向 Agent 和开发者的混合网页抓取工具,先尝试轻量抓取,页面失败或受阻时再逐级升级。它生成可编辑的 YAML 配置,并报告字段覆盖率、页面结果和停止原因。
主要功能
- 01逐级切换抓取方式
- 02生成 YAML 抓取配置
- 03将列表提取为行数据
- 04提供逐页结果与诊断
- 05支持代理池和并行页面
适用场景
- 01将分页列表转换为 CSV 行
- 02诊断受阻或不完整的页面
- 03通过配置的代理组抓取页面
MCP 相关
其 MCP 服务提供 probe、init、try_selector 和 scrape 四个工具。响应包含提取的行数据、数据集位置和逐页结果。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
4收录的工具列表 · 更新于 1 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| url | any | 否 |
详细介绍
certainlyscraped
一个会逐步增加抓取力度的混合型爬虫。
它会先尝试能成功的最低成本抓取方式,只有页面被拦截或仍然失败时才升级,并准确报告获取到的内容以及停止原因。它会读取页面、为你编写配置、检查每个字段是否都已填充,并将列表转换为数据行,而不是只做出承诺。
快速开始:20 秒
pip install "certainlyscraped[mcp]"
cscrape init https://books.toscrape.com/catalogue/page-1.html --name books
cscrape run books.yaml
init 会探测网站,找到页面上重复出现的内容块,猜测字段和下一页链接,并打印每个字段填充了多少条目。run 会抓取页面并写入 out.csv;如果获取的行数少于 --min-rows(默认值为 1),它会明确报错,而不会谎称成功。
如需供 Agent 使用,请将 MCP 服务添加到客户端。使用 uv 时,无需预先安装任何内容:
{
"mcpServers": {
"certainlyscraped": {
"command": "uvx",
"args": ["--from", "certainlyscraped[mcp]", "certainlyscraped", "mcp"]
}
}
}
如果不使用 uv,可运行 pip install "certainlyscraped[mcp]",并使用 "command": "certainlyscraped", "args": ["mcp"]。
这样 Agent 就能使用 probe、init、try_selector 和 scrape;完整说明请参阅 MCP 服务(供 Agent 使用)。

每日在线检查的实际输出:
[probe] https://books.toscrape.com/catalogue/page-1.html -> tier 1 (static page)
[init] wrote books.yaml: 20 items on this page (20 repeats, median text 51 chars, each has a link, each has an image)
field coverage on this page:
title 20/20 (100%)
url 20/20 (100%)
image 20/20 (100%)
price 20/20 (100%)
availability 20/20 (100%)
配置是简单易读、可编辑的 YAML:
name: books
url: "https://books.toscrape.com/catalogue/page-1.html"
tier: 1
max_pages: 5
rate_limit_sec: 2.0
paginate:
selector: "li.next a"
items:
selector: "article.product_pod"
fields:
title: { selector: "h3 a", attr: title }
url: { selector: "h3 a", attr: href, absolute: true }
image: { selector: "img.thumbnail", attr: src, absolute: true }
price: { selector: "p.price_color", attr: text, type: float }
availability: { selector: "p.instock.availability", attr: text }
output: csv
逐级升级机制
每个页面都从探测出的层级(或配置中的 tier)开始,仅在当前级别失败时才升级。
| 返回结果 | 接下来会发生什么 |
|---|---|
| 被限流(429,或没有挑战的 419) | 尝试通过另一个代理组访问页面;如果没有其他代理组,则等待一次(使用 Retry-After,否则使用 cooldown_sec),然后重试。会打印 [ladder] <host>: rate limited, waiting <n>s |
| 挑战页面(Cloudflare、PerimeterX、DataDome 标记) | 保留当前代理,将层级提高一级,最高到 max_tier |
| 普通 403 | 先尝试另一个代理组;如果没有剩余代理组,则提高层级 |
| 5xx | 错误页面会保留当前代理:先在同一层级重试一次,再提高层级。短暂的 503(正文小于 20 kB)会被归类为拦截,而非错误,因此不会在同一层级重试:有其他代理组时会切换到另一个代理组,否则提高层级 |
| 无响应(连接拒绝、DNS、TLS、连接重置) | 有代理池时,先尝试切换到另一个代理组一次;然后无论是否有代理池,都会在同一层级重试一次,再提高层级 |
每次切换代理组时,也会打印 [ladder] <host>: <reason>, trying another proxy group,因此切换过程清晰可见。
网站因挑战页面或 403 而升级后,该网站后续页面会从新的层级开始。仅因错误而升级到的层级不会被记住。页面报告中的 steps 会显示各级尝试情况和租用的代理组,例如 ["t1@dc-a", "t1@dc-b", "t2@dc-b"](一个代理组返回 403,另一个代理组遇到挑战,最后在 tier 2 获取到真实 HTML)。
层级
| 层级 | 说明 | 成本 |
|---|---|---|
| 1 | httpx,普通 HTTP | 每页仅需几毫秒,几乎无需安装 |
| 2 | curl_cffi,TLS 模拟 | 需要安装真正的 Python 依赖;拒绝 Python TLS 指纹的网站,仍可能向它提供真实 HTML |
| 3 | 浏览器栈(scrapling、cloakbrowser 或 playwright) | 可选但较重的额外组件;仅在设置 max_tier: 3 时使用,且不保证适用于特定网站 |
tier: auto 会先进行探测,并选择能返回真实页面的最低层级。配置了代理池时,auto 会跳过探测并从 tier 1 开始:探测会直接发送请求,而使用代理池时,不允许任何请求从源站 IP 发出。之后会按逐级升级机制继续尝试。层级不是保证,而是升级机制的起点。
结果与退出代码
每页一条记录,可通过 --report report.jsonl 获取,也包含在 MCP 响应的 pages 字段中:
{"url": "https://httpbin.org/status/403", "outcome": "blocked", "status": 403, "rows": 0, "attempts": 2, "steps": ["t1", "t2"], "proxy_group": null, "elapsed_ms": 1358, "note": "max_tier 2 reached"}
outcome 的值为 ok、empty、blocked、rate_limited、not_found 或 error。每次运行还会打印一行摘要:
[done] 2 pages: 1 ok, 1 blocked (HTTP 403 on /status/403)
没有响应的页面没有可引用的 HTTP 状态码,因此会显示为
[done] 1 page: 1 error (no response on /list)。
| 退出代码 | 含义 |
|---|---|
| 0 | 完成:至少达到 --min-rows,且没有未完成的页面 |
| 1 | 配置、I/O 或抓取错误;若设置了 --min-rows 0,则表示每个页面都出错 |
| 2 | 行数少于 --min-rows(2 优先于 4);在默认 --min-rows 1 下,如果所有页面都出错,则没有行,因此退出代码为 2 |
| 3 | probe --fail-blocked:目标站点被拦截 |
| 4 | 未完成:至少达到 --min-rows,但一个或多个页面在所有重试后仍以 blocked、rate_limited 或 error 结束 |
只有在 --min-rows 0 要求不返回任何行时,页面级故障才会导致退出代码 1。默认设置为 --min-rows 1 时,如果所有页面都因错误而失败,会先进行行数检查,然后以退出代码 2 结束。
退出代码 4 的适用范围有意设得较窄。范围末尾的 not_found 或 empty 页面不计入,重试后成功的页面也不计入。对于只检查是否为零的脚本,--accept-partial 会将 4 映射为 0。如果某个页面失败,运行仍会写入已获取的行,并在报告中标明缺失的页面。
MCP 服务(供 Agent 使用)
pip install "certainlyscraped[mcp]"
certainlyscraped mcp
客户端配置:
{
"mcpServers": {
"certainlyscraped": {
"command": "certainlyscraped",
"args": ["mcp"]
}
}
}
四个工具,均为对同一 API 的轻量调用:
| 工具 | 返回内容 |
|---|---|
probe(url) | 层级判定、拦截标记、找到的嵌入式 JSON |
init(url) | 配置 YAML 和字段覆盖情况 |
try_selector(url, selector, within?) | 匹配数、示例值、提示 |
scrape(config, min_rows?, max_rows?) | status、行数据(最多 max_rows 行,默认为 50)、实际行数、数据集路径,以及每页的结果 |
config 必须是内联 YAML 或 JSON 文本,不能是路径。配置键 proxy、
proxies、cookies、since、snapshot 和 fetcher 均会被拒绝,未知键(如
json 和 headers)也一样(嵌入式 JSON 应放在 items.json 下)。status 表示本次运行的结果:
complete(至少有 min_rows 行,且没有未完成的页面)、incomplete
(行数足够,但有页面以被拦截、限流或出错结束)、too_few_rows(行数少于 min_rows)、blocked(没有行,且每个页面都被拦截或限流)以及 error(没有行,且每个页面都出错)。这些不是 CLI 的退出代码:仅因拦截而失败的 run 会以退出代码 2 结束,退出代码 3 仅用于 probe --fail-blocked,退出代码 1 表示配置、I/O 或抓取异常(或在 --min-rows 0 时表示所有页面都出错)。错误请求仍会返回状态字典:工具绝不会向 JSON-RPC 通道抛出异常。
默认情况下会拒绝私有地址:回环地址、RFC 1918 地址、链路本地地址(包括
169.254.169.254)以及唯一本地 IPv6 地址;检查会在 DNS 解析后和每次重定向时进行。--allow-private 会关闭此限制,供本地开发使用。CLI 不会拒绝私有地址,只有 MCP 服务会拒绝。拒绝后,该页面将直接终止:仅尝试一次,不重试、不递进,结果为 error,并在备注中说明拒绝原因。
代理只从服务自身的 --proxies proxies.yaml 获取,绝不会来自工具参数,服务也不会读取 CERTAINLYSCRAPED_PROXIES。probe、
init 和 try_selector 会在调用期间从代理池中租用一个代理;如果池中没有可用代理,它们会返回 blocked,并附带 no proxy available for <host>,且不会发出请求。如果未设置 --proxies,服务就没有代理池,会直接抓取。文件读取和数据集均限制在工作目录内。
代理
单个代理仍然可用,并会变成只有一个代理的池:
proxy: "http://10.1.2.3:8000"
池会为代理分组命名,这样遇到 403 或 429 时,可以先尝试切换网络,再考虑付费升级到更高档位:
proxies:
list: proxies.txt # one URL per line, optional "# group" label
gateway: # one generic provider gateway
template: "http://{user}-country-{country}-session-{session}:{password}@gw.example:7000"
user: myuser
password_env: GATEWAY_PASSWORD
country: se
session: rotate # or sticky
per_proxy_rate_sec: 1.0
cooldown_sec: 300
max_retries: 3
proxies.txt:
http://user:[email protected]:8000 # dc-a
http://10.1.9.9:8000 # dc-b
http://10.1.1.7:8000
分组标签取 # 后面的内容;如果没有,则按首次出现的顺序使用中性标签 net-1、net-2……,每个 IPv4 /16、IPv6 /48 或主机名对应一个标签。标签中绝不包含代理地址或主机名。
代理对某个主机不可用时,会针对该主机暂停使用 cooldown_sec;它不会从池中移除。session: rotate 会为每次租用生成新的令牌:每个页面一个令牌;切换分组后遇到 403 或 429 时,也会再次租用令牌。sticky 则会为每个主机和工作进程保留一个令牌。
密码绝不能放在主配置中。请将密码放在专用的代理文件中,并通过 --proxies 传入;或者通过 password_env 从环境变量中读取。提交的 YAML 中若在内联 proxy:/proxies: URL 中包含密码,程序会拒绝运行,并提示这两种替代方式。页面结果和 MCP 响应中会带有分组标签,绝不包含代理 URL 或密码。页面若因代理失效而失败,会将传输错误保留为备注,例如 ConnectionError: Failed to perform, curl: (7) Failed to connect to books.toscrape.com:443 over proxy *** ...(保留目标地址,代理地址显示为 ***);无法解析的主机则显示为 DNSError: ... Could not resolve host: no-such-host.invalid ...。日志中不会出现代理地址,因此分组标签和错误文本就是诊断依据。MCP 防护机制拒绝的私有地址属于终止性错误:只尝试一次,不重试,也不继续升级。
run 的优先级:--no-proxy > --proxies FILE >
CERTAINLYSCRAPED_PROXIES > 配置文件。--no-proxy 会忽略所有已配置的代理,直接获取内容。MCP 没有此类标志,也不会读取 CERTAINLYSCRAPED_PROXIES;如果未使用 --proxies,就没有代理池。
速度取决于访问多个主机或使用代理池,而不只是线程数。该分级机制会按照 per_proxy_rate_sec(若未设置则使用 rate_limit_sec;默认值为 1.0),对每个(主机,代理)组合进行限速。
没有代理池时,无论 concurrency 设置为何值,同一主机在每个时间间隔内最多只会收到一个请求,因此单主机的 pages: 任务仍会受网站限速。有代理池时,同一主机上的 N 个代理可提供 N 个并发时隙:在同一主机上以 1.0 秒间隔抓取八个页面,使用一个代理耗时 7.1 秒,使用四个代理耗时 1.1 秒,无论这四个代理都归入派生出的 net-1 组,还是分别使用四个标签组,结果都一样。
并行页面
pages:
template: "https://news.ycombinator.com/news?p={n}"
from: 1
to: 10
concurrency: 4
也可以使用明确的列表:
urls:
- "https://example.com/a"
- "https://example.com/b"
无论哪个响应先到,输出的行都会按照页面顺序排列。pages: 范围内,只要连续出现两个 not_found 或 empty 页面,抓取就会停止,不仅限于范围末尾:中间出现空档也会提前结束任务。这些页面不属于不完整页面,因此任务仍可能以退出码 0 结束。
更多命令
cscrape try <url> "p.price_color" --in "article.product_pod" # test a field
cscrape fetch <url> --out page.html # save once, iterate offline
cscrape probe <url> # tier verdict
cscrape verify data.csv --min-rows 10 # check a dataset after the fact
try 还会标记由 JavaScript 渲染的页面、指出嵌入的 JSON,并警告 DevTools 中不稳定的“Copy selector”路径。docs/selectors.md 提供了完整指南。
不会做什么
- 不会登录、绕过登录墙,也不会使用不属于你的凭据。
- 不会保证某个网站今天可用,只因为它在另一天曾经可用。
- 不会借助代理绕过指纹识别或 JavaScript 挑战(Cloudflare managed challenge、
PerimeterX、DataDome)。这些页面会报告为
blocked;你提供的代理可以绕过 IP 封禁、IP 段封禁和速率限制,但无法绕过挑战。 - 不会提供抓取的数据,只提供工具。
它能访问哪些内容
COVERAGE.md 是一张包含 40 个热门数据目标的表格,每晚进行探测并附上日期发布:哪些目标返回了数据行,哪些返回了 JavaScript 壳页面,哪些返回了重复区块而非数据(跟踪 JSON、Cookie 对话框、商店里毫无价值的轮播图),哪些被挡在外面。每一行都会列出找到的字段,你可以自行判断结果。该表格根据 targets/popular.yaml 生成,并会自动更新,因此如果某个网站开始拒绝访问,它就会出现在表格中,而不是继续只作为本文中的一项声明。根据定义,需要登录的网站在这里会被列为无法访问:第 1 至 3 层不会获取你的密码,也不会向你索要密码。
为什么这些数字值得信赖
每次运行都会记录每个页面的一项结果;每日实时检查会抓取断言为退出码 0 的同一批示例网站,因此悄然出现的回归会显示为红色检查结果,而不是友好地生成空 CSV。
旧名称
在 0.5.0 之前,scrapeforge 命令仍然可用,并会在 stderr 中打印一条弃用通知;import scrapeforge 也仍然可用,并会触发 DeprecationWarning。Python 会在 __main__ 中执行导入时(通常是脚本场景)显示该警告,而在导入的模块中则会隐藏它。新代码应使用 certainlyscraped 或 cscrape;旧名称将在 0.5.0 中移除。
开发
git clone https://github.com/vhsgreed/certainlyscraped && cd certainlyscraped
pip install -e ".[dev]"
python -m pytest -q
测试会使用 tests/fixtures/ 中保存的页面离线运行。CI 会在 Python 3.11 至 3.14 上运行测试。每日实时检查会使用真实网站运行示例。采用 MIT 许可证。属于 vhsgreed 工具集的一部分。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。