请求头、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 会过期,也可能被服务端在任意一次响应里换掉,不要把它当成配一次就永久有效的东西。

登录态的两条主流方案

方案做法特点
表单登录 + Sessionsession.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 且响应头含跨域提示OriginReferer按浏览器请求补齐这几个头再试
并发时登录十几次每个线程各自发现失效用锁把重登串行化,登录成功后统一继续
账号密码进了 Git 历史硬编码凭据立即改密码,改用环境变量或本地配置文件

排查登录态问题的顺序是:先在浏览器里正常登录,把关键请求 Copy as cURL 出来与代码逐项对比,再看响应头的 Set-CookieLocation,最后才怀疑凭据本身。

小结:请求头按实际值如实填写,Cookie 交给 Session 自动管理;登录态要么走表单登录加会话、要么走 token 放请求头,失效时加锁重登一次即可;凭据只存环境变量,账号、隐私与访问控制这几条边界一步都不能越。

笔记加载中…