requests.Session 会自动保存和发送 cookie,但“带不带”由 http.cookiejar 决定。这篇用 www.example.test、api.example.test、shop.co.uk 这类名字(只在本进程里解析到本机)逐条测:域、路径、端口、Secure、公共后缀,以及手写 Cookie 头和 cookies= 参数的优先关系。
Set-Cookie: k=v (来自 www.example.test)
保存下来的:["k(domain='www.example.test', path='/', domain_specified=False, secure=False)"]
之后请求时带上了:['www.example.test/', 'sub.www.example.test/'];没带:['api.example.test/', 'example.test/', 'other.test/']不写 Domain 的 cookie 保存时 domain_specified=False。实测它发给了设置它的 www.example.test,也发给了 sub.www.example.test,没发给 api.example.test 和 example.test。按我对 RFC 6265 的理解,host-only cookie 只该发回原主机——但那只是凭记忆,我没有核对标准文本;可以确定的是 http.cookiejar 会把它发给子域名。
Set-Cookie: k=v; Domain=example.test (来自 www.example.test)
保存下来的:["k(domain='.example.test', path='/', domain_specified=True, secure=False)"]
之后请求时带上了:['www.example.test/', 'api.example.test/', 'example.test/', 'sub.www.example.test/'];没带:['other.test/', 'notexample.test/']
Set-Cookie: k=v; Domain=.example.test (来自 www.example.test)
保存下来的:["k(domain='.example.test', path='/', domain_specified=True, secure=False)"]
之后请求时带上了:['www.example.test/', 'api.example.test/', 'example.test/'];没带:[] (前导点写法)
Set-Cookie: k=v; Domain=other.test (来自 www.example.test)
保存下来的:无(被拒绝)
之后请求时带上了:[];没带:['www.example.test/', 'other.test/']Domain=example.test 让 cookie 对根域和所有子域生效(保存为 .example.test),notexample.test 这种只是字符串后缀相同的不算;Domain=other.test 是别人的域名,被拒绝。
Set-Cookie: k=v; Path=/app (来自 www.example.test)
保存下来的:["k(domain='www.example.test', path='/app', domain_specified=False, secure=False)"]
之后请求时带上了:['www.example.test/app', 'www.example.test/app/', 'www.example.test/app/x'];没带:['www.example.test/', 'www.example.test/application', 'www.example.test/App']Path=/app 匹配 /app 及其下层,不匹配 /、不匹配只是字符串前缀的 /application,也区分大小写。
从 /a/b/set 设置的 cookie,保存的 path = ['/a/b'] 之后请求这些路径时带上了:['/a/b/x']
没写 Path 时,cookie 的路径取设置它的 URL 的目录。从 /a/b/set 设置的 cookie 路径是 /a/b,在 /a/c 或 / 就看不到了——爬取时 cookie 突然“丢了”,常常是因为路径。
Set-Cookie: k=v (来自 www.example.test)
保存下来的:["k(domain='www.example.test', path='/', domain_specified=False, secure=False)"]
之后请求时带上了:['www.example.test/', '另一个端口的 www.example.test/'];没带:[] (第二个目标 @ 是另一个端口的服务器)cookie 的范围是主机名,不含端口。同一个主机名上的两个服务共用 cookie,这在本机开发时尤其容易互相覆盖。
Set-Cookie: k=v; Secure (来自 www.example.test)
保存下来的:["k(domain='www.example.test', path='/', domain_specified=False, secure=True)"]
之后请求时带上了:[];没带:['www.example.test/'] (服务器是 http,不是 https)
先设置 k=v,jar 里有 1 个;再收到 Max-Age=0 后有 0 个
再设置 k=v 后收到过去的 Expires:jar 里有 0 个经 http 收到的 Secure cookie 会被保存,但对 http 请求不会发送。Max-Age=0 或过去的 Expires 会删掉同名 cookie——这是服务器“清除登录状态”的方式,爬虫的 jar 也会照做。
Set-Cookie: k=v; Domain=test (来自 www.example.test)
保存下来的:无(被拒绝)
之后请求时带上了:[];没带:['www.example.test/', 'other.test/']
Set-Cookie: k=v; Domain=.test (来自 www.example.test)
保存下来的:无(被拒绝)
之后请求时带上了:[];没带:['www.example.test/', 'other.test/']
Set-Cookie: k=v; Domain=co.uk (来自 shop.co.uk)
保存下来的:["k(domain='.co.uk', path='/', domain_specified=True, secure=False)"]
之后请求时带上了:['shop.co.uk/', 'other.co.uk/', 'bank.co.uk/'];没带:[]
Set-Cookie: k=v; Domain=.co.uk (来自 shop.co.uk)
保存下来的:["k(domain='.co.uk', path='/', domain_specified=True, secure=False)"]
之后请求时带上了:['shop.co.uk/', 'other.co.uk/', 'bank.co.uk/'];没带:[]
默认策略 DefaultCookiePolicy().strict_ns_domain = 0(DomainLiberal = 0,DomainStrict = 3)
换成 strict_ns_domain=DomainStrict 后,收到 Domain=co.uk:jar 里有 1 个
DomainStrict 下不写 Domain 的 host-only cookie 照常保存:jar 里有 2 个Python 标准库的 http.cookiejar 没有公共后缀列表。Domain=test 因为没有内嵌的点被拒绝,但 Domain=co.uk 被接受,并发给了 other.co.uk、bank.co.uk;改用 DomainStrict 也没拦住。用同一个 Session 爬很多个同后缀的站点时,一个站点能给其它站点种 cookie。我没有试 blocked_domains 或第三方公共后缀库。
Session 里已有 k=v,请求时再传 cookies={"extra":"1"} → 服务器收到 Cookie: 'k=v; extra=1';jar 里现在有 ['k']
请求时手写 headers={"Cookie": "manual=2"} → 服务器收到 Cookie: 'manual=2'
不用 Session 的 requests.get:r.cookies={'k': 'v'};紧接着再 requests.get 同一个主机,服务器收到 Cookie: ''Session 的 jar、cookies= 参数、手写的 Cookie 头:前两者合并发送,cookies= 里的那份不会存进 jar;手写 Cookie 头则取代 jar,jar 里的不再发送。不用 Session 的 requests.get 不保存 cookie,下一次请求是空的。