Python 爬虫(三):会话、登录与表单,别再每次重新握手

Posted on Thu 15 October 2026 in 技术 • Sucre Liu

Session 不是语法糖

requests.get() 每次调用都会新建一个连接、丢弃 Cookie。requests.Session() 则是一块有状态的客户端:它替你保存 Cookie、复用 TCP 连接、保持默认请求头。

import requests

with requests.Session() as s:
    s.headers.update({"User-Agent": "MySpider/1.0 (+https://example.com/bot)"})
    s.get("https://quotes.toscrape.com/")          # 服务器 Set-Cookie
    r = s.get("https://quotes.toscrape.com/")      # 第二次自动带上 Cookie

要爬需要登录、需要翻页保持状态、需要跟着服务端下发的 Cookie 走的站点,第一件事就是用 Session/Client,而不是每次裸 get。

Cookie 里到底有什么

服务器通过 Set-Cookie 下发键值对,浏览器(以及你的 Session)按 domain/path 决定什么时候带回去。几个属性值得知道:

属性 作用 对爬虫的影响
Domain / Path 作用域 决定了这个 Cookie 会发给哪些请求
Secure 只在 HTTPS 发送 用 http 请求会丢 Cookie
HttpOnly JS 读不到 不影响你的 HTTP 客户端(你不是 JS)
SameSite 跨站策略 跨域请求可能不带,注意请求来源
Expires / Max-Age 过期 会话过期后要重新登录

HttpOnly 常被误认为"爬虫拿不到"——不对。它防的是浏览器里的 JavaScript(XSS 偷 Cookie),而 requests 是直接读 Set-Cookie 的,照样拿得到。

登录:三步,缺一不可

带会话的抓取,标准流程是三步:先 GET 表单页拿 token 和初始 Cookie → 再 POST 凭据 → 最后验证是否真的登录成功。

用练习站 quotes.toscrape.com/login 举例。它的登录表单里有一个隐藏字段 csrf_token:

<form action="/login" method="post">
  <input type="hidden" name="csrf_token" value="...">
  <input name="username"> <input type="password" name="password">
</form>

完整实现:

import requests
from bs4 import BeautifulSoup

BASE = "https://quotes.toscrape.com"
UA = "MySpider/1.0 (+https://example.com/bot)"

def login(username: str, password: str):
    s = requests.Session()
    s.headers.update({"User-Agent": UA})

    # 1) 取登录页:同时拿到会话 Cookie 和 csrf_token
    r = s.get(f"{BASE}/login", timeout=10)
    r.raise_for_status()
    token = BeautifulSoup(r.text, "lxml").select_one("input[name=csrf_token]")["value"]

    # 2) 提交表单,必须带上 token
    r = s.post(
        f"{BASE}/login",
        data={"csrf_token": token, "username": username, "password": password},
        timeout=10,
        allow_redirects=True,
    )
    r.raise_for_status()

    # 3) 验证登录是否真的成功(见下文)
    logged_in = "Logout" in r.text
    return s, logged_in

httpx 的写法同构:

import httpx
with httpx.Client(follow_redirects=True, timeout=10) as c:
    html = c.get(f"{BASE}/login").text
    token = BeautifulSoup(html, "lxml").select_one("input[name=csrf_token]")["value"]
    r = c.post(f"{BASE}/login", data={"csrf_token": token, "username": "u", "password": "p"})

CSRF token:为什么漏了它就登录失败

CSRF(跨站请求伪造)防护要求:提交表单时必须带上服务器本次会话下发的 token,否则拒绝。它有三种常见形态,你的代码要能识别:

  1. 隐藏字段(最常见):<input type="hidden" name="csrf_token" value="...">,随表单一起 POST。
  2. <meta> 标签 + 请求头:页面里 <meta name="csrf-token" content="...">,然后以 X-CSRFToken 之类的头发出。
  3. 双提交 Cookie:Cookie 里存一份,请求头/字段里再镜像一份,服务器比对。

它通常和会话绑定:token 变了、会话过期了,就得重新走第一步。所以别把 token 硬编码——每次登录重新抓。

表单编码:data、files、json

POST 的"格式"选错,服务器就是收不到参数:

参数 Content-Type 用途
data={...} application/x-www-form-urlencoded 普通表单(登录、搜索)
files={...} multipart/form-data 上传文件
json={...} application/json 现代前端 API

很多"登录失败"其实是把 JSON 接口当成表单发了。用 DevTools 看请求的 Content-Type,就知道该用哪个。

登录成功了吗?别靠猜

新手常犯的错:POST 完就假设成功。稳妥的验证有几种:

  • 找标记:登录后页面出现 Logout、用户名、头像等只有登录态才有的元素。
  • 看最终 URL:r.url 是否跳离了 /login;r.history 能看到重定向链。
  • 查 Cookie:会话 Cookie(如 sessionid)是否被更新。
  • 打一个需要鉴权的接口,看是否返回数据而非 401/跳转。

其中"找标记"最省事,但要选稳定的标记——别依赖一个会随文案变的词。

复用会话:省事,但它是敏感数据

每次跑脚本都登录一遍,既慢又容易触发风控。可以把 Cookie 存下来:

import pickle

# 保存
with open("session.pkl", "wb") as f:
    pickle.dump(s.cookies, f)

# 恢复
with requests.Session() as s:
    with open("session.pkl", "rb") as f:
        s.cookies.update(pickle.load(f))

但会话文件等价于"登录凭证"。 它能让任何拿到它的人以你的身份操作。所以:

  • 加进 .gitignore,永远不要提交;
  • 权限设成 600;
  • 有有效期,过期就重新登录;
  • 更好的做法是用环境变量/密钥管理存账号密码,而不是存会话。

边界:验证码与 2FA 不要碰

这里必须把话说死:绕过验证码、破解短信/2FA,可能触犯法律,也不是技术能力的体现。 遇到这类站点,正确做法是:

  • 优先用官方 API(带 token/OAuth),它才是为程序访问设计的;
  • 需要人机验证的,考虑手动登录一次后复用合法会话,或直接放弃;
  • 一些服务提供 OAuth 的 device flow(在另一台设备上确认),这是合规的自动化路径。

requests 能做的是"正常 HTTP 客户端能做的",它不该被用来伪装、绕过或攻击。

调试:抄真实请求最快

遇到"我发的请求服务器不认",最快的办法不是猜,是抄:

  1. 浏览器 DevTools → Network → 找到那个请求 → Copy as cURL;
  2. 把 headers、Cookie、body 逐项对照到你的代码;
  3. 用 httpbin.org/anything 回显你的请求,确认 header/body 真的发对了。

一个常用技巧:httpbin.org/cookies 会原样返回它收到的 Cookie,用来验证你的会话是否被正确携带。

小结

  • 需要状态就用 Session/Client:Cookie 持久化 + 连接复用。
  • HttpOnly 不挡你的 HTTP 客户端;Secure/SameSite 会影响 Cookie 是否被带回。
  • 登录三步:GET 表单拿 token → POST 凭据 → 验证成功;CSRF token 与会话绑定,别硬编码。
  • 表单编码按 Content-Type 选 data/files/json。
  • 会话文件是敏感数据;验证码/2FA 不绕过,走官方 API。
  • 调试靠 DevTools "Copy as cURL",别靠猜。

下一篇讲动态页面:什么时候必须上浏览器(Playwright),以及"先找接口"为什么常常比"上浏览器"更对。