Install
openclaw skills install @kooui/x-tweet-scraperX.com (Twitter) 全量推文采集技能。支持三种采集方案:自动搜索模式、GraphQL API 模式、DOM 滚动模式。Complete X.com tweet scraping skill with three strategies: auto-search, GraphQL API, and DOM scrolling.
openclaw skills install @kooui/x-tweet-scraper中文: 通过 Playwright 浏览器自动化三种方案采集 X 用户的公开推文历史。 English: Scrape public tweet history from X.com users via three Playwright-based strategies.
Tags: #twitter-scraper #x-tweet-collector #playwright-automation #web-scraping #data-collection #social-media-analytics #x-api-scraping #browser-automation #tweet-archive #python-scraper
pip install playwright, python -m playwright install chromiumfrom playwright.sync_api import sync_playwright),不依赖 requests$env:PYTHONIOENCODING="utf-8"# 1. Login to https://x.com in Chrome/Edge
# 2. F12 → Application → Cookies → https://x.com
# 3. Right-click → Export as JSON
# 4. Save as <username>_data/x_cookies.json
| 方案 | 脚本 | 覆盖量 | 限流 | 推荐场景 |
| Strategy | Script | Coverage | Rate Limit | Best For |
|---|---|---|---|---|
| 自动搜索 Auto-Search | scrape_x_auto.py | ~2000+ | 低 Low | 全量采集 Full collection |
| GraphQL API | scrape_x_v9.py | ~900 | 高 High | 补充最新 Fetch latest |
| DOM 滚动 DOM Scroll | scrape_x_scroll.py | ~1400 | 最低 Lowest | Profile 页面滚动 Profile scrolling |
Load seed file (known IDs)
→ Generate 14-day search chunks (2025-07-01 ~ today)
→ For each chunk:
→ Open X.com search: from:user since:date until:date
→ Detect login status (check "Sign in")
→ Scroll to bottom → wait 3s → extract DOM articles
→ If blank: scroll up 2000px → retry
→ Dedup by ID → save new tweets
→ If y-position unchanged for 25 rounds → chunk complete
→ If <30 new tweets in chunk → split into 7-day sub-chunks
→ Merge into seed file
CHUNK_DAYS = 14 # 搜索块大小 days per chunk
SCROLL_DELAY = 3 # 滚动间隔秒数 scroll interval
WEEK_CHUNK_THRESHOLD = 30 # 稀疏块触发拆周阈值 sub-chunk threshold
BLOCK_TIMEOUT = 180 # 每块超时秒秒 per-chunk timeout
SAME_POS_LIMIT = 25 # 滚动不变判定到底 scroll stuck limit
$env:PYTHONIOENCODING="utf-8"
python -u scripts/scrape_x_auto.py --username target_user --seed existing_data.json
Playwright launch → inject cookies → open x.com/home
→ Verify login (check "Sign in")
→ Call UserByScreenName via browser fetch() to get user_id
→ Call UserTweets via browser fetch() to get tweets
URL: https://x.com/i/api/graphql/{QUERY_ID}/UserTweets?variables={...}
NOTE: Must append /UserTweets after QUERY_ID
→ Extract from response: instructions → TimelineAddEntries → tweet_results → legacy
→ Paginate via cursor-bottom
→ 15-min delay between pages (PAGE_DELAY=900)
→ On 429: wait 30 min (RATE_WAIT=1800), max 60 retries (MAX_RATE_LIMITS=60)
screenName not screen_namedata.user.result and data.user_result_by_screen_name.result$env:PYTHONIOENCODING="utf-8"
python -u scripts/scrape_x_v9.py --username target --batch --pages 50 --seed data.json --auto-merge data.json
Playwright launch → inject cookies → open profile page /username
→ Verify login
→ Jump to checkpoint scroll_y (if resuming)
→ Loop:
→ window.scrollTo(0, document.body.scrollHeight)
→ wait 3s → extract article[data-testid="tweet"]
→ If blank: scroll up 2000px → retry (3x)
→ Dedup by ID → save new tweets
→ If scroll_y unchanged for MAX_EMPTY=50 rounds → bottom reached
→ Merge into seed file → cleanup checkpoint
$env:PYTHONIOENCODING="utf-8"
python -u scripts/scrape_x_scroll.py --username target --seed existing_data.json
| # | 问题 Issue | 修复 Fix | 适用脚本 |
|---|---|---|---|
| 1 | Login check false-positive on "Login" string | Only check "Sign in" in content | All |
| 2 | Search page "Something went wrong" | Split into small chunks (14 days) | auto |
| 3 | Scroll blank after big jump | Scroll up 2000px to trigger lazy load | auto, scroll |
| 4 | Fast-skip exits early when all tweets known | Don't increment empty counter when DOM has content | scroll |
| 5 | Time field name mismatch | timestamp (scroll/auto) vs created_at (API) | merge |
| 6 | GraphQL API ~900 tweet hard limit | Use search/scroll for full history | v9 |
| 7 | Query ID + missing /UserTweets suffix | Append operation name: {QID}/UserTweets | v9 |
with open(seed_file) as f:
raw = json.load(f)
target = raw.get('tweets', raw) if isinstance(raw, dict) else raw
d = {t['id']: t for t in target if 'id' in t}
for t in new_tweets:
d[t['id']] = t
merged = list(d.values())
# Write back
x-scraper-comprehensive/
├── SKILL.md # 主配置 Main config
├── README.md # 双语说明 Bilingual readme
├── README_EN.md # 英文版 English version
├── scripts/
│ ├── scrape_x_auto.py # 自动搜索 Auto-search
│ ├── scrape_x_v9.py # GraphQL API
│ └── scrape_x_scroll.py # DOM 滚动 DOM scroll
└── references/
└── workflow.md # 完整工作流 Full workflow