mcp-scrape 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. mcp_scrape-0.2.1/.env.example +75 -0
  2. mcp_scrape-0.2.1/.github/workflows/ci.yml +38 -0
  3. mcp_scrape-0.2.1/.gitignore +13 -0
  4. mcp_scrape-0.2.1/.pre-commit-config.yaml +7 -0
  5. mcp_scrape-0.2.1/CHANGELOG.md +52 -0
  6. mcp_scrape-0.2.1/CODE_OF_CONDUCT.md +42 -0
  7. mcp_scrape-0.2.1/CONTRIBUTING.md +47 -0
  8. mcp_scrape-0.2.1/LICENSE +21 -0
  9. mcp_scrape-0.2.1/PKG-INFO +263 -0
  10. mcp_scrape-0.2.1/README.md +236 -0
  11. mcp_scrape-0.2.1/SECURITY.md +24 -0
  12. mcp_scrape-0.2.1/examples/example_client.py +77 -0
  13. mcp_scrape-0.2.1/pyproject.toml +68 -0
  14. mcp_scrape-0.2.1/scripts/_probe_regression.py +44 -0
  15. mcp_scrape-0.2.1/scripts/demo.py +97 -0
  16. mcp_scrape-0.2.1/scripts/smoke_extract.py +53 -0
  17. mcp_scrape-0.2.1/scripts/smoke_http.py +86 -0
  18. mcp_scrape-0.2.1/src/scrape_mcp/__init__.py +1 -0
  19. mcp_scrape-0.2.1/src/scrape_mcp/config.py +96 -0
  20. mcp_scrape-0.2.1/src/scrape_mcp/core/__init__.py +0 -0
  21. mcp_scrape-0.2.1/src/scrape_mcp/core/browser.py +400 -0
  22. mcp_scrape-0.2.1/src/scrape_mcp/core/cache.py +60 -0
  23. mcp_scrape-0.2.1/src/scrape_mcp/core/detector.py +97 -0
  24. mcp_scrape-0.2.1/src/scrape_mcp/core/fetcher.py +151 -0
  25. mcp_scrape-0.2.1/src/scrape_mcp/core/http_client.py +120 -0
  26. mcp_scrape-0.2.1/src/scrape_mcp/core/ratelimit.py +36 -0
  27. mcp_scrape-0.2.1/src/scrape_mcp/core/robots.py +113 -0
  28. mcp_scrape-0.2.1/src/scrape_mcp/core/session.py +47 -0
  29. mcp_scrape-0.2.1/src/scrape_mcp/extract/__init__.py +0 -0
  30. mcp_scrape-0.2.1/src/scrape_mcp/extract/compact.py +464 -0
  31. mcp_scrape-0.2.1/src/scrape_mcp/extract/schema.py +169 -0
  32. mcp_scrape-0.2.1/src/scrape_mcp/py.typed +0 -0
  33. mcp_scrape-0.2.1/src/scrape_mcp/server.py +379 -0
  34. mcp_scrape-0.2.1/src/scrape_mcp/tokenizer.py +92 -0
  35. mcp_scrape-0.2.1/tests/test_batch.py +57 -0
  36. mcp_scrape-0.2.1/tests/test_cache.py +37 -0
  37. mcp_scrape-0.2.1/tests/test_compact.py +196 -0
  38. mcp_scrape-0.2.1/tests/test_compliance.py +106 -0
  39. mcp_scrape-0.2.1/tests/test_detector.py +48 -0
  40. mcp_scrape-0.2.1/tests/test_fetcher.py +171 -0
  41. mcp_scrape-0.2.1/tests/test_schema.py +100 -0
  42. mcp_scrape-0.2.1/tests/test_session.py +114 -0
  43. mcp_scrape-0.2.1/tests/test_stealth.py +23 -0
