跳到正文

为任意 X 账号或列表生成 Twitter RSS 订阅源

X 不提供任何 RSS 订阅源,所以 Twitter RSS 订阅源要自己生成:脚本以 JSON 读取账号的最新帖子(每次请求约 20 条),再改写成 RSS 2.0 文件,供你的阅读器定时拉取。

  • 更新于
  • 发布于
  • 阅读约 10 分钟

简短回答: 每 15 分钟刷新一次,一个订阅源每月约需 2,880 次请求,而一个公开的 X 列表可以把多达 5,000 个账号折叠进同样的一次请求。要搭建它,可以用 Sorsa API:POST /user-tweets 每次请求返回 20 条帖子,每 1,000 条 $0.07 起,100 次免费请求可以拉取你的前 2,000 条帖子。

接口
POST /user-tweets
每页条数
每次请求 20 条帖子
价格
每 1,000 条帖子 $0.07 起

#X 有 RSS 订阅源吗?

没有。X 对个人主页、搜索和列表都不提供 RSS 订阅源:在个人主页地址后面加上 /rss,x.com 返回的只是它平常的网页,我们在 2026 年 11 月 2 日确认过这一点。如今所有能用的 Twitter RSS 订阅源,都是有人用 X 数据生成的,所以真正的选择在于:用什么来搭,以及谁来维护它的运行。

现成的方案都有附带条件:

  • RSSHub 的 X 路由现在只能靠已登录 X 账号的 auth_token Cookie 或官方 X API 密钥运行,这是它的路由源码里写明的。用 Cookie 会把订阅源绑在一个真实账号上,让这个账号面临风险。
  • 托管的订阅源生成器把订阅源当作订阅服务来卖,每个订阅源保留多少帖子、多久刷新一次,都由它们的套餐决定。

RSS 本身仍然活着。Feedly、Inoreader、NetNewsWire、FreshRSS、Miniflux 和 n8n 的 RSS Read 节点都支持订阅 RSS 2.0,所以你搭出来的订阅源能接入你已经在用的工具。如果你想要的是手机或 Slack 里的提醒,而不是订阅源,请看有人发推时收到通知。

#Twitter RSS 订阅源是怎么搭起来的

一个 Twitter RSS 订阅源分五步,只有第一步接触 X 数据,其余只是一段短脚本和一个定时器。

  1. 以 JSON 获取最新帖子。 一次请求返回账号最近的 20 条帖子,包含正文、日期、媒体和作者。

  2. 保留已有的内容。 一个小小的状态文件可以避免重复,并让订阅源容纳不止一页的帖子。

  3. 把每条帖子映射为一个 RSS 条目。 标题、链接、日期和正文,放进每个阅读器都能识别的元素里。

  4. 写出 XML 文件。 先写到临时文件,再替换过去,这样阅读器不会拉到只有一半的订阅源。

  5. 按计划刷新。 大多数订阅源每 15 到 60 分钟刷新一次就合适。

    每 15 分钟:一次请求,一个新文件 1 Sorsa API /user-tweets 最新 20 条推文 JSON 格式 2 你的脚本 x_rss.py 保留 50 条推文, 映射为条目 3 订阅文件 feed.xml RSS 2.0,由你的 服务器提供 4 你的阅读器 Feedly、Inoreader、 NetNewsWire、 FreshRSS 或 n8n 输入:一个 X 账号、一个公开列表或一个搜索 按自己的计时器轮询该文件
    只有第一步读取 X 数据;脚本、文件和阅读器都由你掌控。

数据这一步,可以用 Sorsa API,一款 Twitter(X)API 与爬虫:一次 POST /user-tweets 请求,用一个 API 密钥就以干净的 JSON 返回账号最新的 20 条帖子,不需要开发者账号,也从不要求你的 X 登录信息。响应结构在各个版本之间保持不变,所以你今天写好的订阅源脚本会一直能用。

阅读器显示的全部内容靠五个 RSS 元素承载,每个元素都对应响应里的一个字段:

