本文へスキップ

Twitter画像スクレイパー:すべての画像をダウンロード

Twitter画像スクレイパーは、アカウントや検索結果から写真付きの投稿を見つけ、各写真のpbs.twimg.comのリンクを読み取り、name=origを付けてダウンロードします。Xが返す素のリンクは幅1,200ピクセルまでだからです。

要点: ログアウト状態のx.comでは、プロフィールの最新の投稿が数件しか見えないため、ブラウザ拡張機能やダウンローダーは、あなたのログインやセッションCookieを通じてXを読み取ります。アカウントを危険にさらさずにすべての画像を集めるなら、Sorsa APIを使います。filter:imagesを付けたPOST /search-tweetsは、1リクエストで約20件の画像付き投稿を、1,000件あたり$0.07からで返し、100回分の無料リクエストで最初の2,000件をまかなえます。

エンドポイント
POST /search-tweets
ページサイズ
1リクエストあたり約20投稿
価格
1,000件あたり$0.07から

#Twitter画像スクレイパーの仕組み

Twitter画像スクレイパーは、何の上で動いていようと、必ず同じ3つのことを行います。1件の投稿なら、どれも要りません。投稿のリンクを無料のメディアダウンローダーに貼り付け、各写真をフルサイズで保存するだけです。数百枚、数千枚の画像になると、作業は次のようになります。

  1. 写真付きの投稿を見つけます。 Xの検索コマンドfilter:imagesは、画像が1枚以上ある投稿だけを残します。from:で1つのアカウントに絞ることも、任意のトピックで検索することもできます。

  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か、キー1つで使えるTwitter/XデータAPIです。

Twitterのスクレイピング方法のガイドでは、すべてのルートと、それぞれが壊れる原因を解説しています。おすすめのTwitterスクレイパーの比較記事では、ツールごとの料金を比べています。

#Xアカウントの全画像をダウンロードする

Twitter(X)API兼スクレイピングツールであるSorsa APIは、アカウントの画像付き投稿を、各写真のリンクを含むJSONとして、1リクエストあたり約20投稿、2006年までさかのぼって返します。ハンドルから元のファイルが入ったフォルダまで、4ステップです。

  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. カーソルをたどります。 カーソルは次のページの開始位置を示すトークンです。空で返るまで、next_cursorを次のリクエストのボディに入れて送り返します。

    From a search query to original-size image files 1. クエリ from:account filter:images 1アカウントかテーマ 2. 検索ページ /search-tweets 約20件 リクエストごと 3. 写真リンク type: photo pbs.twimg.com 投稿あたり1~4枚 4. 保存 name=orig 元ファイル <post id>_1.jpg 空になるまでnext_cursor + images.csvに1行
    各リクエストは約20件の画像付き投稿と次のページへのカーソルを返します。すべての写真のリンクが、オリジナルサイズのファイルとimages.csvの1行になります。
  4. 各写真を元のサイズで保存します。 スクリプトはphotoエンティティだけを残し、各リンクをname=origに書き換え、ファイルを<post id>_<n>.jpgという名前にします。すべてのファイルをimages.csvに記録し、ページごとにカーソルを保存するので、止まった実行は続きから再開できます。

    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は1秒間に20を超えるリクエストを送ったことを意味し、スクリプトは1秒待って再試行します。すでにディスクにあるファイルはスキップされるので、同じコマンドをもう一度実行すれば、新しい画像だけが追加されます。

知っておきたいこと

引用やリツイートには、他人の写真が含まれています。写真はquoted_status.entitiesとretweeted_status.entitiesにあり、スクリプトはこれらを対象外にしています。残したい場合は、この2つのリストをphoto_urlsに追加してください。先に作業の規模を把握するには、GET /info(新しいタブで開きます)が、アカウントが投稿した写真と動画の数であるmedia_countを返します。

検索ではなく、アカウントのタイムラインを投稿ごとにたどるには、ハンドルをPOST /user-tweets(新しいタブで開きます)に送ります。同じツイートオブジェクトが1リクエストで20件返るため、photo_urlsはそのまま画像を読み取れます。ただしテキストのみの投稿も含まれるので、必要なリクエスト数は増えます。

同じスクリプトで、どんなトピックの画像も集められます。変えるのはクエリだけです。filter:imagesはXのあらゆる検索コマンドと組み合わせられ、検索は2006年までさかのぼれます。

目的クエリ
ハッシュタグ#nightphotography filter:images
1か月間のキーワード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
  • 1か月間のキーワード

    クエリ
    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

大きなトピックは、月ごとに日付ウィンドウを分け、それぞれ専用のフォルダで実行します。1回の実行が短く済み、失敗しても繰り返しやすく、ウィンドウはキー1つで並行して実行できます。どのプランでも毎秒20リクエストまでです。orderをpopularにするとXの上位投稿から、latestにすると新しい順に取得します。すべてのコマンドを例付きで解説しているのは、Twitter検索コマンドのガイドです。

