Exporting Twitter Data to CSV: What You Can Get and the Traps in the File
Exporting Twitter Data to CSV: What You Can Get and the Traps in the File
Two separate problems get called "exporting Twitter data", and people usually underestimate the second one.
Getting the data out. Which route, and how complete it can be.
Writing a file that opens correctly. ★This is where exports actually break★ — and they break silently, producing a file that looks fine until someone opens it in Excel and the columns are wrong.
Both are covered here. The second half is the part almost nobody writes about.
What you can actually export, by target
Your own posts — complete
Settings → Your account → Download an archive of your data.
This is the only genuinely complete export that exists. Every post, reply, DM, and like since you signed up. Wait a few hours to a couple of days, get a ZIP containing browsable HTML and raw JSON.
★If you're exporting your own history, stop reading and request this.★ Nothing else matches it for completeness, and it's free.
Its limitation: it's a snapshot. Tomorrow's posts need a fresh request.
For anyone else's data, there's no equivalent — that's the gap an API fills, with the completeness caveats below.
Someone else's posts — partial, and bounded
No archive request exists for accounts you don't own. You page back through their timeline and export what you can reach.
⚠️ You cannot export someone's complete history. X's index doesn't reach back indefinitely, and neither does anything reading it. Expect months, not years — see what's realistically reachable.
Follower and following lists — complete but expensive
Paginated, so a large account is many requests. A 500,000-follower account is 5,000 pages. That's fine technically and matters commercially — paging is what drives cost.
Search results — bounded by the result cap
⚠️ A search export is limited by the per-query cap, not by how many posts match. Exporting a six-month range in one query gives you a fraction of it. Chunk the range or your export is silently incomplete.
Writing the file correctly
Here's the part that bites people. These aren't theoretical — they're the two bugs that make an export look successful and be wrong.
★Trap 1: formula injection★
A CSV cell beginning with =, +, -, or @ is executed as a formula by Excel and most spreadsheet software.
Tweet text is arbitrary user input. Someone can post a display name or text starting with = and your export becomes a spreadsheet that runs it on open. ⚠️ A crypto token named =HYPERLINK("http://evil","click") in your export means whoever opens the file gets a live link that looks like your own data.
The fix is one line: prefix any cell starting with those characters with a single quote.
★Trap 2: encoding★
Write a CSV as plain UTF-8 and open it in Excel on Windows, and every non-English character is garbage. Chinese, Japanese, Arabic, accented European text — all mangled.
The fix: write a UTF-8 BOM. Excel uses it to detect encoding. Without it, Excel guesses the system codepage and guesses wrong.
★Both bugs share a property: the export completes successfully.★ (The fetching half is one paged call; this half is entirely yours.) Nothing errors. You find out when someone opens the file, which is usually after you've sent it to them.
Doing it properly
import csv, requests
BASE = "https://api.socialapi.tech"
KEY = "your_api_key"
HDRS = {"X-API-Key": KEY}
DANGEROUS = ("=", "+", "-", "@")
def safe(value):
"""Neutralise formula injection. Tweet text is untrusted input."""
s = "" if value is None else str(value)
return "'" + s if s.startswith(DANGEROUS) else s
def export_posts(username, path, pages=10):
rows, cursor = [], None
for _ in range(pages):
params = {"username": username, "limit": 100}
if cursor:
params["cursor"] = cursor
r = requests.get(f"{BASE}/v1/user/last_tweets",
params=params, headers=HDRS, timeout=60)
r.raise_for_status()
body = r.json()
batch = body["data"]
if not batch:
break
rows.extend(batch)
cursor = body.get("meta", {}).get("next_cursor")
if not cursor:
break # ★only correct stop condition★
# utf-8-sig writes the BOM Excel needs
with open(path, "w", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f, quoting=csv.QUOTE_ALL)
w.writerow(["id","created_at","text","likes","reposts",
"replies","quotes","views","url"])
for p in rows:
w.writerow([
safe(p["id"]), safe(p["created_at"]), safe(p["text"]),
p.get("like_count",0), p.get("retweet_count",0),
p.get("reply_count",0), p.get("quote_count",0),
p.get("view_count") or 0,
safe(f"https://x.com/{username}/status/{p['id']}"),
])
return len(rows)
print(f"exported {export_posts('nasa', 'nasa.csv')} posts")
Three things doing real work here:
utf-8-sigwrites the BOM. One parameter, and it's the difference between readable and garbage.QUOTE_ALL— tweet text contains commas, quotes, and newlines constantly. Unquoted fields tear the table apart at the first comma.safe()on every text field, applied to the values, not the headers.
⚠️ Don't skip QUOTE_ALL thinking your data is clean. Post text is arbitrary — a single tweet containing a comma splits into two columns and shifts every field after it on that row.
Choosing a format
| Format | Good for | Watch out for |
|---|---|---|
| CSV | Spreadsheets, sharing with non-technical people | The two traps above. No nested data. |
| JSON | Feeding another system, preserving structure | Not spreadsheet-friendly |
| JSONL | Large exports, appending over time | Same, plus one object per line |
| Excel (.xlsx) | Rich formatting | Needs a library; ★immune to formula injection if written as text★ |
★For anything recurring, JSONL beats CSV★ — it appends cleanly, survives interruption, and doesn't have the escaping problems. Convert to CSV only when a person needs to read it.
Questions people ask
How do I export my tweets? Settings → Your account → Download an archive of your data. It's complete and free, and it's the only complete option.
How do I download all tweets from a user? Page through their timeline and write the results out. You'll get what the index still holds, which is months rather than years.
Can I export tweets to Excel?
Yes — write CSV with a UTF-8 BOM, or write .xlsx directly. Without the BOM, non-English text will be garbled.
How do I export my Twitter followers? Page through the follower list and write each profile out. Large accounts take many requests.
Can I export a Twitter list? List members are readable like any other list of accounts, so the same paging-and-write approach applies.
How do I export Twitter search results? Run the search, page through, and write out — but chunk your date range or the cap silently truncates the export.
What format should I export in? CSV for people, JSON or JSONL for systems. JSONL for anything appended over time.
Why is my CSV showing weird characters?
Missing UTF-8 BOM. Write with utf-8-sig and Excel will read it correctly.
Why did my spreadsheet run a formula from a tweet?
Formula injection — a cell starting with =, +, -, or @ executes. Prefix such cells with a single quote when writing.
Why are my CSV columns misaligned?
Unquoted fields containing commas. Use QUOTE_ALL; post text contains commas constantly.
How many tweets can I export at once? Paginated, so it's a question of how many pages you're willing to fetch rather than a hard single-request limit.
Can I export deleted tweets? Only if you captured them before deletion — see what's recoverable.
How do I export Twitter analytics? Your own, from the analytics interface. For public engagement across accounts, compute it from exported posts — how the metrics work.
Can I schedule automatic exports? Not through X's interface. Programmatically, run your export on a schedule and append.
How do I export DMs? Only through your own data archive. They're private and not accessible any other way.
What's in the Twitter data archive? Posts, replies, DMs, likes, and account history — as browsable HTML plus raw JSON. The JSON is the useful part.
How long does the archive take? Usually a few hours, sometimes a couple of days. X emails you when it's ready.
Can I export someone else's followers? For public accounts, yes — the list is public and paginated.
How do I export tweets by date range?
Add since: and until:, and chunk the range into windows small enough that no query hits the cap.
Why is my export missing tweets? Two usual causes: stopping paging on a short page instead of an empty cursor, or a date range that hit the result cap. Both truncate silently.
Can I export in bulk for many accounts? Iterate over accounts. Cost scales with total pages, not with the number of accounts.
Is exporting Twitter data allowed? Reading public data is broadly practised. Redistribution is restricted under X's terms regardless of how you obtained it — see the compliance discussion.
How do I export a Twitter following list? Page the followings endpoint and write each account to CSV. ★A large list is many pages, not one call★ — plan for cursoring.
How do I export my followers list? Same pattern against the followers endpoint. The count you see on the profile is the number of rows to expect, minus accounts deleted since.
Can I export someone else's follower list? Public accounts, yes — the list is public. Protected accounts, no.
How do I export Twitter posts? Page an account's timeline and serialise. ★Archive depth is the real constraint★ — how far back you can go.
How do I scrape data from Twitter? Either drive a browser and maintain it, or call an API. The trade-off in full.
Is there a Twitter scraper API? That is effectively what a read API is — ★the difference is that one breaks when the page markup changes and the other does not★.
What is the best way to export a large account? Cursor through, checkpoint as you go, and resume from the cursor rather than restarting on failure.
Can I export to CSV directly? Yes — the response is JSON, and CSV is a local transform. ★Add a UTF-8 BOM if the file will be opened in Excel★, or non-Latin text will render as mojibake.
Should I be careful with fields starting with = in CSV?
★Yes — prefix them with a quote.★ Display names are arbitrary text, and a name beginning = executes as a formula when opened in a spreadsheet.
How do I export search results? Page the search endpoint the same way. Search reaches back less far than a user timeline does.
Can I schedule recurring exports? Yes, and for anything you might lose, you should — deletion is not recoverable after the fact.
How big is a full account export? Roughly a few hundred bytes per post as JSON. Tens of thousands of posts is still a small file.
Does export include media files? URLs, not the binaries. Downloading the files is a separate step you control.
Can I export engagement counts? Yes — they come on each post, ★though they are a snapshot at fetch time, not a history★.
How do I export data from a deleted account? You cannot. Deletion removes the account and its posts — what survives.
What format should I archive in? Newline-delimited JSON. It appends cheaply, survives partial writes, and converts to anything later.
If you're building an export feature
The data-fetching part is a paging loop. ★The part that determines whether people trust your export is the file-writing★ — BOM, quoting, and injection escaping, all three of which fail silently.
One more that catches teams: paginate until the cursor is empty, never until a page looks short. A short page mid-list is normal, and stopping there produces an export that's missing data with no error to explain it.
Our API covers the fetching — cursor-based paging over timelines, followers, and search, with full engagement counts on every post so your export doesn't need a second lookup per row. Read-only, flat price per call, no rate limit of your own to manage.
Related reading: what's reachable in an account's history · why date ranges truncate · exporting follower lists properly · what paging costs.