RSS 元素Sorsa API 字段作用
titlefull_text 的前 80 个字符阅读器按标题列出条目
link 和 guidhttps://x.com/ + user.username + /status/ + id稳定的 guid 可避免重复
pubDatecreated_at,ISO 8601RSS 需要 RFC 822 格式的日期
descriptionfull_text 加上 entities[].preview 的图片以 HTML 呈现的帖子
dc:creatoruser.display_nameRSS 的 author 必须是邮箱
  • title

    Sorsa API 字段
    full_text 的前 80 个字符
    作用
    阅读器按标题列出条目
  • link 和 guid

    Sorsa API 字段
    https://x.com/ + user.username + /status/ + id
    作用
    稳定的 guid 可避免重复
  • pubDate

    Sorsa API 字段
    created_at,ISO 8601
    作用
    RSS 需要 RFC 822 格式的日期
  • description

    Sorsa API 字段
    full_text 加上 entities[].preview 的图片
    作用
    以 HTML 呈现的帖子
  • dc:creator

    Sorsa API 字段
    user.display_name
    作用
    RSS 的 author 必须是邮箱

#以 JSON 获取账号的帖子

把账号的用户名发给 POST /user-tweets,最新的 20 条帖子就会在一次响应里返回。三步就能从零走到第一次响应。

  1. 获取密钥。 注册后,在控制台复制你的 API 密钥。前 100 次请求免费,永不过期。

  2. 调用接口。 把用户名放在 JSON 请求体里,密钥放在 ApiKey 请求头中。完整的参数列表见用户推文参考(在新标签页打开)。

    Terminal
    curl -X POST "https://api.sorsa.io/v3/user-tweets" \
      -H "ApiKey: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"username": "nasa_launch_desk"}'
    
    Response200 OK
    {
      "tweets": [
        {
          "id": "1985012345678901234",
          "full_text": "Static fire complete. Launch window opens Thursday.",
          "created_at": "2026-11-02T14:05:00Z",
          "lang": "en",
          "likes_count": 1840,
          "retweet_count": 312,
          "reply_count": 95,
          "view_count": 120400,
          "is_reply": false,
          "entities": [
            {
              "type": "photo",
              "link": "https://t.co/k3x9demo",
              "preview": "https://pbs.twimg.com/media/demo-static-fire.jpg"
            }
          ],
          "user": {
            "id": "1700000000000000042",
            "username": "nasa_launch_desk",
            "display_name": "Launch Desk"
          }
        }
      ],
      "next_cursor": "DAABCgABG8a1demoAAA"
    }
    
  3. 选择订阅源显示什么。 type 可选 posts(默认)、replies 或 retweets,with_replies: true 会把回复混入帖子。next_cursor 用来获取更早的下一批 20 条帖子。

每条帖子还带有作者的完整资料,所以订阅源不需要再发第二次请求去取名字或头像。返回 403 表示缺少密钥或请求已用完,返回 429 表示一秒内超过了 20 次请求,失败的请求永远不计费。

#用 Python 把 JSON 转成 RSS

下面的脚本只用 Python 标准库,就能把这些 JSON 转成合法的 RSS 2.0 文件,所以无需安装任何东西。它既适用于账号,也适用于列表或搜索,并且会在两次运行之间记住最近的 50 条帖子。

x_rss.py
"""Write an RSS 2.0 feed of an X account, List or search.

python x_rss.py user nasa_launch_desk
python x_rss.py list 1700000000000000001
python x_rss.py search "from:nasa_launch_desk OR #artemis"
"""
import json
import os
import sys
import urllib.parse
import urllib.request
from datetime import datetime
from email.utils import format_datetime
from xml.sax.saxutils import escape

API = "https://api.sorsa.io/v3"
KEY = os.environ.get("SORSA_API_KEY", "YOUR_API_KEY")
KEEP = 50       # posts the feed keeps
MAX_PAGES = 5   # pages one run may read to catch up


