note.nkmk.me

Python, Requestsの使い方

Date: 2018-07-12 / tags: Python, Requests

Pythonの標準ライブラリurllibを使うとURLを開くことができるが、サードパーティライブラリのRequestsを使うとよりシンプルに書ける。

サードパーティライブラリを自由にインストールできる環境であればurllibよりもRequestsを使うほうが楽。

ここではrequests.get()とその返り値であるResponseオブジェクトを中心に以下の内容について説明する。

  • Requestsのインストール
  • Requestsの基本的な使い方
    • requests.get()
    • Responseオブジェクト
      • url: url属性
      • ステータスコード: status_code属性
      • エンコーディング: encoding属性
      • レスポンスヘッダ: headers属性
      • テキスト: text属性
      • バイナリデータ: content属性
  • URLパラメータを指定: 引数params
  • リクエストヘッダ(カスタムヘッダ)を指定: 引数headers
  • リダイレクトの扱い
  • JSONデータを取得・保存
  • 画像やzipファイルなどをダウンロード

GETだけでなくPOSTDELETEなども含めたWeb APIを利用する際の使い方は以下の記事を参照。

スポンサーリンク

Requestsのインストール

pip(環境によってはpip3)でインストールできる。

$ pip install requests

Requestsの基本的な使い方

Requestsの基本的な使い方としてrequests.get()とその返り値であるResponseオブジェクトについて説明する。

requests.get()

requests.get()は名前の通りHTTPのGETメソッドに相当する。

ほかにrequests.post(), requests.delete()などもある。以下の記事を参照。

ここでは以下の例示用のドメインを使ってrequests.get()を試す。

get()の第一引数にURLを指定するとResponseオブジェクトが取得できる。get()のそのほかの引数については後述。

import requests

url = 'https://example.com/'

response = requests.get(url)

print(response)
# <Response [200]>

print(type(response))
# <class 'requests.models.Response'>

Responseオブジェクト

Responseオブジェクトの属性に様々な情報が格納されている。

url: url属性

url属性でアクセスしたURLを取得できる。リダイレクトされた場合については後述。

print(response.url)
# https://example.com/

ステータスコード: status_code属性

status_code属性でステータスコードを取得できる。

print(response.status_code)
# 200

ステータスコードの種類とその意味については以下のWikipediaのページを参照。

レスポンスヘッダ: headers属性

headers属性でレスポンスヘッダを取得できる。

print(response.headers)
# {'Content-Encoding': 'gzip', 'Accept-Ranges': 'bytes', 'Cache-Control': 'max-age=604800', 'Content-Type': 'text/html', 'Date': 'Thu, 12 Jul 2018 11:58:54 GMT', 'Etag': '"1541025663"', 'Expires': 'Thu, 19 Jul 2018 11:58:54 GMT', 'Last-Modified': 'Fri, 09 Aug 2013 23:54:35 GMT', 'Server': 'ECS (oxr/8313)', 'Vary': 'Accept-Encoding', 'X-Cache': 'HIT', 'Content-Length': '606'}

headers属性はCaseInsensitiveDictという型。基本的には辞書(dict型)だが、小文字と大文字を区別しないという特徴がある。

print(type(response.headers))
# <class 'requests.structures.CaseInsensitiveDict'>

print(response.headers['Content-Type'])
# text/html

print(response.headers['content-type'])
# text/html

print(response.headers['cOntEnt-typE'])
# text/html

[キー名]で値を取得すると存在しないキーの場合はエラーKeyErrorになるが、辞書と同様get()メソッドを使うと存在しないキーに対してデフォルト値が返される。

# print(response.headers['xxxxx'])
# KeyError: 'xxxxx'

print(response.headers.get('xxxxx'))
# None

エンコーディング: encoding属性

encoding属性でRequestsが推測したエンコーディングを取得できる。

print(response.encoding)
# ISO-8859-1

encoding属性には任意の値を代入することが可能。

テキスト: text属性

上述のencoding属性でデコードされたレスポンスの内容(文字列)はtext属性で取得できる。

