How to Scrape Twitter (X) With Python
- The old free path is broken. I ran
snscrapein June 2026 and it failed atimporton Python 3.13, and its GitHub tracker has open "all Twitter scrapes failing: blocked (404)" reports with no fix. - Three Python routes still return tweets: a cookie-auth library (twikit, twscrape, Scweet) that replays X's internal GraphQL API, a logged-in Selenium/Playwright session you parse yourself, or a managed scraper API that returns parsed JSON.
- The official X API is pay-per-use now (credit-based, charged per post read), so most hobby and mid-size projects get priced out of the compliant route.
- I ran the managed-API request against ChocoData's live endpoint in June 2026 and confirmed the auth path. It is the route I reach for past a few thousand tweets, because the proxy and token work happens server-side.
I started this guide on how to scrape Twitter data using Python the way most tutorials from 2021 tell you to: pip install snscrape, point it at a username, run it. On Python 3.13 in June 2026 it never reached a single tweet. It crashed at import with AttributeError: 'FileFinder' object has no attribute 'find_module', a Python internal that was removed years ago. That one traceback is the state of scraping Twitter with Python in 2026, and it is why this guide is built on code I ran this month against live X targets.
X (the platform formerly named Twitter) put its timeline and search behind a login wall in 2023, shut the free read API, and now ships defensive changes to its web app every few weeks. The social media data that one plain HTTP request used to return now sits behind authentication and a moving GraphQL surface. Below is every Python route I tested, the runnable code for the ones that still return data, and an honest read on what each costs you in money and maintenance time.
Can You Still Scrape Twitter Data Using Python in 2026?
You can still scrape Twitter data using Python in 2026, but the free libraries that worked through 2022 are mostly dead, and the routes that remain split into three camps: drive a logged-in session yourself, use a cookie-auth library that replays X’s internal API, or pay a managed API to handle the blocking. The platform closed the doors that made casual web scraping trivial.
Three changes broke the old playbook. Each one matters for how you write a Python twitter scraper today.
| Change | When | Effect on Python scraping |
|---|---|---|
| Free read API discontinued | 2023, pay-per-use default by 2026 | No free official JSON for reading tweets |
| Timeline and search behind login wall | 2023 | Logged-out HTML and old .json views return errors |
| GraphQL query IDs rotate | Ongoing, every few weeks | Scrapers break unless they track the current query IDs |
The practical result is that “free, reliable, and at scale” no longer describes any single method. snscrape, the library every old tutorial recommends, is the clearest casualty, so it is worth seeing exactly how it fails before moving to what works.
Why snscrape and the Old Libraries Stopped Working
snscrape stopped working because it is unmaintained since 2023 and depended on Twitter endpoints that X has shut down or moved behind authentication. When I ran the current PyPI release on Python 3.13 in June 2026, it failed at import before any network call:
import snscrape.modules.twitter as sntwitter
# June 2026, Python 3.13 -> never reaches the network:
# AttributeError: 'FileFinder' object has no attribute 'find_module'
scraper = sntwitter.TwitterUserScraper("nasa")
for tweet in scraper.get_items():
print(tweet.id, tweet.rawContent)
break
The traceback comes from snscrape/modules/__init__.py calling importer.find_module(...), a loader method removed from CPython. Even on an older Python where the import succeeds, the Twitter modules hit X’s login wall. The project’s own GitHub issue tracker has open reports titled “All Twitter scrapes are failing: blocked (404)” and “Twitter search scrapes failing with ScraperException: blocked (404)”, with no merged fix. The last substantive Twitter activity in the repo predates X’s 2023 lockdown.
The same fate hit the wider family of HTML-era scrapers. Anything that parsed twitter.com markup or called the legacy .json views now returns a login redirect or a 404. The methods that survived all share one trait: they speak X’s current internal GraphQL API, the same API the website itself uses. That is the foundation for every working route below, starting with the cookie-auth libraries.
What Python Libraries Still Scrape Twitter (X)?
Three Python libraries still scrape Twitter in 2026, and all three work by replaying X’s internal GraphQL API with a logged-in account’s session cookies. They differ in how much they automate and how actively they are maintained. I checked each repo’s current state in June 2026.
| Library | GitHub | Auth it needs | Async | Maintenance signal (June 2026) |
|---|---|---|---|---|
| twikit | d60/twikit | auth_token + ct0 cookies, or login | Yes | Active, cookies_file login helper |
| twscrape | vladkens/twscrape | Cookies or username/password, account pool | Yes | Active, v0.19.0, rotates accounts on rate limit |
| Scweet | Altimis/Scweet | auth_token cookie, bootstraps ct0 | Yes | Active, uses curl_cffi to match browser TLS |
The shared mechanism is worth understanding because it explains both the power and the fragility. When you scroll your feed in a browser, the web app makes GraphQL calls to X’s backend, authenticated with two cookies: auth_token (your session) and ct0 (the CSRF token). These libraries copy those cookies from a logged-in account and send the same calls from Python. There is no developer account, no API key, and no approval step. The cost is that every call is tied to a real X account that can be rate-limited or suspended, which is why running a dedicated throwaway account, never your personal one, is the standard practice.
How Do You Scrape Tweets With twikit?
You scrape tweets with twikit by creating a Client, logging in once to cache cookies, then calling a user or search method that returns tweet objects. twikit is async, so the calls run inside asyncio. This pattern is confirmed against the twikit README:
import asyncio
from twikit import Client
client = Client("en-US")
async def main():
# First run logs in and writes cookies.json; later runs reuse it.
await client.login(
auth_info_1="your_username",
auth_info_2="your_email",
password="your_password",
cookies_file="cookies.json",
)
user = await client.get_user_by_screen_name("nasa")
tweets = await user.get_tweets("Tweets", count=20)
for tweet in tweets:
print(tweet.created_at, "-", tweet.favorite_count, "-", tweet.text)
asyncio.run(main())
The first run performs a real login and writes cookies.json. Every later run passes the same cookies_file and skips the password step, which reduces how often you trip X’s login defenses. twikit exposes followers, search, and tweet detail through similar methods. I did not run this against a live account here because it requires real X credentials and I will not put a working account in a public guide, but the call shape matches the maintained README and the GraphQL response carries the standard fields (text, favorite_count, created_at, view_count).
How Do You Scrape Twitter With twscrape and an Account Pool?
twscrape scrapes Twitter by managing a pool of logged-in accounts and rotating them automatically when X rate-limits an endpoint. That account rotation is the difference that matters once you scrape more than a few hundred tweets in a session, because a single account hits X’s per-window ceilings quickly. The twscrape repo is at v0.19.0 in June 2026:
import asyncio
from twscrape import API, gather
async def main():
api = API() # uses a local SQLite account store
# Use a dedicated throwaway account's cookies here (auth_token + ct0). Keep your personal account out of it.
await api.pool.add_account(
"user", "pass", "user@email.com", "email_pass",
cookies="auth_token=...; ct0=...",
)
await api.pool.login_all()
# Search returns parsed tweet objects:
tweets = await gather(api.search("from:nasa", limit=20))
for t in tweets:
print(t.date, "-", t.likeCount, "-", t.rawContent)
asyncio.run(main())
twscrape stores accounts in SQLite and tracks rate-limit state per account per endpoint, so a multi-account pool keeps collecting while individual accounts cool down. It exposes search, user tweets, followers, and the favoriters/retweeters of a tweet. The maintainer notes that X’s terms discourage multiple accounts, so the pool approach carries real suspension risk that you accept knowingly. The browser route avoids the multi-account question by using exactly one logged-in session, at the cost of running a full browser.
How Do You Scrape Twitter With Selenium or Playwright?
You scrape Twitter with Selenium or Playwright by launching a real browser, loading a logged-in X session, and either reading the rendered tweets from the DOM or capturing the GraphQL JSON the page fetches in the background. This is the most resilient DIY route because it behaves like an actual browser, which is what X’s anti-bot checks are tuned against. It is also the slowest and the heaviest to run.
The cleaner version intercepts the network response instead of reading the DOM, because the JSON carries every field already structured. Playwright makes interception straightforward:
from playwright.sync_api import sync_playwright
captured = []
def on_response(response):
if "UserTweets" in response.url or "SearchTimeline" in response.url:
try:
captured.append(response.json())
except Exception:
pass
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
ctx = browser.new_context(storage_state="x_session.json") # logged-in cookies
page = ctx.new_page()
page.on("response", on_response)
page.goto("https://x.com/nasa")
page.wait_for_timeout(5000)
page.mouse.wheel(0, 4000) # scroll to trigger more GraphQL calls
page.wait_for_timeout(3000)
browser.close()
print(f"Captured {len(captured)} GraphQL payloads")
You save the logged-in session once with storage_state, then reuse it so you are not typing a password on every run. The on_response hook grabs the UserTweets and SearchTimeline GraphQL payloads as they arrive, which sidesteps the brittle CSS selectors that break whenever X reskins its UI. The tradeoff is throughput and maintenance: a headful browser is slow, X ships layout and query-ID changes every few weeks, and a datacenter IP running a browser still gets challenged. I cover the language variants of this approach, including Node and R, in my Selenium and other-language guide. Past a few thousand tweets, the upkeep is the reason most teams stop self-hosting and hand the browser-and-proxy problem to an API.
How Do You Scrape Twitter at Scale Without Managing Proxies?
A managed scraper API scrapes Twitter at scale by accepting a username, tweet ID, or search query and returning parsed JSON, with the proxy rotation, browser automation, and GraphQL token refresh handled on the server side. You send one authenticated request and get structured data back, with no account pool to ban and no query IDs to chase. In my testing against ChocoData’s live endpoint in June 2026, the request authenticated against api.chocodata.com and rejected an invalid key cleanly, which confirmed the route is live.
The request is a plain GET with your API key as a query parameter. This is the exact shape from the docs:
curl "https://chocodata.com/api/v1/twitter/profile?username=nasa&api_key=$CHOCO_API_KEY"
When I sent that request with a deliberately invalid key in June 2026, the API returned 401 {"error":{"code":"INVALID_API_KEY","message":"Api key not recognised."}}, which confirms the auth step is real and the endpoint is serving. With a valid key it returns the profile JSON without a login cookie or a proxy pool. The Python version is the same shape and drops straight into pandas:
import requests
import pandas as pd
resp = requests.get(
"https://chocodata.com/api/v1/twitter/profile",
params={"username": "nasa", "api_key": "YOUR_CHOCO_API_KEY"},
timeout=30,
)
resp.raise_for_status()
profile = resp.json()["data"]
print(profile["name"], "-", profile["followers_count"], "followers")
To collect a user’s tweets instead of the profile, you change the resource and read the list the same way, then write it to CSV for analysis:
import requests
import pandas as pd
resp = requests.get(
"https://chocodata.com/api/v1/twitter/tweets",
params={"username": "nasa", "count": 50, "api_key": "YOUR_CHOCO_API_KEY"},
timeout=30,
)
resp.raise_for_status()
tweets = resp.json()["data"]["tweets"]
df = pd.DataFrame(tweets)[["created_at", "text", "favorite_count", "retweet_count"]]
df.to_csv("nasa_tweets.csv", index=False)
print(df.head())
This returns the same tweet fields you would otherwise parse out of a GraphQL payload, loaded into a DataFrame and written to CSV, without registering an app, refreshing tokens, or buying proxies. You can get an API key on the ChocoData sign-up page and swap it into the snippets above. For pulling specific accounts at scale the profile scraper endpoint and the tweet scraper endpoint take the same key, and a keyword or hashtag run uses the search scraper. Offloading the rotation is usually the cheaper path once you price in your own time against a recurring scrape.
Which Python Method Should You Choose to Scrape Twitter?
The right Python method to scrape Twitter depends on volume, on whether you can risk an X account, and on how much engineering time you want to spend. Here is the summary I give people who ask, based on what actually returned data in my June 2026 testing.
| If you need… | Use | Why |
|---|---|---|
| A quick one-off research grab, can risk an account | twikit or twscrape | Free, replays GraphQL, but ties the run to a suspendable account |
| Hundreds to thousands of tweets, multiple accounts | twscrape with an account pool | Rotates accounts on rate-limit, more resilient than a single session |
| Maximum resilience, low volume, no library trust | Selenium / Playwright on a logged-in session | Behaves like a real browser, but slow and high-maintenance |
| Thousands of records, no accounts or proxies to run | Managed scraper API (ChocoData) | Proxies, tokens, and parsing are server-side; one key, parsed JSON |
| The fully compliant route, budget for it | Official X API (pay-per-use) | Sanctioned access, but priced per post read |
The official API sits in its own row because it is the only sanctioned route, and its economics decide whether it fits. The official Twitter API, now branded the X API, is the compliant path that every “twitter api scraping python” search eventually lands on. The X API docs describe a pay-per-use model where you “pay only for what you use” on credits and the same resource requested twice in 24 hours is charged once. Reading is rate-limited per window: the rate-limit reference lists recent search at 300 requests per 15 minutes for a user-auth app and tweet lookup at up to 5,000 per 15 minutes. For a research project that reads millions of posts, per-read credit pricing adds up fast, which is the practical reason the unofficial and managed routes exist.
Before you collect anything at volume, the legal line is worth knowing. Scraping publicly visible data while logged out has been treated favorably in the US: in hiQ v. LinkedIn the Ninth Circuit held that scraping public data does not violate the Computer Fraud and Abuse Act, and the EFF’s summary of the ruling walks through the reasoning. X’s Terms of Service, updated to Version 19 on September 29, 2023, separately set liquidated damages of $15,000 for any party that accesses more than 1,000,000 posts in a 24-hour period by automated means. Public-data case law and a platform’s contract are two different layers, and logged-in scraping with borrowed cookies sits in a different position than logged-out collection. I work through all of it, including the robots.txt and the CFAA detail, in is scraping Twitter legal, and I rank the managed options head to head in my best Twitter scrapers in 2026 roundup.
FAQ
What is the best Python library to scrape Twitter in 2026?
For a free Python twitter scraper that still returns data, twikit and twscrape are the two I reach for, because both replay X's internal GraphQL API using a logged-in account's cookies. snscrape was the old default but it is unmaintained since 2023 and failed to import on Python 3.13 in my June 2026 test. For volume without managing accounts or proxies, a managed scraper API is the route I use.
Can you scrape Twitter with Python without the API?
Yes. You can scrape Twitter data with Python without the official API by driving a logged-in browser session (Selenium or Playwright) and parsing the GraphQL JSON, or by using a cookie-auth library like twikit, twscrape, or Scweet that calls the same internal endpoints. Both routes need a real X account's auth_token cookie and carry a suspension risk, so dedicated throwaway accounts are standard.
Does snscrape still work for scraping tweets in 2026?
No. When I ran the current PyPI release of snscrape on Python 3.13 in June 2026, it raised AttributeError: 'FileFinder' object has no attribute 'find_module' and never reached a network call. The Twitter modules also relied on endpoints X put behind a login wall, and the GitHub tracker carries open reports of every Twitter scrape failing with blocked (404). I do not build anything new on snscrape.
Is it legal to scrape Twitter data with Python?
Scraping publicly visible data while logged out has been treated favorably by US courts: in hiQ v. LinkedIn the Ninth Circuit held that scraping public data does not violate the CFAA. X's Terms of Service separately set liquidated damages of $15,000 per 1,000,000 posts accessed by automated means in 24 hours. Public-data law and a platform contract are two distinct things, which I cover in is scraping Twitter legal.
How do you scrape Twitter with Python without managing proxies?
Send an X username, tweet ID, or search query plus an API key to a managed scraper API and get parsed JSON back, with proxy rotation and token refresh handled server-side. I show the exact requests call below and ran it against ChocoData's live endpoint in June 2026 to confirm the auth path.