def page(kind, target, cursor):
    if kind == "list":
        query = {"list_id": target}
        if cursor:
            query["next_cursor"] = cursor
        url = API + "/list-tweets?" + urllib.parse.urlencode(query)
        req = urllib.request.Request(url)
    else:
        if kind == "user":
            path, body = "/user-tweets", {"username": target}
        else:
            path, body = "/search-tweets", {"query": target,
                                            "order": "latest"}
        if cursor:
            body["next_cursor"] = cursor
        req = urllib.request.Request(
            API + path, data=json.dumps(body).encode(),
            headers={"Content-Type": "application/json"})
    req.add_header("ApiKey", KEY)
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.load(resp)


def fetch_new(kind, target, seen):
    new, cursor = [], None
    for _ in range(MAX_PAGES):
        data = page(kind, target, cursor)
        tweets = data.get("tweets") or []
        new += [t for t in tweets if t["id"] not in seen]
        cursor = data.get("next_cursor")
        caught_up = any(t["id"] in seen for t in tweets)
        if not cursor or not seen or caught_up:
            break
    return new


def to_item(t):
    user = t.get("user") or {}
    name = user.get("username", "")
    link = f"https://x.com/{name}/status/{t['id']}"
    text = " ".join(t.get("full_text", "").split())
    when = datetime.fromisoformat(t["created_at"].replace("Z", "+00:00"))
    pics = "".join(f'<p><img src="{escape(e["preview"])}"></p>'
                   for e in t.get("entities") or []
                   if e.get("type") == "photo" and e.get("preview"))
    author = escape(user.get("display_name") or name)
    xml = ("<item>"
           f"<title>{escape(text[:80])}</title>"
           f"<link>{link}</link>"
           f'<guid isPermaLink="true">{link}</guid>'
           f"<pubDate>{format_datetime(when)}</pubDate>"
           f"<dc:creator>{author}</dc:creator>"
           f"<description>{escape(escape(text) + pics)}</description>"
           "</item>")
    return {"id": t["id"], "date": when.isoformat(), "xml": xml}


def main(kind, target):
    safe = (c if c.isalnum() or c in "_-" else "-" for c in target)
    name = "".join(safe)[:60]
    state, feed = name + ".json", name + ".xml"
    items = []
    if os.path.exists(state):
        with open(state, encoding="utf-8") as f:
            items = json.load(f)
    seen = {i["id"] for i in items}
    items += [to_item(t) for t in fetch_new(kind, target, seen)]
    items = sorted(items, key=lambda i: i["date"], reverse=True)[:KEEP]
    with open(state, "w", encoding="utf-8") as f:
        json.dump(items, f)
    home = {"user": f"https://x.com/{target}",
            "list": f"https://x.com/i/lists/{target}"}
    title = escape(f"X {kind}: {target}")
    xml = ('<?xml version="1.0" encoding="UTF-8"?>'
           '<rss version="2.0" '
           'xmlns:dc="http://purl.org/dc/elements/1.1/"><channel>'
           f"<title>{title}</title>"
           f"<link>{home.get(kind, 'https://x.com/')}</link>"
           f"<description>{title}, built with Sorsa API</description>"
           + "".join(i["xml"] for i in items) + "</channel></rss>")
    with open(feed + ".tmp", "w", encoding="utf-8") as f:
        f.write(xml)
    os.replace(feed + ".tmp", feed)
    print(f"{feed}: {len(items)} posts")


if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2])

有三个细节,让这个订阅源比单纯转换一页数据表现得更好:

  • 不会漏掉突发的大量发帖。 当账号在两次运行之间发了超过 20 条帖子时,脚本会顺着 next_cursor 往回翻,直到遇到已有的帖子,最多翻 5 页。
  • 它保留 50 条帖子,而不是 20 条。 JSON 状态文件保存了更早的帖子,所以哪怕某天账号没发帖,订阅源也不会被清空。
  • 它能通过订阅源校验器的常规检查。 日期是 RFC 822 格式,每个条目都有固定的 guid,作者放在 dc:creator 里,帖子的 HTML 只为 XML 转义一次。

运行 python x_rss.py user nasa_launch_desk,脚本旁边就会出现文件 nasa_launch_desk.xml。第一次运行花费一次请求;之后每次运行花费一次请求,外加追赶所需的每个额外页面一次请求。

