Node.js 版 Twitter API:2026 年指南,附可运行的 fetch 代码
Node.js 用内置的 fetch 就能读取 Twitter/X 数据:在请求头里带上 API 密钥,发送 HTTPS 请求,解析 JSON,再把返回的游标传回去获取下一页。官方 X API 每读取一条帖子收费 $0.005。
简短回答: Node.js 读取 X 数据不需要任何包,fetch 是内置的。官方 X API 即使通过 twitter-api-v2 调用,也需要开发者账号和预付额度:每条帖子 $0.005,从 2026 年 11 月 22 日起连作者一起读取是 $0.015。读取公开数据可以用 Sorsa API:一个 ApiKey 请求头,每次请求 20 条推文,每 1,000 条 $0.07 起;100 次免费请求可以获取 2,000 条。
- 接口
POST /search-tweets- 每页条数
- 每次请求 20 条推文
- 价格
- 每 1,000 条推文 $0.07 起
#2026 年哪个 npm 包能配合官方 X API 使用?
在 Node.js 里调用官方 X API 有两个包。社区客户端 twitter-api-v2 在 2026 年 10 月 25 日发布了 1.31.0 版,每周安装量约 33.6 万次。X 自己的 TypeScript SDK @xdevplatform/xdk 目前是 0.6.6。两者封装的都是同一个付费平台,所以都离不开开发者账号、bearer 令牌和预付额度。同样的请求用 Python 来写(对应的包是 Tweepy 和 xdk),见 Python 版 Twitter API指南。
额度是旧的 Node 教程最容易略过的部分。X 对新开发者没有免费额度,响应里返回的每个对象都要计费:每条帖子 $0.005,每个用户 $0.010,每月最多读取 300 万条帖子(见 X 的价格表(在新标签页打开))。从 2026 年 11 月 22 日起,通过扩展字段带出的作者和媒体与直接读取同价(见 X 在 10 月 23 日发布的通知(在新标签页打开))。这样一来,典型的 twitter-api-v2 搜索会为附带的每个作者付费:
import { TwitterApi } from "twitter-api-v2";
const client = new TwitterApi("YOUR_BEARER_TOKEN").readOnly;
const page = await client.v2.search("nodejs lang:en", {
max_results: 100,
expansions: ["author_id"],
});
// 100 posts: $0.50. From Nov 22, 2026, with authors: up to $1.50
它的分页器还有第二个陷阱:该库自己的文档说明,对分页器使用 for await 循环会一直取数据,直到结果取完或触发速率限制,而且每一页都要计费。所有费率、上限和日期都整理在 X API 价格一文里。
官方路线在第一次调用前需要什么
- 在 console.x.com 开通的开发者账号,用同意 X 开发者协议的 X 账号开通。
- 一个应用,它的 bearer 令牌 X 只显示一次。
- 提前购买的额度,按返回的每条帖子、每个用户和每个媒体对象扣费。
- 发帖、点赞或关注等操作需要的 OAuth 用户令牌。
读取公开帖子和用户资料用不到这些。自带密钥的 X 数据 API 走普通 HTTPS 就能回答,Node 的 fetch 就是完整的客户端。
#用一个 API 密钥在 Node.js 中获取推文
要从 Node.js 读取 X 数据,可以用 Sorsa API,一款 Twitter(X)API 与爬虫:用一个 ApiKey 请求头代替 OAuth,所有套餐(包括免费账号)都是每秒 20 次请求,搜索结果每 1,000 条推文 $0.07 起。每条推文都带着作者的完整资料,所以代码里不用设置扩展字段,也不用列字段清单。Sorsa API 从不要求登录 X,所以你自己的 X 账号不会有任何风险。
准备密钥并发送第一个请求
-
获取密钥。 注册后,在控制台复制你的 API 密钥。前 100 次请求免费,无需绑卡,永不过期。
-
检查 Node 版本并保存密钥。 运行
node -v:2026 年 11 月的 LTS 版本线是 22、24 和 26,三者都内置了fetch和--env-file。把密钥放进 git 忽略的.env文件,并且只在服务端代码里调用 API。Terminal node -v # v22 or newer echo 'SORSA_API_KEY=your_key' > .env echo '.env' >> .gitignore -
搜索推文。 把下面的代码保存为
first.mjs,然后运行node --env-file=.env first.mjs。它用 X 的搜索语法向POST /search-tweets(在新标签页打开) 发送一条查询,密钥放在ApiKey请求头里;.mjs扩展名会启用顶层await。const API = "https://api.sorsa.io/v3"; const KEY = process.env.SORSA_API_KEY ?? "YOUR_API_KEY"; const res = await fetch(`${API}/search-tweets`, { method: "POST", headers: { ApiKey: KEY, "Content-Type": "application/json" }, body: JSON.stringify({ query: "nodejs lang:en", order: "latest" }), }); if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).message}`); const { tweets, next_cursor } = await res.json(); for (const t of tweets) { console.log(t.user.username, t.full_text.slice(0, 60)); } console.log("next page:", next_cursor); -
读取这一页。 每个响应最多包含 20 条推文,以及用于获取下一页的
next_cursor。下面的值是按文档格式给出的演示数据。Response200 OK { "tweets": [ { "id": "1985432176540981234", "full_text": "Moved our nightly X report to one Node script.", "created_at": "2026-11-01T18:42:07Z", "lang": "en", "likes_count": 58, "retweet_count": 9, "reply_count": 4, "quote_count": 1, "view_count": 4920, "bookmark_count": 6, "is_reply": false, "user": { "id": "1592041833471827968", "username": "node_stream_digest", "display_name": "Node Stream Digest", "followers_count": 2310, "verified": false } } ], "next_cursor": "DAABCgABF7Y8kQ3wAAA" }
下面每个字段都直接来自响应;JavaScript 类型是 res.json() 给你的类型,ID 刻意保持为字符串。
| 字段 | JavaScript 类型 | 含义 |
|---|---|---|
id | string | 推文的 19 位 ID:千万不要转成 Number |
full_text | string | 帖子的完整文本 |
created_at | string | UTC 的 ISO 8601 格式:new Date(t.created_at) 可以直接解析 |
likes_count, retweet_count, reply_count, quote_count | number | 互动数 |
view_count, bookmark_count | number | 浏览数和收藏数 |
user | object | 作者的资料:username、followers_count 等 19 个字段 |
quoted_status, retweeted_status | object 或不存在 | 被引用或被转发的推文,结构相同 |
id- JavaScript 类型
- string
- 含义
- 推文的 19 位 ID:千万不要转成 Number
full_text- JavaScript 类型
- string
- 含义
- 帖子的完整文本
created_at- JavaScript 类型
- string
- 含义
- UTC 的 ISO 8601 格式:
new Date(t.created_at)可以直接解析
likes_count,retweet_count,reply_count,quote_count- JavaScript 类型
- number
- 含义
- 互动数
view_count,bookmark_count- JavaScript 类型
- number
- 含义
- 浏览数和收藏数
user- JavaScript 类型
- object
- 含义
- 作者的资料:
username、followers_count等 19 个字段
quoted_status,retweeted_status- JavaScript 类型
- object 或不存在
- 含义
- 被引用或被转发的推文,结构相同
#用一个辅助函数重试 429 和服务器错误
让每次调用都经过同一个函数,只重试值得重试的情况。429 表示你的密钥在一秒内发起了超过 20 次请求;5xx 或超时则是临时故障。本指南后面的代码都会导入这个 sorsa 辅助函数。
// sorsa.mjs: one place for the key, timeouts and retries
const API = "https://api.sorsa.io/v3";
const KEY = process.env.SORSA_API_KEY ?? "YOUR_API_KEY";
const RETRY = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((ok) => setTimeout(ok, ms));
export async function sorsa(path, { body, query } = {}) {
const url = new URL(API + path);
if (query) url.search = query; // a URLSearchParams for GET endpoints
for (let attempt = 0; ; attempt++) {
let res;
try {
res = await fetch(url, {
method: body ? "POST" : "GET",
headers: { ApiKey: KEY, "Content-Type": "application/json" },
body: body && JSON.stringify(body),
signal: AbortSignal.timeout(30_000),
});
} catch (err) { // timeout or dropped connection
if (attempt === 4) throw err;
await sleep(1000 * 2 ** attempt);
continue;
}
if (res.ok) return res.json();
if (!RETRY.has(res.status) || attempt === 4) {
const { message } = await res.json().catch(() => ({}));
throw new Error(`${res.status} on ${path}: ${message}`);
}
await sleep(1000 * 2 ** attempt); // 1, 2, 4, 8 seconds
}
}
401 或 403 永远不该重试:说明密钥有误或缺失,或者额度已用完,message 字段会写明是哪种。失败的请求不会计费,所以重试不花钱。Sorsa API 的所有接口都只读,因此重复发送 /search-tweets 这样的 POST 请求不会重复创建任何内容。Sorsa API 不返回速率限制响应头,因为窗口每秒重置一次:等一秒就能消除 429。
#用 next_cursor 翻页读取结果
游标是通往下一页的不透明键:把 next_cursor 原样传回去,响应里没有它时就停止。异步生成器很适合这个循环,因为读取各页的代码完全看不到游标。
import { sorsa } from "./sorsa.mjs";
export async function* pages(path, body, maxPages = 100) {
for (let n = 0; n < maxPages; n++) {
const page = await sorsa(path, { body });
yield page;
if (!page.next_cursor) return; // null, empty or absent: the end
body = { ...body, next_cursor: page.next_cursor };
}
}
import { pages } from "./pages.mjs";
const search = { query: "nodejs lang:en", order: "latest" };
for await (const page of pages("/search-tweets", search, 50)) {
for (const t of page.tweets) console.log(t.id, t.created_at);
}
搜索和时间线每页最多返回 20 条推文,而且一页可能少于 20 条但后面仍有数据,所以不能靠条数判断是否结束。同一个游标链上要保持查询和 order 不变,并把 maxPages 当作预算:50 页就是 50 次请求,不管查询匹配了多少内容。GET 接口则把游标放在查询字符串里:GET /followers(在新标签页打开) 每页最多返回 200 份用户资料,用 query.set("next_cursor", page.next_cursor) 往后翻。POST /user-tweets(在新标签页打开) 可以用同一个生成器;如果要用可续传的游标下载某个用户的全部推文,那篇指南有完整脚本。
#每次请求获取 100 条推文或用户资料
如果你已经有一批 ID 或用户名,就分批发送。POST /tweet-info-bulk(在新标签页打开) 在 tweet_links 里最多接受 100 个推文 ID 或链接,GET /info-batch(在新标签页打开) 最多接受 100 个用户名;每一批算一次请求。
import { sorsa } from "./sorsa.mjs";
// tweetIds and usernames: arrays of strings you already hold
const chunks = (list, size = 100) =>
Array.from({ length: Math.ceil(list.length / size) },
(_, i) => list.slice(i * size, (i + 1) * size));
const tweets = [];
for (const ids of chunks(tweetIds)) {
const body = { tweet_links: ids }; // up to 100 IDs or links
tweets.push(...(await sorsa("/tweet-info-bulk", { body })).tweets);
}
const users = [];
for (const names of chunks(usernames)) {
const query = new URLSearchParams();
for (const name of names) query.append("usernames", name);
users.push(...(await sorsa("/info-batch", { query })).users);
}
GET /info-batch 把 usernames 当作重复参数来读取(usernames=a&usernames=b),所以要用 append 来构造。如果把数组传给 new URLSearchParams({ usernames }),用户名会被逗号连成一个值。一次请求的价格与返回内容的多少无关,所以一批 100 条推文的价格和一条推文一样:每 1,000 条推文 $0.014 起,比官方 X API 的 $5.00 便宜 250 多倍。批量响应不带游标;整批数据都在 tweets 或 users 里。
#用一个密钥跑到每秒 20 次请求
Sorsa API 在所有套餐上对每个密钥允许每秒 20 次请求,一个 Node 进程就能轻松达到:事件循环在单个线程上就能同时挂起几十个请求。需要遵守的是请求的发起速率,所以在每次调用前放一个调度器,并把工作拆成相互独立的链。一条游标链无法并行,因为每一页都需要上一页的游标,所以每个账号跑一条链,或者搜索时每天跑一条链。
import { sorsa } from "./sorsa.mjs";
// Hands out start times 1/19 s apart, however many lanes are waiting.
function pacer(perSecond) {
const gap = 1000 / perSecond;
let next = 0;
return () => {
const now = Date.now();
const start = Math.max(next, now);
next = start + gap;
return new Promise((ok) => setTimeout(ok, start - now));
};
}
const slot = pacer(19); // one pacer per key, one start under the limit
async function lane(query, maxPages = 100) {
const tweets = [];
let body = { query };
for (let n = 0; n < maxPages; n++) {
await slot();
const page = await sorsa("/search-tweets", { body });
tweets.push(...page.tweets);
if (!page.next_cursor) break;
body = { ...body, next_cursor: page.next_cursor };
}
return tweets;
}
const day = (d) => new Date(Date.UTC(2026, 9, d)).toISOString().slice(0, 10);
const queries = Array.from({ length: 31 }, (_, i) =>
`nodejs lang:en since:${day(i + 1)} until:${day(i + 2)}`); // Oct 1-31
const results = await Promise.all(queries.map((q) => lane(q)));
console.log(results.flat().length, "tweets");
这个调度器不需要加锁:JavaScript 一次只运行一个回调,所以两条链不可能拿到同一个起始时间。每秒 19 次发起,给上限留出了一次的余量,因为定时器抖动可能把第 20 次推进下一个窗口。万一还有 429 漏过去,辅助函数里的重试会处理。常见的错误是不加调度器就用 Promise.all:同时发出 60 个调用,其中 40 个会直接撞上 429,并花好几秒在退避上。
无服务器平台会悄悄打破这条规则。Lambda、Vercel 或 Cloud Run 上的每个函数实例都会加载自己的 lanes.mjs。因此三个热实例会在一个密钥上运行三个调度器,合起来每秒最多发起 57 个请求。给每个实例分一份速率(三个实例就用 pacer(6)),或者把批量任务放在一个 worker 里跑。上限是按密钥统计的,所以第二个 worker 配第二个密钥,就有自己的每秒 20 次。按每秒 20 次请求算,每批 100 条的批量请求每秒最多拉取 2,000 条推文,搜索页每秒最多 400 条。
#把推文保存为 JSONL 或 CSV
每页一到就写入,不要把所有推文都留在内存里。JSON Lines 每行一个 JSON 对象,保留所有字段,追加写入也安全;CSV 可以直接用 Excel 和 Google Sheets 打开。磁盘写不过来时,下面两个流都会等待 drain。
import { createWriteStream } from "node:fs";
import { once } from "node:events";
import { pages } from "./pages.mjs";
const jsonl = createWriteStream("tweets.jsonl");
const csv = createWriteStream("tweets.csv");
const put = async (out, line) => {
if (!out.write(line)) await once(out, "drain"); // respect backpressure
};
const cell = (v) => `"${String(v ?? "").replaceAll('"', '""')}"`;
const BOM = String.fromCharCode(0xfeff); // tells Excel the file is UTF-8
await put(csv, BOM + "id,created_at,username,likes_count,full_text\r\n");
const search = { query: "nodejs lang:en" };
for await (const page of pages("/search-tweets", search, 500)) {
for (const t of page.tweets) {
await put(jsonl, JSON.stringify(t) + "\n");
const row = [t.id, t.created_at, t.user.username, t.likes_count,
t.full_text];
await put(csv, row.map(cell).join(",") + "\r\n");
}
}
jsonl.end();
csv.end();
CSV 开头的字节顺序标记告诉 Excel 这个文件是 UTF-8,所以 emoji 和带重音的字符都能正确显示。每个单元格都加了引号,所以推文里的逗号、引号和换行都会留在自己的列里。500 页最多得到 1 万条推文,只需 500 次请求;不管运行多久,内存占用都保持平稳。
须知
JavaScript 的数字只能精确表示不超过 2^53(约 9 千万亿)的整数,而推文 ID 有 19 位。
Number("1985432176540981234")返回 1985432176540981200,那是另一条推文。Sorsa API 把所有 ID 和游标都以字符串发送,所以请保持它们是字符串,按字符串比较,需要按 ID 排序时再用BigInt(id)。
#为响应添加 TypeScript 类型
TypeScript 用户只需几个与 API 字段同名的接口,就能获得类型安全。下面这些依据线上的 OpenAPI 参考文档,涵盖大多数代码会读取的字段;其余字段见参考文档。
// sorsa-types.ts: field names as in the Sorsa API reference
export interface SorsaUser {
id: string; // 64-bit ID: keep it a string
username: string;
display_name: string;
description: string;
followers_count: number;
followings_count: number;
tweets_count: number;
verified: boolean;
protected: boolean;
created_at: string; // ISO 8601
}
export interface SorsaTweet {
id: string;
full_text: string;
created_at: string;
lang: string;
likes_count: number;
retweet_count: number;
reply_count: number;
quote_count: number;
view_count: number;
bookmark_count: number;
is_reply: boolean;
user: SorsaUser;
quoted_status?: SorsaTweet;
retweeted_status?: SorsaTweet;
}
export interface TweetsPage {
tweets: SorsaTweet[];
next_cursor?: string | null;
}
可选字段带有 ?:被引用或被转发的推文只存在于引用和转发上,所以编译器会强制你先检查再读取。在边界处做类型转换,const page = (await sorsa(path, { body })) as TweetsPage,其余代码就都受到检查。Node 22.18 及更高版本通过去掉类型直接运行 .ts 文件,所以 node --env-file=.env job.ts 无需构建步骤就能运行。如果想一次覆盖所有接口,可以用 openapi-typescript 这类 OpenAPI 生成器,把完整的参考文档转成类型。
#能在浏览器的 JavaScript 里调用 Twitter API 吗?
不能:放在浏览器代码里的 API 密钥,任何打开开发者工具的人都能读到,而且 Sorsa API 是为服务端调用设计的,浏览器调用会遇到 CORS 错误。在你的服务器上加一个小路由,把密钥留在服务器上,让前端调用你的路由。
// server.mjs: npm install express
import express from "express";
import { sorsa } from "./sorsa.mjs";
const app = express();
app.get("/api/tweets", async (req, res) => {
const query = String(req.query.q ?? "").slice(0, 200);
const page = await sorsa("/search-tweets", { body: { query } });
res.json(page.tweets.map((t) => ({ id: t.id, text: t.full_text })));
});
app.listen(3000);
这样浏览器就对自己的域名执行 fetch("/api/tweets?q=nodejs"),永远看不到密钥。同样的做法适用于任何服务端运行环境:Cloudflare Worker 或 Lambda 函数把密钥放在自己的环境变量里,页面只和它通信。当很多访客请求同样的内容时,可以把路由的结果缓存一分钟:Sorsa API 每次请求都返回最新数据,一份缓存的结果可以只用一次请求服务所有人。
#一个 Node.js 任务要花多少钱
Sorsa API 按请求收费,与接口返回多少内容无关:搜索结果每 1,000 条推文 $0.07 起,批量请求每 1,000 条 $0.014 起,失败的请求不计费。官方 X API 则按对象收费:每 1,000 条帖子 $5.00,从 2026 年 11 月 22 日起,如果代码扩展出每条帖子的作者(大多数 twitter-api-v2 搜索都是这样),每 1,000 条最高 $15.00。
官方 X API
$5,000
采集 100 万 条推文
每条帖子 $0.005
Sorsa API
$70 起
采集 100 万 条推文
每 1,000 条 $0.07 起
每条推文都自带作者资料
下面的构建器会为一个任务生成 Node.js 循环,并显示请求次数、单个密钥下最快的耗时,以及按 Sorsa API 最低费率计算的成本。
// sorsa() 和 pages() 来自上面的辅助函数
const body = { query: "nodejs lang:en" };
for await (const page of pages("/search-tweets", body, 5000)) {
save(page.tweets); // 每页最多 20 条推文
}
- 请求数
- 5,000
- 单个密钥下最快
- 约 4 分钟
- Sorsa API
- 低至 $7
按每秒 20 次请求计时,按每 1,000 次请求 $1.40 计费。一次搜索就是一条游标链,请拆成多个日期窗口,让各条通道并行运行。
耗时是单个密钥允许的最快速度;单条游标链只能按往返耗时的节奏推进,所以大任务请拆成多条通道。
每 1,000 次请求的费率从 Starter 的 $4.90 到 Hyperscale 的 $1.40 不等,所有接口使用同一个费率。查看剩余额度不花钱:GET /key-usage-info(在新标签页打开) 永远不会计入你的请求次数。
import { sorsa } from "./sorsa.mjs";
const usage = await sorsa("/key-usage-info");
console.log(usage.remaining_requests, "left until", usage.valid_until);
#把 twitter-api-v2 代码迁移到 Sorsa API
twitter-api-v2 的大多数调用都对应 Sorsa API 的一个接口,而且代码不再需要扩展字段和字段清单,因为每条推文已经带有作者和各项计数。下表把常用的 client.v2 方法和对应接口配成了对;如果要在整个代码库里从 Twitter/X API 迁移,那篇指南列出了每个 v2 接口和响应字段的对应关系。
| twitter-api-v2 方法 | 接口 | 有什么变化 |
|---|---|---|
search | POST /search-tweets | 可查 2006 年以来的任意日期,而不只是最近 7 天 |
searchAll | POST /search-tweets | 同一个接口,在查询里加上 since: 和 until: |
userTimeline | POST /user-tweets | 只需用户名,也没有 3,200 条帖子的上限 |
userByUsername | GET /info(在新标签页打开) | 返回全部资料字段,无需 user.fields |
usersByUsernames | GET /info-batch | 每次请求 100 个用户名 |
tweets | POST /tweet-info-bulk | 每次请求 100 个 ID 或链接 |
followers | GET /followers | 每页 200 个粉丝 |
tweetRetweetedBy | POST /retweeters(在新标签页打开) | 同样的游标循环 |
tweet | 无 | 只读:发帖请走官方 X API |
search- 接口
POST /search-tweets- 有什么变化
- 可查 2006 年以来的任意日期,而不只是最近 7 天
searchAll- 接口
POST /search-tweets- 有什么变化
- 同一个接口,在查询里加上
since:和until:
userTimeline- 接口
POST /user-tweets- 有什么变化
- 只需用户名,也没有 3,200 条帖子的上限
userByUsername- 有什么变化
- 返回全部资料字段,无需
user.fields
usersByUsernames- 接口
GET /info-batch- 有什么变化
- 每次请求 100 个用户名
tweets- 接口
POST /tweet-info-bulk- 有什么变化
- 每次请求 100 个 ID 或链接
followers- 接口
GET /followers- 有什么变化
- 每页 200 个粉丝
tweetRetweetedBy- 有什么变化
- 同样的游标循环
tweet- 接口
- 无
- 有什么变化
- 只读:发帖请走官方 X API
字段名也会变:text 变成 full_text,public_metrics.like_count 变成 likes_count,author_id 加 includes.users 变成 user 对象,meta.next_token 变成 next_cursor。分页器的 fetchNext() 变成上面的 pages 生成器,它在达到你设定的页数预算时停止,而不是撞上速率限制才停。POST /user-tweets 还能越过最新 3,200 条帖子的界限,而官方时间线到这里就停了(见 X 的时间线文档(在新标签页打开));官方 X API 的近期搜索只覆盖最近 7 天(见 X 的搜索文档(在新标签页打开))。
#今天就发出你的第一个 Node.js 请求
用 Node.js 读取 X 数据只需要 fetch、一个密钥和四个小部件:负责重试的 sorsa、负责游标的 pages、负责每批 100 条的 chunks,以及限制在每秒 20 次请求的调度器。要做这件事,Sorsa API 是值得先用的 Twitter API:每次搜索请求 20 条推文,每 1,000 条 $0.07 起,作者信息就在每条推文里,搜索可回溯到 2006 年,另有 100 次无需绑卡、永不过期的免费请求。如果想不写代码就把同样的数据拉进工作流,见 n8n 版 Twitter API。
#常见问题
Node.js 应该用哪个 Twitter API?
用 Node.js 读取 X 数据,建议用 Sorsa API:在内置 fetch 的 ApiKey 请求头里放一个密钥,搜索结果每 1,000 条推文 $0.07 起,批量请求每 1,000 条 $0.014 起,所有套餐都是每秒 20 次请求,搜索可回溯到 2006 年,而且无需登录 X,你的账号不会有风险。如果还要发帖,可以搭配官方 X API。
Node.js 有官方的 Twitter API SDK 吗?
有。X 发布了 TypeScript SDK @xdevplatform/xdk(0.6.6 版),可以在 Node.js 和打包工具中使用。它是官方 X API 的客户端,所以每次调用仍然需要开发者账号、bearer 令牌,以及每条帖子 $0.005 的预付额度。Sorsa API 不需要 SDK:普通的 fetch 就能调用它的全部 45 个接口。
获取 Twitter/X 数据最好用哪个 npm 包?
对 Sorsa API 来说,不需要任何包:Node 内置的 fetch 加上本指南里约 40 行的辅助函数就能覆盖所有接口,没有需要更新的依赖。对官方 X API 来说,twitter-api-v2 是安装量最大的客户端,每周下载约 33.6 万次,版本为 1.31.0,旁边还有 X 自己的 @xdevplatform/xdk;两者都按对象从你的 X 额度里扣费。
有不需要 API 密钥、就能获取推文的免费 npm 包吗?
rettiwt-api 之类的包读取的是 X 的网页应用,而不是 API。不登录时,它们只能做少数几种查询;其余调用每次请求都会带上你 X 账号的 cookie。这会把流量和你的账号绑在一起,X 一改网页应用它们就会失效。Sorsa API 从不要求你的 X 登录信息,它的 100 次免费请求也无需绑卡。各种方案的取舍见如何抓取 Twitter。
能用 Sorsa API 在 Node.js 里发推文吗?
不能。Sorsa API 从产品设计上就是只读:它读取公开的帖子、用户资料和粉丝,从不发帖、点赞、关注或发送消息,所以一个密钥无法对任何 X 账号执行操作。要在 Node.js 里发帖,请使用带 OAuth 用户令牌的官方 X API,例如 twitter-api-v2 的 client.v2.tweet,每条帖子 $0.015,而读取仍交给 Sorsa API。
没有开发者账号,能在 Node.js 里获取 Twitter 数据吗?
可以。Sorsa API 只需要它自己的密钥:注册,设置 ApiKey 请求头,就能在 Node.js 里调用它的任何接口,不需要 X 开发者账号、应用审核或 OAuth。前 100 次请求免费,通过搜索最多能获取 2,000 条推文,用每批 100 条的批量请求则最多能获取 1 万条。各种方案的比较见没有开发者账号如何使用 Twitter API。
#我们如何核实
我们根据 Sorsa API 线上的 OpenAPI 参考文档(45 个接口)以及它关于认证、速率限制和分页的文档编写了每个请求,阅读日期为 2026 年 11 月 2 日。这一轮我们没有 Sorsa API 密钥,所以响应使用的是演示值。我们在 Node 26 上,对着一个按文档格式应答的本地模拟服务运行了这些 Node.js 代码块:第一个请求、重试辅助函数(两次 503,然后一轮 60 个调用的突发)、游标生成器、每批 100 条的批量请求、JSONL 和 CSV 导出、密钥用量调用,以及调度器。每秒 19 次发起时,调度器跑了 31 条链、93 个请求,三轮都没有出现 429;恰好每秒 20 次时,三轮中有一轮向重试逻辑送去了 29 个请求。Express、Next.js 和 twitter-api-v2 的代码片段没有运行。同一天,我们还读了 docs.x.com 上 X 的价格、11 月 22 日关于扩展字段的通知和它的工具页面,twitter-api-v2 与 @xdevplatform/xdk 在 npm 注册表中的条目,以及 Node.js 的发布时间表。
来源
- X API pay-per-usage pricing (docs.x.com)(在新标签页打开)
- X API PPU: expansions update (X Developers, October 23, 2026)(在新标签页打开)
- Tools and libraries: the official TypeScript SDK (docs.x.com)(在新标签页打开)
- Timelines (docs.x.com)(在新标签页打开)
- Search Posts (docs.x.com)(在新标签页打开)
- twitter-api-v2 on npm(在新标签页打开)
- @xdevplatform/xdk on npm(在新标签页打开)
- Node.js releases(在新标签页打开)
- Sorsa API reference, OpenAPI
- Sorsa API rate limits(在新标签页打开)
- Sorsa API pagination(在新标签页打开)