X ユーザーの全投稿と返信をダウンロードする方法
X ユーザーの履歴を export する作業は、「大きな count を指定して一度で返ることを期待する」だけでは安定しません。大きな export には cursor loop、raw data の保存先、重複を避けるための簡単な管理が必要です。
このガイドでは TwexAPI のページング endpoint、POST /twitter/tweets-replies/page を使い、ユーザー自身の投稿と返信を 1 ページずつ export します。アーカイブ、調査、コンプライアンス確認、定期的なデータジョブでは、単発の count request より扱いやすい方法です。
エクスポート方針
安全な export では、2 種類のファイルを残します。
- Raw JSONL:1 行に 1 tweet object を保存する source of truth。
- CSV projection:スプレッドシート、BI、アノテーションに使う小さな表。
両方を残してください。CSV は確認しやすい一方、raw JSON には media、quote、reply metadata、author 情報など、後から必要になるフィールドが残ります。
API エンドポイント
POST https://api.twexapi.io/twitter/tweets-replies/page
Authorization: Bearer <your_token>
Content-Type: application/jsonリクエスト body:
| フィールド | 必須 | 説明 |
|---|---|---|
screen_name | はい | 対象 X username。@ はあってもなくても可 |
next_cursor | いいえ | 前ページの response で返った cursor |
各 response には、現在ページの data と pagination metadata が含まれます。
| フィールド | 意味 |
|---|---|
data | 現在ページの投稿と返信 |
has_next_page | 次ページがあるか |
next_cursor | 次の request に渡す cursor |
最初の request には next_cursor を入れません。
curl --request POST \
--url https://api.twexapi.io/twitter/tweets-replies/page \
--header 'Authorization: Bearer <your_token>' \
--header 'Content-Type: application/json' \
--data '{
"screen_name": "elonmusk"
}'続きのページでは、返ってきた cursor を送ります。
{
"screen_name": "elonmusk",
"next_cursor": "1234567890"
}Python Cursor エクスポート
次の script は raw result を tweets-replies.jsonl に書き込みます。tweet_id で重複を除き、has_next_page が false になったら停止します。cursor loop を明示しているため、resume や debug もしやすくなります。
1import json
2import os
3import time
4
5import requests
6
7API_URL = "https://api.twexapi.io/twitter/tweets-replies/page"
8TOKEN = os.environ["TWEXAPI_TOKEN"]
9SCREEN_NAME = "elonmusk"
10OUTPUT_PATH = "tweets-replies.jsonl"
11
12def fetch_page(next_cursor=None):
13 body = {"screen_name": SCREEN_NAME}
14 if next_cursor:
15 body["next_cursor"] = next_cursor
16
17 response = requests.post(
18 API_URL,
19 headers={
20 "Authorization": f"Bearer {TOKEN}",
21 "Content-Type": "application/json",
22 },
23 json=body,
24 timeout=45,
25 )
26 response.raise_for_status()
27 payload = response.json()
28 if payload.get("code") not in (None, 200):
29 raise RuntimeError(payload.get("msg", "TwexAPI request failed"))
30 return payload
31
32seen_ids = set()
33next_cursor = None
34page_count = 0
35saved_count = 0
36
37with open(OUTPUT_PATH, "w", encoding="utf-8") as output:
38 while True:
39 payload = fetch_page(next_cursor)
40 page_count += 1
41
42 items = payload.get("data") or []
43 for tweet in items:
44 if not tweet:
45 continue
46
47 tweet_id = tweet.get("tweet_id") or tweet.get("id")
48 if not tweet_id or tweet_id in seen_ids:
49 continue
50
51 seen_ids.add(tweet_id)
52 output.write(json.dumps(tweet, ensure_ascii=False) + "\n")
53 saved_count += 1
54
55 print(f"page={page_count} fetched={len(items)} saved={saved_count}")
56
57 if not payload.get("has_next_page") or not payload.get("next_cursor"):
58 break
59
60 next_cursor = payload["next_cursor"]
61 time.sleep(0.5)
62
63print(f"Finished: {saved_count} unique tweets and replies saved to {OUTPUT_PATH}")実行方法:
TWEXAPI_TOKEN="your_twexapi_bearer_token" python export-tweets-replies.pyCSV Projection を作る
raw JSONL を保存した後、レビューしやすい小さな CSV を作ります。
1import csv
2import json
3
4INPUT_PATH = "tweets-replies.jsonl"
5OUTPUT_PATH = "tweets-replies.csv"
6
7fields = [
8 "tweet_id",
9 "created_at_datetime",
10 "text",
11 "lang",
12 "favorite_count",
13 "retweet_count",
14 "reply_count",
15 "quote_count",
16 "view_count",
17 "in_reply_to",
18 "in_reply_to_screen_name",
19 "is_quote_status",
20]
21
22with open(INPUT_PATH, encoding="utf-8") as source, open(
23 OUTPUT_PATH, "w", encoding="utf-8", newline=""
24) as target:
25 writer = csv.DictWriter(target, fieldnames=fields)
26 writer.writeheader()
27
28 for line in source:
29 tweet = json.loads(line)
30 row = {field: tweet.get(field) for field in fields}
31 row["tweet_id"] = tweet.get("tweet_id") or tweet.get("id")
32 row["text"] = tweet.get("full_text") or tweet.get("text") or ""
33 writer.writerow(row)
34
35print(f"Wrote {OUTPUT_PATH}")保存すべきフィールド
調査や監査で使う場合、text だけを残さないでください。後から行の意味を説明できる context が必要です。
| フィールド | 理由 |
|---|---|
tweet_id | 安定した deduplication と citation key |
created_at / created_at_datetime | timeline ordering と time-window analysis |
text / full_text | 人が読める本文 |
in_reply_to, in_reply_to_screen_name | reply と standalone post の区別 |
favorite_count, retweet_count, reply_count, quote_count | engagement review |
media, urls, hashtags, cashtags | content enrichment |
user | export 時点の author snapshot |
本番運用の注意点
- ページが返るたびに保存し、全 export 終了まで待たない。
- resume したい場合は、最後に成功した
next_cursorを保存する。 - retry や cursor 変化で重複しないよう、
tweet_idで deduplicate する。 - 今日 CSV だけ使う場合でも raw JSONL は残す。
- export time、対象
screen_name、script version を output と一緒に記録する。 - 定期 export では、保存済み
tweet_idと比較して新規行を判定する。
Count Endpoint と Page Endpoint
TwexAPI には GET /twitter/{screen_name}/tweets-replies/{count} もあります。小規模な一回限りの取得で、必要件数が分かっている場合には便利です。
本格的な export では POST /twitter/tweets-replies/page を使ってください。Cursor pagination により進捗が見えやすく、失敗時の損失が小さく、保存と retry を制御しやすくなります。