跳到正文

Twitter 图片爬虫:下载每一张图片

Twitter 图片爬虫会找出账号或搜索结果中带照片的帖子,读取每张照片的 pbs.twimg.com 链接,并用 name=orig 下载,因为 X 提供的不带参数的链接最宽只有 1,200 像素。

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

简短回答: 未登录时,x.com 只显示一个主页最新的几条帖子,所以浏览器扩展和下载器都是通过你的登录或会话 cookie 来读取 X。想在不危及账号的前提下收集每一张图片,可以用 Sorsa API:带 filter:images 的 POST /search-tweets 每次请求返回约 20 条图片帖子,每 1,000 条 $0.07 起,100 次免费请求可以覆盖前 2,000 条。

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

#Twitter 图片爬虫是如何工作的

无论运行在什么上,每个 Twitter 图片爬虫都在做同样的三件事。如果只是一条帖子,这三件事你都不需要:把它的链接粘贴到免费的媒体下载工具里,就能以原始大小保存每张照片。如果是几百几千张图片,这项工作是这样的:

  1. 找出带照片的帖子。 X 的搜索运算符 filter:images 只保留至少带一张图片的帖子,可以用 from: 限定在某个账号,也可以针对任意话题。

  2. 读取每张照片的链接。 每张图片都放在 X 的媒体服务器上,链接形如 https://pbs.twimg.com/media/G4kQ2vXbIAAm7Rz.jpg。

  3. 下载原图。 请求那个链接时加上 ?format=jpg&name=orig;不加的话,X 发来的是一份适配 1,200 像素的副本。

各种工具的差别在第 1 步,也就是帖子从哪里来。x.com 对未登录的访客只显示单条帖子和一个主页最新的几条帖子,并把搜索导向登录页面。所以浏览器扩展、桌面下载器和命令行工具都要通过已登录的账号读取 X。它们自己的文档就是这么说的:一个广泛使用的批量下载器会要求你提供 auth_token cookie,并警告这可能导致账号被封禁。

只有 API 才能拿到整个账号的图片:要么是带开发者账号和 OAuth 的官方 X API,要么是只需一个密钥的 Twitter/X 数据 API。

如何抓取 Twitter指南涵盖了每条途径以及会被什么打断,最佳 Twitter 爬虫盘点则逐个为这些工具标价。

#下载 X 账号的每一张图片

