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,否则拒绝。它有三种常见形态,你的代码要能识别:
- 隐藏字段(最常见):
<input type="hidden" name="csrf_token" value="...">,随表单一起 POST。 <meta>标签 + 请求头:页面里<meta name="csrf-token" content="...">,然后以X-CSRFToken之类的头发出。- 双提交 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 客户端能做的",它不该被用来伪装、绕过或攻击。
调试:抄真实请求最快
遇到"我发的请求服务器不认",最快的办法不是猜,是抄:
- 浏览器 DevTools → Network → 找到那个请求 → Copy as cURL;
- 把 headers、Cookie、body 逐项对照到你的代码;
- 用
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),以及"先找接口"为什么常常比"上浏览器"更对。