scrape-mcp
⚠️ 这是 scrape-mcp 的官方源仓库(upstream)。 若在别处看到同名/相似项目并带不同作者署名,即为转载或派生,非原作者发布。
反爬感知抓取 + 极省 Token 内容精简的 MCP Server —— 让 AI 读真实网页时,只花 1%~5% 的 token 就能拿到干净、真实、免登录的正文。
自动化分三层应对:默认 curl_cffi(伪装 TLS/JA3 + HTTP2)低成本打头阵,命中 Cloudflare/验证码/WAF 才升级 Playwright 真浏览器渲染;遇到"登录才给内容"的站,先弹窗手动登录一次、登录态落盘,之后自动带上、过期会提示重登。
⚡ 60 秒入门
方式 A:一键安装(推荐,零配置)
pip install mcp-scrape
# 或从源码:cd scrape-mcp && pip install -e .
然后本机任意目录即可:
scrape-mcp --version # 确认装好(应输出 0.x)
claude # 在支持 MCP 的客户端里把它当服务接入
不再需要 clone、手动设
PYTHONPATH或python -m;scrape-mcp命令在任意目录可用, 默认 stdio 直接被 MCP 客户端接管。想用 HTTP 直连(Postman/curl),跑scrape-mcp --transport streamable-http --port 8000。
方式 B:一键演示(拉源仓库跑 web_fetch + web_extract,看省 token 效果)
git clone https://github.com/guanyuyan/scrape-mcp.git
cd scrape-mcp
python -m venv .venv && .venv\Scripts\pip install -e .
.venv\Scripts\python scripts\demo.py
预计输出(对不同站点自动选 L1/L2 分级,给出省 token 压缩比):
=== 文档站 | https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
ok=True tier=l1 status=200 html=260128B → tokens=3730 (约 5.7% 压缩)
标题: 跨源资源共享(CORS) - HTTP | MDN
ok=True tier=l1 data={"heading": "跨源资源共享(CORS)", "h2_count": 10, "links": 665}
第 2 步:作为 MCP 客户端接入(Claude / Cursor 等,命令指向已安装的 scrape-mcp)
scrape-mcp # 已安装时;或 python -m scrape_mcp.server
在客户端里调用 web_fetch(url=...) 即可拿到极省正文。
MDN 整页约 260KB,这里只花几千 token 就拿到干净正文;拦截/登录页会自动升级或如实上报,不会把验证码当正文。
功能说明
它解决什么:模型读网页不是在读正文,而是在读整棵 DOM(脚本、导航、版权、链接一应尽收),又贵又脏。这个工具把页面剪成"只有正文"再喂给 AI。
核心价值(三点)
- 极省 token —— 属性剥离 + 模板块丢弃 + 站外链接降级 + 表格转 CSV + 预算截断。实测 MDN 整页 214KB → 数百 token,压缩率约 1%。
- 自动分级反爬 —— 85% 的站只走最轻的 L1(快、并发高),拦截/JS 骨架页才升级 L2 真浏览器 + stealth 伪装,绝不暴力逐站上浏览器。
- 登录墙处理 + 如实上报 —— 判定登录/拦截时返回
login_required/blocked,引导登录而非硬撞,从不让模型把验证码当正文。
怎么用(一行)
web_fetch(url="https://xxx/article/1") → JSON:{ok, content(极省正文), token_estimate, tier, blocked?, login_required?, cached?}
覆盖的站点类型:文章页 / 文档站 / 门户与列表页 / 需要 JS 渲染的 SPA / 需要登录才给正文的站(如知乎)。
真实站点实测(一键 scripts/demo.py 可复现):
| 站点 | 类型 | 分级 | 原始 | 抓到正文 | 说明 |
|---|---|---|---|---|---|
| MDN CORS 文档 | 文档站 | L1 | 260KB | ~3.7k token(约 5.7%) | 纯静态,最短路径直达 |
| 腾讯新闻首页 | 列表/门户 | L2 | 338KB | ~1.4k token(约 1.6%) | 动态页升级浏览器渲染 |
| 极客邦首页 | SPA/动态 | L2 | 12KB | ~1.4k token | JS 骨架页,需渲染才有正文 |
形态:本地 MCP Server(stdio/HTTP 均可),隐私可控;支持单页 web_fetch、批量 web_batch、结构化抽取 web_extract、登录持久化 login,缓存默认开启。
特性
- 分级抓取:L1
curl_cffi→ L2 Playwright,拦截才升级,不误伤正常页面。 - 内容密度选根:从 article/main/div 中挑出"恰好装住正文的最紧容器",避开导航/侧栏。
- compact 精简:属性剥离、模板块丢弃、站外链接降级、表格转 CSV、token 预算截断, 压缩率可达 1~20% 数量级(如 MDN 214KB → 数百 token)。
- 拦截如实上报:不把挑战页/空壳页当正文喂给模型,返回
blocked/block_reason。 - 稳定错误码:
error_code(timeout/dns/tls/connection/blocked/login_required/http_4xx…),调用方可程序化决策。 - 登录态持久化:
login工具手动登录并落盘,web_fetch自动带上;过期会提示重登。 - 缓存与批量:L1 明文响应按 TTL 磁盘缓存;
web_batch并发抓取一批 URL。 - 轻量爬取:
web_crawl按depth/limit发现站内链接并逐页抓取,静态资源自动跳过。 - 输出格式可选:
format=markdown|text|json,正文可按段落数组直接入库。 - 同域并发闸门:同一站点默认最多 2 个并发请求,跨站仍可并行,避免批量抓取时把单站打穿。
- Stealth 加固:Wipe
webdriver、UA 数据、Canvas/WebGL 确定性指纹噪声、时区对齐。
安装
# L2 渲染所需浏览器(国内可用镜像)
$env:PLAYWRIGHT_DOWNLOAD_HOST="https://cdn.npmmirror.com/binaries/playwright"
pip install "playwright>=1.49"
playwright install chromium
# 或复用系统浏览器,无需下载
$env:SCRAPE_MCP_L2_CHANNEL="chrome" # 或 msedge
启动(MCP,stdio)
scrape-mcp # 默认 stdio,被 MCP 客户端接管
scrape-mcp --transport streamable-http --port 8000 # 切 HTTP 直连
scrape-mcp --version / --help # 版本 / 用法
不传参默认 stdio。传
--transport/--port由 CLI 显式启动,优先级高于.env/环境变量。
环境变量(前缀 SCRAPE_MCP_)
全部配置可用环境变量(SCRAPE_MCP_ 前缀)设置,也可集中放到项目根目录 .env 文件里。
# 项目根目录执行
cp .env.example .env # 生成配置模板
# 编辑 .env 后无需改代码,直接启动即可
优先级:实际进程环境变量 > .env > 默认值。.env 被 gitignore,不入库;.env.example 是随仓库提交的模板。
| 变量 | 默认 | 说明 |
|---|---|---|
IMPERSONATE |
chrome |
curl_cffi 指纹模板 |
REQUEST_TIMEOUT |
20 |
L1 超时(秒) |
PROXY |
— | 代理,L1/L2 共用 |
L2_ENABLED |
true |
是否启用 Playwright 升级链 |
L2_CHANNEL |
— | 关联浏览器:空=自带 Chromium,chrome/msedge=系统 |
L2_SETTLE_TIMEOUT |
8 |
挑战页等待窗口(秒) |
L2_SCROLL_STEPS |
5 |
L2 滚动步数(触发懒加载) |
L2_SCROLL_DELAY_MS |
400 |
L2 每步滚动间隔(毫秒) |
L2_TIMEZONE |
Asia/Shanghai |
隐身加固时区 |
DATA_DIR |
~/.scrape_mcp |
登录态/缓存放盘目录 |
CACHE_TTL |
300 |
L1 缓存 TTL(秒),0 关闭 |
RETRY_TIMES |
2 |
网络异常/5xx 重试次数(429/403 不重试) |
RETRY_BACKOFF |
0.5 |
重试退避基数(秒,指数增长) |
BATCH_MAX_CONCURRENCY |
4 |
web_batch 并发上限 |
BATCH_MAX_URLS |
20 |
web_batch 单批上限 |
HOST_MAX_CONCURRENCY |
2 |
同一主机的并发上限(web_batch/web_crawl 共用) |
CRAWL_MAX_PAGES |
20 |
web_crawl 单次最多抓取页数硬上限 |
RESPECT_ROBOTS |
true |
默认遵循目标站 robots.txt;false 忽略(仅建议自用合规场景关闭) |
ALLOWED_HOSTS |
— | 主机白名单(逗号分隔);非空时仅允许名单内主机 |
DENIED_HOSTS |
— | 主机黑名单(逗号分隔),优先于白名单 |
MAX_QPS |
10 |
全网抓取限流(每秒请求数);0 不限速 |
HTTP 模式(Postman / curl 直连)
默认 stdio,改环境变量即可切到 HTTP,无需改代码:
$env:SCRAPE_MCP_TRANSPORT="streamable-http"
$env:SCRAPE_MCP_HTTP_PORT="8000"
python -m scrape_mcp.server
以 json_response=1(默认)启动,请求/响应都是普通 JSON(非事件流)。stateless 模式无需先 initialize,直接发 tools/call 即可。
健康检查:GET /healthz 返回版本与关键开关,可用于容器探针/网关巡检。
必需项:请求头 MCP-Method、Mcp-Name;body 内 params 需带 _meta(protocolVersion + clientCapabilities)。完整示例见下。
接入 MCP 客户端(Claude / Cursor 等)
先 pip install mcp-scrape(或用项目 .venv 跑 pip install -e .),使 scrape-mcp 命令全局可用;然后按标准 MCP 方式接入。
以 Claude Desktop 为例(claude_desktop_config.json):
{
"mcpServers": {
"scrape-mcp": {
"command": "scrape-mcp"
}
}
}
- 用
command: "scrape-mcp"(已全局安装),免去PYTHONPATH与系统解释器导致的ModuleNotFoundError。 - 若复用系统浏览器(免下载):设环境变量
SCRAPE_MCP_L2_CHANNEL为chrome或msedge。 - 配对成功后重启客户端,即可在会话里直接调用
web_fetch/web_extract/web_batch/web_crawl。
MCP 工具
web_fetch(url, max_tokens=4000, link_policy="internal", format="markdown", ...)→ 状态 + 极省正文link_policy:internal(站内相对链接+站外降级)/all/noneformat:markdown(默认,保留标题/列表/链接)/text(剥掉标记的纯文本)/json(content为段落数组)- 抓取参数:
headers/cookies(自定义请求,不走共享缓存)、timeout、bypass_cache - 渲染参数(仅 L2 生效):
wait_for(等 CSS 选择器)、scroll(滚动触发懒加载)、click(点"加载更多")
web_extract(url, schema={...}, ...)→ 按字段 schema 抽取结构化 JSON(可入库)- 字段类型:
text(默认)/html/attr(需配attr)/count/list/object - 嵌套:
list配fields返回对象数组,object在匹配节点内部继续抽取 - 后处理:
regex+group(正则提取,未命中回落default)、abs_url(相对链接转绝对)、strip - 抓取/渲染参数与
web_fetch相同(不含link_policy/format)
- 字段类型:
web_batch(urls, max_tokens=3000, link_policy="internal", format="markdown", ...)→ 并发抓取一批,逐 URL 返回;单条异常不影响整批- 支持
headers/cookies/timeout/bypass_cache(对该批所有 URL 生效)
- 支持
web_crawl(url, depth=1, limit=10, same_host=True, max_tokens=2000, ...)→ 从起始页按层发现站内链接并逐页抓取depth:跳转层数,0只抓起始页,最大3;limit:总页数上限(受CRAWL_MAX_PAGES约束)- 单页失败只影响该页;图片/压缩包等静态资源链接自动跳过
login(url, timeout=180)→ 打开带界面浏览器手动登录并持久化登录态
返回为 JSON 字符串(落在 content[0].text,请解析文本,勿依赖 structured_content)。
失败时带稳定 error_code;传输失败/5xx 会自动指数退避重试,attempts 为实际尝试次数;L2 交互未命中(如选择器不存在)只在 notes 中提示,不判失败。
web_extract 示例
schema='{"fields": {
"title": {"selector": "h1"},
"price": {"selector": ".price", "regex": "¥([\\d.]+)", "group": 1},
"main_link": {"selector": "a", "type": "attr", "attr": "href", "abs_url": true},
"tag_count": {"selector": ".tag", "type": "count"},
"tags": {"selector": ".tag", "type": "list", "list_key": "text"},
"items": {"selector": ".item", "type": "list", "fields": {
"name": {"selector": ".name"},
"url": {"selector": "a", "type": "attr", "attr": "href", "abs_url": true}
}}
}}'
# → {"ok":true, "data": {"title":"…","price":"299.00","main_link":"https://…",
# "tag_count":3,"tags":["a","b","c"],"items":[{"name":"…","url":"https://…"}]}, "missing":[]}
Docker
# 仅 L1(镜像小,覆盖大多数页面)
docker build -t scrape-mcp .
# 带 L2 真浏览器(Cloudflare/验证码/SPA)
docker build --build-arg INSTALL_BROWSER=1 -t scrape-mcp:l2 .
docker run -p 8002:8002 scrape-mcp
curl http://127.0.0.1:8002/healthz # {"ok":true,"version":"…"}
目录结构
src/scrape_mcp
├── server.py # MCP 入口 + 工具(web_fetch/web_extract/web_batch/web_crawl/login)
├── config.py # 环境变量配置
├── tokenizer.py # tiktoken 计数 + 预算截断
├── core/
│ ├── http_client.py # L1 curl_cffi + 磁盘缓存
│ ├── fetcher.py # 分级调度(L1→L2),对工具层返回 FetchOutcome
│ ├── detector.py # 拦截/骨架页判定
│ ├── browser.py # L2 Playwright + stealth
│ ├── session.py # 登录态持久化
│ ├── cache.py # HTTP 响应缓存
│ ├── errors.py # 统一错误码
│ └── hostlimit.py # 同域并发闸门
└── extract/
├── compact.py # DOM 剪枝 → 极省 token 正文
├── schema.py # 结构化抽取(按字段 schema 出 JSON)
├── links.py # 链接发现(web_crawl 用)
└── render.py # markdown → 纯文本 / 分块
开发
pip install -e ".[dev]"
python -m pytest
Metadata
Release files for mcp-scrape 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| mcp_scrape-0.3.0.tar.gz | 67.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_scrape-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 121.8 kB
Release files / mcp_scrape-0.3.0.tar.gz
| Download URL | mcp_scrape-0.3.0.tar.gz |
|---|---|
| Size | 67.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fdeda58bbc88af4292aed4d3a7a411315639fc2f9b3c9f72cf9d35530798a960
|
|
BLAKE2b-256 checksum How to use checksums |
df0ef04ec78dfd98831391dcebe75844178c965145c299e41d33c8702033562a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.2
|
Release files / mcp_scrape-0.3.0-py3-none-any.whl
| Download URL | mcp_scrape-0.3.0-py3-none-any.whl |
|---|---|
| Size | 53.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b23397667b528cae9fa58a5159d90e3c685fb70d483d46d04ea8a9536b03d6f7
|
|
BLAKE2b-256 checksum How to use checksums |
b1638f562edf76750cff2fe768520e5d0e43c582dd692f1cd269c5fa50b1d852
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.2
|
Security for mcp-scrape 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.