Sorsa API 是一款 Twitter(X)API 与爬虫,它把账号的图片帖子以 JSON 返回,每张照片的链接都在其中,每次请求约 20 条帖子,可回溯到 2006 年。四步就能从一个用户名走到一个装满原始文件的文件夹。

  1. 获取密钥。 注册后从控制台复制 API 密钥。前 100 次请求免费,无需绑卡。

  2. 搜索账号的图片帖子。 把 from: 加上 filter:images 发给 POST /search-tweets(在新标签页打开),并把 order 设为 latest,让最新的排在前面。

    Terminal
    curl -X POST "https://api.sorsa.io/v3/search-tweets" \
      -H "ApiKey: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"query": "from:northfield_photo_lab filter:images",
           "order": "latest"}'
    
    Response200 OK
    {
      "tweets": [
        {
          "id": "1984213377309245441",
          "created_at": "2026-10-30T14:12:09Z",
          "full_text": "Fog over the north ridge this morning.",
          "likes_count": 412,
          "entities": [
            {
              "type": "photo",
              "link": "https://pbs.twimg.com/media/G4kQ2vXbIAAm7Rz.jpg",
              "preview": "https://pbs.twimg.com/media/G4kQ2vXbIAAm7Rz.jpg"
            },
            {
              "type": "photo",
              "link": "https://pbs.twimg.com/media/G4kQ2vZaQAEw9Lp.jpg",
              "preview": "https://pbs.twimg.com/media/G4kQ2vZaQAEw9Lp.jpg"
            }
          ],
          "user": {
            "username": "northfield_photo_lab",
            "display_name": "Northfield Photo Lab",
            "media_count": 1840
          }
        }
      ],
      "next_cursor": "DAADDAABCgABG8vZ0Qm__-oKAAIbuv3MBVvA0AgAAwAAAAIAAA"
    }
    
  3. 沿着 cursor 往下读。 cursor 是标记下一页从哪里开始的令牌:把 next_cursor 放进下一次请求的请求体里发回去,直到它返回为空。

    From a search query to original-size image files 1. 查询 from:account filter:images 一个账号或话题 2. 搜索页 /search-tweets 约 20 条推文 每次请求 3. 图片链接 type: photo pbs.twimg.com 每条推文 1 到 4 张 4. 保存 name=orig 原图文件 <post id>_1.jpg next_cursor,直到为空 + images.csv 中一行
    每次请求返回约 20 条图片帖子和指向下一页的 cursor;每个照片链接都变成一个原始尺寸的文件,以及 images.csv 中的一行。
  4. 以原始尺寸保存每张照片。 脚本只保留 photo 实体,把每个链接改写为 name=orig,并把文件命名为 <post id>_<n>.jpg。它把每个文件记录在 images.csv 里,并在每一页之后保存 cursor,所以中途停下的任务可以从断点继续。

    import csv
    import os
    import sys
    import time
    from urllib.parse import parse_qs, urlparse
    
    import requests
    
    API = "https://api.sorsa.io/v3/search-tweets"
    HEADERS = {"ApiKey": "YOUR_API_KEY"}
    
    
    def orig(url):
        """Rewrite a pbs.twimg.com image link to its original size."""
        u = urlparse(url)
        path = u.path
        fmt = parse_qs(u.query).get("format", ["jpg"])[0]
        if "." in path.rsplit("/", 1)[-1]:
            path, fmt = path.rsplit(".", 1)
        fmt = fmt.split(":")[0]
        return f"https://pbs.twimg.com{path}?format={fmt}&name=orig", fmt
    
    
    def photo_urls(tweet):
        """Original-size links of every photo in a post."""
        for e in tweet.get("entities") or []:
            if e.get("type") != "photo":
                continue
            for link in (e.get("link"), e.get("preview")):
                if link and "pbs.twimg.com/media/" in link:
                    yield orig(link)
                    break
    
    
    def post(body):
        while True:
            r = requests.post(API, headers=HEADERS, json=body, timeout=60)
            if r.status_code == 429:  # over 20 requests a second: wait
                time.sleep(1)
                continue
            r.raise_for_status()  # 403: no key, or no requests left
            return r.json()
    
    
    def download(query, folder):
        os.makedirs(folder, exist_ok=True)
        state = os.path.join(folder, "cursor.txt")
        body = {"query": query, "order": "latest"}
        if os.path.exists(state):  # resume a stopped run
            body["next_cursor"] = open(state).read().strip()
        log_path = os.path.join(folder, "images.csv")
        with open(log_path, "a", newline="", encoding="utf-8") as f:
            log = csv.writer(f)
            while True:
                page = post(body)
                for t in page.get("tweets", []):
                    for n, (url, fmt) in enumerate(photo_urls(t), 1):
                        path = os.path.join(folder, f"{t['id']}_{n}.{fmt}")
                        if os.path.exists(path):
                            continue
                        img = requests.get(url, timeout=60)
                        img.raise_for_status()
                        with open(path, "wb") as out:
                            out.write(img.content)
                        log.writerow([t["id"], t["created_at"], url, path])
                cursor = page.get("next_cursor")
                if not cursor:
                    break
                body["next_cursor"] = cursor
                with open(state, "w") as s:
                    s.write(cursor)
        if os.path.exists(state):
            os.remove(state)
    
    
    if __name__ == "__main__":
        download(sys.argv[1], sys.argv[2])
    

用一个搜索词和一个文件夹名运行它:python twitter_images.py "from:northfield_photo_lab filter:images" northfield_photo_lab。返回 403 表示密钥缺失或请求额度已用完;返回 429 表示一秒内超过 20 次请求,脚本会等一秒再重试。磁盘上已有的文件会被跳过,所以再次运行同一条命令只会补上新的图片。

