问答
snscrape 抓推特一直报 blocked (404),有能用的替代吗?
简短回答
snscrape 的推特模块依赖未登录也能访问的页面,X 关掉之后项目里一直没有修复。可以改用 X 帖子搜索(每页约 20 条,$0.006)和 X 账号帖子列表(每页 $0.006)重建采集。1 万条帖子的研究语料约 $3.00。
论文代码里还在调用 snscrape twitter-search 的话,大概从 2023 年起就只会打印 blocked (404) 了。snscrape 的推特模块读的是 X 以前对未登录访客开放的页面,X 不再开放之后,GitHub 上那个 issue 一直开着,没有修复。好在后面的流程不用推倒重来:把采集这一步换成返回同类记录的接口,清洗和分析代码基本照用。
命令怎么替换
| snscrape | 换成 |
|---|---|
twitter-search "关键词" |
X 帖子搜索,搜索语法相同 |
twitter-user 用户名 |
X 账号帖子列表,从新到旧 |
| 用户资料字段 | X 账号信息 |
原来的字段这样对应:content 对应 text,date 对应 published_at,likeCount 对应 metrics.likes,retweetCount 对应 metrics.reposts,replyCount 对应 metrics.comments,viewCount 对应 metrics.views。引用数在 ext.quotes 里,需要加 view=full。
照 snscrape 的习惯写 JSONL
import json, os, requests
API = "https://api.okdata.online/v1"
H = {"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"}
def pages(path, params, limit):
cursor = None
for _ in range(limit):
body = requests.get(f"{API}/{path}", headers=H, timeout=60,
params={**params, **({"cursor": cursor} if cursor else {})}).json()
if body["code"] != "ok":
raise RuntimeError(f"{body['code']}: {body['message']}")
yield from body["data"]["items"]
if not body["page"]["has_more"]:
return
cursor = body["page"]["next_cursor"]
query = "碳中和 lang:zh since:2026-10-01 until:2026-11-01"
with open("tweets.jsonl", "w", encoding="utf-8") as out:
for post in pages("x/posts/search", {"query": query, "sort": "latest", "view": "full"}, limit=50):
out.write(json.dumps(post, ensure_ascii=False) + "\n")
预算
| 语料规模 | 页数 | 费用 |
|---|---|---|
| 1,000 条 | 50 | $0.30 |
| 1 万条 | 500 | $3.00 |
| 50 个账号,每个 5 页 | 250 | $1.50 |
每页算一次调用。失败不扣费,所以跑到一半崩了,也只付已经拿到的那些页。建议每个月的检索单独设一个页数上限,先跑一个月看看数据量,再决定要不要扩大范围。
方法部分最好写清楚的限制
- 拿到的是 X 搜索对公众返回的结果,既不是随机抽样,也不是全量。
- 受保护账号和已删除的帖子拿不到,这一点和 snscrape 时代一样。
- 关注网络和回复树没有对应接口。
其他路线
X 官方的全量历史搜索可以回溯到 2006 年,按量付费账号就能用,每读一条帖子 0.005 美元。审稿人会追问抽样方式的话,官方数据的来源最好交代。snscrape 本身对另外一些网站仍然能用。如果只缺几百条帖子,在 X 网页上手动搜索、逐条整理进表格也完全可行,只是费时间。不管走哪条路,都把具体的检索式和采集日期跟数据一起保存。类似的采集流程可以参考趋势研究方案。
更新于