ハッシュタグの画像は人々が投稿するたびに変わるので、監視ジョブでは最新のウィンドウをスケジュールで再実行します。スクリプトは取得済みのファイルをスキップし、新しいものだけを記録するため、再実行のたびに前回以降に投稿された画像だけが追加されます。

#すべての画像を元のサイズで取得する

Xはアップロードされた画像をそのまま保持していますが、配信するのは小さなコピーで、手に入るコピーはリンクのnameの値だけで決まります。2026年11月2日に、公開されている4枚の画像をXが提供するすべてのサイズでリクエストし、返ってきたものを測定しました。以下の段階は、2026年10月下旬に投稿された写真のものです。

Width X returned for one photo, by name value 1枚の写真で返った幅(リンクのname値別) name=thumb 150 px7 KB name=small 680 px53 KB 素のリンク (medium) 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リクエストは消費されません。かかるのは投稿を見つける部分だけです。

#コードなしで画像を一括ダウンロードする

コードなしの場合、作業は2つに分かれます。画像のリンクをエクスポートし、ファイルをダウンロードします。

  1. 最近の投稿のリンクをエクスポートします。 無料のツイートエクスポートツールは、アカウントの最新の投稿を、media_urls列付きのCSV、Excel、JSONで保存します。「メディア付きの投稿のみ」をオンにしてください。

  2. アカウント全体や検索結果をエクスポートします。 APIプレイグラウンドで、キーとfrom:northfield_photo_lab filter:imagesのようなクエリを指定してPOST /search-tweetsを実行します。カーソルは自動でたどられ、すべてのページがCSVまたはJSONにエクスポートされます。

  3. リンクを元のサイズに変換します。 画像のリンクを下のコンバーターに貼り付け、返ってきた元のサイズのリンクをコピーします。

  4. リストをダウンロードします。 URLのリストを受け付けるダウンロードマネージャーにリンクを読み込めば、ファイルを1回の実行で保存できます。

    pbs.twimg.comのリンクを何件でも、1行に1つずつ、またはCSVの列からそのまま貼り付けられます。ブラウザの外には何も送信されません。

#画像の一括ダウンロードにかかるコスト

Sorsa APIでは、画像付き投稿のコストは1,000件あたり$0.07からです。約20投稿の検索ページが、何枚の写真を含んでいても1リクエストだからです。公式X APIでは、画像のリンクはメディアの展開(expansion)を通じてしか得られません。2026年11月22日以降、Xは展開された画像1枚ごと(新しいタブで開きます)に、投稿の読み取り$0.005に加えて$0.005を課金します。画像1枚の投稿1,000件で$10.00、1件に4枚付いていると$25.00です。

投稿を取得する方法は、価格だけでなく、必要なものも違います。並べて比べてみましょう。

投稿を取得する方法必要なもの画像付き投稿1,000件あたりのコスト
Sorsa APIAPIキー1つ$0.07から
公式X API開発者アカウントとOAuth2026年11月22日以降は$10.00(投稿と画像1枚)
拡張機能とダウンローダーあなたのXログインまたはセッションCookie無料またはサブスクリプション。ただしアカウントにリスクあり
  • Sorsa API

    必要なもの
    APIキー1つ
    画像付き投稿1,000件あたりのコスト
    $0.07から
  • 公式X API

    必要なもの
    開発者アカウントとOAuth
    画像付き投稿1,000件あたりのコスト
    2026年11月22日以降は$10.00(投稿と画像1枚)
  • 拡張機能とダウンローダー

    必要なもの
    あなたのXログインまたはセッションCookie
    画像付き投稿1,000件あたりのコスト
    無料またはサブスクリプション。ただしアカウントにリスクあり
画像付き投稿

公式X API

$10,000

画像付き投稿100万件の場合

画像1枚の投稿1件あたり$0.010、11月22日から

Sorsa API

$70から

画像付き投稿100万件の場合

1,000件あたり$0.07から

コストは140分の1

公式X APIの従量課金の料金(新しいタブで開きます)も、請求サイクルあたり300万件の投稿読み取りで止まり、それを超えるとXが販売するのはEnterprise契約だけです。Sorsa APIでは、100回分の無料リクエストで約2,000件の画像付き投稿をまかなえます。失敗したリクエストには課金されず、プランの1,000リクエストあたりの単価がすべてのエンドポイントに適用されます。

所要時間はレート制限から決まります。全プランでキー1つにつき毎秒20リクエストです。100万件の画像付き投稿は5万リクエスト、日付ウィンドウを並行して実行すれば約42分です。その後、画像ファイルはあなたの回線速度で、Xのサーバーから直接ダウンロードされます。

#特定の投稿の画像を取得する