#一个订阅源涵盖整个列表或一次搜索

一个公开的 X 列表可以把多达 5,000 个账号变成一个订阅源,花费和单个账号的订阅源一样。用 Sorsa API 的话,GET /list-tweets 会按时间顺序在一页里返回列表中所有成员的最新帖子,所以不管订阅源关注多少账号,每次刷新都只花一次请求。

200 个账号,每 15 分钟刷新 每月请求数,30 天 每个账号一个订阅源 每次刷新 200 次请求 576K Sorsa API 中的一个列表订阅源 每次刷新 1 次请求 2,880
分开的订阅源会让请求数乘以账号数;列表订阅源不会。

每 15 分钟把 200 个账号当作 200 个独立订阅源来关注,每月需要 57.6 万次请求;同样的 200 个账号放进一个列表,只需要 2,880 次。要设置一个,就在 X 应用里创建一个公开列表(或者用别人的公开列表),复制它地址末尾的数字(x.com/i/lists/<number>),并把它作为 list_id 传入。参数见列表推文参考(在新标签页打开),Twitter 列表 API 指南还讲了成员和关注者。

Terminal
python x_rss.py list 1700000000000000001

搜索订阅源的做法一样,通过 POST /search-tweets 并把 order 设为 latest。想关注少数几个账号又不想建列表,或者想关注某个话题,都可以用它:

搜索订阅源的查询词
from:nasa_launch_desk OR from:orbit_news_digest
#artemis -filter:replies
"launch window" lang:en min_faves:50

X 的所有搜索运算符在这里都能用,可以回溯到 2006 年;Twitter 搜索运算符指南列出了它们并附有示例,请求格式见搜索推文参考(在新标签页打开)。一个热闹的列表或话题标签,在两次刷新之间发帖可能超过 20 条:这时脚本会多读几页,每页一次请求,所以热闹的订阅源应该刷新得更勤,而不是更少。

#按计划运行并订阅

脚本在定时器上运行,再由网络服务器把文件交给你的阅读器,订阅源才能保持新鲜。在任何一台 Linux 服务器上,crontab 里的两行就能保存密钥,并每 15 分钟刷新一次订阅源:

crontab -e
SORSA_API_KEY=YOUR_API_KEY
*/15 * * * * cd /srv/feeds && python3 x_rss.py user nasa_launch_desk

然后把这个文件夹发布出去。机器上已经在运行的任何网络服务器(nginx、Caddy、Apache)都可以发布 /srv/feeds;同一台服务器上自建的阅读器,比如 FreshRSS 或 Miniflux,可以从本地地址读取这个文件。想快速测试,可以用 python3 -m http.server 8080 --directory /srv/feeds,它会在 http://localhost:8080/nasa_launch_desk.xml 提供这个文件。

把订阅源的地址粘贴到阅读器的“添加订阅”输入框里。把阅读器的刷新频率设成不比 cron 更频繁:每 5 分钟检查一次的阅读器,四次里有三次看到的是同一个文件。

须知

订阅源的网址只供你自己的阅读器或团队的工具使用。请把它放在一段很长的随机路径后面,或者放在阅读器的登录之后,而不是放在公开页面上:订阅源里的帖子属于它们的作者,而且订阅源只含公开帖子。

#改用 n8n 提供订阅源