须知

引用或转发带的是别人的照片:它们在 quoted_status.entities 和 retweeted_status.entities 里,而脚本把它们排除在外。想保留它们,就把这两个列表加进 photo_urls。想先估算任务规模,GET /info(在新标签页打开) 会返回账号的 media_count,也就是它发布过的照片和视频数量。

想逐条帖子遍历账号的时间线而不是搜索它,可以把用户名发给 POST /user-tweets(在新标签页打开)。它返回同样的推文对象,每次请求 20 条,所以 photo_urls 无需改动就能读取其中的图片;纯文字帖子也会一并返回,所以任务需要更多请求。

同一个脚本可以收集任意话题的图片:只需更换查询。filter:images 可以与所有 X 搜索运算符组合,搜索可回溯到 2006 年。

任务查询
一个话题标签#nightphotography filter:images
某个关键词在一个月内aurora filter:images since:2026-10-01 until:2026-11-01
只要热门图片#interiordesign filter:images min_faves:500
不含回复的账号from:northfield_photo_lab filter:images -filter:replies
同时查几个账号(from:northfield_photo_lab OR from:harborline_studio_co) filter:images
  • 一个话题标签

    查询
    #nightphotography filter:images
  • 某个关键词在一个月内

    查询
    aurora filter:images since:2026-10-01 until:2026-11-01
  • 只要热门图片

    查询
    #interiordesign filter:images min_faves:500
  • 不含回复的账号

    查询
    from:northfield_photo_lab filter:images -filter:replies
  • 同时查几个账号

    查询
    (from:northfield_photo_lab OR from:harborline_studio_co) filter:images

对于大话题,每个月一个日期窗口,各放进自己的文件夹运行。每次运行都不长,失败的那次也容易重来,而这些窗口还能在一个密钥上并行运行,所有套餐都是每秒最多 20 次请求。把 order 设为 popular 可以先看 X 的热门帖子,设为 latest 则是最新的。每个运算符及其示例,都在 Twitter 搜索运算符指南里。

话题标签下的图片会随着人们发帖而变化,所以监控任务要按计划重新运行最新的窗口。由于脚本会跳过已有的文件,并且只记录新文件,每次重跑只会补上自上次以来发布的图片。

#获取每一张图片的原始尺寸

X 完整保存每一次上传,但提供的是较小的副本,你拿到哪个副本,只取决于链接里的 name 值。2026 年 11 月 2 日,我们以 X 提供的所有尺寸请求了四张公开图片,并测量了返回的结果;下面的阶梯是 2026 年 10 月下旬发布的一张照片。

Width X returned for one photo, by name value 一张图片按链接中 name 值返回的宽度 X name=thumb 150 px7 KB name=small 680 px53 KB 无参数链接 (中等) 1,200 px143 KB name=large 2,048 px351 KB name=4096x4096 4,096 px1.03 MB name=orig 8,000 px1.95 MB 上传的文件为 8,000 × 4,501 px。在 pbs.twimg.com 实测,2026 年 11 月 2 日。
只有 name=orig 返回了上传的文件:宽 8,000 像素、1.95 MB,而不带参数的链接是 1,200 像素、143 KB。
  • 不带参数的链接是 medium 副本。 没有 name 的 pbs.twimg.com 链接,返回的是适配 1,200 像素的图片。
  • large 最大到 2,048 像素,4096x4096 最大到 4,096。 比这更小的上传,会以它自己的尺寸返回。
  • name=orig 返回上传的原文件。 对上面那张照片来说,就是 8,000 × 4,501 像素,像素数是不带参数链接的 44 倍。
  • format=png 不会增加任何细节。 它把 JPG 上传重新编码成大好几倍的 PNG,而且对 JPG 使用 format=png&name=orig 会返回 404,所以请保留链接原有的格式。