print(response.text)
# <!doctype html>
# <html>
# <head>
#     <title>Example Domain</title>
# 
#     <meta charset="utf-8" />
#     <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
#     <meta name="viewport" content="width=device-width, initial-scale=1" />
#     <style type="text/css">
#     body {
#         background-color: #f0f0f2;
#         margin: 0;
#         padding: 0;
#         font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
#         
#     }
#     div {
#         width: 600px;
#         margin: 5em auto;
#         padding: 50px;
#         background-color: #fff;
#         border-radius: 1em;
#     }
#     a:link, a:visited {
#         color: #38488f;
#         text-decoration: none;
#     }
#     @media (max-width: 700px) {
#         body {
#             background-color: #fff;
#         }
#         div {
#             width: auto;
#             margin: 0 auto;
#             border-radius: 0;
#             padding: 1em;
#         }
#     }
#     </style>    
# </head>
# 
# <body>
# <div>
#     <h1>Example Domain</h1>
#     <p>This domain is established to be used for illustrative examples in documents. You may use this
#     domain in examples without prior coordination or asking for permission.</p>
#     <p><a href="http://www.iana.org/domains/example">More information...</a></p>
# </div>
# </body>
# </html>
# 

print(type(response.text))
# <class 'str'>

Webページの内容を取得したい場合はこのtext属性を使う。

バイナリデータ: content属性

デコードされていないレスポンスの内容(バイト列)はcontent属性で取得できる。

print(response.content)
# b'<!doctype html>\n<html>\n<head>\n    <title>Example Domain</title>\n\n    <meta charset="utf-8" />\n    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />\n    <meta name="viewport" content="width=device-width, initial-scale=1" />\n    <style type="text/css">\n    body {\n        background-color: #f0f0f2;\n        margin: 0;\n        padding: 0;\n        font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;\n        \n    }\n    div {\n        width: 600px;\n        margin: 5em auto;\n        padding: 50px;\n        background-color: #fff;\n        border-radius: 1em;\n    }\n    a:link, a:visited {\n        color: #38488f;\n        text-decoration: none;\n    }\n    @media (max-width: 700px) {\n        body {\n            background-color: #fff;\n        }\n        div {\n            width: auto;\n            margin: 0 auto;\n            border-radius: 0;\n            padding: 1em;\n        }\n    }\n    </style>    \n</head>\n\n<body>\n<div>\n    <h1>Example Domain</h1>\n    <p>This domain is established to be used for illustrative examples in documents. You may use this\n    domain in examples without prior coordination or asking for permission.</p>\n    <p><a href="http://www.iana.org/domains/example">More information...</a></p>\n</div>\n</body>\n</html>\n'

print(type(response.content))
# <class 'bytes'>

content属性をencoding属性のエンコーディングでデコードしたものはtext属性と等価。

print(response.content.decode(response.encoding) == response.text)
# True

content属性は画像やzipなどのテキストではないデータをダウンロードするときなどに使う。後述。

URLパラメータを指定: 引数params

URLの末尾に?をつけてそのあとにkey=valueの形式で値を指定することでパラメータを指定することができる。このようなパラメータをURLパラメータやクエリパラメータ、その文字列をクエリ文字列(クエリストリング)などと呼ぶ。

例えば検索ワード「日本代表」の「ニュース」カテゴリのGoogle検索のURLは以下のようになる。

https://www.google.co.jp/search?q=%E6%97%A5%E6%9C%AC%E4%BB%A3%E8%A1%A8&tbm=nws

パラメータが複数ある場合は&で接続し、日本語はパーセントエンコーディングする。

Requestsを使うと、辞書でパラメータを作成してget()などの引数paramsに指定することでURLパラメータを付与できる。Responseurl属性で正しく処理されていることが確認できる。

import requests

url = 'https://www.google.co.jp/search'

params = {'q': '日本代表', 'tbm': 'nws'}

r = requests.get(url, params=params)

print(r.url)
# https://www.google.co.jp/search?q=%E6%97%A5%E6%9C%AC%E4%BB%A3%E8%A1%A8&tbm=nws

なお、URLパラメータとして同じ名前のものを繰り返し指定する場合は辞書の値をリストにすればOK。

Pythonの標準ライブラリurllibでもURLパラメータの生成は可能。以下の記事を参照。

