X 内容下载器
将 X/Twitter 内容自动分类下载。支持:
- 单条推文:纯文字+图片、视频+转录、GitHub 仓库推荐帖
- 整账号批量下载:获取某账号所有推文、批量下载图片、提取结构化内容
触发条件
- 用户发送
x.com/xxx/status/xxx或x.com/xxx/article/xxx链接 → 路径 A/B/C - 用户发送
x.com/xxx或@xxx账号链接,要求下载"所有内容" → 路径 D - 用户说"下载这篇文章/视频"、"存下来"且链接是 X 的 → 路径 A/B/C
- 用户说"下载这个账号所有推文/图片/提示词" → 路径 D
前置依赖
| 工具 | 用途 | 安装检查 |
|---|---|---|
| xreach (agent-reach) | 批量获取推文、分页 | which xreach |
| yt-dlp | 视频/推文信息获取、视频下载 | which yt-dlp |
| whisper (openai-whisper) | 语音转文字 | which whisper |
| ffmpeg | 音频提取 | which ffmpeg |
| git | GitHub 仓库 clone | which git |
第零步:内容类型检测(必做,优先于一切)
在执行任何下载操作之前,先用浏览器打开推文,提取正文文本,判断内容类型。
0.1 打开推文 + 提取文本
browser_navigate → url = {用户给的链接}
用 browser_console 提取正文和所有链接:
const article = document.querySelector('article[data-testid="tweet"]');
if (article) {
const text = article.innerText;
const links = Array.from(article.querySelectorAll('a[href]')).map(a => a.href);
JSON.stringify({ text: text.substring(0, 10000), links });
} else { JSON.stringify({ text: '', links: [] }); }
0.2 判断逻辑
根据提取到的文本和链接,按以下规则判断:
| 条件 | 类型 | 执行路径 |
|---|---|---|
文本中包含 ≥1 个 github.com/xxx/xxx 仓库链接 且 帖子主题是推荐/介绍工具库 |
GitHub 推荐帖 | → 执行 路径 C |
| yt-dlp 检测到视频(有 duration/vcodec) | 视频帖 | → 执行 路径 B + 路径 A 的文本提取 |
推文引用了 x.com/i/article/xxx(X Article),text 中无代码块 |
Article 引用帖 | → 记录,但跳过 Article 内容(Article 需登录);xreach text 的描述性文字作为参考 |
推文引用了 x.com/i/article/xxx,且 text 中有代码块 |
含提示词的 Article 相关帖 | → 执行 路径 A,代码块就是提示词 |
| 以上都不满足 | 纯文字帖 | → 执行 路径 A |
重要区分:
x.com/i/article/xxx= X Article(长文格式,需要登录,xreach 没有 Article API)- 推文 Quote Tweet 引用 Article 时,xreach text 只显示 Quote 部分(标题+摘要),不是 Article 全文
- 正确策略:遇到 Article 引用帖,直接跳过;遍历账号全部推文,靠其他有代码块的推文来获取真实提示词(而不是死磕 Article)
GitHub 推荐帖的识别信号:
- 文本中出现 1 个以上
github.com/owner/repo格式链接 - 关键词:开源、GitHub、工具、推荐、仓库、clone、star、fork
- 帖子核心目的是推荐工具/库,而非讨论某个库的 issue 或 PR
注意:如果帖子只有 1 个 GitHub 链接且内容是讨论 issue/PR/代码片段,不属于 GitHub 推荐帖,走路径 A。
路径 A:纯文字 + 图片(默认路径)
第 1 步:提取完整正文
用 browser_console 提取正文(可能需要滚动拼接):
const article = document.querySelector('article[data-testid="tweet"]');
if (article) {
article.innerText.substring(0, 30000);
} else { 'No article found'; }
如果文本被截断,browser_scroll 向下滚动后再抓一次,拼接。
如果是英文内容:翻译成中文。将原文分段(每段 ≤2000 字),逐段翻译后合并。翻译风格保持口语化,专业术语保留英文原文加括号注释。
第 2 步:提取图片 URL
const imgs = document.querySelectorAll('img');
const results = [];
imgs.forEach(img => {
if (img.src && img.src.includes('twimg') && !img.src.includes('profile_images')) {
results.push(img.src);
}
});
JSON.stringify(results);
过滤掉 profile_images(头像),只保留 media 图片。如果图片 URL 含 format=png,保持原格式。
第 3 步:下载图片到本地
curl -sL -o "{output_dir}/images/{序号}-{描述}.jpg" "{img_url}?format=jpg&name=large"
第 4 步:组装 Markdown
创建目录结构:
<OUTPUT_DIR>/{清理后的标题}/
├── {清理后的标题}.md
└── images/
├── 01-xxx.jpg
└── ...
Markdown 内容:
- 文件头加元信息(作者、日期、原文链接)
- 在对应位置插入
 - 如果原文是英文,正文使用中文翻译,文末附上英文原文
