「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つです。
-
requests.get()でAPIにアクセスする -
raise_for_status()で、失敗の返事ではないか確かめる -
.json()で、JSONをPythonのリスト(辞書の集まり)に変換する -
csv.DictWriterで、必要な列だけ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を守るための仕組みです。
この場合は、作業用のフォルダに仮想環境(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 を実行してください。
エラーメッセージには --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 |
| … | … | … |
コードのポイント
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に差し替える準備は完了ですよ。
関連する本・記事
- Pythonの準備から始めたい方へ:Python入門|環境構築と最初の1本を動かす
- 保存したCSVをまとめて集計したいなら:Pythonで毎朝のCSV集計を自動化する(pandas+定期実行)
- 複数のCSVを結合したいなら:Excel・CSVを毎回手で結合していませんか?Pythonで1分にする方法(重複削除・列名ゆれ対応)
- 毎朝自動で実行したいなら:毎朝の作業をPCを触らず動かしたい|cron入門(Pythonを定時実行)
- GASで同じことをしたいなら:GASでWeb APIを呼ぶ|UrlFetchAppとJSONの基本
- メンバーのKindle本の一覧:biliotechまわりのKindle著書リスト