n8n 用四个节点就能在它自己的网址上提供同样的订阅源,不需要你自己的服务器。每次你的阅读器拉取这个网址,工作流就调用一次 Sorsa API,并返回新的 XML。

  1. 添加一个 Webhook 节点。 把 HTTP Method 设为 GET,给它一段很长的随机路径,并把 Respond 设为 “Using 'Respond to Webhook' Node”。

  2. 添加一个 HTTP Request 节点。 用 POST 请求 https://api.sorsa.io/v3/user-tweets,带上请求头 ApiKey,并发送 JSON 请求体 {"username": "nasa_launch_desk"}。

  3. 添加一个 Code 节点。 它把 tweets 数组转成 RSS 条目;代码在下面的工作流里。

  4. 添加一个 Respond to Webhook 节点。 把 Respond With 设为 Text,把 Code 节点的 xml 字段拖进 Response Body,并添加响应头 Content-Type,值为 application/rss+xml; charset=utf-8。

  5. 启用工作流。 把你的阅读器订阅到 Webhook 节点的 Production URL。

    n8n 工作流:四个节点,一个订阅地址
    1. WebhookFeed URLGET,通过最后一个节点响应
    2. HTTP RequestSorsa API user tweetsPOST /user-tweets,请求头 ApiKey
    3. CodeBuild RSS把推文转成 RSS 2.0 条目
    4. Respond to WebhookReturn the feed文本,application/rss+xml
    const tweets = $input.first().json.tweets || [];
    const esc = (s) => String(s).replace(/&/g, '&amp;')
      .replace(/</g, '&lt;').replace(/>/g, '&gt;');
    const items = tweets.map((t) => {
      const link = 'https://x.com/' + t.user.username + '/status/' + t.id;
      const text = t.full_text.replace(/\s+/g, ' ');
      return '<item><title>' + esc(text.slice(0, 80)) + '</title>' +
        '<link>' + link + '</link>' +
        '<guid isPermaLink="true">' + link + '</guid>' +
        '<pubDate>' + new Date(t.created_at).toUTCString() + '</pubDate>' +
        '<description>' + esc(esc(text)) + '</description></item>';
    }).join('');
    const xml = '<?xml version="1.0" encoding="UTF-8"?>' +
      '<rss version="2.0"><channel><title>X feed</title>' +
      '<link>https://x.com/</link>' +
      '<description>Built with Sorsa API</description>' +
      items + '</channel></rss>';
    return [{ json: { xml } }];

    在 n8n 中,把 JSON 粘贴到空白画布,在 HTTP Request 节点里填入你的密钥,激活工作流,然后订阅 Production URL。

    Code 节点把 Sorsa API 返回的 tweets 数组转成 RSS 2.0;工作流 JSON 一次导入全部四个节点。

这里由阅读器的刷新间隔决定费用:每小时检查一次的阅读器,每月为这个订阅源花 720 次请求。要做列表或搜索订阅源,就修改 HTTP Request 节点的网址和请求体。如果这些帖子只是喂给工作流的其余部分(Slack、Sheets、AI 步骤),那就跳过 RSS,直接读取 JSON,在 n8n 中使用 Twitter API指南里有演示。

#Twitter RSS 订阅源要花多少钱

一个 Twitter RSS 订阅源每次刷新花一次请求,所以它的价格取决于刷新的频率和你运行的订阅源数量,而不是账号发了多少帖子。以 30 天的一个月计:

刷新间隔一个订阅源每月请求数Starter 的 1 万次请求可支持的订阅源数
5 分钟8,6401
15 分钟2,8803
30 分钟1,4406
1 小时72013
6 小时12083
  • 5 分钟

    一个订阅源每月请求数
    8,640
    Starter 的 1 万次请求可支持的订阅源数
    1
  • 15 分钟

    一个订阅源每月请求数
    2,880
    Starter 的 1 万次请求可支持的订阅源数
    3
  • 30 分钟

    一个订阅源每月请求数
    1,440
    Starter 的 1 万次请求可支持的订阅源数
    6
  • 1 小时

    一个订阅源每月请求数
    720
    Starter 的 1 万次请求可支持的订阅源数
    13
  • 6 小时

    一个订阅源每月请求数
    120
    Starter 的 1 万次请求可支持的订阅源数
    83
刷新间隔
每月请求数 2,880 每个订阅源每次刷新 1 次请求,按 30 天计
能覆盖的 Sorsa API 套餐 Starter 10K 次请求/月,$49,可容纳 3 个这样的订阅源

每个 List 或每次搜索算一个订阅源,不论它关注多少个账号。

