Python の Requests で、『HTTP レスポンスヘッダー』だけを取得するコード例です。
レスポンスヘッダーの内容を見てから、本文 (body) を『受信するか?しないか?』を決めるために書きました。
レスポンスヘッダーだけを取得する方法は、以下の 2 種類を試しました。
- GET メソッドの
requests.get(url, stream=True)
を使用した方法。 - HEAD メソッドの
requests.head(url, **kwargs)
を使用した方法。
『GET メソッド』の方は、レスポンスヘッダーの内容によって、本文を『受信するか?しないか?』を分ける場合に良さそうでした。
一方で、『HEAD メソッド』の方は、ウェブページの状態を確認するだけ、という場合に良さそうでした。
ところで、レスポンスヘッダーの内容は、GET メソッドで取得した場合と、HEAD メソッドで取得した場合とで、異なる場合がありました。
当然のことながら、どのメソッドで、どんなレスポンスヘッダーが返ってくるかは、ウェブサイトのサーバーによって異なっていました。
マニュアルの場所
『レスポンスヘッダー』の説明です。
(MDN Web Docs) Response header (レスポンスヘッダー)
コード例で使用した Requests と Python のマニュアルの場所です。
(Requests) requests.request(method, url, **kwargs)
(Requests) requests.get(url, params=None, **kwargs)
(Requests) requests.head(url, **kwargs)
(Requests) Timeouts – requests.get(url, timeout)
(Requests) class requests.Response
(Requests) requests.Response.close()
.close()
は、通常は明示的に呼び出す必要は無いとのことでした。
ですが、.get(url, stream=True)
で、かつ、本文を受信しなかったときは、with
文などで閉じる必要がありました。
(Requests) Body Content Workflow – with requests.get(url, stream=True) as r:
(Requests) requests.Response.headers=None
(Requests) requests.Response.content
(Requests) requests.Response.raw=None
(Python) dict – get(key[, default])
KeyError を避けるために使用しました。
Response.headers
の特別な辞書にも、これと同じ働きをする .get()
がありました。
GET メソッドのコード例
GET メソッドで、最初に『レスポンスヘッダー』だけを取得するコード例です。
本文は、'Content-Type'
に 'text/html'
があるときだけ、取得するようにしてみました。
"""get_method.py"""
import requests
def main():
"""メイン関数です。"""
print('start\n')
print('■ URL を決めます。')
url = 'https://example.com/'
print(f'{url}\n')
print('■ GET メソッド (stream=True) で URL を開きます。\n')
# stream=True を指定したときは、with 文を使用して、
# 最終的に接続が閉じるようにします。
# たとえば、レスポンスヘッダーの内容を見て、結局
# r.content 属性にアクセスしなかった場合です。その場合は、
# 接続が開いたままになっていました。なので、
# それを with 文の働きで閉じてあげます。
with requests.get(url, stream=True, timeout=30) as r:
print('■ レスポンスヘッダーの内容を表示します。')
print(f'type(r) {type(r)}')
print(f'type(r.headers) {type(r.headers)}')
for (k, v) in r.headers.items():
print(f' {k}: {v}')
print('(ヘッダの表示おわり)\n')
print('■ 接続が『まだ閉じていない』ことを確認します。')
# r.raw.closed 属性が False になっているはずです。
print(f'r.raw.closed: {r.raw.closed} (← まだ False のはず)\n')
print('■ まだ、『本文を受信していない』ことを確認します。')
# 隠し属性の r._content が、False になっているはずです。
print(f'type(r._content)): {type(r._content)}')
print(f'r._content: {r._content} (← まだ False のはず)\n')
# たとえば、レスポンスヘッダを見て、
# 'Content-Type' に 'text/html' があるときだけ、
# 本文を受信するようにしてみます。
if 'text/html' in r.headers.get('Content-Type', default=''):
# (説明) r.headers['Content-Type'] だと、キーが無かった時に、
# KeyError が発生しました。なので .get(key, default='') で、
# キーが無かった時に『空文字列』を返すようにしました。
print('(r.content 属性にアクセスします)\n')
# まだ本文を受信していなければ、
# この属性にアクセスするだけで、
# 受信する処理が行われました。
r.content
print('■ 本文を受信したことを確認します。')
# 隠し属性の r._content に、本文の
# バイナリデータが入っているはずです。
# その長さを表示してみます。
print(f'type(r._content)): {type(r._content)}')
print(f'len(r._content) {len(r._content)}\n')
print('■ 普通の r.content 属性のほうも確認します。')
print(f'type(r.content)): {type(r.content)}')
print(f'len(r.content): {len(r.content)}\n')
print('■ 接続が『閉じている』ことを確認します。')
# r.content 属性にアクセスした後は、自動的に
# 接続が閉じていました。
# r.raw.closed 属性が True になっているはずです。
print(f'type(r.raw) {type(r.raw)}')
print(f'r.raw.closed {r.raw.closed} (← True のはず)\n')
else:
# 本文を取得せずに終了します。
pass
print('end')
return
if __name__ == '__main__':
main()
ところで、『デバッグモード』のウォッチ式で r.content
を見てしまうと、そこで本文の受信が始まってしまいました。
なので、もし、『デバッグモード』で本文の受信タイミングを確認するときは、ウォッチ式で r.content
を見ないようにします。
r.content
属性は、(Python) @property になっていて、本文を受信する処理が書かれていました。
GET メソッドの実行結果
GET メソッドの実行結果です。
期待した通り、最初は『レスポンスヘッダー』だけを受信して、本文は取得していませんでした。
そして、レスポンスヘッダーの内容を確認してから、本文を受信することができました。
start
■ URL を決めます。
https://example.com/
■ GET メソッド (stream=True) で URL を開きます。
■ レスポンスヘッダーの内容を表示します。
type(r) <class 'requests.models.Response'>
type(r.headers) <class 'requests.structures.CaseInsensitiveDict'>
Content-Encoding: gzip
Age: 391489
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Tue, 23 Mar 2021 08:55:53 GMT
Etag: "3147526947+gzip"
Expires: Tue, 30 Mar 2021 08:55:53 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (sjc/4E76)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 648
(ヘッダの表示おわり)
■ 接続が『まだ閉じていない』ことを確認します。
r.raw.closed: False (← まだ False のはず)
■ まだ、『本文を受信していない』ことを確認します。
type(r._content)): <class 'bool'>
r._content: False (← まだ False のはず)
(r.content 属性にアクセスします)
■ 本文を受信したことを確認します。
type(r._content)): <class 'bytes'>
len(r._content) 1256
■ 普通の r.content 属性のほうも確認します。
type(r.content)): <class 'bytes'>
len(r.content): 1256
■ 接続が『閉じている』ことを確認します。
type(r.raw) <class 'urllib3.response.HTTPResponse'>
r.raw.closed True (← True のはず)
end
HEAD メソッドのコード例
HEAD メソッドで、『レスポンスヘッダー』だけを取得するコード例です。
"""head_method.py"""
import requests
def main():
"""メイン関数です。"""
print('start\n')
print('■ URL を決めます。')
url = 'https://example.com/'
print(f'{url}\n')
print('■ HEAD メソッドで URL を開きます。\n')
with requests.head(url, timeout=30) as r:
print('■ レスポンスヘッダーの内容を表示します。')
print(f'type(r) {type(r)}')
print(f'type(r.headers) {type(r.headers)}')
for (k, v) in r.headers.items():
print(f' {k}: {v}')
print('(ヘッダの表示おわり)\n')
print('■ 接続の状態を確認します。')
print(f'r.raw.closed: {r.raw.closed}\n')
print('■ r.content 属性の内容を表示します。')
print(f'type(r.content)): {type(r.content)}')
print(f'len(r.content): {len(r.content)} (← 0 のはず)\n')
print('end')
return
if __name__ == '__main__':
main()
HEAD メソッドの実行結果
HEAD メソッドの実行結果です。
期待した通り、『レスポンスヘッダー』だけを受信して、本文は取得していませんでした。
また、接続は、レスポンスヘッダーを受信し終わった時点で、閉じていました。
あと、HEAD メソッドで取得したレスポンスヘッダーの内容は、GET メソッドの場合と比べて、少し異なっていました。
コード例の場合だと、HEAD メソッドの方は 'Content-Encoding'
が無かったり、'Content-Length'
が gzip で圧縮する前の長さになっていたりしました。
start
■ URL を決めます。
https://example.com/
■ HEAD メソッドで URL を開きます。
■ レスポンスヘッダーの内容を表示します。
type(r) <class 'requests.models.Response'>
type(r.headers) <class 'requests.structures.CaseInsensitiveDict'>
Accept-Ranges: bytes
Age: 65754
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Tue, 23 Mar 2021 09:18:21 GMT
Etag: "3147526947"
Expires: Tue, 30 Mar 2021 09:18:21 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (sec/96DC)
X-Cache: HIT
Content-Length: 1256
(ヘッダの表示おわり)
■ 接続の状態を確認します。
r.raw.closed: True
■ r.content 属性の内容を表示します。
type(r.content)): <class 'bytes'>
len(r.content): 0 (← 0 のはず)
end
以上です。