从 pbs.twimg.com 下载只是一次普通的 HTTP 请求,不需要密钥,所以文件本身不消耗 API 请求:只有查找帖子才消耗。

#不写代码批量下载图片

不写代码的话,这项工作分成两步:先导出图片链接,再下载文件。

  1. 导出近期帖子的链接。 免费的导出推文工具会把账号最新的帖子保存为带 media_urls 列的 CSV、Excel 或 JSON;请打开“仅含媒体的帖子”。

  2. 导出整个账号或搜索结果。 在 API Playground 里,用你的密钥和 from:northfield_photo_lab filter:images 这样的查询运行 POST /search-tweets;它会替你沿着 cursor 往下读,并把每一页导出为 CSV 或 JSON。

  3. 把链接变成原图。 把图片链接粘贴到下面的转换器里,复制它返回的原始尺寸链接。

  4. 下载这份列表。 把链接载入任意接受 URL 列表的下载管理器,它一次运行就能保存全部文件。

    粘贴任意数量的 pbs.twimg.com 链接,每行一个,或直接从 CSV 列粘贴;内容不会离开你的浏览器。

#批量下载图片要花多少钱

在 Sorsa API 上,图片帖子每 1,000 条 $0.07 起,因为约 20 条帖子的一页搜索结果就是一次请求,不管其中有多少张照片。在官方 X API 上,图片链接只能通过它的媒体扩展字段获得。从 2026 年 11 月 22 日起,X 对每张展开的图片计费(在新标签页打开) $0.005,叠加在 $0.005 的帖子读取之上:带一张图片的帖子每 1,000 条 $10.00,每条带四张时是 $25.00。

并排来看,获取这些帖子的不同方式,对你的要求和价格一样各不相同。

获取帖子的方式需要什么每 1,000 条图片帖子的成本
Sorsa API一个 API 密钥$0.07 起
官方 X API开发者账号和 OAuth自 2026 年 11 月 22 日起 $10.00(帖子加一张图片)
扩展和下载器你的 X 登录或会话 cookie免费或订阅制,账号有风险
  • Sorsa API

    需要什么
    一个 API 密钥
    每 1,000 条图片帖子的成本
    $0.07 起
  • 官方 X API

    需要什么
    开发者账号和 OAuth
    每 1,000 条图片帖子的成本
    自 2026 年 11 月 22 日起 $10.00(帖子加一张图片)
  • 扩展和下载器

    需要什么
    你的 X 登录或会话 cookie
    每 1,000 条图片帖子的成本
    免费或订阅制,账号有风险
图片帖子数

官方 X API

$10,000

100 万 条图片帖子

带一张图片的帖子每条 $0.010,自 11 月 22 日起

Sorsa API

$70 起

100 万 条图片帖子

每 1,000 条 $0.07 起

便宜 140 倍

官方 X API 的按量付费定价(在新标签页打开)同样在每个计费周期 300 万条帖子读取时封顶;超过之后,X 只出售 Enterprise 合同。在 Sorsa API 上,100 次免费请求可以覆盖约 2,000 条图片帖子,失败的请求从不收费,并且你所选套餐每 1,000 次请求的费率适用于所有接口。

时间由速率限制决定,所有套餐的每个密钥都是每秒 20 次请求。100 万条图片帖子需要 5 万次请求,在日期窗口并行运行时约 42 分钟;之后图片文件直接从 X 的服务器下载,速度取决于你的网络。

#获取指定帖子的图片

要获取你手上已有帖子的图片,可以把它们的链接或 ID 发给 POST /tweet-info-bulk(在新标签页打开):每次请求最多 100 条帖子,每 1,000 条 $0.014 起,返回的 entities 列表与搜索结果相同。

Terminal
curl -X POST "https://api.sorsa.io/v3/tweet-info-bulk" \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tweet_links": [
        "https://x.com/northfield_photo_lab/status/1984213377309245441",
        "1983850021164118016"]}'

