norandom/x_likes_scraper
GitHub: norandom/x_likes_scraper
一个本地运行的 X(Twitter)点赞导出工具,支持多种格式输出和断点续传,并内置 MCP Server 可通过本地 LLM 对点赞历史进行自然语言检索与智能排序。
Stars: 1 | Forks: 0
# X Likes Exporter (Python)
将你从 X(原 Twitter)点赞的推文导出为 JSON、CSV、Pandas DataFrame、包含图片的 Markdown 或 HTML 格式。
## 功能特性
- 导出为 JSON、CSV、Excel、Pandas、Markdown 或 HTML 格式。
- 支持从检查点恢复中断的导出,而无需重新开始。
- 下载图片和视频,并重写 Markdown 中的链接以指向本地文件。
- 遵循 X 的请求频率限制标头,并在额度耗尽时自动等待。
- 自动遍历基于游标的分页,确保获取每一条点赞记录,而不仅仅是第一页。
- 本地运行。你的 Cookie 和推文数据绝对不会离开本机。
## 环境要求
Python 3.12 或更高版本,以及用于依赖管理的 [uv](https://docs.astral.sh/uv/)。运行 `uv sync` 会创建 `.venv/` 目录,安装 `pyproject.toml` 中列出的依赖项,并将其固定在 `uv.lock` 中。
## 快速开始
```
uv sync # install dependencies
cp /path/to/cookies.json . # see "Exporting cookies" below
cp .env.sample .env && $EDITOR .env
./scrape.sh
```
`scrape.sh` 会加载 `.env` 文件,通过 `uv run` 调用 `cli.py`,并传递 `--resume` 参数,以便中断的运行可以从检查点恢复。额外的参数也会被转发:
```
./scrape.sh --no-media # skip media download
./scrape.sh --stats # print stats at the end
./scrape.sh --format markdown # only the per-month Markdown
```
### 导出 Cookie
你需要 X 的会话 Cookie 进行身份验证。有两种方法。
**使用浏览器扩展** — 安装扩展后,登录 [x.com](https://x.com),将 Cookie 导出为 JSON,并保存为 `cookies.json`:
- Chrome/Edge: [Cookie-Editor](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)
- Firefox: [cookies.txt](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/)
**手动获取** — 在 x.com 上打开开发者工具 (F12) → Application → Cookies → https://x.com,然后将 `ct0` (CSRF token) 和 `auth_token` 复制到符合以下格式的 `cookies.json` 文件中。
### 查找你的用户 ID
`X_USER_ID` 是数字 ID,而不是 @用户名。可以在 [tweeterid.com](https://tweeterid.com/) 查找,或者通过以下方式获取:
```
curl "https://tweeterid.com/ajax.php?username=YOUR_USERNAME"
```
### 直接调用 CLI
`scrape.sh` 只是一个轻量级的包装脚本。要自行调用 `cli.py`,请在前面加上 `uv run`(或者先执行 `source .venv/bin/activate`):
```
uv run python cli.py cookies.json YOUR_USER_ID --resume
uv run python cli.py cookies.json YOUR_USER_ID --no-media
uv run python cli.py cookies.json YOUR_USER_ID --format json --format markdown
uv run python cli.py cookies.json YOUR_USER_ID --format markdown --single-file
```
### 在 Python 中调用
```
from x_likes_exporter import XLikesExporter
exporter = XLikesExporter(cookies_file="cookies.json", output_dir="output")
tweets = exporter.fetch_likes(
user_id="YOUR_USER_ID",
download_media=True,
resume=True,
)
exporter.export_all()
```
## 断点续传
导出数万条点赞记录可能需要数小时,并且运行过程中最终会遇到网络波动或触发频率限制等待。导出器在运行过程中会不断将进度写入输出目录下的 `.export_checkpoint.json` 和 `.export_tweets.pkl` 文件中。在 CLI 中传入 `--resume`(或在 Python 中传入 `resume=True`)即可从上次停止的地方继续。检查点保存了目前获取的推文和当前的分页游标;在恢复时,导出器会将新推文与已保存的集合合并,并根据 ID 进行去重。一旦导出完成,检查点文件会被自动删除。
## MCP Server
运行 `python -m x_likes_mcp`(或 `uv run x-likes-mcp`)会启动一个基于 stdio 的 MCP server,并通过以下四个工具暴露你的点赞历史:
- `search_likes(query, year, month_start, month_end)` — 自然语言搜索。可选的日期过滤器用于限定 LLM 遍历的月份范围(比开放式搜索更快)。结果会根据互动量、时间新旧以及你之前对同一作者点赞的频率进行排序——此设计借鉴了 `twitter/the-algorithm` 的重度排序器,仅使用了导出数据中已有的特征,而非直接移植该算法。
- `list_months()` — 返回存在每月 Markdown 文件的月份列表,按时间倒序排列。
- `get_month(year_month)` — 获取某个月的原始 Markdown 内容。
- `read_tweet(tweet_id)` — 根据 ID 获取某条推文的元数据。
### 前置条件
1. 至少运行过一次 `./scrape.sh`,以生成 `output/likes.json` 和 `output/by_month/` 目录。
2. 如果你从早期版本升级而来,请重新运行一次 `./scrape.sh --no-media --format markdown`,以便每月的文件能反映索引器所期望的新格式(无 h1 标题)。
3. 一个兼容 OpenAI Chat Completions 接口的本地 LLM 端点。许多本地代理(LiteLLM proxy server、vLLM、llama-cpp-server、Ollama 等)都暴露了 `/v1/chat/completions` 接口。
### 配置
在 `.env` 中新增三个变量:
```
OPENAI_BASE_URL=http://10.0.0.59:8317/v1
OPENAI_API_KEY=sk-dummy
OPENAI_MODEL=claude-opus-4-1-20250805
```
`openai` Python SDK 会在构建客户端时从进程环境中读取 `OPENAI_BASE_URL`,因此任何兼容 OpenAI 的端点都可以使用。模型字符串是端点所期望的名称(例如,如果代理将 OpenAI 请求映射到 Anthropic 后端,则填入 Anthropic 模型名称)。
可选的排序器权重(覆盖代码中的默认值):
```
# 候选推文的最终得分:
# score = walker_relevance * W_RELEVANCE
# + log1p(favorite_count) * W_FAVORITE
# + log1p(retweet_count) * W_RETWEET
# + log1p(reply_count) * W_REPLY
# + log1p(view_count) * W_VIEW
# + author_affinity[handle] * W_AFFINITY
# + recency_decay(created_at, anchor) * W_RECENCY
# + verified_flag * W_VERIFIED
# + has_media_flag * W_MEDIA
RANKER_W_RELEVANCE=10.0
RANKER_W_FAVORITE=2.0
RANKER_W_RETWEET=2.5
RANKER_W_REPLY=1.0
RANKER_W_VIEW=0.5
RANKER_W_AFFINITY=3.0
RANKER_W_RECENCY=1.5
RANKER_W_VERIFIED=0.5
RANKER_W_MEDIA=0.3
RANKER_RECENCY_HALFLIFE_DAYS=180
```
`author_affinity[handle]` 是根据用户自身的点赞历史预先计算的,公式为 `log1p(count_of_likes_from_handle)`。它用于捕捉你持续关注的作者。
### 注册到 Claude Code
`uv run x-likes-mcp` 会从当前工作目录解析 `pyproject.toml`、`.venv/` 和 `.env`,因此 MCP 配置必须显式设置该目录,或者从项目根目录进行调用。有两种配置形式:
**项目级别**(位于项目根目录的 `.mcp.json`,仅在 Claude Code 打开此目录时生效):
```
{
"mcpServers": {
"x-likes": {
"command": "uv",
"args": ["run", "x-likes-mcp"]
}
}
}
```
**全局可用**(你的用户级配置 `~/.claude.json`,或者是包含绝对路径的 `.mcp.json` 文件):
```
{
"mcpServers": {
"x-likes": {
"command": "uv",
"args": [
"run",
"--directory",
"/absolute/path/to/x_likes_exporter_py",
"x-likes-mcp"
]
}
}
}
```
`uv run --directory ` 会让 uv 从 `` 解析项目,而不管 Claude Code 从哪里启动进程。包含绝对路径的 `.mcp.json` 在此代码库中已被 gitignore,因为路径因用户而异。
或者使用 CLI 进行用户级别的注册:
```
claude mcp add x-likes --scope user -- \
uv run --directory /absolute/path/to/x_likes_exporter_py x-likes-mcp
```
首次运行会在 `output/tweet_tree_cache.pkl` 构建一个树状缓存(根据每月文件的修改时间进行失效处理)。随后的启动将是瞬间完成的。
## 使用示例
### 基础导出
```
from x_likes_exporter import XLikesExporter
exporter = XLikesExporter("cookies.json", "output")
tweets = exporter.fetch_likes("123456789")
exporter.export_all()
```
### 导出为特定格式
```
# 仅 JSON
exporter.export_json("my_likes.json")
# 用于电子表格分析的 CSV
exporter.export_csv("my_likes.csv")
# 带图片的 Markdown
exporter.export_markdown("my_likes.md", include_media=True)
# 用于在浏览器中查看的 HTML
exporter.export_html("my_likes.html")
```
### 结合 pandas 使用
```
# 获取为 DataFrame
df = exporter.get_dataframe()
# 分析
print(f"Total tweets: {len(df)}")
print(f"Most liked: {df['favorite_count'].max()}")
print(f"Average likes: {df['favorite_count'].mean()}")
# 筛选
popular = df[df['favorite_count'] > 1000]
with_media = df[df['has_media'] == True]
# 导出筛选数据
popular.to_csv("popular_tweets.csv", index=False)
```
### 进度监控
```
def progress_callback(current, total):
print(f"Fetched {current} tweets...")
tweets = exporter.fetch_likes(
user_id="123456789",
progress_callback=progress_callback
)
```
### 仅获取数据(不下载媒体)
```
# 如果不需要图片会更快
tweets = exporter.fetch_likes(
user_id="123456789",
download_media=False
)
```
### 获取统计信息
```
stats = exporter.get_stats()
print(f"Total tweets: {stats['total_tweets']}")
print(f"Total media: {stats['total_media']}")
print(f"Total likes: {stats['total_likes']}")
```
## 输出格式
### JSON
包含用户信息、媒体和互动数据的推文数据:
```
[
{
"id": "1234567890",
"text": "Tweet text here...",
"created_at": "Wed Jan 01 12:00:00 +0000 2025",
"user": {
"id": "987654321",
"screen_name": "username",
"name": "Display Name"
},
"retweet_count": 10,
"favorite_count": 50,
"media": [
{
"type": "photo",
"url": "https://...",
"local_path": "media/1234567890_0.jpg"
}
]
}
]
```
### CSV / Excel
扁平化表格,每行一条推文:
| tweet_id | text | user_screen_name | favorite_count | retweet_count | created_at |
|----------|------|------------------|----------------|---------------|------------|
| 123... | Tweet... | username | 50 | 10 | 2025-01-01 |
### Markdown(按月拆分)
默认情况下,Markdown 导出是按月拆分的。导出器解析每条推文的 `created_at` 字段,按年月分组,并将每个分组写入 `output/by_month/` 中的一个文件(例如 `likes_2025-01.md`)。如果将数年的点赞推文合并在单个 Markdown 文件中,会导致大多数编辑器严重卡顿,这就是此功能存在的原因。
如果要强制生成单个 `likes.md` 文件,请在 CLI 中传入 `--single-file`,或在 Python 中传入 `split_by_month=False`。
包含嵌入图片的可读格式:
```
## 2025-01 (15 条推文)
### @username
**Display Name** ✓
*2025-01-01 12:00:00*
Tweet text here...

*🔄 10 • ❤️ 50 • 💬 5*
🔗 [View on X](https://x.com/username/status/1234567890)
---
```
### HTML
一个可以在浏览器中打开的单文件 HTML。推文已进行样式美化,并且媒体以行内方式嵌入。
## 高级用法
### 导出前过滤推文
```
# 按日期筛选
from datetime import datetime, timedelta
recent = datetime.now() - timedelta(days=30)
recent_tweets = [t for t in tweets if t.get_created_datetime() > recent]
# 按互动量筛选
popular = [t for t in tweets if t.favorite_count > 1000]
# 按内容筛选
with_media = [t for t in tweets if t.media]
with_hashtag = [t for t in tweets if 'python' in t.hashtags]
# 导出筛选结果
exporter.tweets = popular
exporter.export_json("popular.json")
```
### 自定义分析
```
import pandas as pd
df = exporter.get_dataframe()
# 您点赞最多的用户
top_users = df['user_screen_name'].value_counts().head(10)
print("Top 10 users you like:")
print(top_users)
# 随时间变化的互动量
df['month'] = pd.to_datetime(df['created_at']).dt.to_period('M')
monthly_likes = df.groupby('month')['favorite_count'].sum()
print("\nMonthly engagement:")
print(monthly_likes)
# 最常用的标签
all_hashtags = []
for hashtags in df['hashtags']:
all_hashtags.extend(hashtags.split(','))
hashtag_counts = pd.Series(all_hashtags).value_counts()
print("\nTop hashtags:")
print(hashtag_counts.head(20))
```
## 架构
大致流程与启发该项目的 Chrome 扩展程序相同:
```
┌──────────────────┐
│ CookieManager │ ← Parse cookies.json
└────────┬─────────┘
↓
┌──────────────────┐
│ XAuthenticator │ ← Extract Bearer token & Query ID
└────────┬─────────┘
↓
┌──────────────────┐
│ XAPIClient │ ← Fetch likes with pagination
│ │
│ • Rate limiting │
│ • Cursor paging │
│ • Data parsing │
└────────┬─────────┘
↓
┌──────────────────┐
│ MediaDownloader │ ← Download images/videos
└────────┬─────────┘
↓
┌──────────────────┐
│ Formatters │ ← Export to formats
│ │
│ • JSON │
│ • CSV/Excel │
│ • Markdown │
│ • HTML │
└──────────────────┘
```
## 请求频率限制
X 的 API 在每个 15 分钟的窗口期内大约允许 500 次请求。客户端会从每个响应中读取 `x-rate-limit-limit`、`x-rate-limit-remaining` 和 `x-rate-limit-reset` 标头。当 `remaining` 降至 1 时,它会休眠直到重置时间戳(外加 5 秒的缓冲时间),然后继续。请求之间也会有 1 秒的停顿,以避免猛烈请求端点。
进度会在运行过程中实时打印:
```
Fetching page 25...
Fetched 20 likes. Total: 500
Rate limit: 475/500
```
对于超过 10,000 条的点赞记录,预计需要 1-2 小时,其中包括等待频率限制解除的时间。
## 故障排除
### "无效的 cookies"
- 确保你的 `cookies.json` 中包含 `ct0` 和 `auth_token`
- 尝试注销并重新登录后再次导出 Cookie
### "认证失败"
- 你的会话可能已过期
- 从 x.com 注销,重新登录,并导出全新的 Cookie
### "超出速率限制"
- 脚本本应自动处理此问题
- 如果未能处理,请等待 15 分钟后重试
### "未找到推文"
- 验证你的 User ID 是否正确
- 确保你的账号中确实有点赞过的推文
- 检查 Cookie 是否有效
### 图片未下载
- 检查网络连接
- 某些媒体 URL 可能已过期
- 尝试先使用 `download_media=False` 运行
## 项目结构
```
x_likes_exporter_py/
├── x_likes_exporter/
│ ├── __init__.py # Package exports
│ ├── cookies.py # Cookie parsing
│ ├── auth.py # Token extraction
│ ├── client.py # API client
│ ├── models.py # Data models
│ ├── downloader.py # Media downloader
│ ├── formatters.py # Export formatters
│ ├── checkpoint.py # Resume checkpoints
│ └── exporter.py # Main exporter class
├── cli.py # Command-line interface
├── scrape.sh # .env-driven entry point
├── .env.sample # Config template
├── examples/ # Usage examples
├── pyproject.toml # Project + dependencies
├── uv.lock # Pinned dependency versions
└── README.md
```
## API 参考
### XLikesExporter
主导出器。
```
exporter = XLikesExporter(cookies_file: str, output_dir: str = "output")
```
**方法:**
- `fetch_likes(user_id, download_media=True, progress_callback=None, resume=False)` → List[Tweet]
- `export_json(filename, include_raw=False)` → None
- `export_csv(filename)` → None
- `export_excel(filename)` → None
- `export_markdown(filename, include_media=True, split_by_month=True)` → None
- `export_html(filename)` → None
- `export_all(base_name="likes", include_raw=False)` → None
- `get_dataframe()` → pandas.DataFrame
- `get_stats()` → dict
### Tweet 模型
单条推文。
**属性:**
- `id`: str - 推文 ID
- `text`: str - 推文文本
- `created_at`: str - 创建时间戳
- `user`: User - 用户对象
- `retweet_count`: int
- `favorite_count`: int
- `reply_count`: int
- `quote_count`: int
- `view_count`: int
- `media`: List[Media] - 媒体项列表
- `urls`: List[str]
- `hashtags`: List[str]
- `mentions`: List[str]
**方法:**
- `to_dict()` → dict
- `get_url()` → str
- `get_created_datetime()` → datetime
## 性能表现
获取吞吐量大约为每秒 20 条点赞记录,受限于 X 的请求频率限制。媒体下载速度取决于你的网络连接。内存处理开销可忽略不计(处理 1,000 条推文不到一秒)。
内存使用量大约为每条推文 2-5 KB,因此 10,000 条点赞记录约为 20-50 MB,50,000 条约为 100-250 MB。
粗略的完成时间,包括等待频率限制解除的时间:
| 点赞数 | 耗时 |
|--------|-----------------|
| 100 | 5-10 秒 |
| 1,000 | 1-2 分钟 |
| 10,000 | 15-30 分钟 |
| 50,000 | 1-2 小时 |
## 隐私与安全
一切均在本地运行。脚本会读取你的 `cookies.json`,使用你现有的会话直接与 X 的 API 通信,并将文件写入磁盘。没有任何数据会被发送到第三方服务器。源代码就在这里,你可以在运行前自行阅读审查。
## 许可证
MIT。
## 鸣谢
灵感来源于 [Twitter Exporter](https://chrome.google.com/webstore/detail/twitter-exporter/lnklhjfbeicncichppfbhjijodjgaejm) Chrome 扩展程序。
## 免责声明
本工具与 X Corp 或 Twitter 无关。使用风险自负,请勿滥用 API。
cookies.json 格式
``` [ { "domain": ".x.com", "name": "ct0", "value": "your_ct0_token_here", "path": "/", "secure": true, "httpOnly": false }, { "domain": ".x.com", "name": "auth_token", "value": "your_auth_token_here", "path": "/", "secure": true, "httpOnly": true } ] ```标签:API 抓取, Cookie 认证, CSV, ESC4, Excel, HTML, JSON, Markdown, OSINT, Petitpotam, Python, SOCMINT, Twitter, URL抓取, uv 包管理, X, 代码示例, 数据分析, 数据同步, 数据导出, 数据抓取, 数据泄露, 断点续传, 无后门, 无头浏览器, 本地部署, 社交媒体情报, 逆向工具