PythonでURLからJSONファイルを読み込んで解析する

PythonでURLからJSONを読み込むと、リモートレスポンスをすぐに使えるデータに変換できます。urllib.requestでレスポンスを取得し、ボディをjson.loadsに渡すことで辞書またはリストを得られます。このパターンはパブリックAPI、ホストされた設定ファイル、コードとは独立して変化するデータセットに適しています。

PythonでURLからJSONを読み込むサンプル

出力:

ここに出力が表示されます...

出力:

Squad: Super Hero Squad
Members: 3

このサンプルの動作説明

  1. urllib.request.urlopen(url)は同期GETを送信してHTTPレスポンスを返します。withブロックが終了後に接続を閉じます。
  2. response.read()はボディをraw bytesとして返します。json.loadsはbytesを直接受け付けるため、手動で.decode("utf-8")する必要はありません。
  3. 解析によりJSONオブジェクトがPythonのdictに変換され、data["squadName"]でインデックスアクセスし、len(data["members"])で件数を取得できます。

このランタイムではurlopenは同期的なので、多くのチュートリアルがネットワーク呼び出しを非同期コードで包んでいますが、awaitasyncioも必要ありません。

json.load と json.loads の違い

どちらもJSONをPythonオブジェクトに逆シリアル化しますが、読み取り元が異なります。

関数入力使用するタイミング
json.loads(...)strまたはbytesの値response.read()のようにコンテンツがすでにメモリにある場合
json.load(...)ファイルまたはストリームオブジェクトopen(path)やレスポンス自体のような開いたハンドルがある場合

ここではjson.loads(response.read())json.load(response)は同じdictを生成します。よくあるエラーは型の誤り:json.loadに文字列、またはjson.loadsにファイルオブジェクトを渡すとAttributeErrorTypeErrorが発生します。

Run DetailsでJSONリクエストを確認する

プログラム終了後にRun Detailsを開きます。スクリプトが行ったネットワークリクエストと、実行のためにランタイムが読み込んだパッケージを分けて表示します。

Run Detailsのシグナル意味
Requestsにステータス200でraw.githubusercontent.comへのGETが表示されるJSONファイルがネットワーク経由で正常に取得された
リクエストの時間(ms)解析とは別に、取得自体にかかった時間
リクエストが403/404、ブロック、または失敗URLに到達できないかCORSが無効で、コードに到達しなかった
Packagesが空(“No runtime packages”)jsonurllibは標準ライブラリのため何もインストールされなかった

Packagesセクションが空なのは、このアプローチがインストール不要であることの証拠です。beautifulsoup4pandasをインポートする例では、ウェブスクレイピングの例のようにここにパッケージが表示されます。標準ライブラリでJSONを読む場合は何も表示されません。

ブラウザでURLから読み込む場合、エンドポイントがCORSヘッダーを送信する場合にのみ機能します。上記のfixtureはGitHub rawのCORS対応ファイルです。そのヘッダーのないクロスオリジンURLはコード実行前にブロックされ、JSONDecodeErrorではなく失敗したリクエストとして表示されます。

URLからJSONを読み込む際のよくある間違い

間違い:レスポンスストリームを2回読み込む。

誤り:

raw = response.read()
data = json.loads(response.read())

正しい:

data = json.loads(response.read())

なぜ起きるか:urlopenは一度だけ消費できるストリームを返すため、2回目のread()は空のbytesを返します。

間違い:解析したJSON配列に.get()を呼び出す。

誤り:

data = json.loads(response.read())
first = data.get("name")

正しい:

data = json.loads(response.read())
first = data[0] if isinstance(data, list) else data["name"]

なぜ起きるか:トップレベルのJSON配列はPythonのリストに解析され、.getがないためインデックスアクセスが必要です。

エラー処理と欠損キー

urlopenは4xxや5xxレスポンスに対してHTTPErrorを発生させますが、エラーオブジェクトはまだ読み取り可能なため、try/exceptでキャッチすることでJSONエラーボディを解析できます:

from urllib.error import HTTPError

try:
    with urllib.request.urlopen(url) as response:
        data = json.loads(response.read())
except HTTPError as err:
    data = json.loads(err.read())

省略可能なフィールドにはdata.get("key")を使用してKeyErrorの代わりにNoneを返します。標準ライブラリのjsonモジュールは信頼できない入力を安全に解析するため、JSONの読み込みにeval()を使用しないでください。

FAQ

PythonでJSONファイルを読み込むには?

リモートファイルの場合はurllib.request.urlopen(url)で開き、bytesをjson.loadsに渡します。ディスク上のファイルにはwith open(path) as f: data = json.load(f)を使います。どちらもPythonのdictまたはリストを返します。

json.loadとjson.loadsの違いは?

json.loadはファイルまたはストリームオブジェクトから読み込み、json.loadsstrまたはbytesの値から読み込みます。末尾の「s」はstringを意味します。コンテンツがすでにメモリにある場合はloadsを使用してください。

PythonでJSONを読み込むにはrequestsライブラリが必要ですか?

いいえ。urllib.requestjsonはどちらも標準ライブラリなので、インストールなしでJSONの取得と解析ができます。requestsライブラリは.json()ヘルパーやセッション管理などの利便性を追加しますが、サードパーティの依存関係です。

PythonにおけるJSONとは?

JSONは構造化データのテキスト形式です。PythonはJSONオブジェクトをdictに、配列をリストに、プリミティブをstrintfloatboolNoneにマップします。これがjson.loadsが解析後に返すものです。