我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

requests模拟登录与Session会话保持:从登录原理到爬虫实战

requests模拟登录与Session会话保持:从登录原理到爬虫实战 “模拟登录 会话保持”是我自己学爬虫时最兴奋的一个里程碑。那时候刚会用 requests 写GET请求到处抓静态页面但遇到“必须先登录才能看到的内容”就只能干瞪眼。直到把requests的Session机制搞明白才发现登录这件事本质上是一次“请求-响应接力赛”你带着正确的凭证去敲门服务器验明正身后塞给你一张会员卡之后你再去任何房间只要把卡一亮就能过。这篇文章要聊的就是这套流程用 requests 实现模拟登录并通过 session 对象让登录状态一路延续下去适合刚学完requests.get、想更进一步的新手也适合做过几个静态页面采集、总在登录环节翻车的人。这套方案能解决什么问题最典型的就是“需要身份才能看的数据”自己的订单记录、购物车、收藏夹、发布过的内容、私信列表等等。没有别人教自己踩坑也能走通但完整地把抓包思路和代码串在一起讲的教程不多所以我把实际项目中沉淀的经验整理出来。下面从登录的原理讲起再进入抓包、写代码、踩坑排查最后聊几句我能想到的注意事项。内容稍微有点长建议跟着敲一遍比只看不练效果好得多。1. 登录这件事本质上就是一次请求-响应接力赛1.1 为什么偏偏用 requests 而不上浏览器自动化不少人第一反应是登录不是得在浏览器里输账号密码吗那直接用 Selenium 或 Playwright 模拟点击不就行了能用但大多数场景我不建议。Selenium 每次启动一个完整浏览器慢且重跑一个登录脚本光等浏览器起来就要好几秒连续跑几十个账号就非常难受。而且浏览器自动化特征容易被检测不少平台对这类访问盯得紧反而 requests 直接构造 HTTP 请求毫秒级完成轻量到几乎感觉不到资源消耗。也有同学担心 requests 没有界面登录过程里那些“看不见的交互”是不是搞不定。实际上登录不管界面怎么花里胡哨最终提交的那个 POST 请求才是关键。只要在浏览器的开发者工具里把这次 POST 的地址、请求头和表单字段抓出来requests 完全能 1:1 模拟。这种方式对站的依赖更小也更贴近后端交互的本质后续做批量任务、定时脚本都顺理成章。当然requests 方案也有代价如果目标站的登录接口里有 JS 加密、动态 token、复杂验证码你得自己处理。相比 Selenium 的“绕开问题”requests 是“直面问题”。但正因如此理解清楚登录原理之后你的能力边界会一下子拓宽很多。1.2 Session 到底帮你记住了什么底层机制拆解先做一个实验你就懂了。打开一个普通网站用requests.get(url)连续访问两次两次响应看起来一样但服务器是把你当路人甲的每次都是新会话没有记忆。而 browsers 里你登录一次之后再刷新页面、点链接、看资料服务器都知道“这是刚才那个人”靠的是一张叫Cookie的凭证。登录成功后服务器会通过响应头里的Set-Cookie下发一段身份标识浏览器的后续请求会自动带上它。requests 里的Session对象做了同样的事它内部维护了一个 Cookie 容器收到Set-Cookie后自动存下并在下一次请求时自动附加到请求头里。说得直白点requests.Session()就是帮你养成了一只“自动感知 Cookie 的手”不像单独调用requests.get()那样每次都是一张白纸。用一个类比你会记得更牢登录就像去健身房办卡GET登录页是问前台“有什么套餐”POST账号密码是把身份证递过去“验证通过”后前台给你一张门禁卡。这张门禁卡就是 Cookie。用普通requests.get的用户每次进门都被当成散客用Session的用户卡一直揣兜里每次进门滴一下就能过。Session 还有一层优势它复用底层 TCP 连接连续请求同一个站点时可以减少反复握手的时间开销。别小看这点优化采集大量页面时速度差距能到倍级。用完记得session.close()或者用with requests.Session() as s:管理生命周期免得连接不释放。2. 动手前先做两件事抓包和分析表单2.1 开发者工具里如何快速定位登录接口写代码之前永远先抓包。别凭感觉猜登录接口我有段时间就是直接在网上搜“某某站登录接口”结果十个里有八个是错的白折腾一晚上。正确做法是打开目标站点的登录页面按F12进入开发者工具切到 Network 面板。这里有个细节登录后页面会跳转如果一开始没注意POST 请求很容易一闪而过。Network 面板里先勾上Preserve log保留日志和Disable cache禁用缓存再手动做一次登录就能把所有请求完整留下来。然后过滤关键词login、auth、session重点看 Method 为 POST 的那几条这就是候选登录接口。点开候选请求后看三个地方URL、请求头、Payload。URL 是提交地址Payload 是实际提交的数据。注意一点很多站点的接口已经改成application/json格式Payload 里是一段 JSON而不是传统的application/x-www-form-urlencoded表单。这个格式差异在后面写代码时直接影响你是用data还是json传参一定要看清。2.2 分析请求头和表单字段的隐藏细节请求头里常见需要照搬的有User-Agent、Referer、Origin、X-Requested-With。前两个不用说Referer 表示“我是从哪个页面跳过来的”不少站点会校验它少了直接返回 403Origin 和它类似表示跨域请求来源X-Requested-With: XMLHttpRequest则常见于前端用 AJAX 提交的场景后端会据此判断这是一次普通页面提交还是一次“脚本式的接口调用”。表单字段也不要只看“用户名、密码”两个。实际抓包时你很可能看到一串奇怪的字段名比如csrf_token、xxx_sign、timestamp。这些往往对应站点的安全校验逻辑。我曾经抓过某个站的登录接口除了账号密码还必须带一个服务端下发的随机 token这个 token 藏在登录页 HTML 里需要先GET一次登录页用正则或解析库把它抠出来再放进登录请求里一起提交。遇到某个字段看起来像加密过的值先忍住别去硬啃 JS 逆向。第一步看它是不是固定的第二步看它是不是上一步接口动态返回的第三步才考虑它是不是前端对某些参数做了加签。绝大多数学习场景里处理好 token 和验证码就能登录成功不要一上来就把精力耗在逆向加密上。2.3 验证码要不要处理怎么处理验证码是最劝退新人的一道坎。处理策略取决于验证码类型字符验证码、滑动验证码、点选验证码、无感验证。学习阶段我的建议是先找一个没有验证码或者验证码很简单的站点把流程跑通这样你才能真正理解“登录请求本身”的细节而不是被验证码把注意力拖走。如果你的目标站登录时必须验证码常见工程方案有三种。一是打码平台把验证码图片传过去、返回识别结果成本低适合字符类。二是自己训练 OCR 或接入现成的图像识别模型这条路更自由但训练数据、调参都是时间。三是滑动验证码这类往往需要分析轨迹或本地参数难度高一些不建议新手在这个阶段死磕。这里必须提一句边界验证码机制本身是站点为防御自动化批量访问设计的学习如何提交验证码、了解识别原理都没有问题但不要以突破风控为目标去研究绕过手段。把注意力放在理解整套请求链路和会话机制上反而收获更大。3. 完整代码实现从登录到保持会话的每一步3.1 初始化 Session 并伪装请求头代码从requests.Session()开始。这个对象就是我们的“带卡用户”后续所有 GET 和 POST 都用它发起不要再用裸的requests.xxx否则 Cookie 存不下登录状态也传不下去。import requests # 以某书影音平台为例实际操作时替换成你的目标站点 BASE_URL https://example.com LOGIN_URL BASE_URL /login PROFILE_URL BASE_URL /profile # 创建会话相当于一个带 Cookie 记忆的浏览器 session requests.Session() # 伪装成一个正常浏览器访问 session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Referer: LOGIN_URL, })这一段看似简单其实值得展开说。Referer在登录页跳转到登录接口时很关键有的站点后端会校验来源页少了直接拒绝。User-Agent不建议用默认的python-requests因为几乎所有风控都会优先盯上它。更好的习惯是打开自己浏览器复制当前 UA因为同版本浏览器的 UA 在实际使用场景里最不容易让人起疑。关于UA我踩过一个小坑不同平台对 UA 的校验粒度不一样有的只查“是不是浏览器”有的连浏览器版本都查。所以强烈建议把 UA 写的和你平时浏览器完全一致而不是随手抄一个网上流传的旧 UA。3.2 构造登录请求提交账号密码正式提交登录数据前最好先访问一次登录页这一步有两个目的拿初始 Cookie以及从页面 HTML 里提取动态参数。有些平台的登录页会下发一个csrf_token或login_token需要在下一次提交时原样带回去。先把这一步写进代码import re # 先 GET 一次登录页让服务器认识我们 resp session.get(LOGIN_URL, timeout10) print(登录页状态码: , resp.status_code) # 尝试从页面里提取隐藏字段 token有些站点没有这一项 token_match re.search(rnamecsrf_token value([^]), resp.text) csrf_token token_match.group(1) if token_match else None接着构造登录请求。登录接口的位置和具体字段名我用注释代替——你自己抓包后对照修改就行。不同站点的字段差异很大写过几个站就会发现大同小异。login_data { username: 你的账号, password: 你的密码, # 有 token 就加进去 } if csrf_token: login_data[csrf_token] csrf_token # 提交登录请求 login_resp session.post(LOGIN_URL, datalogin_data, timeout10) print(登录接口状态码: , login_resp.status_code) print(登录接口返回: , login_resp.text[:200])这里有几个容易踩的细节。第一如果抓包时看到 Payload 是 JSON 格式代码里要改成session.post(LOGIN_URL, jsonlogin_data)而不是datalogin_data两者 Content-Type 不一样后端解析方式也不同。第二有些站点要求 X-Requested-With 头可以临时在字典里加上。第三密码字段名不一定叫 password可能是passwd、pwd一切以抓包结果为准。登录请求的响应不要只看状态码200 不代表登录成功很多登录失败时后端也返回 200 HTML把错误提示渲染在页面里。所以要配合打印响应前 200 个字符快速瞄一眼有没有“验证码错误”“密码错误”之类的文案。3.3 验证会话状态并访问登录后页面登录请求发出后最关键的一步是验证“会话是否真的建立成功”。方法很简单用同一个 Session 去访问一个必须登录才能看到的页面比如个人主页、收藏列表、订单中心。profile_resp session.get(PROFILE_URL, timeout10) print(个人主页状态码:, profile_resp.status_code) # 判断是正常内容还是被重定向回登录页 if profile_resp.status_code 200 and 个人中心 in profile_resp.text: print(会话保持成功登录状态有效) else: print(页面内容异常可能仍未登录)判断“页面内容异常”这一点很关键。有些站点的未登录重定向是通过 JS 跳转实现的直接把一个 200 的登录页作为响应返回你只看状态码发现不了问题。所以我会从响应里找一个“登录后才能看到的关键词”作为锚点比如自己的昵称、专属功能入口等比状态码可靠得多。如果这步返回的是 200 且内容正常恭喜你登录状态已经成功保持住了。从这一刻开始你再用session.get访问同站点的任何页面它都会自动带上登录 Cookie就像浏览器里登录后疯狂点链接一样顺畅。3.4 把 Cookie 持久化到本地下次免登录登录一次不容易尤其是碰上验证码的时候每次重跑脚本都重新登录很烦人。好在 Session 的 Cookie 可以存到本地文件下次启动时直接加载省掉登录步骤。import json from requests.cookies import dict_from_cookiejar, cookiejar_from_dict # 保存 Cookie 到文件 with open(session_cookies.json, w, encodingutf-8) as f: json.dump(dict_from_cookiejar(session.cookies), f, ensure_asciiFalse) print(Cookie 已保存) # 下次运行时恢复 Cookie with open(session_cookies.json, r, encodingutf-8) as f: cookies_data json.load(f) new_session requests.Session() new_session.cookies.update(cookies_data) # 直接访问登录后才能看到的页面 res new_session.get(PROFILE_URL, timeout10) print(用恢复的 Cookie 访问:, res.status_code)千万别以为保存了 Cookie 就一劳永逸。Cookie 本身有过期时间短则几小时长则数天服务端也可能因为账号异地登录、密码修改、风控策略把旧 Cookie 拉黑。所以脚本里要有“Cookie 失效时自动重新登录”的兜底逻辑判断标准就是访问受保护页面时发现被重定向回登录页。我在真实项目中一般单独写一个ensure_login()函数先试 Cookie不行再走完整登录流程非常省事。4. 真实踩坑记录这些报错我把排查思路写给你4.1 登录后访问页面却跳回登录页最让人摸不着头脑的现象就是明明登录接口返回了成功紧接着session.get访问其他页面却被重定向回登录页。别急这大概率是 Cookie 没有保持住。检查点有两个一是排查整个过程是不是都在同一个 Session 对象上完成的有没有在某个地方不小心用了requests.get或新建了Session二是打印一下session.cookies.get_dict()看看登录成功后返回的Set-Cookie是不是真的被收下了。我犯过的一个低级错误是先session.get登录页拿到了一个 Cookie但登录 POST 用了裸的requests.post导致服务器随登录成功下发的身份 Cookie 被丢掉了后续再用 Session 访问自然没状态。这种问题靠打印 Cookie 字典一眼就能看出来。有时候还要注意“登录成功是不是跳转到了另一域名”。有些站点的登录页和登录接口不在同一个域名下Set-Cookie的 Path、Domain 比较特殊requests 虽然会自动处理但遇到域不一致的情况后续请求地址也要跟着保持一致否则 Cookie 带不过去。4.2 明明登录成功一换页面就 403表现是登录成功首页或个人主页都能正常访问但打开某个具体页面直接 403。这种通常和“身份无关和请求头有关”。排查方向先看目标请求的Referer和Origin是否符合站点预期。比如从列表页点进详情页详情页接口可能校验Referer必须是列表页如果你直接无 Referer 访问就会撞上拦截。另一个常见原因是数据接口是 AJAX 动态加载的少传了X-Requested-With: XMLHttpRequest或某个自定义 Header被后端识别成“非浏览器”。这其实是好事说明登录态本身没问题纯粹是请求维度不完整。我的习惯是把浏览器里那次成功请求的 Header 复制下来逐项对照缺哪个补哪个。补充一句403 频繁出现时先冷静一下别急着加大请求速度。如果同一个接口短时间内请求次数太多也有可能是触发了频率限制这种时候加代理不如降低频率更有效也更能保持账号良好状态。4.3 验证码识别不准导致大量失败请求带验证码登录时最常见的连环坑识别一次失败重试再失败又重试最后账号被临时封禁或者 IP 被限制。识别准确率再高遇到歪斜字体、干扰线失败几次都正常。问题不在识别率而在重试策略。我现在的做法是控制节奏单账号最多连续重试 3 次超过阈值就暂停几分钟每次验证码失败后time.sleep一个随机间隔把请求频率降下来。这个随机间隔不是随便写的作用在于让请求时间分布更接近人工操作而不是秒秒钟连续试探。代码里加个随机延时并不复杂import time import random for attempt in range(1, 4): login_response session.post(LOGIN_URL, datalogin_data, timeout10) if 验证码 in login_response.text: wait_time random.uniform(2, 5) print(f第 {attempt} 次失败等待 {wait_time:.1f} 秒后重试) time.sleep(wait_time) else: break这套写法后续做任何带验证的自动化任务都能复用。核心思想是把“失败重试”当成流程的正常组成部分而不是一个意外分支。4.4 一个通用排查清单速查表遇到问题不知道从哪查起时我习惯过一遍这张表。整理成表格方便你收藏。现象优先检查解决方向登录接口返回 200 但没登录成功响应正文里是否有错误提示打印响应前 200 字对照“密码错误/验证码错误”等字样登录后访问页面跳回登录页Session 是否被复用、Cookie 是否保存打印session.cookies.get_dict()确认身份 Cookie某个页面 403Referer / Origin / 自定义 Header复制浏览器成功请求的全部请求头逐项补齐登录后频繁失效Cookie 过期或账号被异地登录实现 Cookie 存盘和自动重新登录逻辑请求被识别为脚本User-Agent 过于明显改成本机浏览器 UA必要时模拟完整浏览器 Header连续请求后接口返回异常触发频率限制增加随机延时降低请求频率避免密集试探排查过程有个心法永远先确认是“登录状态”还是“单次请求”的问题。用 Session 访问一个稳定页面作为探针判断登录态是否还活着再去纠结单个接口的 Headers。如果连探针页面都 302问题在登录态只有探针正常而个别接口异常才需要钻进请求细节里。5. 进阶玩法与边界意识5.1 同一 Session 下的多任务扩展登录一次之后能做的事就多了同一会话下可以连续抓取整个用户相关的数据链路。比如先访问个人主页拿到用户 ID再通过 ID 请求他发布的列表、收藏的记录、关注的用户。所有请求都用同一个 SessionCookie 自动跟着走代码看起来干干净净。# 伪代码示例登录后分页抓取个人发布的内容列表 page 1 while True: list_resp session.get( f{BASE_URL}/my/posts?page{page}, headers{Referer: PROFILE_URL}, timeout10, ) if list_resp.status_code ! 200: break # 解析当前页数据然后翻页 page 1 time.sleep(random.uniform(1, 3))注意翻页接口的 Referer 逻辑第二次翻页时部分站点校验 Referer 应为上一页的地址这个动态头如果懒得维护可以先设置为列表第一页地址绝大多数情况都能过。只有遇到严格校验的站点才需要每次请求前更新 Referer 为上一次 URL。5.2 异常重试与频率控制做任何网络请求都应该对异常有预案。requests在遇到超时、连接重置时会抛异常不 try/except 的话脚本直接崩。from requests.exceptions import RequestException for i in range(3): try: res session.get(PROFILE_URL, timeout10) break except RequestException as exc: print(f第 {i1} 次请求异常: {exc}) time.sleep(2) else: raise SystemExit(连续 3 次失败停止运行)重试次数加上限间隔随机化。这套模板虽然简单但在真实任务里能避免很多“脚本跑了一半静默死掉”的尴尬。尤其适合挂在服务器上的定时任务没人盯着容错就靠这些细节兜底。5.3 一个必须反复强调的前提最后说点心得之外的话。模拟登录这个技术本身是中性的它可以让你高效地完成个人数据备份、日常打卡、自动报表也可能被用于批量采集他人隐私、恶意刷接口。这里要泼一盆冷水代码能力增长最快的方式不是把一个站点的门槛攻下来而是理解它背后的通用机制。我用 requests 做模拟登录这么多年最大的体会是遇到一个新平台抓包、分析、构造请求、保持会话这套流程几乎可以复制到绝大多数场景里区别只是字段名和加密手法。与其执着于某一个站的硬核对抗不如把通用能力打扎实。所以本文的示例站点我特意用了虚构地址和通用字段名你只要理解了“先 GET 登录页、再 POST 凭证、之后用 Session 保持 Cookie”这三板斧任何平台对你来说都只是换个皮而已。我个人在实际操作中还有个习惯每次写完登录代码一定多看几眼“登录接口的响应文本”而不是只看状态码这个习惯已经帮我避免了很多次看似成功、实则无效的登录。最后再分享一个小技巧——用 Session 的时候可以顺手打印一下当前 Cookie 的所有键名不用管值光看键名你就能快速判断服务端下发了几种身份标记这对排查“登录到底成没成”非常有用。希望对你有帮助。
返回列表