@@ -0,0 +1,75 @@
1
+ # ================================================================
2
+ # scrape-mcp 配置模板
3
+ # 复制为 .env 后按需修改:cp .env.example .env (.env 不入库)
4
+ # 所有键可省去前缀 SCRAPE_MCP_,也支持直接写全名(见 README)
5
+ # 优先级:实际进程环境变量 > 本 .env > 代码默认值
6
+ # ================================================================
7
+
8
+ # ---- L1 curl_cffi(默认打头阵的抓取层)----
9
+ # 浏览器指纹模板:chrome / safari 等
10
+ # SCRAPE_MCP_IMPERSONATE=chrome
11
+ # L1 请求超时(秒)
12
+ # SCRAPE_MCP_REQUEST_TIMEOUT=20
13
+ # 最大响应体(字节)
14
+ # SCRAPE_MCP_MAX_RESPONSE_BYTES=5242880
15
+ # 单次返回默认 token 预算上限
16
+ # SCRAPE_MCP_DEFAULT_MAX_TOKENS=4000
17
+ # max_tokens 可申请的上限
18
+ # SCRAPE_MCP_HARD_MAX_TOKENS=8000
19
+ # HTTP 代理,L1/L2 共用,如 http://127.0.0.1:7890
20
+ # SCRAPE_MCP_PROXY=
21
+ # 请求 Accept-Language
22
+ # SCRAPE_MCP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9,en;q=0.8
23
+
24
+ # ---- M4 缓存与批量 ----
25
+ # L1 明文响应磁盘缓存 TTL(秒);0 关闭
26
+ # SCRAPE_MCP_CACHE_TTL=300
27
+ # web_batch 单批并发上限
28
+ # SCRAPE_MCP_BATCH_MAX_CONCURRENCY=4
29
+ # web_batch 单批 URL 上限
30
+ # SCRAPE_MCP_BATCH_MAX_URLS=20
31
+
32
+ # ---- 阶段 0 合规(责任边界)----
33
+ # 是否遵循目标站 robots.txt(默认 true;false 时会忽略抓取规则,仅建议自用合规场景关闭)
34
+ # SCRAPE_MCP_RESPECT_ROBOTS=true
35
+ # robots.txt 缓存 TTL(秒)
36
+ # SCRAPE_MCP_ROBOTS_CACHE_TTL=86400
37
+ # 主机白名单(逗号分隔,域名不含协议/端口)。非空时仅允许名单内主机
38
+ # SCRAPE_MCP_ALLOWED_HOSTS=
39
+ # 主机黑名单(逗号分隔)。denied 优先于 allowed
40
+ # SCRAPE_MCP_DENIED_HOSTS=
41
+ # 全网抓取限流(每秒请求数,web_fetch 与 web_batch 共用);0 不限速
42
+ # SCRAPE_MCP_MAX_QPS=10
43
+
44
+ # ---- L2 浏览器渲染(Playwright,拦截才升级)----
45
+ # 是否启用升级链
46
+ # SCRAPE_MCP_L2_ENABLED=true
47
+ # 无头模式
48
+ # SCRAPE_MCP_L2_HEADLESS=true
49
+ # 浏览器渠道:留空=自带 Chromium;chrome / msedge=复用系统浏览器(免下载)
50
+ # SCRAPE_MCP_L2_CHANNEL=chrome
51
+ # 导航超时(秒)
52
+ # SCRAPE_MCP_L2_NAVIGATE_TIMEOUT=30
53
+ # 挑战页观察窗口(秒)
54
+ # SCRAPE_MCP_L2_SETTLE_TIMEOUT=8
55
+ # 页面语言
56
+ # SCRAPE_MCP_L2_LOCALE=zh-CN
57
+ # 隐身加固时区
58
+ # SCRAPE_MCP_L2_TIMEZONE=Asia/Shanghai
59
+
60
+ # ---- M3 登录态持久化 ----
61
+ # 登录态(cookie/localStorage)与缓存落盘目录(不提交到仓库)
62
+ # SCRAPE_MCP_DATA_DIR=~/.scrape_mcp
63
+ # login 工具手动登录等待上限(秒)
64
+ # SCRAPE_MCP_LOGIN_TIMEOUT=180
65
+
66
+ # ---- 传输模式 ----
67
+ # stdio(默认) / streamable-http(Postman/curl 直连)
68
+ # SCRAPE_MCP_TRANSPORT=stdio
69
+ # streamable-http 时监听 host/port
70
+ # SCRAPE_MCP_HTTP_HOST=127.0.0.1
71
+ # SCRAPE_MCP_HTTP_PORT=8000
72
+ # streamable-http 纯 JSON 响应(0=事件流)
73
+ # SCRAPE_MCP_HTTP_JSON=1
74
+ # streamable-http 免握手(0=有状态)
75
+ # SCRAPE_MCP_HTTP_STATELESS=1
@@ -0,0 +1,38 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.12"
16
+ - name: Install
17
+ run: pip install ruff
18
+ - name: Ruff check
19
+ run: ruff check .
20
+ - name: Ruff format check
21
+ run: ruff format --check .
22
+
23
+ test:
24
+ runs-on: ubuntu-latest
25
+ strategy:
26
+ matrix:
27
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: actions/setup-python@v5
31
+ with:
32
+ python-version: ${{ matrix.python-version }}
33
+ - name: Install
34
+ run: |
35
+ python -m pip install --upgrade pip
36
+ pip install -e ".[dev]"
37
+ - name: Test
38
+ run: pytest -q
@@ -0,0 +1,13 @@
1
+ .venv/
2
+ .venv*/
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+ *.log
12
+ .env
13
+ /docs/
@@ -0,0 +1,7 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.6.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ 本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。所有显著变更都会记录在本文件。
4
+
5
+ ## [Unreleased]
6
+
7
+ (暂无)
8
+
9
+ ## [0.2.1] - 2026-09-18
10
+
11
+ ### Added
12
+ - CLI 零配置入口:`scrape-mcp` 默认以 stdio 运行,任意目录可用;支持 `--transport`/`--port`/`--version`/`--help`
13
+
14
+ ### Fixed
15
+ - 修复全新安装时 `selectolax>=1.0` 废弃 modest 后端导致 `ImportError`、命令无法启动的问题;改用官方推荐的 lexbor 后端(`LexborHTMLParser`/`LexborNode`),依赖下限改为 `selectolax>=0.4.11`
16
+
17
+ ### Changed
18
+ - PyPI 发行包名调整为 `mcp-scrape`(因与已有项目 `scrapemcp` 名称相似被拒);命令行工具名仍为 `scrape-mcp`
19
+
20
+ ## [0.2.0] - 2026-09-18
21
+
22
+ 在 v0.1.0 基础上新增结构化抽取与合规限流,并完善上手体验。
23
+
24
+ ### Added
25
+ - **web_extract**:按字段 schema 从 HTML 抽取结构化 JSON(`text`/`attr`/`count`/`list` 类型、简写选择器、`default` 兜底、`missing` 如实上报)
26
+ - 阶段 0 合规:`respect_robots`(robots.txt 遵循)、`allowed_hosts`/`denied_hosts` 白黑名单、`max_qps` 全局限流
27
+ - `scripts/demo.py`、`scripts/smoke_extract.py`:一键演示与 HTTP 冒烟
28
+ - README 增加 60 秒入门、MCP 客户端接入教程、真实站点实测用例集
29
+
30
+ ### Changed
31
+ - 抓取逻辑提取为 `_fetch_html`,供 `web_fetch`/`web_extract` 共用,避免重复抓取
32
+
33
+ ### Engineering
34
+ - ruff + pre-commit 工程化配置,`pyproject.toml` 增加 dev 依赖与 lint 规则
35
+ - CI 增加 ruff 检查步骤
36
+ - `CONTRIBUTING.md`、`CODE_OF_CONDUCT.md`;README 增补发版流程
37
+
38
+ ## [0.1.0] - 2026-09-16
39
+
40
+ 首个可用的 MCP Server 版本,完成 M1–M5 全链路。
41
+
42
+ ### Added
43
+ - **web_fetch**:反爬感知抓取,L1 curl_cffi → L2 Playwright 梯度升级
44
+ - **compact 精简**:密度选根 + DOM 剪枝,将整页 HTML 压缩为极省 token 的正文(MDN 214KB → 数百 token)
45
+ - **拦截如实上报**:`blocked`/`block_reason`,不把挑战页/空壳页当正文喂给模型
46
+ - **login / 登录态持久化**:手动登录落盘,`web_fetch` 自动带上、过期提示重登
47
+ - **web_batch**:并发抓取一批 URL,单条异常不影响整批
48
+ - **磁盘缓存**:L1 明文响应按 TTL 缓存
49
+ - **Stealth 加固**:Wipe webdriver、Canvas/WebGL 指纹噪声、时区对齐等
50
+ - **传输模式**:stdio / streamable-http(Postman/curl 直连)双支持
51
+ - **配置**:全部可通过 `SCRAPE_MCP_*` 环境变量或项目根 `.env` 覆盖
52
+ - 44 项单元测试 + GitHub Actions CI(Python 3.10–3.13)
@@ -0,0 +1,42 @@
1
+ # 贡献者公约(Contributor Covenant)2.1
2
+
3
+ ## 我们的承诺
4
+
5
+ 为营造开放、友善的社区,我们作为贡献者与维护者承诺:无论年龄、体型、残障、族裔、性别认同与表达、经验水平、教育程度、社会经济地位、国籍、个人外貌、种族、宗教,或性取向与身份,参与本项目的每个人都免受骚扰与排斥。
6
+
7
+ ## 我们的标准
8
+
9
+ 有助于营造积极环境的行为举例:
10
+ - 使用友善、包容的语言
11
+ - 尊重不同的观点和经验
12
+ - 优雅地接受建设性批评
13
+ - 关注对社区最有利的事
14
+ - 对其他社区成员表现出同理心
15
+
16
+ 不可接受的行为举例:
17
+ - 使用与性相关的言语或图像,以及不受欢迎的性关注或挑逗
18
+ - 挑衅、侮辱或贬低性评论,以及人身攻击或政治攻击
19
+ - 公开或私下的骚扰
20
+ - 未经明确许可,发布他人的私人信息(如住址或电子邮箱)
21
+ - 其他在专业环境下可合理认为不适当的行为
22
+
23
+ ## 我们的责任
24
+
25
+ 维护者负责明确可接受行为的标准,并对任何不可接受行为采取恰当而公平的纠正措施。
26
+
27
+ 维护者有权利且负有以下义务:删除、编辑或拒绝不符合本公约的评论、提交、代码、维基编辑、issue 及其他贡献,并在适当情况下禁止任何其认为不恰当、威胁、冒犯或有害的贡献者。
28
+
29
+ ## 适用范围
30
+
31
+ 本公约适用于所有项目空间,也适用于个人代表项目及其社区时的公开场合。
32
+
33
+ ## 执行
34
+
35
+ 如遇辱骂、骚扰或其他不可接受行为,请通过维护者公开渠道(issue)或项目仓库维护者邮箱举报。所有投诉都会被审核与调查,并将针对具体情形采取必要且得当的回应。维护者有义务对举报人身份保密。
36
+
37
+ ## 归属
38
+
39
+ 本公约改编自 [Contributor Covenant][homepage] 2.1 版,
40
+ 可在 https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 查阅。
41
+
42
+ [homepage]: https://www.contributor-covenant.org
@@ -0,0 +1,47 @@
1
+ # Contributing
2
+
3
+ 感谢你对 scrape-mcp 的关注!以下是参与本项目的约定。
4
+
5
+ ## 环境准备
6
+
7
+ ```bash
8
+ cd scrape-mcp
9
+ python -m venv .venv
10
+ source .venv/bin/activate # Windows: .\.venv\Scripts\Activate.ps1
11
+ pip install -e ".[dev]" # 含 pytest / ruff / pre-commit
12
+ pre-commit install # 安装 git 钩子
13
+ ```
14
+
15
+ ## 开发流程
16
+
17
+ 1. 从 `main` 切新分支:`git checkout -b feat/your-feature`
18
+ 2. 修改代码,风格遵循 ruff(`ruff check .`、`ruff format .` 零告警)
19
+ 3. 补对应测试到 `tests/`,`pytest -q` 全绿
20
+ 4. 更新 `CHANGELOG.md`(`Unreleased` 段)
21
+ 5. 提交前确保 `pre-commit` 通过,push 后 CI 需绿
22
+
23
+ ## 代码风格
24
+
25
+ - 遵循 ruff 默认规则,行宽 100,双引号
26
+ - 类型注解完整(项目目标 Python 3.10+)
27
+ - 纯函数优先,副作用集中在调用入口
28
+
29
+ ## 提交信息
30
+
31
+ 建议遵循 [Conventional Commits](https://www.conventionalcommits.org/zh-hans/):
32
+ `feat:`、`fix:`、`docs:`、`chore:`、`refactor:` 等前缀。
33
+
34
+ ## 发版流程
35
+
36
+ 项目遵循 [语义化版本](https://semver.org/lang/zh-CN/),避免长期停留在一个版本,
37
+ 保持稳定的小步快跑节奏(如每月一次 v0.x):
38
+
39
+ 1. 合并目标改动到 `main`,确保 `pytest -q` 与 `ruff check .` 全绿
40
+ 2. `CHANGELOG.md` 从 `Unreleased` 摘要生成对应版本的 `Added/Fixed/Changed` 段
41
+ 3. 打 tag:`git tag v0.x.x && git push --tags`
42
+ 4. 在 GitHub 用该 tag 创建 Release,把该版 CHANGELOG 段复制进 release notes
43
+
44
+ ## 反爬与合规
45
+
46
+ 本项目用于"让 AI 读取网页正文改善效率"的正当目的。请勿利用其绕开付费墙或授权控制。
47
+ 参见 [SECURITY.md](SECURITY.md)。
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 guanyuyan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,263 @@
1
+ Metadata-Version: 2.5
2
+ Name: mcp-scrape
3
+ Version: 0.2.1
4
+ Summary: 反爬感知抓取 + 极省 Token 内容精简的 MCP Server
5
+ Author: guanyuyan
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: anti-bot,llm,mcp,scraping,token-efficiency,web-fetch
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Internet :: WWW/HTTP
14
+ Requires-Python: >=3.10
15
+ Requires-Dist: curl-cffi>=0.7.0
16
+ Requires-Dist: mcp<3,>=2.0.0
17
+ Requires-Dist: playwright>=1.49
18
+ Requires-Dist: pydantic-settings>=2.2.0
19
+ Requires-Dist: selectolax>=0.4.11
20
+ Requires-Dist: tiktoken>=0.7.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pre-commit>=3.8; extra == 'dev'
23
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
24
+ Requires-Dist: pytest>=8.0; extra == 'dev'
25
+ Requires-Dist: ruff>=0.6.0; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # scrape-mcp
29
+
30
+ > **⚠️ 这是 scrape-mcp 的官方源仓库(upstream)。** 若在别处看到同名/相似项目并带不同作者署名,即为转载或派生,非原作者发布。
31
+
32
+ > **反爬感知抓取 + 极省 Token 内容精简的 MCP Server —— 让 AI 读真实网页时,只花 1%~5% 的 token 就能拿到干净、真实、免登录的正文。**
33
+ >
34
+ > 自动化分三层应对:默认 curl_cffi(伪装 TLS/JA3 + HTTP2)低成本打头阵,命中 Cloudflare/验证码/WAF 才升级 Playwright 真浏览器渲染;遇到"登录才给内容"的站,先弹窗手动登录一次、登录态落盘,之后自动带上、过期会提示重登。
35
+
36
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
37
+ [![Release](https://img.shields.io/github/v/release/guanyuyan/scrape-mcp?color=blue&label=release)](https://github.com/guanyuyan/scrape-mcp/releases)
38
+ [![CI](https://github.com/guanyuyan/scrape-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/guanyuyan/scrape-mcp/actions)
39
+
40
+ ---
41
+
42
+ ## ⚡ 60 秒入门
43
+
44
+ **方式 A:一键安装(推荐,零配置)**
45
+
46
+ ```bash
47
+ pip install mcp-scrape # 已发布到 PyPI 后
48
+ # 或从源码:cd scrape-mcp && pip install -e .
49
+ ```
50
+
51
+ 然后本机任意目录即可:
52
+
53
+ ```bash
54
+ scrape-mcp --version # 确认装好(应输出 0.x)
55
+ claude # 在支持 MCP 的客户端里把它当服务接入
56
+ ```
57
+
58
+ > 不再需要 clone、手动设 `PYTHONPATH` 或 `python -m`;`scrape-mcp` 命令在任意目录可用,
59
+ > 默认 stdio 直接被 MCP 客户端接管。想用 HTTP 直连(Postman/curl),跑 `scrape-mcp --transport streamable-http --port 8000`。
60
+
61
+ **方式 B:一键演示**(拉源仓库跑 `web_fetch` + `web_extract`,看省 token 效果)
62
+
63
+ ```bash
64
+ git clone https://github.com/guanyuyan/scrape-mcp.git
65
+ cd scrape-mcp
66
+ python -m venv .venv && .venv\Scripts\pip install -e .
67
+ .venv\Scripts\python scripts\demo.py
68
+ ```
69
+
70
+ 预计输出(对不同站点自动选 L1/L2 分级,给出省 token 压缩比):
71
+
72
+ ```
73
+ === 文档站 | https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
74
+ ok=True tier=l1 status=200 html=260128B → tokens=3730 (约 5.7% 压缩)
75
+ 标题: 跨源资源共享(CORS) - HTTP | MDN
76
+ ok=True tier=l1 data={"heading": "跨源资源共享(CORS)", "h2_count": 10, "links": 665}
77
+ ```
78
+
79
+ **第 2 步:作为 MCP 客户端接入**(Claude / Cursor 等,命令指向已安装的 `scrape-mcp`)
80
+
81
+ ```bash
82
+ scrape-mcp # 已安装时;或 python -m scrape_mcp.server
83
+ ```
84
+
85
+ 在客户端里调用 `web_fetch(url=...)` 即可拿到极省正文。
86
+
87
+ > MDN 整页约 260KB,这里只花几千 token 就拿到干净正文;拦截/登录页会自动升级或如实上报,不会把验证码当正文。
88
+
89
+ ---
90
+
91
+ ## 功能说明
92
+
93
+ **它解决什么**:模型读网页不是在读正文,而是在读整棵 DOM(脚本、导航、版权、链接一应尽收),又贵又脏。这个工具把页面剪成"只有正文"再喂给 AI。
94
+
95
+ **核心价值(三点)**
96
+ 1. **极省 token** —— 属性剥离 + 模板块丢弃 + 站外链接降级 + 表格转 CSV + 预算截断。实测 MDN 整页 214KB → 数百 token,压缩率约 1%。
97
+ 2. **自动分级反爬** —— 85% 的站只走最轻的 L1(快、并发高),拦截/JS 骨架页才升级 L2 真浏览器 + stealth 伪装,绝不暴力逐站上浏览器。
98
+ 3. **登录墙处理 + 如实上报** —— 判定登录/拦截时返回 `login_required`/`blocked`,引导登录而非硬撞,从不让模型把验证码当正文。
99
+
100
+ **怎么用(一行)**
101
+ ```
102
+ web_fetch(url="https://xxx/article/1") → JSON:{ok, content(极省正文), token_estimate, tier, blocked?, login_required?, cached?}
103
+ ```
104
+
105
+ **覆盖的站点类型**:文章页 / 文档站 / 门户与列表页 / 需要 JS 渲染的 SPA / 需要登录才给正文的站(如知乎)。
106
+
107
+ **真实站点实测**(一键 `scripts/demo.py` 可复现):
108
+
109
+ | 站点 | 类型 | 分级 | 原始 | 抓到正文 | 说明 |
110
+ |---|---|---|---|---|---|
111
+ | MDN CORS 文档 | 文档站 | L1 | 260KB | ~3.7k token(约 5.7%) | 纯静态,最短路径直达 |
112
+ | 腾讯新闻首页 | 列表/门户 | L2 | 338KB | ~1.4k token(约 1.6%) | 动态页升级浏览器渲染 |
113
+ | 极客邦首页 | SPA/动态 | L2 | 12KB | ~1.4k token | JS 骨架页,需渲染才有正文 |
114
+
115
+ **形态**:本地 MCP Server(stdio/HTTP 均可),隐私可控;支持单页 `web_fetch`、批量 `web_batch`、结构化抽取 `web_extract`、登录持久化 `login`,缓存默认开启。
116
+
117
+ ---
118
+
119
+ ## 特性
120
+
121
+ - **分级抓取**:L1 `curl_cffi` → L2 Playwright,拦截才升级,不误伤正常页面。
122
+ - **内容密度选根**:从 article/main/div 中挑出"恰好装住正文的最紧容器",避开导航/侧栏。
123
+ - **compact 精简**:属性剥离、模板块丢弃、站外链接降级、表格转 CSV、token 预算截断,
124
+ 压缩率可达 1~20% 数量级(如 MDN 214KB → 数百 token)。
125
+ - **拦截如实上报**:不把挑战页/空壳页当正文喂给模型,返回 `blocked`/`block_reason`。
126
+ - **登录态持久化**:`login` 工具手动登录并落盘,`web_fetch` 自动带上;过期会提示重登。
127
+ - **缓存与批量**:L1 明文响应按 TTL 磁盘缓存;`web_batch` 并发抓取一批 URL。
128
+ - **Stealth 加固**:Wipe `webdriver`、UA 数据、Canvas/WebGL 确定性指纹噪声、时区对齐。
129
+
130
+ ## 安装
131
+
132
+ ```bash
133
+ # L2 渲染所需浏览器(国内可用镜像)
134
+ $env:PLAYWRIGHT_DOWNLOAD_HOST="https://cdn.npmmirror.com/binaries/playwright"
135
+ pip install "playwright>=1.49"
136
+ playwright install chromium
137
+
138
+ # 或复用系统浏览器,无需下载
139
+ $env:SCRAPE_MCP_L2_CHANNEL="chrome" # 或 msedge
140
+ ```
141
+
142
+ ## 启动(MCP,stdio)
143
+
144
+ ```bash
145
+ scrape-mcp # 默认 stdio,被 MCP 客户端接管
146
+ scrape-mcp --transport streamable-http --port 8000 # 切 HTTP 直连
147
+ scrape-mcp --version / --help # 版本 / 用法
148
+ ```
149
+
150
+ > 不传参默认 stdio。传 `--transport`/`--port` 由 CLI 显式启动,优先级高于 `.env`/环境变量。
151
+
152
+ ## 环境变量(前缀 `SCRAPE_MCP_`)
153
+
154
+ 全部配置可用环境变量(`SCRAPE_MCP_` 前缀)设置,也可集中放到**项目根目录 `.env`** 文件里。
155
+
156
+ ```bash
157
+ # 项目根目录执行
158
+ cp .env.example .env # 生成配置模板
159
+ # 编辑 .env 后无需改代码,直接启动即可
160
+ ```
161
+
162
+ 优先级:实际进程环境变量 > `.env` > 默认值。`.env` 被 gitignore,不入库;`.env.example` 是随仓库提交的模板。
163
+
164
+ | 变量 | 默认 | 说明 |
165
+ |---|---|---|
166
+ | `IMPERSONATE` | `chrome` | curl_cffi 指纹模板 |
167
+ | `REQUEST_TIMEOUT` | `20` | L1 超时(秒) |
168
+ | `PROXY` | — | 代理,L1/L2 共用 |
169
+ | `L2_ENABLED` | `true` | 是否启用 Playwright 升级链 |
170
+ | `L2_CHANNEL` | — | 关联浏览器:空=自带 Chromium,`chrome`/`msedge`=系统 |
171
+ | `L2_SETTLE_TIMEOUT` | `8` | 挑战页等待窗口(秒) |
172
+ | `L2_TIMEZONE` | `Asia/Shanghai` | 隐身加固时区 |
173
+ | `DATA_DIR` | `~/.scrape_mcp` | 登录态/缓存放盘目录 |
174
+ | `CACHE_TTL` | `300` | L1 缓存 TTL(秒),`0` 关闭 |
175
+ | `BATCH_MAX_CONCURRENCY` | `4` | `web_batch` 并发上限 |
176
+ | `BATCH_MAX_URLS` | `20` | `web_batch` 单批上限 |
177
+ | `RESPECT_ROBOTS` | `true` | 默认遵循目标站 robots.txt;`false` 忽略(仅建议自用合规场景关闭) |
178
+ | `ALLOWED_HOSTS` | — | 主机白名单(逗号分隔);非空时仅允许名单内主机 |
179
+ | `DENIED_HOSTS` | — | 主机黑名单(逗号分隔),优先于白名单 |
180
+ | `MAX_QPS` | `10` | 全网抓取限流(每秒请求数);`0` 不限速 |
181
+
182
+ ## HTTP 模式(Postman / curl 直连)
183
+
184
+ 默认 stdio,改环境变量即可切到 HTTP,无需改代码:
185
+
186
+ ```bash
187
+ $env:SCRAPE_MCP_TRANSPORT="streamable-http"
188
+ $env:SCRAPE_MCP_HTTP_PORT="8000"
189
+ python -m scrape_mcp.server
190
+ ```
191
+
192
+ 以 `json_response=1`(默认)启动,请求/响应都是普通 JSON(非事件流)。**stateless 模式无需先 `initialize`,直接发 `tools/call` 即可。**
193
+
194
+ 必需项:请求头 `MCP-Method`、`Mcp-Name`;body 内 `params` 需带 `_meta`(`protocolVersion` + `clientCapabilities`)。完整示例见下。
195
+
196
+ ## 接入 MCP 客户端(Claude / Cursor 等)
197
+
198
+ 先 `pip install mcp-scrape`(或用项目 `.venv` 跑 `pip install -e .`),使 `scrape-mcp` 命令全局可用;然后按标准 MCP 方式接入。
199
+
200
+ 以 Claude Desktop 为例(`claude_desktop_config.json`):
201
+
202
+ ```json
203
+ {
204
+ "mcpServers": {
205
+ "scrape-mcp": {
206
+ "command": "scrape-mcp"
207
+ }
208
+ }
209
+ }
210
+ ```
211
+
212
+ - 用 `command: "scrape-mcp"`(已全局安装),免去 `PYTHONPATH` 与系统解释器导致的 `ModuleNotFoundError`。
213
+ - 若复用系统浏览器(免下载):设环境变量 `SCRAPE_MCP_L2_CHANNEL` 为 `chrome` 或 `msedge`。
214
+ - 配对成功后重启客户端,即可在会话里直接调用 `web_fetch` / `web_extract` / `web_batch`。
215
+
216
+ ## MCP 工具
217
+
218
+ - **`web_fetch(url, max_tokens=4000, link_policy="internal")`** → 状态 + 极省正文
219
+ - `link_policy`: `internal`(站内相对链接+站外降级)/ `all` / `none`
220
+ - **`web_extract(url, schema={...})`** → 按字段 schema 抽取结构化 JSON(可入库)
221
+ - 字段类型:`text`(默认)/ `attr`(需配 `attr` 取属性)/ `count` / `list`(`list_key` 取值)
222
+ - **`web_batch(urls, max_tokens=3000, link_policy="internal")`** → 并发抓取一批,逐 URL 返回;单条异常不影响整批
223
+ - **`login(url, timeout=180)`** → 打开带界面浏览器手动登录并持久化登录态
224
+
225
+ 返回为 JSON 字符串(落在 `content[0].text`,请解析文本,勿依赖 `structured_content`)。
226
+
227
+ ### web_extract 示例
228
+
229
+ ```bash
230
+ schema='{"fields": {
231
+ "title": {"selector": "h1", "type": "text"},
232
+ "main_link":{"selector": "a", "type": "attr", "attr": "href"},
233
+ "tag_count":{"selector": ".tag", "type": "count"},
234
+ "tags": {"selector": ".tag", "type": "list", "list_key": "text"}
235
+ }}'
236
+ # → {"ok":true, "data": {"title":"…","main_link":"…","tag_count":3,"tags":["a","b","c"]}, "missing":[]}
237
+ ```
238
+
239
+ ## 目录结构
240
+
241
+ ```
242
+ src/scrape_mcp
243
+ ├── server.py # MCP 入口 + 工具(web_fetch/web_batch/login)
244
+ ├── config.py # 环境变量配置
245
+ ├── tokenizer.py # tiktoken 计数 + 预算截断
246
+ ├── core/
247
+ │ ├── http_client.py # L1 curl_cffi + 磁盘缓存
248
+ │ ├── fetcher.py # 分级调度(L1→L2),对工具层返回 FetchOutcome
249
+ │ ├── detector.py # 拦截/骨架页判定
250
+ │ ├── browser.py # L2 Playwright + stealth
251
+ │ ├── session.py # 登录态持久化
252
+ │ └── cache.py # HTTP 响应缓存
253
+ └── extract/
254
+ ├── compact.py # DOM 剪枝 → 极省 token 正文
255
+ └── schema.py # 结构化抽取(按字段 schema 出 JSON)
256
+ ```
257
+
258
+ ## 开发
259
+
260
+ ```bash
261
+ pip install -e ".[dev]"
262
+ python -m pytest
263
+ ```