当每页都返回满页数据时(比如热闹的列表,或第一次运行),Sorsa API 提供帖子的价格是每 1,000 条 $0.07 起。官方 X API 的按量付费价格(在新标签页打开)是每读取 1,000 条帖子 $5.00,而且要先有开发者账号和 OAuth 应用,按量付费每月最多读取 300 万条帖子。从 2026 年 11 月 22 日起,X 还会对通过 expansions 返回的每条帖子的作者和媒体另行计费,每位用户 $0.010,见 X 在 10 月 23 日的通知(在新标签页打开)。

#今天就搭建你的第一个订阅源

Twitter RSS 订阅源就是一个约 100 行的脚本加一个定时器,唯一没法自己写的部分是数据。这部分可以用 Sorsa API:每次刷新一次请求,每次请求 20 条帖子,多达 5,000 个账号的整个列表放进一个订阅源,所有套餐统一每秒 20 次请求。它只需一个 API 密钥,不需要开发者账号,也从不要求你的 X 登录信息。你的 100 次免费请求就能拉取前 2,000 条帖子,足够你在选套餐之前先搭好订阅源,看着它刷新。

#常见问题

Twitter RSS 订阅源应该多久刷新一次?

大多数 Twitter RSS 订阅源每 15 到 60 分钟刷新一次就合适。每 15 分钟刷新的订阅源每月用 2,880 次请求,每小时刷新的用 720 次,因为每次刷新就是一次请求。让阅读器的刷新频率与脚本的计划保持一致,对热闹的列表或话题标签则刷新得更勤一些,这样每次运行一两页就能追上。

有免费的 Twitter RSS 订阅源吗?

X 自己没有任何 RSS 订阅源,无论免费还是付费。免费起步的办法是 Sorsa API 的 100 次免费请求:无需绑卡,永不过期,能拉取约 2,000 条帖子,足够你在选套餐之前搭建、测试并运行几天订阅源。

能把我的 X 主页时间线变成 RSS 订阅源吗?

不能直接做到,因为主页时间线属于已登录的账号。实际可行的替代是列表:在 X 应用里把你关注的账号加进一个公开列表,再用 GET /list-tweets 从中生成一个订阅源。一个列表最多容纳 5,000 个账号,每次刷新花一次请求,和单个账号一样。

RSSHub 现在还能用于 Twitter 吗?

RSSHub 的 X 路由还在,但现在需要一个已登录的 X 账号或官方 X API 密钥。它的路由源码列出了两种接入方式:已登录 X 账号的 auth_token Cookie,或官方按量付费的 X API 密钥。用 Sorsa API 搭的订阅源只需一个 API 密钥,也从不要求你的 X 登录信息。

能给受保护的 X 账号做 RSS 订阅源吗?

不能。受保护账号的帖子只对获批的关注者可见,所以没有任何订阅源工具或 API 能读取它们。Sorsa API 只返回公开数据,所以同样读不到。订阅源适用于任何公开账号、任何公开列表以及对公开帖子的任何搜索。

搭建 Twitter RSS 订阅源该用哪个 API?

搭建 Twitter RSS 订阅源可以用 Sorsa API:POST /user-tweets 每次请求返回 20 条帖子,每 1,000 条 $0.07 起,GET /list-tweets 让多达 5,000 个账号只用每次刷新一次请求就合并成一个订阅源,所有套餐都允许每秒 20 次请求。它只需一个 API 密钥,从不要求你的 X 登录信息,所以你的任何账号都不会有风险。

#我们如何核实

2026 年 11 月 2 日,我们请求了 x.com/NASA/rss,得到的是 X 的网页而不是订阅源;我们阅读了 X 的价格页面和它 10 月 23 日关于 expansions 计费的通知,还在 GitHub 上阅读了 RSSHub 的 X 路由源码,以及 n8n 的 Webhook 和 Respond to Webhook 节点的源码。请求、响应和脚本都遵循 Sorsa API 线上的 API 参考;这一轮我们没有测试用的密钥,所以响应展示的是文档里的字段和示例值。

Sorsa API 团队撰写。 我们开发和运营 Sorsa API,这是只读的 Twitter/X API 与爬虫,2022 年起运行于生产环境(前身为 TweetScout API),累计处理请求超 60 亿次。 关于 Sorsa API

更新内容
  • 首次发布。