PythonでWeb APIのJSONを取得してCSVに保存する|requests入門

「Web APIからデータを取ってこられると便利」と聞いて、試してみたものの、返ってきたのは { と [ だらけの文字の山。ここからどうやって表にすればいいの……と手が止まってしまうこと、ありますよね。

私も最初は、APIの結果をブラウザで開いて、必要なところを手でスプレッドシートにコピーしていました。件数が10件を超えたあたりで、「これはPythonにやってもらおう」と決めたんです。

💡
この記事で解決すること

Pythonの requests ライブラリでWeb APIからJSONを取得し、必要な列だけを選んでCSVに保存するスクリプトを作ります。
Excelで開いても文字化けしにくい保存方法と、失敗したときに止まり続けない書き方もあわせて紹介します。


この記事の前提

  • Pythonがインストールされていて、.py ファイルを実行できる(まだの方は Python入門|環境構築と最初の1本を動かす からどうぞ)
  • 練習用に、誰でも使えるダミーデータのAPI「JSONPlaceholder」を使います。APIキーは不要です
  • この記事のコードは、Debian 13 の検証環境(Python 3.13.5、仮想環境に入れた requests 2.34.2)で動作を確認しています
  • Linux(Debian / Ubuntu など)では、仮想環境(venv)を作ってから進めます(手順1を参照)

用語メモ
用語 ざっくりした意味
Web API URLにアクセスすると、データを決まった形で返してくれる窓口
JSON {"id": 1, "title": "…"} のような、名前と値の組でデータを表す書き方
requests PythonからWebにアクセスするときによく使われるライブラリ
CSV 値をカンマで区切って並べた表のファイル。Excelやスプレッドシートで開ける

結論:「取得 → チェック → 変換 → 保存」の4ステップ

やることは、次の4つです。

  1. requests.get() でAPIにアクセスする
  2. raise_for_status() で、失敗の返事ではないか確かめる
  3. .json() で、JSONをPythonのリスト(辞書の集まり)に変換する
  4. csv.DictWriter で、必要な列だけCSVに書き出す

APIのデータをCSVに保存するまでの流れ

手順1:requests をインストールする

requests はPythonに最初から入っていないので、追加します。ターミナル(Windowsならコマンドプロンプトや PowerShell)で次を実行してください。

  • 1
pip install requests

「pip が見つかりません」と出る場合は、python -m pip install requests と書くと動くことがあります。Windowsなら py -m pip install requests、Macなら python3 -m pip install requests も試してみてください。

Linux(Debian / Ubuntu)の場合

Debian や Ubuntu では、そのまま pip install requests を実行すると、pip や python のコマンドが見つからないか、pip を入れても「error: externally-managed-environment」というエラーで止まります。OSが使うPythonを守るための仕組みです。

Debian 13で pip install requests を実行すると externally-managed-environment で止まる

この場合は、作業用のフォルダに仮想環境(venv)を作ってから入れます。

  • 1
  • 2
  • 3
  • 4
sudo apt install python3-venv
python3 -m venv .venv
source .venv/bin/activate
pip install requests

行頭に (.venv) と出ていれば準備完了です。この中では python と pip がそのまま使えます。次にターミナルを開いたときは、フォルダに移動して source .venv/bin/activate を実行してから python fetch_to_csv.py を実行してください。

venvを作って有効化すると python・pip が使え、pip install が成功する

エラーメッセージには --break-system-packages という回避方法も出てきますが、OSのPythonを壊すおそれがあるので、この記事ではすすめません。


手順2:APIの返事をのぞいてみる

いきなりCSVにする前に、どんなデータが返ってくるか見ておきましょう。今回使うのは、ダミーの投稿データを返すURL https://jsonplaceholder.typicode.com/posts です。

1件分は、次のような形をしています。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "(本文が入っています)"
}

これがリスト([ ])にたくさん並んで返ってきます。userId・id・title・body の4つの項目があるので、今回は本文の body を除いた3つをCSVにします。


手順3:JSONを取得してCSVに保存するコード

fetch_to_csv.py という名前で、次のコードを保存します。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
import csv

import requests

API_URL = "https://jsonplaceholder.typicode.com/posts"
OUTPUT_FILE = "posts.csv"


def fetch_posts(user_id):
    """APIからJSONを取得して、Pythonのリストとして返す"""
    response = requests.get(
        API_URL,
        params={"userId": user_id},  # URLの「?userId=1」の部分
        timeout=10,                  # 10秒待っても返事がなければ止める
    )
    response.raise_for_status()      # 404や500のときはここでエラーにする
    return response.json()


def save_csv(rows, path):
    """辞書のリストをCSVに保存する"""
    fieldnames = ["id", "userId", "title"]
    with open(path, "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(rows)


def main():
    try:
        posts = fetch_posts(user_id=1)
    except requests.exceptions.RequestException as e:
        print(f"取得に失敗しました: {e}")
        return

    save_csv(posts, OUTPUT_FILE)
    print(f"{len(posts)}件を {OUTPUT_FILE} に保存しました")


if __name__ == "__main__":
    main()

実行すると「10件を posts.csv に保存しました」と表示され、同じフォルダに次のようなCSVができます(Debian 13 の検証環境で確認)。

id userId title
1 1 sunt aut facere repellat provident occaecati excepturi optio reprehenderit
2 1 qui est esse
… … …

できた posts.csv を LibreOffice Calc で読み込んで表にしたもの(Debian 13 の検証環境)

コードのポイント

params でURLの「?」以降を作る

params={"userId": user_id} と書くと、requests がURLの後ろに ?userId=1 を付けてくれます。自分で文字をつなげるより、書き間違いが減って安心です。

timeout は必ず付ける

requests の公式ドキュメントでは、本番のコードではほぼすべてのリクエストに timeout を付けるべきだとされています。付けないと、相手が返事をしないときにいつまでも待ち続けてしまうからです。

raise_for_status で失敗を見逃さない

ここは見落としやすいポイントです。.json() が成功しても、リクエストが成功したとは限りません。サーバーはエラーのときもJSONで理由を返すことがあるからです。raise_for_status() を呼ぶと、404や500などのときに HTTPError という例外(=処理を止めるエラー)を出してくれます。試しに存在しないURL(/posts/99999)にアクセスすると、この記事のコードでは「取得に失敗しました: 404 Client Error: Not Found for url: …」と表示されて終わります。

except で requests の失敗をまとめて受け止める

通信できない、時間切れ、404など、requests が出す例外はすべて requests.exceptions.RequestException の仲間です。これを except で受けておけば、失敗したときにメッセージを出して終われます。
ただし、受け止めるのは requests の失敗だけです。CSVに書き出すときの ValueError などはこの except では受けられず、エラー表示(Traceback)で止まります。

CSVは newline="" と utf-8-sig で開く

Pythonの公式ドキュメントでは、csvモジュールでファイルを開くときは newline="" を指定するよう案内されています。指定しないと、主にWindowsで空行が1行おきに入ることがあります。encoding="utf-8-sig" は、ExcelでCSVを開いたときに日本語が文字化けしにくくするための指定です。

extrasaction="ignore" は、fieldnames にない項目(今回は body)を無視する設定です。これがないと、余分な項目があるだけでエラーになります。

自分の使いたいAPIに合わせて書き換えるときは、AIにこう頼むとスムーズです。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
次のPythonコードは、requestsでWeb APIのJSONを取得してCSVに保存するものです。

【やりたいこと】
- 取得先のAPIを(APIのURL)に変えたい
- 返ってくるJSONの例は下に貼ります。この中の(欲しい項目名)だけをCSVの列にしたい
- timeout と raise_for_status、utf-8-sig での保存はそのまま残してください
- APIキーが必要な場合は、コードに直接書かず環境変数から読む形にしてください

【返ってくるJSONの例】
(1件分だけ貼る。個人情報は伏せる)

【今のコード】
(fetch_to_csv.py を貼る)

つまずきやすい点

私が最初につまずいたのは、APIによってはJSONのいちばん外側がリストではないことでした。{"items": [ … ]} のように、辞書の中にリストが入っている形だと、そのままでは writerows に渡せません。この場合は response.json()["items"] のように、リストの部分を取り出してから渡します。

症状 原因 対処
ModuleNotFoundError: No module named 'requests' requests が入っていない、または別のPythonに入れた python -m pip install requests で、実行するPythonに入れる。Linuxはvenvを有効にしてから pip install requests(行頭に (.venv) が出ているか確認)
ValueError: dict contains fields not in fieldnames JSONに fieldnames にない項目がある。このエラーは記事の except では受けられず、そこで止まる extrasaction="ignore" を付ける。止まったときは見出し行だけの posts.csv が残るので、直したら作り直す
CSVをExcelで開くと日本語が化ける 文字コードの指定がない encoding="utf-8-sig" で保存する
CSVに空行が1行おきに入る(主にWindowsで起きる) newline="" がない open に newline="" を付ける
処理がずっと終わらない timeout がなく、相手の返事を待ち続けている timeout=10 などを付ける
「取得に失敗しました: Expecting value: line 1 column 1 (char 0)」と出る(JSONDecodeError) 返事がJSONではない(HTMLのページなど)。今のコードでは except で受け止めて、このメッセージになる URLが正しいか確認し、print(response.text[:300]) で中身を見る
AttributeError: 'str' object has no attribute 'get' JSONのいちばん外側がリストではなく辞書 response.json()["items"] のように、リストの部分を取り出して渡す

エラーが出たら、メッセージをそのままAIに渡すのが早いです。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
Pythonの requests でAPIからJSONを取得し、csv.DictWriter でCSVに保存しようとしたところ、
次のエラーが出ました。

【エラー全文】
(Traceback から最後の行までを貼る)

【コード】
(ここに貼る。APIキーやトークンが入っている場合は必ず伏せる)

【返ってきたJSONの先頭】
(print(response.text[:300]) の結果を貼る)

原因と、直したコードを教えてください。

まとめ

  • requests.get(URL, params=…, timeout=…) で取得し、raise_for_status() で成否を確かめる
  • .json() でPythonのリストにして、csv.DictWriter で必要な列だけ書き出す
  • CSVは newline="" と encoding="utf-8-sig" で開くと、空行や文字化けを防ぎやすい

次の一歩は、user_id=1 の部分を 2 や 3 に変えて実行し、CSVの中身が変わるのを確かめてみることです。そこまでできたら、自分が使いたいAPIに差し替える準備は完了ですよ。


関連する本・記事