{"id":51002556,"url":"https://github.com/chenhaodev/agentcurl","last_synced_at":"2026-06-20T16:32:35.469Z","repository":{"id":365066826,"uuid":"1270231347","full_name":"chenhaodev/agentcurl","owner":"chenhaodev","description":"Switchable web-crawler middleware: one interface over pluggable backends (static/trafilatura, Playwright, crawl4ai, Firecrawl, jina) with DeepSeek structured extraction. Repo + MCP server + Claude Code SKILL.","archived":false,"fork":false,"pushed_at":"2026-06-15T17:29:25.000Z","size":418,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-15T19:25:09.671Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chenhaodev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-15T14:08:15.000Z","updated_at":"2026-06-15T17:29:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/chenhaodev/agentcurl","commit_stats":null,"previous_names":["chenhaodev/agentcurl"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/chenhaodev/agentcurl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenhaodev%2Fagentcurl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenhaodev%2Fagentcurl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenhaodev%2Fagentcurl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenhaodev%2Fagentcurl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chenhaodev","download_url":"https://codeload.github.com/chenhaodev/agentcurl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenhaodev%2Fagentcurl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34578089,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-20T16:32:34.344Z","updated_at":"2026-06-20T16:32:35.454Z","avatar_url":"https://github.com/chenhaodev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# agentcurl · 可切换后端的网页爬取中间件\n\n[![CI](https://github.com/chenhaodev/agentcurl/actions/workflows/ci.yml/badge.svg)](https://github.com/chenhaodev/agentcurl/actions/workflows/ci.yml)\n\n\u003e 一套接口（`fetch` / `crawl` / `extract`）封装五种爬虫引擎，**一个环境变量切换**，\n\u003e 上层叠加 **DeepSeek 结构化抽取**（网页 → JSON）。可从**代码库 / MCP 服务 / Claude Code SKILL**\n\u003e 三种入口驱动 —— 三者都是同一个 `CrawlManager` 的薄封装。\n\n`5 种后端` · `零依赖默认引擎` · `1 个变量切换` · `网页 → JSON` · `3 种入口` · `纯 CPU` · `实测可爬全球 Top10 的 9 个`\n\n![demo](docs/demo.gif)\n\n\u003e 上面这段演示由 [`demo.tape`](demo.tape) 用 [vhs](https://github.com/charmbracelet/vhs) 录制（`vhs demo.tape`）。\n\n[agentmem](https://github.com/chenhaodev/agentmem) 的姊妹项目，沿用同一形态：*一套通用接口、多个可插拔后端、环境变量切换*。\n\n---\n\n## 一分钟看懂（无需爬虫背景）\n\n**遇到的问题**：爬一个网站从来不是「一种活」。有的页面是纯 HTML（解析飞快）；有的靠 JavaScript\n渲染内容（得开真浏览器）；有的会反爬封你（得用托管代理服务）；而且拿到页面后你通常想要的是\n**结构化数据**，不是一大坨文字。大家最后都在反复拼装 trafilatura + Playwright + 某个 API + 一次\n大模型调用，每个站点都重写一遍胶水代码。\n\n**这个项目怎么做**：把这些引擎统统藏到**一套接口**后面 —— `fetch`（取一页）/ `crawl`（爬整站）/\n`extract`（抽 JSON）—— 用一个环境变量选引擎：\n\n```\nCRAWL_BACKEND=static → browser → crawl4ai → firecrawl → jina\n```\n\n默认引擎零额外安装；重型引擎按需开启。`extract` 再用 DeepSeek 把任意页面变成 JSON ——\n**没配密钥时自动降级返回原始 markdown，离线也绝不崩**。\n\n\u003e **一句话**：别再为每个站点重写「取页 → 渲染 → 抽取」的胶水代码了 ——\n\u003e 把一个 `CrawlManager` 指向 URL，用环境变量切引擎即可。\n\n---\n\n## 核心概念（先读这 4 个词）\n\n后面反复用到，先用大白话讲清楚（完整版见文末[名词速查](#附录名词速查)）：\n\n- **后端 / backend**：实际干活的爬虫引擎。本项目有 5 个，能力不同（纯 HTML、真浏览器、托管 API……），\n  但**对外长一个样**，靠一个环境变量切换。\n- **结构化抽取 / extract**：把网页正文交给大模型，按你给的「字段表」或「一句话要求」吐回 **JSON**\n  （比如 `{标题, 价格, 作者}`），而不是让你自己写正则去抠。\n- **路由 / 回退链 / router**：把 `CRAWL_BACKEND` 写成 `static+jina` 这样的「加号串」，就会先试便宜的\n  `static`，**取不到内容再自动落到** `jina`。一行配置搞定「先省钱、不行再上重的」。\n- **meta 学习 / 配方**：仓库会**记住每个域名**怎么爬最顺（哪个后端有效、登录会话），**下次自动复用**——\n  看你登录一遍，以后这个站点就自动带着登录态爬。详见[下面](#meta-学习看一遍下次就会)。\n\n---\n\n## 看它怎么爬\n\n\u003e 想一次看完下面所有案例？跑 `./demo.sh`（README 这一节的可复现版）。\n\u003e 想录成 GIF/视频分享：`asciinema rec demo.cast -c ./demo.sh` 或用 `vhs`。\n\n### 案例 ① 一行命令，网页直接变 JSON\n\n```bash\n$ python -m agentcurl https://en.wikipedia.org/wiki/Web_scraping \\\n    --schema '{\"title\":\"str\",\"first_sentence\":\"str\",\"key_topics\":\"list\"}' --json\n```\n```json\n{\n  \"url\": \"https://en.wikipedia.org/wiki/Web_scraping\",\n  \"data\": {\n    \"title\": \"Web scraping\",\n    \"first_sentence\": \"Web scraping, web harvesting, or web data extraction is data scraping used for extracting data from websites.\",\n    \"key_topics\": [\"web harvesting\", \"data scraping\", \"web crawling\", \"techniques\", \"legal issues\"]\n  },\n  \"raw\": false\n}\n```\n\n### 案例 ② 爬 YouTube 视频页 → 抽出结构化信息（真实输出）\n\nYouTube 是纯 JS 渲染，`static` 只能拿到 146 字的空壳；换 `jina` 远程阅读器拿到 55KB 正文，\nDeepSeek 再抽成 JSON：\n\n```bash\n$ CRAWL_BACKEND=jina python -m agentcurl \"https://www.youtube.com/watch?v=dQw4w9WgXcQ\" \\\n    --schema '{\"video_title\":\"str\",\"channel\":\"str\",\"duration\":\"str\",\"views\":\"str\"}' --json\n```\n```json\n{\n  \"data\": {\n    \"video_title\": \"Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)\",\n    \"channel\": \"Rick Astley\",\n    \"duration\": \"3:33\",\n    \"views\": \"1,783,100,870\"\n  },\n  \"raw\": false\n}\n```\n\n### 案例 ③ 中文站点 + 回退链\n\n`static` 先试，空了自动落到 `jina`；中文 GBK 站点（如寻医问药网）也能正确解码、不再乱码：\n\n```bash\n$ CRAWL_BACKEND=static+jina ROUTER_MODE=fallback python -m agentcurl https://www.xywy.com/\n# 标题: 寻医问药网_值得信赖的互联网医疗健康服务平台\n# 命中后端会标记在 metadata[\"router_backend\"]\n```\n\n---\n\n## 它到底能不能爬 Top 网站\n\n不空口说。下表是**真实跑出来的**结果（2026-06，本机网络）：用 `static` 和 `jina` 各抓一次\n全球访问量 Top10 站点的首页，记录 HTTP 状态码与拿到的 markdown 字符数。\n\n| 站点 | `static`（状态/字符） | `jina`（状态/字符） | 推荐后端 |\n|------|---------------------:|-------------------:|:--------|\n| wikipedia.org | 200 / 11,891 | 200 / 61,343 | **static** ✅ |\n| facebook.com | 200 / 387 | 200 / 7,415 | static / jina ✅ |\n| google.com | 200 / 62 | 200 / 6,217 | **jina** ✅ |\n| youtube.com¹ | 200 / 146 | 200 / 669 | **jina** ✅ |\n| amazon.com | 200 / 142 | 200 / 182,216 | **jina** ✅ |\n| baidu.com | 200 / 81 | 200 / 12,832 | **jina** ✅ |\n| bing.com | 200 / 4 | 200 / 26,978 | **jina** ✅ |\n| instagram.com | 200 / 0 | 200 / 2,282 | **jina** ✅ |\n| reddit.com | **403（封）** | 200 / 82,259 | **jina** ✅ |\n| x.com | 200 / 295（空壳） | 451（法律拦截） | 需登录 → meta ⚠️ |\n\n**结论：Top10 里 9 个能直接爬下来**（`static` 或 `jina`）。`reddit` 对 `static` 直接 403 反爬，\n但 `jina` 拿到 82KB —— 这正是「**回退链**」的价值：`static+jina` 一行配置自动兜住。唯一的硬骨头\n`x.com` 需要登录态，而这正是下面 [meta 学习](#meta-学习看一遍下次就会) 的登录捕获功能要解决的。\n\n\u003e ¹ YouTube **首页**对所有人都稀疏（登录墙/启动页）；**视频页**内容很丰富 —— 见上面案例 ②，\n\u003e `jina` 拿到 55KB 并成功抽出标题/频道/时长/播放量。\n\u003e\n\u003e 复现：`RUN_LIVE=1 python tests/test_live.py`，或 `python benchmark.py --extract static jina`。\n\n---\n\n## 为什么选这些工具（不是拍脑袋）\n\n每个后端都选用了**各自领域里最受认可的开源/托管方案**。下表 star 数为 `gh api` 实时拉取（2026-06）：\n\n| 后端 | 底层项目 | GitHub | Star ⭐ | 为什么是它 |\n|------|---------|--------|-------:|-----------|\n| `static` | [adbar/trafilatura](https://github.com/adbar/trafilatura) | Python | **6.1k** | 学术界公认最准的正文抽取器之一（多次 ScrapingHub/CommonCrawl 评测领先），纯 CPU、毫秒级 |\n| `browser` | [microsoft/playwright](https://github.com/microsoft/playwright) | TS | **91k** | 微软出品、事实标准的浏览器自动化，渲染 JS 页面 |\n| `crawl4ai` | [unclecode/crawl4ai](https://github.com/unclecode/crawl4ai) | Python | **68.5k** | GitHub trending 常客、最火的「LLM 友好」开源爬虫，fit-markdown + 原生深度爬 |\n| `firecrawl` | [firecrawl/firecrawl](https://github.com/firecrawl/firecrawl) | TS | **133k** | 当前最热的托管爬取服务，自带反爬/代理/渲染，是「被封了」时的逃生舱 |\n| `jina` | [jina-ai/reader](https://github.com/jina-ai/reader) | TS | **11.2k** | 零安装远程阅读器（`r.jina.ai`），远端处理 JS，无需本地装任何东西 |\n\n合计 **30 万+ star**，且全部在最近一个月内有提交（活跃维护）。换句话说：agentcurl 不发明新轮子，\n而是把这五个**被社区投票选出来的**引擎统一到一套接口下，让你按场景一键切换。\n\n---\n\n## 架构\n\n```mermaid\nflowchart TB\n    U[\"你的代码 / MCP / SKILL\"] --\u003e M[\"CrawlManager（门面）\"]\n    M --\u003e R{\"CRAWL_BACKEND\"}\n    R --\u003e|static| S[\"httpx + trafilatura\u003cbr/\u003e纯 HTML · 零依赖默认\"]\n    R --\u003e|browser| B[\"Playwright 无头 Chromium\u003cbr/\u003eJS / 动态页\"]\n    R --\u003e|crawl4ai| C[\"crawl4ai\u003cbr/\u003efit-markdown + 深度爬\"]\n    R --\u003e|firecrawl| F[\"Firecrawl 托管 API\u003cbr/\u003e反爬逃生舱\"]\n    R --\u003e|jina| J[\"r.jina.ai 远程阅读器\u003cbr/\u003e零安装\"]\n    R --\u003e|a+b| RT[\"RouterBackend\u003cbr/\u003e回退链 / 扇出\"]\n    M --\u003e X[\"Extractor · DeepSeek\u003cbr/\u003e网页 → JSON\"]\n    M --\u003e RC[\"RecipeStore（meta 学习）\u003cbr/\u003e记住每域名最佳后端 + 登录会话\"]\n```\n\n\u003e 不支持 mermaid 的查看器可读作：\n\u003e `CrawlManager` 按 `CRAWL_BACKEND` 选一个后端（或把 `a+b` 包成回退链 router），\n\u003e 取回统一的 `Document`；`Extractor` 用 DeepSeek 把它变 JSON；`RecipeStore` 记住每个域名怎么爬最顺。\n\n**契约**：所有后端都实现同一个 `CrawlBackend` 协议 —— `fetch(url) -\u003e Document` 与\n`crawl(url) -\u003e [Document]`，`Document` 是最小公共结构\n（`url, status, markdown, html, title, links, metadata`）。后端的「超能力」（Firecrawl 截图、\ncrawl4ai 的 fit-markdown 评分）放进 `metadata`，不污染契约 —— 所以换引擎是改配置，不是改代码。\n\n---\n\n## 快速开始\n\n```bash\n# ① 安装（默认 static 后端零额外依赖）\npip install -e .                       # 核心: httpx, trafilatura, openai, dotenv\n# pip install -e \".[browser]\"          # + Playwright（再跑 playwright install chromium）\n# pip install -e \".[crawl4ai]\"         # + crawl4ai（再跑 crawl4ai-setup）\n\n# ② 配置（抽取需要 DeepSeek 密钥）\ncp .env.example .env                   # 填入 DEEPSEEK_API_KEY\n\n# ③ 用起来\npython demo.py                                   # 取页 + 抽取，离线也能跑\npython -m agentcurl https://example.com          # 打印干净 markdown\npython -m agentcurl https://example.com --extract \"标题和一句话摘要\"\npython -m agentcurl https://example.com --schema '{\"title\":\"str\"}' --json\npython -m agentcurl https://example.com --crawl --depth 1 --max-pages 5\n```\n\n### 切换后端\n\n```bash\nCRAWL_BACKEND=static   python demo.py   # 默认，零额外安装\nCRAWL_BACKEND=jina     python demo.py   # 远程阅读器，零安装（JS 远端处理）\nCRAWL_BACKEND=browser  python demo.py   # Playwright 渲染 JS（需 playwright install chromium）\nCRAWL_BACKEND=crawl4ai python demo.py   # fit-markdown + 深度爬（需 crawl4ai-setup）\nCRAWL_BACKEND=static+firecrawl ROUTER_MODE=fallback python demo.py   # 回退链\n```\n\n### 命令行参数速查（`python -m agentcurl`）\n\n| 参数 | 含义 |\n|------|------|\n| `--extract \"\u003c一句话\u003e\"` | 用自然语言描述要抽什么 → JSON |\n| `--schema '\u003cJSON\u003e'` | 用字段表（`{字段:类型}`）精确指定要抽什么 → JSON |\n| `--crawl` | 爬整站（同域链接），而非只取一页 |\n| `--depth N` / `--max-pages N` | 爬取深度 / 页数上限 |\n| `--json` | 输出 JSON 而非美化文本 |\n| `--learn-login` | 开浏览器让你登录一次，保存会话供以后复用 |\n\n---\n\n## meta 学习：看一遍，下次就会\n\nagentcurl 不该让你每次都重新攻克同一个站点。**meta 层**（默认开，`AGENTCURL_LEARN=0` 关）会为每个\n域名维护一份它学到的「配方」并自动复用：\n\n- **从结果里自学（零操作）**：每次取页都记录「哪个后端在这个域名真的拿到了内容」。设\n  `CRAWL_BACKEND=auto`，下次爬这个域名就**自动路由到学到的最佳后端**。\n- **学一次登录（看你登一遍）**：对登录后才能看的页面，用真浏览器捕获一次会话：\n\n  ```bash\n  python -m agentcurl https://example.com/dashboard --learn-login\n  # 浏览器弹出 → 你手动登录 → 回车，会话被保存\n  ```\n\n  保存的是 Playwright `storage_state`（cookies + localStorage）+ cookie 集。**之后每次爬这个域名都自动回放** ——\n  `browser` 后端重载登录态，`static`/`jina` 带上 cookies —— 登录页就这么「下次做对了」，无需改代码。\n\n\u003e 配方是 `AGENTCURL_RECIPES_DIR`（默认 `.agentcurl/`，已 git 忽略）下的 JSON。因为含会话凭据，\n\u003e 文件以 **owner-only（0600）** 权限写入。\n\n---\n\n## 验证\n\n```bash\npython tests/test_smoke.py        # 离线：loopback 起本地服务跑真实 static 取页/爬取\n                                  # + router/extractor/factory/charset/recipe 单测（18 个）\nRUN_LIVE=1 python tests/test_live.py   # 可选：真实站点 + DeepSeek + 已装后端（未装的自动跳过）\npython benchmark.py --extract static jina crawl4ai   # 延迟 / 字符数 / 链接 / 字段命中率\n```\n\n离线测试以 `ResourceWarning` 为错误运行（连接泄漏即挂），CI（`.github/workflows/ci.yml`）在\nPython 3.10/3.11/3.12 上字节编译 + 跑离线套件，仅核心依赖、不开浏览器。live 套件从不在 CI 跑。\n\n---\n\n## 配置（环境变量 / `.env`）\n\n| 变量 | 默认 | 含义 |\n|------|------|------|\n| `DEEPSEEK_API_KEY` | — | DeepSeek 密钥（OpenAI 兼容）；为空 → 抽取降级为原始 markdown |\n| `DEEPSEEK_MODEL` | `deepseek-v4-flash` | 模型 id |\n| `CRAWL_BACKEND` | `static` | `static` \\| `browser` \\| `crawl4ai` \\| `firecrawl` \\| `jina` \\| `auto`（按域名学习）\\| 加号串如 `static+jina` |\n| `ROUTER_MODE` | `fallback` | 加号串模式：`fallback`（首个非空）\\| `fan-out`（最丰富） |\n| `CRAWL_DEPTH` / `CRAWL_MAX_PAGES` | `1` / `20` | 爬取深度 / 页数上限 |\n| `RESPECT_ROBOTS` | `1` | 链接遍历爬取时遵守 robots.txt |\n| `FIRECRAWL_API_KEY` | — | `firecrawl` 后端必需 |\n| `JINA_API_KEY` | — | 可选，提高 r.jina.ai 速率上限 |\n| `AGENTCURL_LEARN` | `1` | 记录每域名结果 + 自动复用配方（`0` 关闭） |\n\n完整变量（含 browser/crawl4ai 细项）见 `.env.example`。\n\n---\n\n## 三种入口\n\n- **代码库**：`from agentcurl import CrawlManager`，用 `with CrawlManager() as cm:` 自动释放连接池。\n- **MCP 服务**：`mcp/server.py` 暴露 `agentcurl_fetch / _crawl / _extract` 三个工具，注册进\n  `~/.claude.json` 即可在 Claude Code 里调用（详见 `mcp/README.md`，已 live 验证 stdio JSON-RPC）。\n- **Claude Code SKILL**：`skill/SKILL.md`（`/agentcurl`）是薄会话层，shell 调 `python -m agentcurl`。\n\n---\n\n## 项目结构\n\n```text\nsrc/agentcurl/\n  manager.py            CrawlManager 门面：fetch / crawl / extract + meta 层\n  extract.py            DeepSeek 字段表/自然语言 → JSON（+ 离线原始降级）\n  recipes.py            每域名学习配方（最佳后端、登录会话、成功率统计）\n  login.py              看用户登录一次的捕获（有头 Playwright）\n  llm.py                DeepSeek-V4-Flash OpenAI 兼容客户端\n  fetch_utils.py        连接池 GET、字符集解码、robots 闸、链接抽取、限速\n  config.py             环境变量配置\n  types.py              Document, ExtractResult\n  mcp.py                MCP 服务（agentcurl_fetch / _crawl / _extract）\n  backends/\n    base.py             CrawlBackend 协议 + CrawlMixin（默认 crawl）\n    static.py           httpx + trafilatura（默认）\n    browser.py          Playwright\n    crawl4ai_backend.py crawl4ai（原生深度爬）\n    firecrawl_backend.py Firecrawl REST\n    jina_backend.py     r.jina.ai 阅读器\n    router.py           回退链 / 扇出\ndemo.py · benchmark.py · mcp/server.py · skill/SKILL.md · tests/\n```\n\n---\n\n## 设计取舍\n\n- **零依赖默认、重型按需**：`static`（httpx + trafilatura）零额外安装就能搞定大多数静态 HTML；\n  只有站点真需要时才装 Playwright / crawl4ai 或配 Firecrawl 密钥。\n- **爬取免费送**：`CrawlMixin` 给每个后端一个基于自身 `fetch` 的同域广度优先 `crawl()`，所以只会取单页的\n  后端也能爬站；有原生深度爬的（crawl4ai / firecrawl）则覆盖它。\n- **抽取是一等公民、且优雅降级**：`extract` 不是事后补丁；没密钥（或任何 LLM 出错）时返回原始 markdown 而非报错。\n- **凭据文件 owner-only**：配方/登录会话含 cookies，目录 0700、文件 0600 写入。\n\n### v1 不做\n\n代理轮换 / 反指纹隐身（用 **firecrawl** 当逃生舱）、分布式队列大规模爬（Scrapy/Crawlee）——\n深度爬请用 **crawl4ai** / **firecrawl** 的原生能力。\n\n---\n\n## 附录：名词速查\n\n| 名词 | 一句话解释 |\n|------|------------|\n| **后端 / backend** | 实际干活的爬虫引擎。本项目 5 个，能力不同但接口统一，靠 `CRAWL_BACKEND` 切换。 |\n| **Document** | 所有后端返回的统一结构：`url / status / markdown / html / title / links / metadata`。 |\n| **结构化抽取 / extract** | 把网页正文交给大模型，按字段表或一句话要求吐回 JSON。没密钥时降级返回原始 markdown。 |\n| **回退链 / router** | `CRAWL_BACKEND=a+b`：先试 a，取不到内容自动落到 b。`fan-out` 则全试取最丰富的。 |\n| **fit-markdown** | crawl4ai 产出的「为大模型精简过」的高信息密度 markdown。 |\n| **meta 学习 / 配方 / recipe** | 仓库记住每个域名怎么爬最顺（最佳后端 + 登录会话），下次自动复用。 |\n| **storage_state** | Playwright 的会话快照（cookies + localStorage），登录捕获后用于「重放登录态」。 |\n| **robots.txt 闸** | 链接遍历爬取前检查站点的 robots 规则；取不到则放行（fail-open）。 |\n| **DeepSeek** | 本项目用于结构化抽取的大模型服务（OpenAI 兼容，需自备密钥）。 |\n\n---\n\n## 贡献 \u0026 许可\n\n贡献指南见 [CONTRIBUTING.md](CONTRIBUTING.md)（含「如何新增一个后端」的步骤）。许可：[MIT](LICENSE)。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchenhaodev%2Fagentcurl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchenhaodev%2Fagentcurl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchenhaodev%2Fagentcurl/lists"}