bigywater.tech
爬虫工程 · 9

Cookie 的域与路径匹配:CookieJar 什么时候带上、什么时候不带

requests.Session 会自动保存和发送 cookie,但“带不带”由 http.cookiejar 决定。这篇用 www.example.test、api.example.test、shop.co.uk 这类名字(只在本进程里解析到本机)逐条测:域、路径、端口、Secure、公共后缀,以及手写 Cookie 头和 cookies= 参数的优先关系。

sub.www
host-only cookie 竟然也发给了子域名 sub.www.example.test
3 个
shop.co.uk 设置 Domain=co.uk 后,被带上这个 cookie 的站点数(包括 other.co.uk、bank.co.uk)
/a/b
从 /a/b/set 设置、没写 Path 的 cookie 的默认路径

host-only cookie

实测 · 本机执行输出
  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 会把它发给子域名。

Domain 属性

实测 · 本机执行输出
  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 是别人的域名,被拒绝。

Path 属性

实测 · 本机执行输出
  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,这在本机开发时尤其容易互相覆盖。

Secure 与删除

实测 · 本机执行输出
  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 或第三方公共后缀库。

三种来源的 cookie

实测 · 本机执行输出
  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,下一次请求是空的。

分步演示

Cookie 匹配
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / requests 2.34.2 / aiohttp 3.14.3 / lxml 6.1.1(系统 Python);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、断连、压缩、重定向等行为,并记录收到的原始请求;计时用 time.perf_counter,数字每次运行会有小幅抖动。*.test 与 *.uk 的主机名通过替换本进程的 socket.getaddrinfo 解析到 127.0.0.1(没有改系统 hosts),所以 URL、Host 头、cookie 匹配用的都是这些主机名,服务器记录收到的 Cookie 头;http.cookiejar 为 Python 3.14.8 标准库自带
    • 没有验证:RFC 6265 原文对 host-only、Path 默认值的规定(上文只比较了 http.cookiejar 的行为);HTTPS 站点上 Secure cookie 的发送;SameSite 属性(http.cookiejar 不处理);浏览器的行为;自带公共后缀列表的库(tldextract 等)能否补上这个缺口;cookie 数量与大小上限;aiohttp 的 CookieJar(后面可能单独看)
    京ICP备2026067502号-1京公网安备11010502063487号