链接内容提取
术-流程 · 术层 skill 全文
本页是 <code>rules/skills/workflow_content_extraction.md</code> 的逐字投影(仅隐私清洗,零改写)。
时点提示:本页是仓内文件 rules/skills/workflow_content_extraction.md 的逐字投影(仅做隐私清洗:仓库根绝对路径→相对路径、家目录→~/;除此零改写)。若源文件后续有修订,以仓内真源为准。
Skill: 链接内容提取
When to Use
当用户发送一个 URL(无论是在微信对话、直接粘贴、还是文件中提及),需要提取其内容时触发。适用场景:
- 微信转发的链接(通过 WeClaude bridge 到达)
- 对话中直接发送的 URL
- 文档中引用的外部链接需要本地化
- 批量处理多个链接
核心原则:统一管线 + 分层特殊化。大部分网页(包括未见过的)走通用 handler;常用平台(GitHub/X/小红书/微信/B站/YouTube)有专用 handler 做更精细的处理。所有结果都进同一套 PostProcessor 链(质量门控 → 噪声过滤 → 图片下载 → OCR → 转录 → 去重 → frontmatter → 保存)。
四维数据模型
任何链接被当作可能包含下列四种内容的混合体:
| 维度 | 存储方式 |
|---|---|
| 文字 | ExtractionResult.text |
| 图片 | ExtractionResult.images[](url + 本地 path + OCR 文本) |
| 视频 | ExtractionResult.videos[](url + 本地 path + duration + transcript) |
| 音频 | ExtractionResult.audios[](同上) |
默认只提取正文 + 图片。视频/音频下载与转录需要 --transcribe 标志。评论、弹幕、互动数据默认不提取。
Prerequisites
工具依赖(按名引用,路径查 tools/INDEX.md):
| 工具 | 用途 | 必需 |
|---|---|---|
link_router | 统一入口(detector + HandlerRegistry + PostProcessorChain) | 是 |
bilibili_download | B站 BBDown 下载 | B站链接时 |
video_download | yt-dlp 下载(YouTube 等) | 非B站视频时 |
browser_capture | OCR helpers、Playwright cookie 注入 | handler 内部调用 |
audio_transcribe | Groq Whisper API 语音转录(默认,备选 Windows remote GPU) | 需要转录时 |
Python 依赖:trafilatura(GenericHandler 主内容提取)。已装在 user site-packages。
Handler 清单
link_router 内部 7 个 handler,按 URL 特征自动匹配:
| Handler | URL 特征 | 实现策略 |
|---|---|---|
bilibili | bilibili.com/video/BV b23.tv/ | BBDown CLI |
youtube | youtube.com/watch youtu.be/* | yt-dlp |
twitter | x.com//status/ twitter.com//status/ | FxTwitter API(api.fxtwitter.com)主路径 → Playwright fallback |
github | github.com/{owner}/{repo} | GitHub REST API /repos/{o}/{r}/readme(raw markdown) |
xiaohongshu | xiaohongshu.com/ xhslink.com/ | Playwright + Cookie → 解析 __INITIAL_STATE__ JSON → 图片 URL rebuild |
wechat | mp.weixin.qq.com/s/* | Playwright + Cookie + 图片下载 + OCR |
generic | 其他所有 | Playwright + Cookie + trafilatura(语义正文提取) |
短链接处理
b23.tv、xhslink.com、t.co、youtu.be 等短链接由 detector.py 自动 curl -L 跟随重定向,解析后重新路由。
PostProcessor 链(所有 handler 共享)
| Processor | 作用 | 触发条件 |
|---|---|---|
QualityGate | 拒绝反爬页面 / 登录墙 / 平台首页 / 文本 < 100 字符 | 永远运行,失败即短路 |
NoiseFilter | 按平台过滤已知 UI 噪声(X / GitHub / 小红书 / 通用 cookie) | 有 text 时 |
ImageDownload | 下载图片到 .image_cache/<slug>/ | 默认 on,--no-download-images 关闭 |
ImageOCR | Vision Framework OCR 大于 300x300 的图片 | wechat 默认开;其他平台 --ocr 开启 |
MediaTranscribe | Groq Whisper API 转录音视频(默认) | --transcribe 时 |
URLDedup | 扫 library 的 frontmatter url 字段 | --on-duplicate {skip,overwrite,version},默认 skip |
Frontmatter | 按统一 schema 生成 YAML | 所有成功结果 |
LibrarySave | 幂等写入 contexts/library/{articles,videos}/ | --save 时 |
CLI 使用
# 仅检测,返回 JSON(platform、handler_name、confidence 等)
python3 tools/link_router/link_router.py detect <url>
# 提取,保存到 library
python3 tools/link_router/link_router.py extract <url> --save
# 含音视频转录
python3 tools/link_router/link_router.py extract <url> --save --transcribe
# 指定 Chrome 小号 profile 读取 cookie(例如 Profile 2 / Michel)
python3 tools/link_router/link_router.py extract <url> --save --cookie-profile "Profile 2"
# 所有平台图片 OCR
python3 tools/link_router/link_router.py extract <url> --save --ocr
# URL 重复时处理策略
python3 tools/link_router/link_router.py extract <url> --save --on-duplicate version
# 批量
python3 tools/link_router/link_router.py batch <urls.txt> --save --transcribe场景 A:单个链接提取
- 不确定类型 → 先
detect,再extract --save - 明确是文章 →
extract --save - 明确是视频/音频 →
extract --save --transcribe
场景 B:微信转发链接处理
微信通过 WeClaude bridge 转发的链接以纯文本形式到达:
python3 tools/link_router/link_router.py extract <url> --save --transcribe--transcribe 只对视频生效,--save 只对成功提取的结果写盘。文章和视频一条命令通吃。
场景 C:批量链接处理
python3 tools/link_router/link_router.py batch urls.txt --save --transcribe统一 Frontmatter Schema
所有 library 文件使用同一套字段:
---
title: string # 必需
url: string # 必需
resolved_url: string # 可选(短链接解析后)
platform: enum # github/twitter/xiaohongshu/wechat/bilibili/youtube/generic
content_type: enum # article/video/tweet/image_set/readme
author: string # 必需,可空串
pub_date: ISO 8601 # 可选
extracted_at: ISO 8601 UTC # 必需
extraction_method: string # 具体走的路径(如 fxtwitter_api、github_api、generic_trafilatura)
status: full | partial | failed # 必需
confidence: float # 0-1
text_length: int # 必需
image_count: int # 必需
video_count: int # 必需
# 视频专用
duration_seconds: float
transcription_engine: string
# 平台特有
metadata:
stars: int # GitHub
topics: [string] # GitHub
license: string # GitHub
likes: int # XHS/Twitter
...
warnings: [string] # 部分失败的提示
---旧文件(source:、saved_at: 等字段名)不会自动迁移,但不影响新提取。如需统一,后续写迁移脚本。
端到端验证状态(2026-04-16 统一管线重构后)
7/7 真实 URL 测试全部通过,评分全 A 或 A-:
| 链路 | 测试用例 | 质量 | extraction_method |
|---|---|---|---|
| X Article → articles/ | garrytan/status/2042925773300908103 "Thin Harness, Fat Skills" | A | fxtwitter_api |
| GitHub README → articles/ | github.com/JoeanAmier/XHS-Downloader | A | github_api |
| 小红书图文 → articles/ | 带 xsec_token 的真实 note URL | A- | xiaohongshu_initial_state |
| 小红书失效 URL(反向测试) | 693fb734000000001e013823(xsec_token 过期) | A | QualityGate 正确拒绝 |
| 微信公众号 → articles/ | 机器之心文章(回归) | A | wechat_playwright |
| 一般网页 → articles/ | trafilatura.readthedocs.io/en/latest/ | A | generic_trafilatura |
| B站视频 → videos/ | BV1GVXzBaELr(36s 技术讲解) | A | bbdown + audio_transcribe |
| YouTube 视频 → videos/ | jNQXAC9IVRw "Me at the zoo"(19s) | A | yt-dlp + audio_transcribe |
关键质量指标:
- X Article 12066 字符,UI 噪声 0
- GitHub README 26701 字符原生 markdown + stars/topics/license metadata
- 微信公众号 10 图全下 + 9 张 OCR
"无人声视频"处理:转录引擎返回空字符串时,MediaTranscribe 添加 warning,不生成垃圾 transcript 文件。
关键决策
| 决策点 | 选择 | 原因 |
|---|---|---|
| 架构 | Handler Registry + PostProcessor Chain | 统一管线 + 少量特殊化;新增平台只需写一个 handler |
| X/Twitter 策略 | FxTwitter API 主,Playwright fallback | 零成本、无 UI 噪声、支持 Article Draft.js 渲染 |
| GitHub 策略 | REST API /readme | raw markdown,零 UI 噪声;60 req/h 足够个人使用 |
| 小红书图片 | URL rebuild + 本地下载 | CDN 签名 URL 几小时过期;rebuild 到 ci.xiaohongshu.com/{token}?imageView2/format/png 得到永久链接 |
| 通用网页正文 | trafilatura + BeautifulSoup 语义选择器 fallback | ROUGE-N.f1 0.64,优于 document.body.innerText |
| B站下载工具 | BBDown | 比 yt-dlp 对 B 站稳定 |
| Cookie 来源 | Chrome 本地数据库 via pycookiecheat | 零配置,用户已登录的平台自动可用 |
| 转录引擎 | Groq Whisper API(默认,engine chain groq-whisper,remote-gpu) | 识别计算走远端 API / 远端 GPU,本机不占推理资源;实现见 tools/audio_transcribe |
| 质量门控 | QualityGate 短路整条链 | 反爬 / 登录墙 / 首页 title 的废页面不入库 |
| URL 去重 | 默认 skip,可 version/overwrite | 避免重复写入和误覆盖用户手工整理 |
陷阱
- Python 解释器:handler 内部调用的
video_download/browser_capture走/usr/bin/python3(系统 Python 3.9),pycookiecheat 的 Keychain 授权绑在此解释器。link_router本身的 CLI 可以用任何 python3 - 短链接超时:
b23.tv等短链接解析依赖 curl 跟随重定向,网络不好时可能超时。默认 10 秒 - audio_transcribe venv:
tools/audio_transcribe/.venv/bin/如果被 homebrew upgrade 破坏,需要python3.12 -m venv tools/audio_transcribe/.venv --without-pip重建 - ASR 错别字:转录引擎会把专业术语识别错(如 "Claude" → "cloud")。未做 LLM 后处理
- XHS xsec_token:小红书 note URL 没有 xsec_token 或 token 过期时,平台只返回首页。QualityGate 会识别并拒绝
5a. 小红书账号隔离:默认 cookie 来源是 Chrome 默认 profile。测试小号时必须先让小号登录一个独立 Chrome profile,再用 --cookie-profile 或 LINK_ROUTER_COOKIE_PROFILE 指向该 profile;显式指定的 profile 找不到时不会回退到默认 profile
- FxTwitter 依赖第三方:
api.fxtwitter.com不是官方服务,理论上可能挂。TwitterHandler 会自动 fallback 到api.vxtwitter.com,再失败 fallback 到 Playwright - GitHub API 配额:未认证 60 req/h。设置环境变量
GITHUB_TOKEN提升到 5000 req/h - trafilatura 对 SPA 支持有限:我们先用 Playwright 抓 rendered HTML 再喂给 trafilatura,所以多数 SPA 可用。但需要用户交互(点击"展开")才显示内容的页面仍抓不到
Python API(供其他工具调用)
from tools.link_router import extract, detect, ExtractionOptions
# 检测
detection = detect("https://x.com/garrytan/status/2042925773300908103")
# → LinkDetection(platform="twitter", handler_name="twitter", confidence=0.9, ...)
# 提取(options 是 keyword-only 参数)
result = extract(
"https://x.com/garrytan/status/2042925773300908103",
options=ExtractionOptions(save=True, transcribe=False, on_duplicate="skip"),
)
# → ExtractionResult(status="full", text="...", images=[...], ...)必须用 /usr/bin/python3(pycookiecheat 的 Keychain 授权和 Playwright 绑在该解释器)。
代码组织
tools/link_router/
link_router.py # 向后兼容 wrapper(CLI 入口)
cli.py # 新 CLI 实现
router_types.py # ExtractionResult、ExtractionOptions 等
detector.py # URL → LinkDetection
registry.py # HandlerRegistry + PostProcessorChain
frontmatter_schema.py # 统一 schema + 生成器
handlers/
base.py # PlatformHandler Protocol
_browser_utils.py # 共享 Playwright + Cookie 工具
bilibili.py youtube.py wechat.py twitter.py github.py xiaohongshu.py generic.py
post_processors/
base.py
quality_gate.py noise_filter.py image_download.py image_ocr.py
media_transcribe.py url_dedup.py frontmatter.py library_save.py后续改进方向
已完成(之前列为改进方向):
- ✅ URL 去重(
URLDedupProcessor) - ✅ 质量门控(
QualityGateProcessor) - ✅ 统一 frontmatter schema(
FrontmatterProcessor) - ✅ 小红书图片本地化(通过 image rebuild + ImageDownloadProcessor)
- ✅ GitHub UI 噪声(改用 GitHub API)
- ✅ X/Twitter UI 噪声(改用 FxTwitter API)
剩余(低优先级):
- Library 旧文件迁移:现有 library 文件的 frontmatter schema 未自动升级,需要单独脚本
- LLM 后处理 ASR:转录引擎输出通过 LLM 修正专业术语
- 下载缓存:相同 URL 近期已处理时跳过,省流量
- 内部 API 直调:B站和小红书的内部 API 直调可以避免 Playwright 开销(MediaCrawler 30k stars 有现成实现)
- URL 等价性判断:
URLDedup当前按完整字符串匹配,可扩展为规范化(去 www、去尾部 /)后比较 - 小红书 OCR 默认开启:小红书图文笔记的内容主要在图片里,默认开 OCR 可能更实用