路径 B:视频下载 + 转录 + 翻译
第 5 步:检测视频
yt-dlp --flat-playlist -j "{url}" 2>&1 | head -5
检查 "duration" 和 "vcodec" 字段。有视频则继续。
第 6 步:下载视频
优先使用 HTTP 直链(避免 HLS 极慢问题):
# 1. 获取 HTTP 格式直链
yt-dlp -j --no-playlist "{url}" 2>/dev/null | python3 -c "
import sys, json
info = json.load(sys.stdin)
for f in info.get('formats', []):
if 'http-' in f.get('format_id','') and f.get('vcodec','none') != 'none':
print(f\"{f['format_id']}: {f['url']}\")
"
# 2. curl 下载
curl -L -o video_silent.mp4 "{http_url}"
如果找不到 HTTP 直链:
yt-dlp -f "http-832" --no-playlist -o "video.%(ext)s" "{url}"
最后的退路:
yt-dlp -f "bestvideo[height<=720]+bestaudio/best[height<=720]/best[height<=720]" \
-o "{output_dir}/videos/video.mp4" --continue "{url}"
大视频务必用 background=true + notify_on_complete=true
第 7 步:提取音频 + Whisper 转录
ffmpeg -i "{output_dir}/videos/video.mp4" -vn -acodec pcm_s16le -ar 16000 -ac 1 "{output_dir}/videos/audio.wav" -y
whisper "{output_dir}/videos/audio.wav" --model medium --output_format all --output_dir "{output_dir}/transcripts" --verbose False
模型选择:base 快但中文差,medium 平衡(推荐),large 最准但慢。长视频(>20min)用 background=true。
第 8 步:翻译(如果是英文)
- 读取
transcripts/audio.txt - 分段翻译成中文(每段 ≤2000 字)
- 生成
transcripts/audio-zh.md
第 9 步:组装输出
{output_dir}/
├── {标题}.md
├── images/
├── videos/
│ └── video.mp4
├── transcripts/
│ ├── audio.txt
│ ├── audio.srt
│ └── audio-zh.md(英文视频才有)
└── transcript.md
路径 C:GitHub 仓库推荐帖
第 10 步:提取所有 GitHub 仓库 URL
从第零步提取的文本和链接中,用正则匹配所有 GitHub 仓库地址:
github.com/[\w.-]+/[\w.-]+
注意:
- 过滤掉非仓库页面(如
github.com/features、github.com/topics等) - 过滤掉指向 issue/PR/pull/releases 的链接(只保留仓库根路径)
- 对于 t.co 短链接,先
curl -sI -L解析真实地址 - 去重
第 11 步:确认数量
| 仓库数量 | 操作 |
|---|---|
| 1-5 个 | 直接 clone,无需确认 |
| >5 个 | 用 clarify 列出仓库名和简介,让用户选择要 clone 哪些 |
第 12 步:批量 clone
默认 clone 到 ~/repos/ 目录:
mkdir -p ~/repos
cd ~/repos
git clone https://github.com/{owner}/{repo}.git
逐个 clone,避免并行(& 在 foreground terminal 不可用)。如果仓库数量多,可以写临时脚本用 background=true 后台批量执行。
第 13 步:报告结果
clone 完成后,输出汇总表格:
| # | 仓库 | 简介 |
|---|------|------|
| 1 | owner/repo | 一句话描述 |
如果部分 clone 失败,单独列出失败的仓库和原因。
路径 C 不需要生成 Markdown 文件,只需要 clone 仓库。 如果用户同时要求"存下来"或"做笔记",额外走路径 A 保存推文文本。
已验证有效的路径
| 步骤 | 方法 | 状态 |
|---|---|---|
| xreach 认证 | xreach auth extract --browser chrome |
✅ 有效 |
| 批量获取推文 | xreach tweets @user --json -n 100 + cursor 分页 |
✅ 有效(9页→122条) |
| xreach text 含代码块 | 代码块保留完整 | ✅ 真实提示词 |
| xreach text 无代码块 | 只有描述性文字,Article 需登录 | ⚠️ 需浏览器补全或跳过 |
| r.jina.ai 批量 100+ 条 | SSL EOF 100% 失败 | ❌ 不适合批量 |
| 检测视频 | yt-dlp --flat-playlist -j 检查 duration/vcodec | ✅ 有效 |
| 获取正文 | browser_navigate + browser_console(article.innerText) |
✅ 有效 |
| 获取图片 URL | xreach media[].url 直接给出高清 URL |
✅ 优于浏览器提取 |
| Article 类型图片 | r.jina.ai 解析推文 → 提取 pbs.twimg.com URL → Bearer Token 下载 | ✅ 88/88 成功(2026-05-09 实测) |
| 下载图片 | urllib 或 curl,ThreadPoolExecutor(max_workers=8) | ✅ 93/99 成功 |
| 下载视频 | yt-dlp HLS m3u8 合并 | ✅ 有效 |
| 提取音频 | ffmpeg -i video.mp4 -vn -ar 16000 audio.wav | ✅ 有效 |
| 语音转录 | whisper --model medium audio.wav | ✅ 有效 |
| 组装 Markdown | write_file 相对路径引用 | ✅ 有效 |
| GitHub clone | git clone 到 ~/repos/ | ✅ 有效 |
| t.co 解析 | curl -sI -L 查看最终 Location | ✅ 有效 |
无效的方法(不要再试)
| 方法 | 为什么不行 |
|---|---|
| xreach tweet 获取 Article 内容 | xreach 没有 Article API,只返回推文正文 |
| curl + cookies 请求 Article | 返回的是需要 JS 渲染的 HTML,无法提取内容 |
| r.jina.ai 批量提取 | 批量请求 100+ 条时 100% SSL EOF 错误,只适合单条验证 |
边界情况
- 文本太长一次抓不全 → 滚动后多次抓取拼接
- 图片 URL 带
profile_images→ 头像,跳过 - 图片 URL 含
format=png→ 保持原格式 - 视频很大(>300MB)→
background=true - 视频很长(>30min)→ whisper 用
background=true - GitHub 推荐帖中 t.co 短链需要先解析 →
curl -sI -L - GitHub 推荐帖配图中有仓库地址截图 → 用 vision_analyze 识别
- 帖子同时有 GitHub 链接和视频 → 按主体意图判断,优先走 GitHub 路径
- 仓库已存在 →
git pull更新而非重新 clone - 纯英文文字帖 → 翻译成中文,文末附英文原文
路径 D:整账号批量下载
当用户要求下载某个 X 账号的所有内容(所有推文、所有图片、提示词等)时走此路径。
⚠️ 前置沟通(重要):开始前先检查账号的推文结构。发现大量推文引用 Article 时立即告知用户:Article 需要登录才能查看;能提取的是推文正文、代码块、佐料、图片,不能提取 Article 中的完整提示词/汤底。让用户决定是否继续或提供登录凭据——不要下载完了才说"需要登录"。
完整流程见 references/x-batch-account.md:xreach 认证 → 分页抓取全部推文 → 并发批量下载图片 → 组装账号级输出 → 内容提取与分类(含 xreach text 截断的浏览器补全、r.jina.ai 提取 Article、浏览器风控应对)。
路径 E:Article 类型推文图片下载
Article 引用帖(x.com/i/article/xxx)的图片不在推文 media 数组里(xreach 返回 media: []),直接用 media URL 下载会得到 0 字节。需 r.jina.ai 解析 Article 页面提取图片 URL,再用 Twitter 公开 Bearer Token 绕过 403 下载。完整工作流见 references/x-article-images.md。
辅助脚本
scripts/process_video.sh — 自动化视频下载+转录流程(供参考,主要逻辑由 skill 在线执行)。