Lightweight X/Twitter timeline client (GraphQL via cURL or auth strategies)
Project description
X-Timeline Scraper
A Python client to scrape tweets from X (formerly Twitter) timelines using a cURL command.
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 ⌨️
To use the X-Timeline Scraper, you need to provide a cURL command that accesses the desired X timeline. The instructions can be found in curl_example.txt. Then, you can use the XTimelineClient class to fetch and parse tweets.
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.
License 📜
This project is licensed under the MIT License. See the LICENSE file for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xtimeline-0.1.6.tar.gz.
File metadata
- Download URL: xtimeline-0.1.6.tar.gz
- Upload date:
- Size: 21.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58cdc82d03cacfc3bf14e8b231753c1e5330aa51c124c86ffe6e530c2e280011
|
|
| MD5 |
3c542c8e2805fec5960b3df24a42d698
|
|
| BLAKE2b-256 |
dcef540ef7e9dc39a8c8c4405ef96b362ed6c5ce1326aa244994106c09b3153f
|
Provenance
The following attestation bundles were made for xtimeline-0.1.6.tar.gz:
Publisher:
publish.yml on StephanAkkerman/x-timeline-scraper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
xtimeline-0.1.6.tar.gz -
Subject digest:
58cdc82d03cacfc3bf14e8b231753c1e5330aa51c124c86ffe6e530c2e280011 - Sigstore transparency entry: 1682601222
- Sigstore integration time:
-
Permalink:
StephanAkkerman/x-timeline-scraper@99a54c9982f9e56392d39b69991780d3c525e01d -
Branch / Tag:
refs/tags/v0.1.6 - Owner: https://github.com/StephanAkkerman
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@99a54c9982f9e56392d39b69991780d3c525e01d -
Trigger Event:
release
-
Statement type:
File details
Details for the file xtimeline-0.1.6-py3-none-any.whl.
File metadata
- Download URL: xtimeline-0.1.6-py3-none-any.whl
- Upload date:
- Size: 14.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e991e5b43a81cef2634b3380f63b39ed3a1dab1ffcf78dac50174faa4296b085
|
|
| MD5 |
d07849ef3dde8d91f4c680f61e0fb178
|
|
| BLAKE2b-256 |
d20f7049dbfd71d9a1daf56da76fc0bf40e650d2e292cad4fe6c67c9ac683a35
|
Provenance
The following attestation bundles were made for xtimeline-0.1.6-py3-none-any.whl:
Publisher:
publish.yml on StephanAkkerman/x-timeline-scraper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
xtimeline-0.1.6-py3-none-any.whl -
Subject digest:
e991e5b43a81cef2634b3380f63b39ed3a1dab1ffcf78dac50174faa4296b085 - Sigstore transparency entry: 1682601241
- Sigstore integration time:
-
Permalink:
StephanAkkerman/x-timeline-scraper@99a54c9982f9e56392d39b69991780d3c525e01d -
Branch / Tag:
refs/tags/v0.1.6 - Owner: https://github.com/StephanAkkerman
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@99a54c9982f9e56392d39b69991780d3c525e01d -
Trigger Event:
release
-
Statement type: