请求头、Cookie 与登录态处理
请求头决定服务端认不认你,Cookie 决定你以谁的身份访问,这两块是采集里最容易出错、也最容易越界的地方。本章先逐个头说明作用,再讲 Cookie 的几种管理方式与登录态的两条主流方案,最后给出可运行的登录与重登骨架,并把合规边界写清楚。
常见请求头各自管什么
| 请求头 | 作用 | 采集时的写法 |
|---|---|---|
User-Agent | 标识客户端类型 | 如实写上采集器名称与联系方式,不要伪装浏览器做违规访问 |
Referer | 说明从哪个页面跳来 | 部分接口会校验,按浏览器里的实际值填写 |
Accept | 期望的响应类型 | 抓 JSON 接口时写 application/json, text/plain, */* |
Accept-Language | 期望语言 | 需要中文内容时写 zh-CN,zh;q=0.9 |
Content-Type | 请求体的格式 | 表单用 application/x-www-form-urlencoded,JSON 用 application/json |
Origin | 请求来源站点 | 跨域接口会校验,与 Referer 一起缺失常导致 403 |
X-Requested-With | 标识为 Ajax 请求 | 传统接口写 XMLHttpRequest 才会返回 JSON |
Content-Type 写错是最隐蔽的坑:服务端按表单去解析你发的 JSON,参数一个都收不到,返回 200 但数据为空。
Cookie 从哪里来、怎么管
| 方式 | 说明 | 适用场景 |
|---|---|---|
response.cookies | 本次响应 Set-Cookie 下发的 Cookie | 只需要看一眼服务端设了什么 |
session.cookies | 会话内累积的 Cookie,后续请求自动携带 | 有登录态的批量采集 |
cookies= 参数 | 单次请求手工指定 Cookie | 从浏览器复制 Cookie 做一次性验证 |
RequestsCookieJar | 手工组装的 Cookie 容器,可设域与路径 | 需要精细控制多个域下的 Cookie |
import requests
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0 (compatible; MyCollector/1.0)"})
resp = session.get("https://example.com/list", timeout=(3, 10))
print(resp.cookies) # 本次响应下发的 Cookie
print(session.cookies.get_dict()) # 会话里累积的 Cookie,后续会自动携带
jar = requests.cookies.RequestsCookieJar() # 需要精细控制时手工组装
jar.set("lang", "zh-CN", domain="example.com")
session.get("https://example.com/list", cookies=jar, timeout=(3, 10))
Cookie 会过期,也可能被服务端在任意一次响应里换掉,不要把它当成配一次就永久有效的东西。
登录态的两条主流方案
| 方案 | 做法 | 特点 |
|---|---|---|
表单登录 + Session | session.post 提交账号密码,之后 session.get 自动带 Cookie | 兼容老站点,失效靠重定向判断 |
| 登录接口取 token | 调登录接口拿 token,放进 Authorization 或自定义头 | 接口化站点的标准做法,注意有效期与刷新 |
两种方案共同点是先认证、再访问、失效就重登,区别只在凭据放在 Cookie 里还是请求头里。
合规边界与凭据管理
- 只用于自动化操作自己已授权账号的合法动作,不得共享账号、不得批量注册、不得绕过他人账号或付费墙。
- 不采集个人隐私信息与受版权保护的付费内容,不尝试绕过登录、验证码等访问控制,数据使用遵守《个人信息保护法》《数据安全法》《著作权法》。
- 凭据只放在环境变量或本地配置文件里,不硬编码进代码、不提交到 Git、不写进日志。
export SITE_USER='my-account' # 只在当前会话生效,不要写进代码
export SITE_PASS='my-password'
printf '%s\n' '.env' 'out/' >> .gitignore # 顺手挡住误提交
可直接运行的登录与重登骨架
"""用环境变量里的凭据登录自己的账号,并演示登录态失效后的自动重登。"""
import os
import threading
import time
import requests
BASE_URL = "https://example.com"
UA = "Mozilla/5.0 (compatible; MyCollector/1.0; +https://example.com/bot)"
class Client:
"""带登录态的客户端:仅用于自动化操作自己已授权的账号。"""
def __init__(self, username: str, password: str) -> None:
self._username, self._password = username, password
self._lock = threading.Lock() # 防止并发时重复登录
self._session = requests.Session()
self._session.headers.update({"User-Agent": UA})
self.login()
def login(self) -> None:
resp = self._session.post(f"{BASE_URL}/login", timeout=(3, 10),
data={"username": self._username, "password": self._password})
resp.raise_for_status()
if not self._session.cookies:
raise RuntimeError("登录失败:没拿到 Cookie,检查凭据或登录接口")
print("登录成功,Cookie 数:", len(self._session.cookies))
def get(self, path: str, **params: object) -> dict | None:
"""带失效检测的 GET:重登一次仍失败就放弃,避免死循环。"""
for _ in range(2):
resp = self._session.get(f"{BASE_URL}{path}", params=params, timeout=(3, 10))
# 两种典型失效信号:被跳到登录页,或响应体里提示未登录
logged_out = "/login" in resp.url or (
"json" not in resp.headers.get("Content-Type", "") and "请登录" in resp.text[:2000]
)
if logged_out:
with self._lock: # 同一时刻只允许一个线程重登
self.login()
continue
resp.raise_for_status()
return resp.json()
print("重登后仍处于未登录状态,停止本次采集")
return None
def main() -> None:
username, password = os.environ.get("SITE_USER"), os.environ.get("SITE_PASS")
if not username or not password:
raise SystemExit("请设置 SITE_USER / SITE_PASS 环境变量,不要把凭据写进代码")
client = Client(username, password)
data = client.get("/api/my/orders", page=1, size=20)
print("记录数:", len(data.get("items", [])) if data else 0)
time.sleep(1) # 控制请求频率
if __name__ == "__main__":
main()
常见坑与要点对照
| 现象 | 常见原因 | 处理要点 |
|---|---|---|
| 带 Cookie 仍返回登录页 | Cookie 已过期或被服务端换掉 | 检测后重新登录,不要反复重试同一份 Cookie |
| 参数全丢、返回 200 空数据 | Content-Type 与实际请求体格式不一致 | 表单用 data=,JSON 用 json=,两者不要混用 |
| 403 且响应头含跨域提示 | 缺 Origin、Referer | 按浏览器请求补齐这几个头再试 |
| 并发时登录十几次 | 每个线程各自发现失效 | 用锁把重登串行化,登录成功后统一继续 |
| 账号密码进了 Git 历史 | 硬编码凭据 | 立即改密码,改用环境变量或本地配置文件 |
排查登录态问题的顺序是:先在浏览器里正常登录,把关键请求 Copy as cURL 出来与代码逐项对比,再看响应头的 Set-Cookie 与 Location,最后才怀疑凭据本身。
小结:请求头按实际值如实填写,Cookie 交给 Session 自动管理;登录态要么走表单登录加会话、要么走 token 放请求头,失效时加锁重登一次即可;凭据只存环境变量,账号、隐私与访问控制这几条边界一步都不能越。