恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
requests模拟登录豆瓣:Session会话保持与Cookie原理详解
首页
资讯中心
/
requests模拟登录豆瓣:Session会话保持与Cookie原理详解
requests模拟登录豆瓣:Session会话保持与Cookie原理详解
发布时间:2026/10/14 15:44:05
上一篇我们把 requests 的基础玩法过了一遍能拿公开页面了。但很快就会有朋友碰到新问题豆瓣上想抓自己的收藏、标记或者“我参加的活动”一访问就跳回登录页代码里明明写了网址返回的却是 login 页面。问题出在哪儿因为浏览器里你登录过服务器认你而 requests 每次发请求都是“陌生人模式”它没有携带任何能证明身份的凭证。这期就来专门解决这个事用 requests 模拟登录豆瓣网并通过 session 会话保持让代码拥有和浏览器一致的登录态。适合刚会 requests 基础用法、想搞懂 Cookie 和 Session 区别的人看完你不光能登录豆瓣换到别的网站也能套同一套思路。1. 登录态是什么、为什么单独发请求总被当成路人1.1 HTTP 为什么“记不住”你先聊本质。HTTP 协议本身是无状态的意思是服务器处理完你这一次请求转头就把你忘了下一次请求来的时候它根本不记得你是谁。浏览器里你能一直保持登录是因为浏览器帮你做了一件额外的事——保存 Cookie并在每个请求里自动带上它。我打个比方。HTTP 无状态就像去展会每个展馆门口都有保安你每进一个新展馆都要重新出示门票。第一次你买了一张通票保安在你手背盖了个章后面进下一个展馆时你把盖章的手伸过去保安一看就知道你是买过票的人放你进去。这里“手背上的章”就是 Cookie“盖章”的动作就是登录而 requests 默认就是那个每次进门都忘了伸手背、被保安拦在外面的倒霉蛋。1.2 Cookie 是怎么把你“认出来”的你打开浏览器按 F12 进入开发者工具切到 Network网络标签勾选 Preserve log保留日志然后在豆瓣登录一次。你会看到登录成功之后服务器返回的响应头里带了一段 Set-Cookie而后续每一个请求的 Request Headers 里都会出现 Cookie。这段 Cookie 里面通常包含几类信息会话标识服务器用它来关联你已经登录的账号用户偏好比如语言、皮肤这类不敏感配置安全标记有些站点会把登录凭证拆成多个片段或者放一个加密签名防止 Cookie 被篡改。对爬虫来说最重要的就是“会话标识”这一块。只要你在请求里带上它服务器就会认为“这个请求就是我刚刚登录过的那个浏览器”不带就一律按匿名用户处理。1.3 为什么单独用 requests.get() 很难模拟登录很多人踩过这个坑在浏览器里登录豆瓣然后从开发者工具里复制一串 Cookie 贴到代码里用 requests.get() 访问个人主页发现能通。看起来是成功了但是过一会儿再来跑突然又不行了。原因很简单Cookie 会过期。豆瓣的登录态不是永久的而且登录态还可能绑定 IP 和 User-Agent。你复制 Cookie 这种方案本质上是在“偷”浏览器的登录态而不是“模拟登录”。一次两次能用一旦过期或者换了环境脚本立刻废掉。真正可靠的思路是让代码自己完成“登录”这个动作把返回的 Cookie 留在内存里之后的请求全部带上。这就是 Session 会话保持做的事情。2. 先抓包再看代码豆瓣登录请求的完整拆解2.1 抓包先看浏览器登录时真实做了什么写代码之前我强烈建议你先抓一次包因为网上的登录接口地址和参数经常变照抄老教程大概率翻车。抓包步骤很简单打开浏览器无痕窗口按 F12 进 Network勾选 Preserve log这样页面跳转后请求记录不会清空在登录页输入账号密码先别点登录点一下“清空日志”按钮让记录干净点登录等待页面跳转完成在 Network 里找到刚才发出的那个 POST 请求点开看 Headers 和 Payload。这个 POST 请求的 URL 就是登录接口。豆瓣早期常用的登录地址形如https://accounts.douban.com/login后来也出现过其他路径具体以你抓到的为准。请求体里一般能看到类似这样的字段source: None redir: https://www.douban.com/ form_email: 你的账号 form_password: 你的密码 remember: on login: 登录2.2 登录请求里的关键字段别只盯着账号和密码很多人以为模拟登录就是把“用户名”和“密码”两个字段 POST 过去其实还差得远。注意看 Payload 里这几个字段redir登录成功后的跳转地址。这个字段如果为空或者指向了错误页面会导致登录后重定向异常source标识登录来源一般直接沿用浏览器里的值即可remember勾选“记住我”时才会有通常值是 on作用是延长登录态有效期ck / csrf_token部分场景下会有一个防跨站请求伪造的令牌它往往要从登录页 HTML 或 Cookie 里获取。密码字段的处理也要留意。有的站点前端会做 JS 加密把明文密码处理成一串密文再提交豆瓣的登录逻辑在不同时期不一样如果你抓到的是密文就得去前端 JS 里找加密算法这是比模拟登录更麻烦的话题。如果抓到的是明文那就简单直接在 HTTPS 下提交即可。我的经验是不要盲目照抄任何教程里的参数名一定要和你自己的抓包结果对齐。曾经有朋友拿网上的老代码改参数写的是username实际接口认的是form_email结果死活登录不上。2.3 302 跳转背后的逻辑登录成功不等于页面到手登录接口返回的响应正常情况下不是让你直接看到登录后的页面而是一个 302 重定向响应头里的 Location 指向 redir 参数指定的地址。浏览器会自动跟着这个 Location 跳转同时把登录 Cookie 存下来。requests 默认也会自动跟随重定向所以一般你不需要手动处理 302。但你要理解这个流程因为在实际调试中如果登录失败响应体里往往不是“密码错误”这种清晰提示而是又把你重定向回登录页或者返回一段 JSON里面包含msg之类的错误信息。所以判断登录成不成功不能光看状态码要看最终拿到的页面内容是不是登录后才能看到的东西。这一点后面再展开。3. 手写登录流程Session 建会话、带请求头、提交参数、验证结果3.1 第一步创建 Session 对象让 Cookie 有地方安放先明确一个关键点requests库的顶层方法比如requests.get()、requests.post()每次调用都是独立的不会保存任何状态。而requests.Session()创建的是一个会话对象它会自动帮你做两件事保存本次会话中服务器 Set-Cookie 的 Cookie并在后续请求中自动带上复用底层 TCP 连接减少重复握手开销。代码非常简单import requests s requests.Session()就是这么一行。之后所有需要携带登录态的请求都用s.get()、s.post()来发而不是requests.get()、requests.post()。3.2 第二步带上像浏览器一样的请求头服务器判断你是不是真实用户第一道关卡就是 User-Agent。如果你用 requests 默认的python-requests/x.x.x部分站点会直接拒绝或把你扔到验证码页面。我在代码里通常这样设置基础请求头headers { 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: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } s.headers.update(headers)User-Agent里的 Chrome 版本号不需要完全一致只要格式像真实浏览器就行。注意整个登录过程中 User-Agent 要始终保持一致不要登录前用一个登录后换一个否则容易被判定为异常会话。3.3 第三步提交登录参数与验证码处理核心提交代码如下我把账号密码从环境变量里读取避免把敏感信息硬编码到脚本里import os login_url https://accounts.douban.com/login # 以你抓到的实际地址为准 # 先访问一次登录页这一步很重要 # 目的是让 session 拿到最基础的 Cookie 和页面内容 s.get(login_url, timeout10) payload { source: None, redir: https://www.douban.com/, form_email: os.environ.get(DOUBAN_EMAIL), form_password: os.environ.get(DOUBAN_PASSWORD), remember: on, login: 登录, } resp s.post(login_url, datapayload, timeout10)为什么提交前要先 GET 一次登录页因为很多站的登录逻辑会先下发一个初始 Cookie没有这个 Cookie 就直接 POST会被风控拦下。简单说就是先让服务器认识你再让它验证你的身份。如果账号比较干净、操作频率不高这一步通常就能直接登录成功。但如果你看到响应内容里出现“验证码”相关字样说明触发了二次验证就需要额外处理。验证码这块我的处理原则是能人工介入就人工介入不建议碰任何自动打码服务。原因是自动打码在合规上属于灰色操作而且容易导致账号被加重风控。简单做法是把验证码图片下载到本地人工看一眼再输入from pathlib import Path if captcha in resp.text or 验证码 in resp.text: # 从响应体或页面中提取验证码图片地址这里示意为示例链接 captcha_img_url https://example.com/captcha.jpg img_resp s.get(captcha_img_url, timeout10) Path(captcha.jpg).write_bytes(img_resp.content) code input(请打开 captcha.jpg 输入验证码) payload[captcha-solution] code payload[captcha-id] 从页面中提取的captcha_id resp s.post(login_url, datapayload, timeout10)不同时期豆瓣验证码的字段名和获取方式可能不同具体以你抓包看到的为准。核心思路是响应里出现验证码特征时把图片保存下来人工输入再重新提交一次。3.4 第四步用“特征点”验证登录是否真的成功这一步很多人忽略但恰恰是最容易翻车的地方。登录接口返回 200 不代表登录成功返回 302 也不代表登录成功。唯一可靠的判断标准是访问一个只有登录后才能看到内容的页面检查里面是否出现你账号相关的特征文字。profile_url https://www.douban.com/people/me/ r s.get(profile_url, timeout10) if 退出 in r.text or 我的主页 in r.text: print(登录成功) else: print(登录失败可能被重定向到登录页)“退出”这个字样是登录态的一个典型标志因为未登录用户根本看不到退出按钮。你还可以把r.url打出来看一眼如果它最终停在登录页地址那基本可以断定登录失败。4. 登录态不止于登录会话复用、连接池与本地持久化4.1 同一个会话的连续请求抓取“我的收藏”列表登录完成之后session 的最高价值就体现出来了。你不需要在每一个请求里手动拼接 Cookie只要继续用同一个s对象发请求登录态会自动带上。比如抓取当前账号的收藏列表豆瓣的书影音收藏通常是通过带参数的 GET 请求分页加载地址形如https://xxx.douban.com/people/me/interests参数可能包括type、start、count等。代码大致是这样的params { type: movie, start: 0, count: 30, } page s.get(interest_url, paramsparams, timeout10)注意这里的start参数表示从第几条开始取count表示一次取多少条。很多新人在抓分页时会犯一个错误把参数直接拼在 URL 字符串里遇到参数值需要 URL 编码时就乱了。用params字典让 requests 帮你编码更稳妥也更清晰。4.2 连接池带来的速度提升为什么 session 更快除了自动带 CookieSession还有一个容易被低估的好处——连接复用。底层是 urllib3 的连接池对同一个站点发起多次请求时会复用已经建立的 TCP 连接省去重复的 TCP 握手和 TLS 握手。我在实际测试中对比过抓取同样的 200 个页面用requests.get()每次新建连接的方式大概需要几十秒换成同一个 session 复用连接之后耗时能下降一半以上。数据量越大差距越明显。会话保持的“保持”二字不光是保持登录态也包含保持连接状态。如果你需要并发抓取还可以在 session 对象上设置连接池大小from requests.adapters import HTTPAdapter adapter HTTPAdapter(pool_connections10, pool_maxsize10) s.mount(https://, adapter) s.mount(http://, adapter)这个配置会为 HTTP 和 HTTPS 分别维护一个连接池。不过注意并发数量不要开太大否则会被对方服务器判定为恶意抓取。4.3 持久化登录态程序重启后免登录session 对象里的 Cookie 默认只存在内存里程序一退出就没了。如果你写的是一个需要每天自动跑一次的脚本每次都重新走一遍登录流程也不是不行但更容易触发验证码。更好的做法是登录成功之后把 Cookie 序列化保存到本地下次启动直接加载。import pickle # 保存 with open(douban_cookies.pkl, wb) as f: pickle.dump(s.cookies, f) # 恢复 with open(douban_cookies.pkl, rb) as f: cookies pickle.load(f) new_session requests.Session() new_session.cookies cookies需要注意两点Cookie 有有效期失效后还是要重新登录所以代码里要加一个“检测未登录就重新登录”的逻辑Cookie 可能绑定 IP 和 User-Agent如果你换了网络环境或者改了请求头恢复出来的 Cookie 不一定有效。“优先使用会话保持登录失效后自动重新登录”是我个人很推荐的一种优雅模式它既减少了验证码触发概率也让脚本更稳定。避免踩到这些坑你的登录脚本基本就已经能稳定跑了。下面再说几个我实际调试中经常见到的问题每个都很典型。5. 我踩过且值得你避开的坑登录失败、验证码、请求头缺失5.1 账号密码没问题却提示登录失败这是初学者遇到最多的问题。如果你确定账号密码正确但登录后访问个人主页依然跳回登录页按这个顺序排查可能原因检查方法登录前没有先 GET 登录页在 POST 之前加一次s.get(login_url)表单字段名与接口不一致对比抓包里的 Payload确认是form_email还是username缺少redir/source等辅助字段浏览器里有啥字段代码里就带啥字段请求头不一致或 UA 缺失检查 headers 是否设置了完整 UACookie 域不一致POST 登录接口的域名和 GET 登录页的域名要一致别跨域提交其中“跨域提交”这个问题比较隐蔽。有的站点登录页在accounts.example.com但主站在www.example.com如果你用 session 先访问了主站再向登录域提交Cookie 的域对不上登录态就存不住。解决方案是先访问登录域下的页面再向登录域提交表单。5.2 验证码“小黑屋”与低频率试探频繁登录、账号环境异常、同一 IP 下多个账号切换都可能触发验证码。一旦触发你面临的就不只是“输入一次验证码”的问题而是被临时限流短时间内怎么试都过不去。我的建议是把请求频率降下来登录前先访问几次公开页面“预热”一下会话模拟真人使用轨迹。两次登录尝试之间至少间隔几十秒不要在一个死循环里反复提交。如果验证码已经出现人工输入之后还失败就停下来歇一会儿别硬刚。硬刚的结局往往是账号被临时锁定。5.3 Referer、Origin 这类隐形头掉了就 403不少站点除了检查 User-Agent还会检查 Referer 和 Origin。Referer 含义是“你从哪个页面跳过来的”Origin 含义是“这个请求的发起源头”。豆瓣的部分接口对这类头很敏感不带或者带错直接返回 403。抓包时在请求 Headers 里能看到浏览器发送的完整请求头Referer: https://accounts.douban.com/login Origin: https://accounts.douban.com把它们补到代码里一般就好了。我给 session 设置的默认请求头里就会带上这两个s.headers.update({ Referer: https://accounts.douban.com/login, Origin: https://accounts.douban.com, })需要注意的是Referer不是所有接口都要带同一个值它应该随着你“当前所处页面”的变化而变化。比如你登录后访问个人主页合适的 Referer 可能是登录成功后的跳转地址而不是登录页地址。遇到 403 时优先去抓包里看那个请求实际带了什么 Referer照抄即可。5.4 302 重定向循环allow_redirects 的正确用法有时你会遇到一种很诡异的错误请求最终报“too many redirects”或者明明登录成功了但代码里访问某个页面一直在登录页和个人主页之间来回弹。这种问题通常是 redir 参数指向了一个需要登录才能访问的地址而后端发现你登录态尚未完全生效又把你踢回登录页。实际上你已经登录成功了只是跳转逻辑出了问题。调试方法很简单把allow_redirects设为False手动跟踪每一步跳转resp s.post(login_url, datapayload, allow_redirectsFalse) print(resp.status_code) print(resp.headers.get(Location))这样你能看到服务端到底让你往哪跳。如果发现 Location 地址怪异直接把redir改成首页即可不让登录流程陷入死循环。6. 模拟登录的红线合规操作与账号安全6.1 哪些场景算“可以接受”的自动化聊完技术必须聊聊边界。模拟登录本身不是洪水猛兽关键看你怎么用。我理解的合规场景是登录自己的账号抓取自己的收藏、标记、日记等个人数据用于本地备份或个人统计分析学习 HTTP 协议、Cookie 与 Session 机制在可控范围内做技术验证个人自动化小工具比如定时检查某个自己参与的页面变化。这类场景下请你把请求频率控制在合理范围趴着别太猛给服务器留点喘息空间。一个比较实用的参考标准是不要比你正常人手点鼠标还快。另外建议看一下目标站点的 robots 协议文件了解哪些路径是不希望被自动程序抓取的即使网站没有强制限制也尽量秉持克制原则。6.2 绝对不要碰的雷区有几类行为是红线中的红线我在文章末尾再强调一次用撞库、爆破的方式尝试他人账号批量注册账号绕过平台的风控体系抓取非公开数据用于商业交易破解验证码以大规模自动操作。这些行为轻则封号重则牵涉法律问题。写爬虫第一原则是“先做人再做技术”。很多人觉得验证码破解或者绕过风控很酷但其实这恰恰是最容易把自己搭进去的地方。我写这篇文章默认读者是拿模拟登录去处理自己的账号和个人数据场景而不是去攻击任何平台。6.3 账号安全与代码卫生最后给几个账号安全建议都是我自己的做法不要把账号密码硬编码在脚本里用环境变量或单独配置文件保存并确保这些文件不会被提交到公开仓库不要在代码里打印密码、Cookie 全文等敏感信息万一要分享代码先把敏感字段打码只保存自己的 Cookie不要把别人的 Cookie 拿到本地加载避免不必要的隐私风险登录脚本运行完后及时清理临时保存的验证码图片和 Cookie 文件。如果你做到了以上几点那么 session 会话保持这个技能会成为一个非常顺手的自动化工具基础。我自己现在的习惯是先抓包分析、再写代码、最后用特征点验证这套流程跑通一次之后换成别的网站也就改改 URL 和字段名的事。登录这件事确实不难难的是细心和克制。希望这篇对你有所帮助下一篇有机会可以聊聊登录之后的数据解析与存储让抓下来的数据真正变成可用的东西。