agentsclimarketplace

Twitter scraper

Skill kohoj/skills/twitter-scraper

X/Twitter CDP Tweet Scraper — Scrapes a user's complete tweet history via browser CDP protocol. Use when the user needs to fully and accurately collect all tweets from a specific Twitter/X user. Applicable scenarios: collecting all tweets from a user for a given year, exporting a Twitter timeline, bulk-fetching tweet data for analysis. Preferred over x-tweet-fetcher (Nitter-based) when full long-form tweets, precise timestamps, or higher completeness is required. Supports Arc, Chrome, Edge, Brave, and all Chromium-based browsers. Requires the user to be logged into Twitter in the browser.From its SKILL.md

Install
npx -y skills add kohoj/skills --skill twitter-scraper

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 8 commands, including `python3 <skill-path>/scripts/cdp_tweet_fetcher.py <username> --since 2026-02 [--until 2026-02-28] [--output-dir DIR]` and 7 more.

SKILL.md

7.4 KB, ~1.8k tokens by cl100k_base, as published. Nobody here has run it

X/Twitter CDP Tweet Scraper v2

Extracts authentication from a Chromium browser via CDP, then calls the Twitter GraphQL API directly with httpx for high-speed bulk tweet collection. Outputs JSON + Markdown.

Quick Start

# By date range (recommended)
python3 <skill-path>/scripts/cdp_tweet_fetcher.py <username> --since 2026-02 [--until 2026-02-28] [--output-dir DIR]

# By year (shorthand)
python3 <skill-path>/scripts/cdp_tweet_fetcher.py <username> --year 2026 [--output-dir DIR]

# No date specified -> defaults to current year to date
python3 <skill-path>/scripts/cdp_tweet_fetcher.py <username> [--output-dir DIR]

Arguments:

  • username (required): Twitter username (without @)
  • --since: Start date (inclusive). Accepts YYYY-MM-DD, YYYY-MM, YYYY
  • --until: End date (inclusive), defaults to today
  • --year: Target year (shorthand for --since YYYY-01-01)
  • --output-dir: Output directory, defaults to current working directory
  • --page-delay: Seconds between API pages, default 1.0
  • --max-pages: Maximum pages to fetch, default 200
  • --cdp-port: CDP debugging port, default 9222

Output files:

  • {username}_tweets_{YYYYMMDD}_{YYYYMMDD}.json — Full structured data
  • {username}_tweets_{YYYYMMDD}_{YYYYMMDD}.md — Human-readable Markdown report

Prerequisites

  1. Chromium-based browser (Chrome / Edge / Brave / Arc / Chromium) installed and logged in to Twitter/X
  2. Python dependencies: pip install playwright httpx && playwright install chromium
  3. Browser must be launched with CDP enabled — the script auto-detects your OS and shows the correct launch command

Launch browser with CDP (pick your browser):

macOS:

# Chrome
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
# Edge
/Applications/Microsoft\ Edge.app/Contents/MacOS/Microsoft\ Edge --remote-debugging-port=9222
# Brave
/Applications/Brave\ Browser.app/Contents/MacOS/Brave\ Browser --remote-debugging-port=9222
# Arc
/Applications/Arc.app/Contents/MacOS/Arc --remote-debugging-port=9222

Linux:

google-chrome --remote-debugging-port=9222
# or: chromium-browser / microsoft-edge / brave-browser

Windows (PowerShell):

& "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
# or: msedge.exe / brave.exe in their respective paths

Architecture — Why It's Fast

v2 minimizes browser interaction: authentication is extracted once (~5 s), then all API calls go through httpx directly.

ChangeEffect
Browser only used for cookie + query ID extraction (one-time)Init drops from ~18 s to ~5 s
httpx direct HTTP requests (no CDP round-trips)Each API call 3-5x faster
Single endpoint: UserTweetsAndReplies (superset)Half the pagination
count=40 (was 20)Half the pagination again
page-delay 1.0 s (was 2.5 s)60% less wait per page