すでに手元にある投稿の画像を取得するには、リンクまたはIDをPOST /tweet-info-bulk(新しいタブで開きます)に送ります。1リクエストで最大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"]}'

1件の投稿なら、tweet_linkを付けてPOST /tweet-info(新しいタブで開きます)に送ります。各写真のリンクを同じorig関数に通せば、フルサイズのファイルが得られます。公式X APIでは、同じ検索にベアラートークンと、expansions=attachments.media_keys、media.fields=urlが必要で、11月22日以降は結果に含まれる画像1枚ごとに$0.005が加算されます。

動画とGIFは、同じentitiesのリストに、それぞれのtypeで入ってきます。MP4ファイルと画質のバリエーションの取得にはもう1ステップ必要で、APIでTwitterメディアをダウンロードのガイドで解説しています。メディア列付きの全文エクスポートについては、ユーザーの全ツイートをダウンロードする方法を参照してください。

#最初の画像ダウンロードを始める

Xアカウントや検索結果のすべての画像をフルサイズでダウンロードするなら、Sorsa APIを使います。1リクエストで約20件の画像付き投稿を1,000件あたり$0.07から取得でき、Xアカウントにリスクはありません。スクリプトをコピーしてキーを入れれば、100回分の無料リクエストで最初の2,000件の画像付き投稿をダウンロードできます。

#よくある質問

無料のTwitter画像スクレイパーはありますか?

小規模な作業ならあります。無料のメディアダウンローダーは、1件の投稿のすべての写真をフルサイズで保存し、無料のツイートエクスポートツールは、アカウントの最新の投稿を画像リンク付きでエクスポートします。アカウント全体や検索結果には、Sorsa APIの新規アカウントごとに100回分の無料リクエストが付き、約2,000件の画像付き投稿に使えます。カードは不要で、リクエストの有効期限もありません。

Twitterの画像をスクレイピングするにはどのAPIを使うべきですか?

Sorsa APIです。filter:imagesを付けたPOST /search-tweetsは、1リクエストで約20件の画像付き投稿を1,000件あたり$0.07から返します。公式X APIでは、2026年11月22日以降、画像1枚の投稿が1,000件あたり$10.00です。Sorsa APIは、開発者アカウントやOAuthの代わりにAPIキー1つで使え、2006年までさかのぼって検索でき、全プランで毎秒20リクエストに対応し、Xのログイン情報も求めません。

Twitterから画像をスクレイピングするのは合法ですか?

はい。研究やアーカイブのために公開画像をスクレイピングすることは合法で、一般的に行われています。各画像の著作権は作成した人に残るため、ファイルを公開したり販売したりする前に、所有者の許可を得てください。Sorsa APIはXのログイン情報を求めないので、ご自身のXアカウントは関与しません。

非公開のTwitterアカウントの画像をダウンロードできますか?

できません。非公開アカウントの投稿は、承認したフォロワーにしか見えないため、Sorsa APIを含むどのAPIでも取得できず、そのリクエストに課金されることもありません。そうでないと主張するツールは、そのアカウントをフォローしているアカウントのログインを通じてXを読み取っています。

アカウントの持ち主は、自分の画像をダウンロードされたことが分かりますか?

分かりません。Xは、投稿や画像が閲覧またはダウンロードされたことを誰にも通知しません。Sorsa APIでは、リクエストはSorsa APIからXへ送られるため、Xも持ち主も誰が要求したのかを知ることはなく、あなた自身のXアカウントも関与しません。

Twitterの動画やGIFも同じ方法でダウンロードできますか?

一部は可能です。動画とGIFは写真と同じentitiesのリストに、それぞれのtypeで入っていて、サムネイルはpreviewにあるので、検索とカーソルは同じように機能します。再生可能なMP4ファイルとその画質のバリエーションにはもう1ステップ必要で、その手順はAPIでTwitterメディアをダウンロードするガイドで解説しています。

#検証の方法

2026年11月2日に、pbs.twimg.comの4枚の公開画像を、すべてのサイズと形式でリクエストし、各レスポンスの幅とファイルサイズを記録しました。また、Sorsa API上で動く当社の無料メディアダウンローダーに公開投稿を通して、返ってくる写真のリンクを確認しました。両方のスクリプトのリンク処理とファイル保存は、APIの代役と本物のpbs.twimg.comのファイルを使って検証しました。エンドポイント、フィールド、ページサイズは稼働中のSorsa APIリファレンスに基づき、公式X APIの価格は、docs.x.comと2026年10月23日のXのお知らせから、同日に読み取ったものです。表示しているAPIレスポンスは、リファレンスをもとに作成した短縮版の例です。

Sorsa APIチーム 読み取り専用のX(Twitter)APIとスクレイピングツール、Sorsa APIを開発・運営しています。2022年から本番稼働し(旧TweetScout API)、累計60億リクエスト以上を処理しています。 Sorsa APIについて