Python 操作 Cookie 从入门到实战:爬虫登录与 Web 安全防护全解
做爬虫的同学肯定都跟 Cookie 打过交道做 Web 开发的同学也一定没少被 Cookie 折腾。我早期接一个数据采集项目时因为没搞懂 Cookie 的完整生命周期花了一整天排查登录态失效的问题后来一步步把 Python 操作 Cookie 的整套流程理顺了才发现这块知识不管在爬虫还是 Web 开发里都是绕不开的基础功。这篇文章就想把 Python 操作 Cookie 的干货一次性讲透从请求头里的表现形式到 Requests、Selenium 等工具里的实际操作再到 Web 后端怎么设置和读取 Cookie、怎么和 Token 配合、怎么防爬虫把爬虫和 Web 开发两个视角拼在一起帮你少走弯路。1. Cookie 基础认知别再把请求头和响应头搞混了1.1 Cookie 到底是什么、放在哪里Cookie 本质上就是服务端下发给客户端的一串键值对数据。比如你在网站登录成功后服务端会在响应里加一个Set-Cookie: sessionidabc123浏览器收到后把这段数据存到本地下一次请求同一站点时自动带上Cookie: sessionidabc123服务端一看就知道“这个用户已经登录过了”。关于“cookie是在请求头里吗”这个高频问题严格来说它既在请求头里也在响应头里。请求时它出现在 HTTP 请求头的Cookie字段中下发时它在 HTTP 响应头的Set-Cookie字段里。很多新手只盯着请求头看忘了响应头才是 Cookie 的出生地结果调试了半天抓不到包。理解这一点对爬虫尤其重要。你用 Requests 模拟登录时第一次 POST 请求带的是用户名密码服务器返回的Set-Cookie就是登录凭证如果你没有正确处理这个响应头后续请求不带 Cookie服务端永远认为你是个游客自然拿不到登录后的数据。1.2 Cookie、Session、Token 三者的核心区别热搜词里有“cookie和session和token详解”我用自己的理解给你拆一下概念存储位置核心作用典型用法Cookie客户端浏览器或本地文件携带会话标识或用户信息每次请求自动带上Session服务端内存或缓存数据库保存登录态、购物车等会话数据通过 Session ID 关联 CookieToken客户端可放 Cookie、localStorage自包含的凭证服务端验签即可放 Authorization 头或放 Cookie 里自动携带打个生活化比方Cookie 是钥匙串上的标签Session 是保险柜本身Token 是一张自带防伪标识的卡片。你拿着卡片Token去开门门卫验一下卡上的签名就行不需要翻保险柜里存的资料——这就是无状态认证优于服务端 Session 的地方。在 Python 爬虫场景里大多数网站还是用 Cookie Session 的组合。你在爬虫里维持一个 Session 对象本质就是在模拟浏览器帮你自动管理这套 Cookie 机制。后面我会详细讲 Requests 的 Session 到底帮你做了什么。2. Python 操作 Cookie 的工具与手段从 Requests 到 Selenium2.1 Requests 的 Session自动管理 Cookie 这件事Requests 库是所有 Python 爬虫的起点而它的Session对象是操作 Cookie 最省心的方式。你可能会问为什么不能直接用requests.get()函数因为这个函数每次调用都是完全独立的不保留任何状态而Session对象会自己维护一个 CookieJar在多个请求之间自动保存和携带 Cookie。import requests session requests.Session() # 先登录服务器返回的 Set-Cookie 会被 session 自动保存 login_resp session.post(https://example.com/api/login, json{username: demo, password: 123456}) # 后续请求自动携带 Cookie不需要手动处理 profile_resp session.get(https://example.com/api/profile) print(profile_resp.status_code) print(session.cookies.get_dict())这段代码就实现了爬虫中最重要的“登录态维持”。很多初学者直接用 Session 还是会遇到“登录成功后请求还是 302”的问题最大的可能是登录请求的成功判断条件写错了或者账号密码字段名搞不对但这不怪 Session是业务层面的问题。Session的 Cookie 管理机制底层依赖的是http.cookiejar.CookieJar你可以直接拿到 Cookie 字典手动查看# 查看当前 Session 保存了哪些 Cookie cookies_dict session.cookies.get_dict() print(cookies_dict) # 例如 {sessionid: abc123, csrf_token: xyz}2.2 http.cookiejar底层 Cookie 存储的详细控制http.cookiejar是 Python 标准库里的 Cookie 控制器Requests 内部就是基于它实现的。它可以让你自定义 Cookie 的策略、保存到文件、从文件加载这在爬虫需要“离线 Cookie”、或者下次启动时复用登录态的场景里特别有用。import http.cookiejar import urllib.request # 创建一个支持文件持久化的 CookieJar cookie_jar http.cookiejar.MozillaCookieJar(cookies.txt) # 创建 opener绑定 CookieJar opener urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie_jar)) # 访问页面Cookie 会被保存到 jar resp opener.open(https://example.com) cookie_jar.save(ignore_discardTrue, ignore_expiresTrue) # 下次运行时加载 Cookie继续访问 cookie_jar.load(cookies.txt, ignore_discardTrue, ignore_expiresTrue) opener2 urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie_jar)) resp2 opener2.open(https://example.com/profile)虽然日常爬虫用 Requests 就够了但理解http.cookiejar的原理能帮你在排查 Session 是否丢 Cookie、或者需要跨线程共享 Cookie 时找到问题的根源。特别是当你用多线程爬虫时每个线程一个 Session 是不现实的通常需要手动把session.cookies的内容复制到其他 Session 中import requests session_a requests.Session() session_a.get(https://example.com/login) session_b requests.Session() # 把 session_a 的 Cookie 手动复制到 session_b session_b.cookies.update(session_a.cookies)2.3 Selenium 的 get_cookies 与 add_cookie浏览器和脚本的桥接Selenium 是处理动态渲染页面时的选择这里的 Cookie 操作思路跟 Requests 完全不同。Selenium 操作真实浏览器所以你要从浏览器实例里“取” Cookie或者“塞” Cookie 进浏览器。from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--headlessnew) driver webdriver.Chrome(optionsoptions) # 先打开页面让浏览器生成基础 Cookie driver.get(https://example.com/login) # 通过 JS 动态设置 Cookie模拟 JS 生成的 Cookie 场景 driver.add_cookie({name: from_js, value: helloworld, domain: example.com}) # 跑完一段流程后拿到所有 Cookie cookies driver.get_cookies() print(cookies) driver.quit()这里有一个细节add_cookie()前必须先driver.get()进入目标域否则浏览器会报 “You may only add cookies to a domain identifiable as the current site” 的错误。这个坑我在早期踩过很多次现在记住了先把页面打开再操作 Cookie。从 Selenium 拿到的 Cookie 是列表每个元素是一个字典包含name、value、domain、path、expiry等字段。如果你想把这些 Cookie 转成 Requests 能用的格式最简单的办法是直接拼成 Cookie 字符串cookie_str ; .join([f{c[name]}{c[value]} for c in cookies if c.get(name) and c.get(value)])或者更好一点把整个列表交给requests.Session的 cookies 更新方法import requests session requests.Session() for c in cookies: session.cookies.set(c[name], c[value], domainc.get(domain), pathc.get(path, /))2.4 手动构造 Cookie 头什么时候不该偷懒手动构造Cookie头是最“笨”但最直接的方案。有些场景下你不想维护 Session或者 API 接口只需要一个稳定的 Cookie 凭证那直接拼请求头就行import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Cookie: sessionidabc123; csrftokenxyz456; themedark } resp requests.get(https://example.com/api/data, headersheaders)为什么要强调双方因为手动构造意味着你要自己负责 Cookie 的时效性一旦过期就得重新获取。在爬虫项目里最佳实践是两个方案结合先用 Session 自动管理偶尔遇到需要手动指定 Cookie 的接口再临时构造请求头。没必要对每个请求都手动拼 Cookie那样反而容易因为硬编码导致登录态过期后全部请求一起挂掉。3. 爬虫实战模拟登录、Cookie 迁移与反爬应对3.1 模拟登录全流程从 POST 到 Set-Cookie 再到维持会话现在我把爬虫里最典型的模拟登录流程拆解一遍。假设目标是登录一个普通网站然后抓取登录后的个人中心数据。第一步抓包分析登录接口。打开 Chrome 开发者工具切换到 Network 面板勾选 Preserve log然后手动登录一次找到发起登录请求的接口把请求地址、请求方法、参数格式都记录下来。第二步用 Requests 模拟登录。import requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }) login_url https://example.com/api/login payload { username: your_account, password: your_password, remember: true } resp session.post(login_url, datapayload) # 检查是否登录成功有些接口返回 JSON有些返回 302 跳转 print(resp.status_code) print(resp.json()) # 假设返回 {code: 0, msg: success}第三步验证登录态并抓取数据。登录成功后用同一个 Session 请求目标页面如果返回内容里包含个人专有信息说明 Cookie 被正确携带了。profile_resp session.get(https://example.com/dashboard) print(profile_resp.text[:500])这里最容易犯的错是登录成功后重新创建 Session或者用了requests.get()这种无状态请求结果带不上 Cookie。记住登录到后续请求务必用同一个 Session 实例。3.2 浏览器 Cookie 导出到 Python从 Chrome 到 Requests热搜词里有“360浏览器怎么导出cookie”这类需求很多时候是因为登录过程太复杂比如需要扫码、需要验证码脚本不好模拟干脆手动在浏览器里登录一次把 Cookie 拿过来给 Python 用。拿 Chrome 举例打开开发者工具F12切到 Network 面板随便点击一个同域的请求在 Request Headers 里找到Cookie:那行整个复制出来就是可以直接用的 Cookie 字符串。import requests # 从 Chrome 里复制出来的 Cookie 字符串 chrome_cookie sessionidabc123; csrftokenxyz456; Hm_lvt_xxx1710000000 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Cookie: chrome_cookie } resp requests.get(https://example.com/private-data, headersheaders) print(resp.status_code)注意手动导出的 Cookie 有一定时效性短的可能几分钟就失效。如果目标站点把你登出了说明 Cookie 过期需要重新在浏览器里操作一遍。更省事的方式是用浏览器插件比如 EditThisCookie一键导出成 JSON然后 Python 里动态加载import json import requests # 假设从浏览器导出的 cookies.json 长这样 with open(cookies.json, r, encodingutf-8) as f: cookie_list json.load(f) session requests.Session() for c in cookie_list: session.cookies.set(c[name], c[value], domainc.get(domain, example.com))3.3 动态 Cookie网站用 JS 生成 Cookie 时怎么办有些网站会在加载首页时通过 JavaScript 动态生成 Cookie比如token或者fingerprint没有这个 Cookie后续接口一律拒绝。处理这种场景有两个方向方向一用 Selenium 先把页面跑一遍让浏览器自动生成这些动态 Cookie然后取出入库转给 Requests 使用。这个方案最贴“爬虫实战”的真实情况也是我目前最常用的。方向二逆向 JS找到生成 Cookie 的算法用 Python 复现。这个方向难度更高适合那些对性能要求高、需要频繁刷新 Cookie 的场景。一般来说先用 Selenium 顶住等业务规模需要再考虑纯脚本生成。3.4 反爬机制的正确姿势不要硬刚学会伪装热搜词里有不少关于“反爬虫”的搜索其实反爬不是一个可以一概而论的话题。从操作 Cookie 的角度看最核心的是对请求头的完整性负责Cookie、User-Agent、Referer 这些字段保持和真实浏览器一致能大幅降低被拦截的概率。import requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9, Referer: https://example.com/, }) # 保持同一个 Session避免频繁变化 for page in range(1, 6): resp session.get(fhttps://example.com/list?page{page}) # 业务处理...另一个角度看站点做反爬防护时也要注意合理性过于激进的防护会误伤正常用户。比如同一 IP 的请求频率要看具体业务场景来定阈值而不是一刀切。爬虫技术本身是中性工具用来做数据调研、自动化测试等正当用途完全没问题关键是别做任何违法或骚扰站点的事情。4. Web 开发实战在 Flask 与 Django 中设置、读取、守护 Cookie4.1 Flask 中设置和读取 Cookie顺手搞定登录态Web 开发里操作 Cookie 和爬虫视角完全不同这时候你是“发 Cookie”的那一方。用 Flask 写一个简单的登录接口设置 Cookie 只需要在 Response 对象上调用set_cookie。from flask import Flask, request, make_response, jsonify app Flask(__name__) app.route(/login, methods[POST]) def login(): username request.json.get(username) password request.json.get(password) # 这里简化验证实际项目请查数据库 if username admin and password 123456: resp make_response(jsonify({code: 0, msg: 登录成功})) resp.set_cookie( sessionid, generated-session-token-here, max_age24 * 60 * 60, # 有效期 24 小时 httponlyTrue, # 禁止 JS 读取 secureFalse, # 开发环境设置 False生产环境记得开 True samesiteLax, # 防 CSRF 的关键属性之一 path/, ) return resp return jsonify({code: 1, msg: 登录失败}), 401 app.route(/profile) def profile(): sessionid request.cookies.get(sessionid) if sessionid ! generated-session-token-here: return jsonify({code: 401, msg: 未登录}), 401 return jsonify({username: admin, bio: Hello})这段代码里httponlyTrue一定要养成习惯。虽然它不影响服务端读取 Cookie但能防止恶意 JS 脚本通过document.cookie窃取你的登录凭证是对抗 XSS 攻击的第一道防线。4.2 Django 中设置和删除 Cookie框架自带的安全属性Django 设置 Cookie 的 API 也是直接加在 Response 对象上from django.http import HttpResponse def login_view(request): response HttpResponse(登录成功) response.set_cookie( token, jwt-or-session-token, max_age60 * 60 * 24 * 7, httponlyTrue, secureTrue, samesiteStrict, ) return response def logout_view(request): response HttpResponse(已登出) response.delete_cookie(token) return response在 Django 里还有一种更高级的玩法把 JWT 或 Token 放在 Cookie 里而不是存在 localStorage。好处是请求时会自动携带不需要前端手动拼 Header配合HttpOnly属性即使页面被注入脚本也拿不到 Token。Django 的CsrfViewMiddleware默认会检查 CSRF token如果你的 Cookie 里有csrftoken模板里输出{% csrf_token %}时就可以取到形成一个完整的防护闭环。4.3 Cookie 与 Token 的配合一种更实用的认证组合关于“django cookie 设置 token”核心是把 Token 放进 Cookie由浏览器自动携带。这种方式适合传统的服务端渲染页面或者前后端同源的 Web 应用。流程如下用户登录服务端生成 Token可以是 JWT也可以是随机字符串。服务端把 Token 写入 Cookie开启 HttpOnly、Secure、SameSite。后续请求浏览器自动带Cookie: tokenxxx。服务端从request.COOKIES.get(token)读取并验签。对比把 Token 放Authorization头的做法Cookie 方案对前端更友好所有请求无需 JS 介入但缺点是不适合跨域场景。如果你的前端部署在app.example.comAPI 在api.example.com那么 Cookie 的 Domain 要设置成.example.com同时后端还需要处理 CORS 配置里的credentials字段。4.4 Web 端如何有效防护爬虫热搜词里有“java controller层 如何防护 防止爬虫”虽然说的是 Java但思路对 Python Web 开发同样适用。防护绝对不是靠“不让爬虫拿 Cookie”这种单一手段因为爬虫完全可以伪造 Cookie。更有效的措施包括接口频率限制按用户、IP、设备指纹做限流超过阈值就暂时封禁。行为分析判断请求间隔是否过于规整、点击路径是否合理。验证码高危操作注册、登录、支付、批量查询增加验证码。接口鉴权敏感接口不要全部裸奔至少做一层 Token 校验。业务阈值比如单个账号每天最多查询 100 条数据超过就要求人工验证。从操作 Cookie 的角度Web 端合理的防护是设置合理的 Cookie 有效期、绑定用户 IP 或者设备指纹。比如 Cookie 的有效期不要设成永久同时Secure和SameSite属性按需设置。如果发现某个 Cookie 在同一时间被多个不同 IP 使用基本就是被共享了可以针对性失效。5. 常见问题与排查技巧实录5.1 Chrome 开发者工具里找不到 Cookie热搜词里有“chrome开发者工具没有cookie”。这个问题常见于开发者工具停留在 Elements 面板或者没有切换到正确的域名上下文。正确做法是打开 Network 面板点击任意请求在 Request Headers 里找到Cookie字段或者切到 Application 面板在左侧 Storage 下找到 Cookies选择对应域名查看。还有一种可能是站点使用SameSiteNone; Secure的第三方 Cookie这种 Cookie 不会记录在首次请求的域名下而是在 iframe 或跨域请求的上下文里生成。遇到这种情况你要找到生成该 Cookie 的最高层级域名在开发者工具的 Network 面板里全局搜索Set-Cookie用 CtrlF 搜索 Cookie 名。5.2 Cookie 里的中文变成乱码热搜词里有“cookie中文”。Cookie 头的规范是只允许 ASCII 字符你在set_cookie(username, 张三)时Flask 会自动做 URL 编码存成username%E5%BC%A0%E4%B8%89。爬虫端拿到这个值需要解码from urllib.parse import unquote raw_value %E5%BC%A0%E4%B8%89 name unquote(raw_value) # 张三更稳妥的方案是别把中文直接放 Cookie放 Base64 后的字符串。比如用户名可以先base64.b64encode(张三.encode(utf-8))再放 Cookie需要读取时再解码。Cookie 体积有限一般单域名 4KB 左右不适合放大数据。5.3 登录态频繁失效Session 和 Cookie 的相爱相杀爬虫最常见的问题是“登录成功了但过一会儿就掉了”。排查思路如下第一步检查 Cookie 的过期时间。登录成功后打印session.cookies.get_dict()看返回的 Cookie 里有没有expires属性如果没有且站点的 Set-Cookie 设置了Session那说明是会话型 Cookie浏览器关闭即失效但你用 Requests 保持 Session 一般不会掉。第二步检查服务器有没有做 Cookie 绑定。很多站点会把 Cookie 和 User-Agent、IP 绑定如果你的爬虫在两次请求之间换了 User-Agent 或者 IP或者用了两个不同的 Session 实例服务端会立即判定会话异常。第三步检查重试逻辑是否用了同一个 Session。我见过很多代码在重试时重新requests.post()了一次登录这倒不是坏事但如果你在循环里每次都新建 Session等于每次都重新登录不仅低效还可能触发风控。5.4 跨域请求导致 Cookie 丢失前后端分离项目里前端在localhost:3000后端在localhost:8000前端发起请求可能带不上 Cookie。这个问题的根源是 Cookie 的 Domain 和 SameSite 属性。在 Flask 里解决resp make_response(...) resp.set_cookie(token, xxx, samesiteNone, secureTrue)注意浏览器对SameSiteNone有一个硬性要求必须同时设置Secure也就是 Cookie 只能在 HTTPS 下使用。本地开发没有 HTTPS你可以用http://localhost测试时把secureFalse、samesiteLax或者用代理工具给 localhost 加上 HTTPS。在 Django 里同样要配置SESSION_COOKIE_SAMESITE和CSRF_COOKIE_SAMESITE跨域时还要在 CORS 中间件里明确允许携带凭证CORS_ALLOW_CREDENTIALS True CORS_ALLOWED_ORIGINS [http://localhost:3000]5.5 爬虫请求被限流先分清是 IP 问题还是 Cookie 问题有时候不是 Cookie 失效了而是你的请求频率太高触发风控。识别方法是单独用浏览器访问目标页面如果浏览器能正常打开而用脚本同样的 Cookie 访问却返回 403 或需要验证码大概率是 IP 被限流了。这时候正确做法是降低请求频率、增加随机延时、把并发降下来。如果用代理 IP更要注意代理的稳定性和质量频繁更换 IP 反而容易被服务端判定为异常行为。另外很多站点对“无 Cookie 的请求”特别敏感哪怕你只是访问一个公开页面也要先访问一次首页获取基础 Cookie再带着 Cookie 请求其他接口否则会被 WAF 直接拦掉。我在实际项目里总结出的习惯是这样给爬虫专门封装一个“会话管理器”统一负责 Session 的创建、Cookie 的获取和刷新、请求头的维护。这样业务代码里只需要调get(url)完全不用关心 Cookie 细节。会话管理器内部做好 Cookie 过期判断和自动重登整个爬虫的稳定性会提升一大截。最后再分享一个小技巧不管做爬虫还是 Web 开发都建议在代码里把 Cookie 相关的操作单独封装一层不要散落在各处。爬虫端把它叫cookie_managerWeb 端把它叫auth_controller。这样一来遇到 Cookie 失效、属性调整、防护升级你只需要改一个文件而不是满项目找代码。真正的坑往往不是技术复杂而是 Cookie 逻辑分散、前后端不一致导致的问题把入口收敛好你能省下大量排错时间。