Execution Flow

  1. Connect to browser: CDP connection, extract cookies and CSRF token
  2. Discover API: Parse JS bundles for GraphQL query IDs (no page navigation)
  3. HTTP client: Create httpx client with extracted auth
  4. Resolve user ID: Via UserByScreenName API
  5. Bulk fetch: UserTweetsAndReplies endpoint, 40 tweets/page, direct HTTP
  6. Output: JSON + Markdown files

Output Schema

Each tweet contains:

{
  "tweet_id": "123456789",
  "text": "Full tweet text...",
  "datetime": "2026-01-15T10:30:00+00:00",
  "url": "https://x.com/user/status/123456789",
  "author": "username",
  "is_reply": false,
  "reply_to": null,
  "reply_to_tweet_id": null,
  "is_retweet": false,
  "retweet_of": null,
  "is_quote": false,
  "quoted_tweet_url": null,
  "likes": 100,
  "retweets": 20,
  "replies": 5,
  "bookmarks": 30,
  "views": 10000,
  "media": ["https://pbs.twimg.com/..."],
  "links": ["https://example.com/..."]
}

Fetching Twitter Articles (Long-Form Posts)

Links in tweets matching x.com/i/article/{article_id} are Twitter Articles (long-form posts). Article content is NOT in the tweet API response and requires additional steps to extract.

Usage

python3 <skill-path>/scripts/fetch_articles.py <tweets_json> [--output-dir DIR] [--cdp-port 9222]
  • tweets_json: Path to a JSON file previously output by cdp_tweet_fetcher.py
  • --output-dir: Directory for article Markdown files (default: ./articles)
  • --cdp-port: CDP debug port (default: 9222)

Technical Details

  1. API endpoint: TweetResultByRestId (GET), queried with the article's associated tweet_id
  2. Critical parameter: fieldToggles: {"withArticleRichContentState": true, "withArticlePlainText": false}
  3. Data location: data.tweetResult.result.article.article_results.result.content_state
  4. Content format: Draft.js — blocks (paragraphs / headings / lists / blockquotes / code blocks / atomic) + entityMap (links / media references)
  5. Type pitfall: entityMap is sometimes a dict (keyed by string index) and sometimes a list (indexed by position) — must handle both

Browser Request Parameters (Verified 2026-02)

{
  "variables": {
    "tweetId": "<tweet_id>",
    "includePromotedContent": true,
    "withBirdwatchNotes": true,
    "withVoice": true,
    "withCommunity": true
  },
  "fieldToggles": {
    "withArticleRichContentState": true,
    "withArticlePlainText": false
  }
}

Lessons Learned

DateLessonAction
2026-02-27Twitter timeline is reverse-chronological; if all tweets on a page are before the target date, subsequent pages are even olderAdded early-stop condition to script
2026-02-27DOM validation wastes time when API phase yields 0 tweets in target rangeSkip DOM validation directly
2026-02-27DOM scrolling has no date filter, collects many irrelevant IDsSnowflake ID date filtering
2026-02-27v1 architecture bottleneck: page.evaluate relay is slow, dual endpoints redundant, count=20 conservative, DOM validation heavyv2 rewrite: httpx direct, single endpoint, count=40, DOM disabled by default
2026-02-28Twitter Article (long-form) content is NOT in the tweet text; requires separate extractionAdded fetch_articles.py script
2026-02-28TweetResultByRestId does not return article body by default; requires fieldToggles: {"withArticleRichContentState": true}Critical parameter documented
2026-02-28Article content is in Draft.js format (content_state.blocks + entityMap); entityMap can be either a list or a dictScript handles both types
2026-02-28Playwright CDP dialog event dismiss can throw ProtocolError: No dialog is showing and kill the Node processMust wrap in try/except
2026-02-28When facing unknown API behavior, trace the browser's actual request parameters first, then write scraping codeMethodology: observe before guessing

What ships with it: 2 files

57.5 KB alongside SKILL.md, 2 of them executable

scripts/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.