Skip to main content

X-Timeline Scraper

A Python client to scrape tweets from X (formerly Twitter) timelines using a cURL command.


Supported versions pypi downloads License Code style: black

Introduction

This project provides a Python client to scrape tweets from X (formerly Twitter) timelines using a cURL command. It leverages asynchronous programming for efficient data retrieval and includes features for parsing tweet data.

Table of Contents 🗂

Installation ⚙️

To install the X-Timeline Scraper, you can use pip:

pip install xtimeline

Usage ⌨️

The client polls your Following timeline as your own logged-in X session. It needs two cookies from that session:

  1. Log into x.com in your browser.
  2. Open DevTools (F12) → Application (Chrome/Edge) or Storage (Firefox) → Cookies → https://x.com.
  3. Copy the values of auth_token and ct0.

Treat these like a password: they grant full access to your account. They stay valid until you log out of that browser session; client.last_status becomes 401/403 once they expire.

import asyncio
import os
from xclient import XTimelineClient

async def main():
    async with XTimelineClient.from_cookies(
        os.environ["X_AUTH_TOKEN"], os.environ["X_CT0"]
    ) as xc:
        for t in await xc.fetch_tweets():
            print(t.to_markdown())

asyncio.run(main())

If X rotates the timeline's GraphQL query id before a new release ships, pass the current one (from the HomeLatestTimeline request URL in DevTools → Network) via from_cookies(..., query_id="...").

Alternative: a captured cURL command

Instead of cookies you can pass a full captured request, which also picks up any header or feature flag changes X makes. The capture steps are in curl_example.txt; the examples below use this form.

Fetching tweets once

import asyncio
from xclient import XTimelineClient

async def main():
    async with XTimelineClient("curl.txt") as xc:
        tweets = await xc.fetch_tweets()
        for t in tweets:
            print(t.to_markdown())

asyncio.run(main())

Streaming new tweets

import asyncio
from xclient import XTimelineClient

async def main():
    async with XTimelineClient(
        "curl.txt", persist_last_id_path="state/last_id.txt"
    ) as xc:
        async for t in xc.stream():
            print(t.to_markdown())

asyncio.run(main())

By default, stream() now polls every ~30 seconds with built-in jitter (fuzzy interval) so requests do not follow an identical cadence.

# 30s base with +-20% jitter (default)
async for t in xc.stream():
    process(t)

# Custom base interval and jitter
async for t in xc.stream(interval_s=45.0, jitter_ratio=0.15):
    process(t)

# Disable jitter if you need a fixed cadence
async for t in xc.stream(interval_s=30.0, jitter_ratio=0.0):
    process(t)

Fetch modes

Both fetch_tweets() and stream() accept a mode parameter that controls which tweets are returned:

Mode Behaviour
"new_only" (default) Only returns tweets newer than the last-seen cursor. The cursor advances so the same tweet is never emitted twice.
"all" Returns every tweet in each response. Nothing is filtered. Useful when your own store (e.g. a SQLite database) handles deduplication.
"with_updates" Returns new tweets and re-emits previously seen tweets whenever their metrics change (likes, retweets, views). Re-emitted tweets have is_update=True.
# Hand all deduplication to your own store
async for t in xc.stream(mode="all"):
    upsert_to_db(t)

# Only new tweets, cursor persisted across restarts
async with XTimelineClient(
    "curl.txt", persist_last_id_path="state/last_id.txt"
) as xc:
    async for t in xc.stream(mode="new_only"):
        process(t)

# New tweets + engagement updates
async for t in xc.stream(mode="with_updates"):
    if t.is_update:
        update_metrics_in_db(t)
    else:
        insert_new_tweet(t)

Tweet fields

Each Tweet object contains:

Field Type Description
id int Tweet ID
text str Full text, HTML entities unescaped, t.co links expanded, long-form tweets supported
user_name str Display name
user_screen_name str @handle (without @)
user_img str Profile image URL
url str Canonical tweet URL
created_at str Post time in ISO 8601 format (2026-04-01T19:15:49Z)
likes int Like count
retweets int Retweet count
replies int Reply count
views int View count
media list[MediaItem] Attached photos/videos
tickers list[str] Uppercased $TICKER symbols
hashtags list[str] Uppercased hashtags
title str Human-readable summary, e.g. "TraderSZ retweeted Jelle"
is_update bool True if this tweet was seen in a previous fetch this session

Citation ✍️

If you use this project in your research, please cite as follows:

@misc{project_name,
  author  = {Stephan Akkerman},
  title   = {X-Timeline Scraper},
  year    = {2025},
  publisher = {GitHub},
  journal = {GitHub repository},
  howpublished = {\url{https://github.com/StephanAkkerman/x-timeline-scraper}}
}

Contributing 🛠

Contributions are welcome! If you have a feature request, bug report, or proposal for code refactoring, please feel free to open an issue on GitHub. We appreciate your help in improving this project.
https://github.com/StephanAkkerman/x-timeline-scraper/graphs/contributors

License 📜

This project is licensed under the MIT License. See the LICENSE file for details.

Metadata

Release files for xtimeline 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xtimeline 0.2.0
File Size Uploaded
xtimeline-0.2.0.tar.gz 25.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for xtimeline 0.2.0
File Interpreter ABI Platform
xtimeline-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 41.6 kB

Release files / xtimeline-0.2.0.tar.gz

Download URL xtimeline-0.2.0.tar.gz
Size 25.1 kB
Tags Source
SHA-256 checksum
How to use checksums
791298ed9aba9bbd599632f779fd6e940941e183ee6cb65659bd9e753735d6e8
BLAKE2b-256 checksum
How to use checksums
7e528831485f350cdfd5e29d85b83071858fffa985e1cdcbaa538ae4e039cf4a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Release files / xtimeline-0.2.0-py3-none-any.whl

Download URL xtimeline-0.2.0-py3-none-any.whl
Size 16.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2698dd27c78c6d3ff22655f1ec83294f99410e341b1d4786c157d624df958643
BLAKE2b-256 checksum
How to use checksums
43e0a23c8a761bc877f205eb791664ba231f70be904f9e9d950b5293af8a699b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

2 release files

This release

0.2.0 This release

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page