
Content Extract
- 14 installs
- 82 repo stars
- Updated August 2, 2026
- aaaaqwq/claude-code-skills
content-extract is a Claude Code skill that converts URLs to Markdown using a web_fetch probe with a MinerU API fallback.
About
content-extract is a Claude Code skill that converts a URL into readable Markdown for downstream OpenClaw workflows. A developer uses it when web_fetch or the browser is blocked or messy, especially for WeChat pages. It probes cheaply first, falls back to the MinerU API, and returns a standardized result contract with source links.
- Converts a URL into readable Markdown with a traceable source contract
- Uses a cheap web_fetch probe first, then falls back to the MinerU API for hard sites
- Forces MinerU-HTML model for whitelisted anti-scraping domains like WeChat
Content Extract by the numbers
- 14 all-time installs (skills.sh)
- Ranked #1,419 of 2,719 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
content-extract capabilities & compatibility
- Capabilities
- url to markdown · content extraction · web scraping · mineru fallback
- Use cases
- web scraping · research
What content-extract says it does
Robust URL-to-Markdown extraction for OpenClaw workflows.
先用低成本 probe 判断可不可以直接抓;不行再走重解析(MinerU)。
npx skills add https://github.com/aaaaqwq/claude-code-skills --skill content-extractAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 14 |
|---|---|
| repo stars | ★ 82 |
| Last updated | August 2, 2026 |
| Repository | aaaaqwq/claude-code-skills ↗ |
What it does
Convert a hard-to-scrape URL into traceable Markdown via a probe-then-MinerU fallback.
Who is it for?
Pipelines that need clean Markdown from pages web_fetch cannot handle
Skip if: Bypassing logins or captchas (the skill states it does not attempt this)
When should I use this skill?
When you want to extract, summarize, or convert a blocked or messy webpage to markdown
What you get
Clean Markdown plus a traceable source contract, or a next-step suggestion on failure.
- markdown output
- result contract JSON with sources
By the numbers
- 3-step decision tree (whitelist, probe, fallback)
Files
content-extract — 上层内容解析入口(MCP 语义对齐,但不跑 MCP Server)
目标:把“给我一个 URL → 产出可读 Markdown + 可追溯入口”变成一个统一入口,供后续所有业务 skill(github-explorer、写作类 skills、日报等)复用。
核心原则(来自你发的 Excel Skill 拆解文章的启发):
- 行为规约层:永远给出可追溯入口(原文 URL + 解析产物路径/链接),绝不编造来源。
- Token 探针:先用低成本 probe 判断可不可以直接抓;不行再走重解析(MinerU)。
- 反弹机制:失败时返回“下一步动作建议”,而不是一堆异常栈。
工作流(Decision Tree)
输入:url
0) Domain Whitelist(跳过 probe):若 URL 属于高概率反爬/动态站点(微信/知乎等),直接走 MinerU
- 白名单文件:
references/domain-whitelist.md - 对命中白名单的 URL:强制
model_version=MinerU-HTML
1) Probe(低成本):优先用 web_fetch(url)
- 目标:拿到正文 markdown(便宜、快)
- 判断“失败/不合格”条件(见
references/heuristics.md)包括: - 403/401/反爬
- 只有“环境异常/验证码/请在微信打开”等提示
- 内容极短/明显导航页/丢正文
2) Fallback(高保真):走 MinerU 官方 API
- 调用下游 driver:
skills/mineru-extract/scripts/mineru_parse_documents.py - 对 HTML 页面(微信等):强制
model_version=MinerU-HTML
3) 输出统一结果合同(Result Contract)
无论用 probe 还是 MinerU,都返回同一套结构:
{
"ok": true,
"source_url": "...",
"engine": "web_fetch" ,
"markdown": "...",
"artifacts": {
"out_dir": "...",
"markdown_path": "...",
"zip_path": "..."
},
"sources": [
"原文URL",
"(如使用MinerU)MinerU full_zip_url",
"(如使用MinerU)本地markdown_path"
],
"notes": ["任何重要限制/失败原因/下一步建议"]
}注意:engine可能是web_fetch或mineru。
MinerU 调用(给 agent 的确定性脚本)
当需要 MinerU 时,用这个命令(返回 JSON,且可把 markdown 内联进 JSON,便于下游总结):
python3 mineru-extract/scripts/mineru_parse_documents.py \
--file-sources "<URL>" \
--model-version MinerU-HTML \
--emit-markdown --max-chars 20000路径说明: 上述命令假设你在 skills 安装根目录下执行。如果 mineru-extract 安装在其他位置,请替换为实际路径。
交付规范(强制)
- 输出必须包含
sources(原文入口 + 解析产物入口)。 - 如果 MinerU 成功:必须把
markdown_path(本地路径)写进sources,方便复查。 - 如果两条链路都失败:必须明确失败原因,并给出下一步(例如:让 Boss 提供可访问镜像链接 / 允许我用浏览器 relay 导出 HTML / 走上传 HTML 文件解析的兜底方案)。
本 skill 自身不做什么
- 不跑 MCP Server(避免常驻服务与运维负担)
- 不试图绕过登录/验证码(这属于访问层问题;我们只做解析层和工作流路由)
References
- MinerU API docs: https://mineru.net/apiManage/docs
- MinerU output files: https://opendatalab.github.io/MinerU/reference/output_files/
Domain whitelist → force MinerU
When source_url matches any domain in this list (exact host match or subdomain), skip probe and go directly to MinerU-HTML.
Rationale: these sites frequently block automated fetches, render dynamically, or return interstitial/captcha pages.
Default whitelist
- mp.weixin.qq.com
- mmbiz.qpic.cn (WeChat images; usually appears as assets)
- zhuanlan.zhihu.com
- www.zhihu.com
- zhihu.com
- xhslink.com
- xiaohongshu.com
Notes
- Matching rule:
host == domainORhost.endswith('.' + domain) - You can add/remove domains here based on real failures.
Extraction heuristics (probe → fallback)
This file defines when to accept web_fetch output vs when to fallback to MinerU.
Accept web_fetch when
- HTTP status is 200 and extracted markdown has meaningful body content.
- Content length is not trivially small (rule of thumb: > 2k chars for articles; adjust by site).
- Does NOT look like a captcha/interstitial page.
Force fallback to MinerU (MinerU-HTML) when
Domain whitelist (skip probe)
If URL host matches the whitelist in domain-whitelist.md, skip probe and go straight to MinerU.
See: references/domain-whitelist.md
Obvious interstitial / anti-bot patterns
If web_fetch markdown contains any of:
- "环境异常"
- "完成验证"
- "拖动下方滑块"
- "验证码"
- "请在微信客户端打开"
- "访问过于频繁"
Content too thin / nav-only
- Content length < 800 chars and URL is expected to be an article.
- Extracted text is mostly navigation items / footer.
fetch failure
- web_fetch returns 401/403/429/5xx.
- web_fetch times out repeatedly.
MinerU failure fallback (future)
If MinerU fails to fetch protected pages:
- Ask for a mirror URL, OR
- Use browser relay to export HTML, then add "upload HTML then parse" flow.
(Upload flow is not implemented yet in OpenClaw A-route.)
#!/usr/bin/env python3
"""content-extract: deterministic MinerU-only extractor for OpenClaw.
Why this exists:
- OpenClaw's `web_fetch` is a tool, not available inside scripts.
- This script provides a stable "fallback engine" that the agent can call
after probing with `web_fetch`.
It wraps mineru-extract's MCP-aligned script and returns a compact JSON contract.
Usage:
python3 scripts/content_extract.py --url <URL> [--model MinerU-HTML]
Output (stdout):
{ ok, source_url, engine, markdown, artifacts, sources, notes }
"""
from __future__ import annotations
import argparse
import json
import os
import pathlib
import subprocess
import sys
def _find_mineru_wrapper() -> str:
"""Locate mineru_parse_documents.py relative to this script or via env."""
# 1. Env override
if v := os.environ.get("MINERU_WRAPPER_PATH"):
return v
here = pathlib.Path(__file__).resolve().parent
# 2. Monorepo sibling: ../mineru-extract/scripts/mineru_parse_documents.py
candidate = here.parent.parent / "mineru-extract" / "scripts" / "mineru_parse_documents.py"
if candidate.exists():
return str(candidate)
# 3. OpenClaw workspace default
default = pathlib.Path.home() / ".openclaw" / "workspace" / "skills" / "mineru-extract" / "scripts" / "mineru_parse_documents.py"
if default.exists():
return str(default)
raise FileNotFoundError(
"Cannot find mineru_parse_documents.py. "
"Set MINERU_WRAPPER_PATH env or install mineru-extract skill as a sibling directory."
)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--url", required=True)
ap.add_argument("--model", default="MinerU-HTML")
ap.add_argument("--language", default="ch")
ap.add_argument("--emit-markdown", action="store_true", default=True)
ap.add_argument("--max-chars", type=int, default=20000)
ap.add_argument("--force", action="store_true")
args = ap.parse_args()
try:
wrapper = _find_mineru_wrapper()
except FileNotFoundError as e:
out = {
"ok": False,
"source_url": args.url,
"engine": "mineru",
"markdown": None,
"artifacts": {},
"sources": [args.url],
"notes": [str(e)],
}
sys.stdout.write(json.dumps(out, ensure_ascii=False))
return 2
cmd = [
sys.executable,
wrapper,
"--file-sources",
args.url,
"--model-version",
args.model,
"--language",
args.language,
"--emit-markdown",
"--max-chars",
str(args.max_chars),
]
if args.force:
cmd.append("--force")
p = subprocess.run(cmd, capture_output=True, text=True)
if p.returncode not in (0, 1):
out = {
"ok": False,
"source_url": args.url,
"engine": "mineru",
"markdown": None,
"artifacts": {},
"sources": [args.url],
"notes": [
"mineru wrapper crashed",
(p.stderr or "").strip()[:500],
],
}
sys.stdout.write(json.dumps(out, ensure_ascii=False))
return 2
try:
j = json.loads(p.stdout)
except Exception:
out = {
"ok": False,
"source_url": args.url,
"engine": "mineru",
"markdown": None,
"artifacts": {},
"sources": [args.url],
"notes": ["mineru wrapper returned non-json", (p.stdout or "")[:300]],
}
sys.stdout.write(json.dumps(out, ensure_ascii=False))
return 2
if not j.get("items"):
out = {
"ok": False,
"source_url": args.url,
"engine": "mineru",
"markdown": None,
"artifacts": {},
"sources": [args.url],
"notes": ["no items", json.dumps(j.get("errors") or [], ensure_ascii=False)[:800]],
}
sys.stdout.write(json.dumps(out, ensure_ascii=False))
return 1
item = j["items"][0]
sources = [args.url]
if item.get("full_zip_url"):
sources.append(item["full_zip_url"])
if item.get("markdown_path"):
sources.append(item["markdown_path"])
out = {
"ok": True,
"source_url": args.url,
"engine": "mineru",
"markdown": item.get("markdown"),
"artifacts": {
"out_dir": item.get("out_dir"),
"markdown_path": item.get("markdown_path"),
"zip_path": item.get("zip_path"),
"task_id": item.get("task_id"),
"cache_key": item.get("cache_key"),
},
"sources": sources,
"notes": ["mcp-aligned: mineru_parse_documents"],
}
sys.stdout.write(json.dumps(out, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())