リクエストヘッダ(カスタムヘッダ)を指定: 引数headers

リクエストヘッダに情報を追加したい場合は引数headersを使う。

例えばUser-Agentに任意のユーザーエージェント文字列を指定すると、ユーザーエージェントを変更(偽装)できる。Yahoo! JAPANなどはユーザーエージェントによってレスポンスが異なる。

import requests

url = 'https://www.yahoo.co.jp/'

ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'

headers = {'User-Agent': ua}

r_ua = requests.get(url, headers=headers)

詳しくは以下の記事を参照。

リダイレクトの扱い

指定したURLからリダイレクトされた場合、デフォルトでは最終的なページのレスポンスが返る。

例えば以下の例ではhttps://en.wikipedia.orgにアクセスしているが、返ってきたResponseurl属性はリダイレクト先のhttps://en.wikipedia.org/wiki/Main_Pageになっている。

import requests

url = 'https://en.wikipedia.org'

r = requests.get(url)

print(r.url)
# https://en.wikipedia.org/wiki/Main_Page

print(r.status_code)
# 200

Responsehistory属性で、辿ってきた履歴をResponseのリストとして取得できる。この例ではリダイレクトが1回だけなので要素数1のリストとなる。

print(r.history)
# [<Response [301]>]

print(len(r.history))
# 1

print(type(r.history[0]))
# <class 'requests.models.Response'>

履歴のResponseの属性urlstatus_codeでリダイレクト元のURLやステータスコードを確認できる。

print(r.history[0].url)
# https://en.wikipedia.org/

print(r.history[0].status_code)
# 301

この例では履歴が1つだけなのであまり意味ないが、リスト内包表記を使うと辿ってきたURLなどのリストを簡単に作成できて便利。

print([response.url for response in r.history])
# ['https://en.wikipedia.org/']

get()の引数allow_redirects=Falseとするとリダイレクトを無効にすることができる。

r_not_redirect = requests.get(url, allow_redirects=False)

print(r_not_redirect.url)
# https://en.wikipedia.org/

print(r_not_redirect.status_code)
# 301

JSONデータを取得・保存

Web APIから取得するJSONデータはUnicodeエスケープされていたり面倒なことが多いが、Requestsならそれらの処理も簡単。

例として天気予報を取得できるWeb APIを使う。

引数paramsでURLパラメータを指定する。レスポンスヘッダのContent-Typeを確認するとレスポンスがJSONだと確認できる。

import requests
import pprint
import json

url = 'http://weather.livedoor.com/forecast/webservice/json/v1'

params = {'city': 130010}

r = requests.get(url, params=params)

print(r.headers['Content-Type'])
# application/json; charset=utf-8

Responseオブジェクトのjson()メソッドを使うとレスポンスの内容を辞書または辞書のリストに変換して取得できる。特に指定しなくても日本語も正しく扱われる。

pprintで適当に整形・省略して表示する。

json_data = r.json()

print(type(json_data))
# <class 'dict'>

pprint.pprint(json_data, depth=2, compact=True)
# {'copyright': {'image': {...},
#                'link': 'http://weather.livedoor.com/',
#                'provider': [...],
#                'title': '(C) LINE Corporation'},
#  'description': {'publicTime': '2018-07-12T18:33:00+0900',
#                  'text': ' 日本の東には高気圧があって東に進んでいます。また、日本の南にも高気\n'
#                          '圧があり停滞しています。関東甲信地方は、高気圧の間で気圧の谷になって\n'
#                          'います。\n'
#                          '\n'
#                          '【関東甲信地方】\n'
#                          ' 関東甲信地方はおおむね曇りで、雷を伴い非常に激しい雨の降っている所\n'
#                          'があります。\n'
#                          '\n'
#                          ' 12日は、気圧の谷や湿った空気の影響により曇りで、所により雷を伴い\n'
#                          '非常に激しく降る所がある見込みです。\n'
#                          '\n'
#                          ' 13日は、上空の気圧の谷や湿った空気の影響で曇りますが、昼頃から次\n'
#                          '第に晴れるでしょう。朝晩は雨の降る所がある見込みです。\n'
#                          '\n'
#                          ' 関東近海では、13日にかけてうねりを伴って波がやや高いでしょう。ま\n'
#                          'た、所々で霧が発生する見込みです。船舶は視程障害に注意してください。\n'
#                          '\n'
#                          '【東京地方】\n'
#                          ' 12日は、曇りですが、夜のはじめ頃まで雷を伴って激しく降る所がある\n'
#                          'でしょう。\n'
#                          ' 13日は、曇りで昼過ぎから晴れますが、明け方まで雨の降る所がある見\n'
#                          '込みです。'},
#  'forecasts': [{...}, {...}, {...}],
#  'link': 'http://weather.livedoor.com/area/forecast/130010',
#  'location': {'area': '関東', 'city': '東京', 'prefecture': '東京都'},
#  'pinpointLocations': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...},
#                        {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...},
#                        {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...},
#                        {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...},
#                        {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...},
#                        {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...},
#                        {...}, {...}, {...}, {...}, {...}],
#  'publicTime': '2018-07-12T17:00:00+0900',
#  'title': '東京都 東京 の天気'}

