16.

Python で Cookie を扱う|requests の Session と http.cookies

編集
この記事の要点
  • Cookie を「送る側(クライアント)」なら requests.Session を使えば自動で保持される
  • 1 回だけ付けたいなら requests.get(url, cookies={"key": "value"})
  • 受け取った Cookie は res.cookies。属性まで見たいなら session.cookies を走査する
  • Cookie を「返す側(サーバー)」なら Set-Cookie ヘッダーを組み立てる。標準の http.cookies が使える
  • ログイン状態をファイルに残すなら http.cookiejar.LWPCookieJar で保存・読み込みする

Session を使うのが基本

Cookie は「1 回のリクエストの設定」ではなく「やり取りをまたいで持ち回る状態」です。requests.Session を使うと、サーバーから返ってきた Cookie を自動で保存し、次のリクエストに付けてくれます。

import requests

session = requests.Session()

# 1. ログイン。返ってきたセッション Cookie が session に保存される
session.post("https://example.com/login",
             data={"username": "taro", "password": "secret"},
             timeout=10)

# 2. 以降のリクエストには自動で付く
res = session.get("https://example.com/mypage", timeout=10)
print(res.status_code)

print(session.cookies.get_dict())   # {'sessionid': 'abc123'}
session.close()

requests.get() を単発で呼ぶ方法では Cookie が保持されないため、ログインが必要なページは必ず 401 / ログイン画面に戻されます。

Cookie を明示的に設定する

import requests

# その 1 回だけ付ける
res = requests.get("https://example.com/",
                   cookies={"lang": "ja", "theme": "dark"},
                   timeout=10)

# Session に常設する(以降すべてのリクエストに付く)
session = requests.Session()
session.cookies.set("lang", "ja")
session.cookies.set("sessionid", "abc123",
                    domain="example.com", path="/")

# ヘッダーとして直接書くこともできるが、Session の管理から外れるので非推奨
res = requests.get(url, headers={"Cookie": "lang=ja; theme=dark"}, timeout=10)

domain を指定しないと、そのリクエストのホストにだけ送られます。複数のサブドメインをまたぐ場合は明示してください。

受け取った Cookie を読む

res = requests.get("https://example.com/", timeout=10)

print(res.cookies.get_dict())        # そのレスポンスで設定されたものだけ
print(res.cookies.get("sessionid"))  # 無ければ None
print(res.headers.get("Set-Cookie")) # 生のヘッダー文字列

# 属性まで見たいときは 1 個ずつ走査する
for c in res.cookies:
    print(c.name, c.value, c.domain, c.path, c.secure, c.expires)
書き方返るもの
res.cookiesそのレスポンスで新しく設定された Cookie
session.cookiesこれまでに蓄積されたすべての Cookie
res.request.headers["Cookie"]実際に送信した Cookie ヘッダー

「送ったつもりが送られていない」場合は 3 番目を確認します。ドメインやパスが合っていないと、保持されていても送信対象から外れます。

削除と更新

session.cookies.set("lang", "en")          # 同じ名前なら上書き
del session.cookies["lang"]                # 1 個削除
session.cookies.clear()                    # 全削除(ログアウト相当)
session.cookies.clear("example.com")       # ドメイン単位で削除

print("lang" in session.cookies.get_dict())   # False

ログイン状態をファイルに保存する

import http.cookiejar
import os
import requests

JAR_PATH = "cookies.txt"

jar = http.cookiejar.LWPCookieJar(JAR_PATH)
if os.path.exists(JAR_PATH):
    jar.load(ignore_discard=True)          # 前回の Cookie を復元

session = requests.Session()
session.cookies = jar                      # Session に差し込む

session.get("https://example.com/mypage", timeout=10)

jar.save(ignore_discard=True)              # セッション Cookie も含めて保存

ignore_discard=True を付けないと、ブラウザを閉じたら消える種類の Cookie が保存されません。保存したファイルはログイン情報そのものなので、権限を絞り、リポジトリに入れないでください。

サーバー側で Cookie を返す

Web アプリを自作する場合は Set-Cookie ヘッダーを組み立てます。標準ライブラリの http.cookies が属性のエスケープまで面倒を見てくれます。

from http.cookies import SimpleCookie

cookie = SimpleCookie()
cookie["sessionid"] = "abc123"
cookie["sessionid"]["path"] = "/"
cookie["sessionid"]["max-age"] = 3600
cookie["sessionid"]["httponly"] = True     # JavaScript から読めなくする
cookie["sessionid"]["secure"] = True       # HTTPS のときだけ送らせる
cookie["sessionid"]["samesite"] = "Lax"    # 別サイトからの送信を制限

print(cookie.output(header="Set-Cookie:"))
# Set-Cookie: sessionid=abc123; HttpOnly; Max-Age=3600; Path=/; SameSite=Lax; Secure

# 受け取ったヘッダーを解析する
incoming = SimpleCookie("lang=ja; theme=dark")
print(incoming["lang"].value)               # ja
属性意味推奨
HttpOnlyJavaScript から読めなくするセッション ID には必ず付ける
SecureHTTPS のときだけ送る本番では付ける
SameSite他サイト由来のリクエストで送るかLax が既定的。決済系は Strict
Max-Age / Expires有効期限省略するとブラウザを閉じるまで
Path / Domain送信対象の範囲広げすぎない

よくある詰まりどころ

  • Session を使っていない — 単発の requests.get() では Cookie が引き継がれない
  • リダイレクト先で消えるallow_redirects=False にして途中の Set-Cookie を確認する
  • ログインが JavaScript 経由 — フォーム POST ではなく API を叩いている。ブラウザの開発者ツールで実際のリクエストを見る
  • CSRF トークンが必要 — ログインページを一度 GET してトークンを取り出し、POST に含める
  • 日本語や記号が入る値urllib.parse.quote() でエンコードしてから入れる

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本的なルール
  2. 変数
  3. 演算子
  4. 標準ライブラリ
  5. 外部ライブラリ
  6. 制御構文
  7. リスト(配列)
  8. タプル
  9. セット
  10. 辞書(dict)
  11. クラスとメソッド
  12. 継承の概念と必要性
  13. 継承の構文
  14. コンストラクタ
  15. cookieの値の設定と取得
  16. 例外処理
  17. 例外を文字列で出力する方法
  18. httpリクエスト(curl)をする方法
  19. Responseオブジェクトの中身の確認
  20. 変数が空かどうか判定する方法
  21. タイムゾーンの設定と現在日時の取得と文字列化
  22. シングルクォーテーションとダブルクォーテーションの違い

最近更新/作成されたページ