单条帖子则带着 tweet_link 发给 POST /tweet-info(在新标签页打开)。把每张照片的链接交给同一个 orig 函数处理,就得到了原始大小的文件。在官方 X API 上,同样的查询需要 bearer 令牌加上 expansions=attachments.media_keys 和 media.fields=url,并且从 11 月 22 日起,返回结果中的每张图片还要加收 $0.005。

视频和 GIF 会在同一个 entities 列表里,以各自的 type 返回。它们的 MP4 文件和各种画质版本还要多一步,详见用 API 下载 Twitter 媒体指南;想要带媒体列的完整文本导出,请看如何下载用户的全部推文。

#开始你的第一次图片下载

要以完整尺寸下载 X 账号或搜索结果的每一张图片,可以用 Sorsa API:每次请求约 20 条图片帖子,每 1,000 条 $0.07 起,对你的 X 账号没有风险。复制脚本,填上你的密钥,100 次免费请求就能下载前 2,000 条图片帖子。

#常见问题

有免费的 Twitter 图片爬虫吗?

有,适合小任务。免费的媒体下载工具能以原始大小保存一条帖子里的每张照片,免费的导出推文工具能导出账号最新的帖子及其图片链接。针对整个账号和搜索,每个新的 Sorsa API 账号都有 100 次免费请求:约 2,000 条图片帖子,无需绑卡,请求也永不过期。

抓取 Twitter 图片应该用哪个 API?

用 Sorsa API:带 filter:images 的 POST /search-tweets 每次请求返回约 20 条图片帖子,每 1,000 条 $0.07 起,而官方 X API 从 2026 年 11 月 22 日起,带一张图片的帖子是每 1,000 条 $10.00。它只需一个 API 密钥,不需要开发者账号和 OAuth,搜索可回溯到 2006 年,所有套餐统一每秒 20 次请求,并且从不索要你的 X 登录信息。

从 Twitter 抓取图片合法吗?

是的。为研究或存档抓取公开图片,既合法又常见。每张图片的版权仍属于创作者,所以发布或出售这些文件之前,要先取得所有者的许可。Sorsa API 从不索要你的 X 登录信息,所以你的 X 账号不会被牵扯进来。

能下载私密 Twitter 账号的图片吗?

不能。受保护账号的帖子只对它批准的关注者可见,所以没有任何 API 会返回它们,Sorsa API 也不例外,对这类请求也不收费。声称能做到的工具,读取的是一个关注了该账号的账号的登录状态。

账号主人会看到我下载了他们的图片吗?

不会。X 不会告诉任何人他们的帖子或图片被查看或下载过。使用 Sorsa API 时,请求是从 Sorsa API 发往 X 的,所以无论 X 还是账号主人,都不会知道是谁请求的,你自己的 X 账号也没有参与其中。

能用同样的方式下载 Twitter 视频和 GIF 吗?

部分可以。视频和 GIF 与照片在同一个 entities 列表里,以各自的 type 出现,缩略图放在 preview 中,所以搜索和 cursor 的用法相同。可播放的 MP4 文件及其各种画质版本还要多一步,用 API 下载 Twitter 媒体的指南里有完整说明。

#我们如何核实

2026 年 11 月 2 日,我们以所有尺寸和格式从 pbs.twimg.com 请求了四张公开图片,并记录了每次返回的宽度和文件大小。我们还让一条公开帖子经过我们基于 Sorsa API 运行的免费媒体下载工具,查看它返回的照片链接。我们用一个 API 替身和真实的 pbs.twimg.com 文件,测试了两个脚本的链接处理和文件保存。接口、字段和每页大小来自线上的 Sorsa API 参考文档;官方 X API 的价格来自 docs.x.com 和 X 在 2026 年 10 月 23 日的通知,均在同一天查阅。文中展示的 API 响应是根据参考文档缩短的示例。

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

更新内容
  • 全文重写:实测的图片尺寸和 name=orig 规则,带文件日志、可续传的 Python 和 Node.js 脚本,话题标签和搜索查询,附链接转换器的免代码方案,以及包含 X 在 11 月 22 日起媒体计费的成本。
  • 首次发布。