辞書なので任意の要素を取り出すのも簡単。

print(json_data['description']['text'])
#  日本の東には高気圧があって東に進んでいます。また、日本の南にも高気
# 圧があり停滞しています。関東甲信地方は、高気圧の間で気圧の谷になって
# います。
# 
# 【関東甲信地方】
#  関東甲信地方はおおむね曇りで、雷を伴い非常に激しい雨の降っている所
# があります。
# 
#  12日は、気圧の谷や湿った空気の影響により曇りで、所により雷を伴い
# 非常に激しく降る所がある見込みです。
# 
#  13日は、上空の気圧の谷や湿った空気の影響で曇りますが、昼頃から次
# 第に晴れるでしょう。朝晩は雨の降る所がある見込みです。
# 
#  関東近海では、13日にかけてうねりを伴って波がやや高いでしょう。ま
# た、所々で霧が発生する見込みです。船舶は視程障害に注意してください。
# 
# 【東京地方】
#  12日は、曇りですが、夜のはじめ頃まで雷を伴って激しく降る所がある
# でしょう。
#  13日は、曇りで昼過ぎから晴れますが、明け方まで雨の降る所がある見
# 込みです。

pprint.pprint(json_data['forecasts'][0])
# {'date': '2018-07-12',
#  'dateLabel': '今日',
#  'image': {'height': 31,
#            'title': '曇り',
#            'url': 'http://weather.livedoor.com/img/icon/8.gif',
#            'width': 50},
#  'telop': '曇り',
#  'temperature': {'max': None, 'min': None}}

ファイルとして保存したい場合は標準ライブラリのjsonモジュールのjson.dump()を使う。

with open('data/temp/download.json', 'w') as f:
    json.dump(json_data, f, ensure_ascii=False, indent=4)

jsonモジュールの詳細は以下の記事を参照。

画像やzipファイルなどをダウンロード

画像やzipファイルなどのテキストではないデータをダウンロードすることも可能。

Responsecontent属性で取得できるバイナリデータをそのままバイナリとして保存するだけ。

画像の例。

import requests
import os

url_image = 'https://www.python.org/static/community_logos/python-logo.png'

r_image = requests.get(url_image)

print(r_image.headers['Content-Type'])
# image/png

filename_image = os.path.basename(url_image)
print(filename_image)
# python-logo.png

with open('data/temp/' + filename_image, 'wb') as f:
    f.write(r_image.content)

zipファイルの例。

url_zip = 'http://www.post.japanpost.jp/zipcode/dl/oogaki/zip/13tokyo.zip'

r_zip = requests.get(url_zip)

print(r_zip.headers['Content-Type'])
# application/zip

filename_zip = os.path.basename(url_zip)
print(filename_zip)
# 13tokyo.zip

with open('data/temp/' + filename_zip, 'wb') as f:
    f.write(r_zip.content)

いずれの場合もos.path.basename()でURLからファイル名を抽出してその名前で保存している。

open()によるファイルの読み書きについての詳細は以下の記事を参照。

標準ライブラリのurllibモジュールでも同様の処理が可能。以下の記事を参照。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事