1. 项目概述与核心需求解析
最近在写爬虫或者做自动化测试的朋友,估计没少跟“人机验证”这块硬骨头较劲。不管是谷歌的reCAPTCHA,还是Cloudflare的5秒盾,又或者是各种网站自研的滑动拼图、点选验证码,它们的目的都高度一致:把机器流量挡在门外,只放真人通过。对于咱们开发者来说,有时候只是想合法地、低频次地获取一些公开数据,或者测试一下自家服务的接口,却总被这些验证码拦得没脾气。手动处理吧,效率太低;上打码平台吧,又增加了成本和复杂度。这时候,一个更“优雅”的思路就浮出水面了:获取并复用有效的Cookie来绕过初始的人机验证环节。
这个项目的核心,就是探讨如何利用Python的requests库,模拟真实用户行为,从目标网站“骗”到一个已经通过了人机验证的、有效的Cookie会话(Session),并利用这个会话来发起后续的请求,从而避免每次请求都触发验证。这听起来有点像拿到了一个“临时通行证”。但请注意,我们的所有讨论都建立在合规、合法、尊重网站robots.txt协议、且不对目标服务器造成负担的前提下。任何试图绕过安全机制进行恶意爬取、攻击或破坏服务的行为,都是绝对不可取的。
简单来说,这个项目适合以下场景:你需要对一个有验证码的网站进行低频、间歇性的数据采集或接口调用;你拥有该网站的合法测试账号或获取公开信息的权限;你希望用技术手段解决重复人工验证的麻烦。我们将深入拆解requests库在此过程中的应用,从原理到实操,再到避坑指南,一步步带你搞定这个“通行证”难题。
2. 核心原理:Cookie、Session与人机验证的攻防逻辑
要绕过防御,得先理解防御是怎么建立的。我们得把Cookie、Session和人机验证这三者之间的关系捋清楚。
2.1 Cookie与Session:身份的凭证
首先明确一点,Cookie和Session是Web开发中用于维持用户状态的两种主要机制,它们经常协同工作。
- Cookie:是一小段文本数据,由服务器发送到用户的浏览器并保存起来。当浏览器再次向同一服务器发起请求时,会自动携带这个Cookie。Cookie里可以存储会话标识(Session ID)、用户偏好、登录状态等。它是**存储在客户端(浏览器)**的。
- Session:是服务器端为了保存用户状态而创建的一个对象。每个Session都有一个唯一的ID(Session ID)。服务器通常将这个Session ID通过Cookie发送给浏览器。浏览器后续请求时带上这个Cookie(内含Session ID),服务器就能找到对应的Session,从而知道你是谁。Session数据是存储在服务器端的。
在requests库的语境下,我们通常用requests.Session()对象来维持一个会话。这个对象会自动处理请求间的Cookie,模拟了浏览器的一个标签页行为。你第一次登录成功后获得的Cookie,会被Session对象保存下来,用于后续的请求,这样就保持了登录状态。
2.2 人机验证的触发点与Cookie的作用
人机验证(如reCAPTCHA)通常在以下几个关键点被触发:
- 首次访问/新会话:当你用一个全新的IP或浏览器环境(没有携带任何该网站有效Cookie)访问特定页面(尤其是登录、注册、提交表单页)时。
- 高频或异常行为:即使有Cookie,如果你的请求频率远超正常人(例如,一秒十几次请求),服务器也可能触发二次验证,甚至直接封禁IP(返回429状态码)。
- Cookie失效后:登录态Cookie有过期时间,过期后再次访问受保护资源,就会跳转到验证或登录页面。
我们的核心策略就是针对第1点:通过一次“人工”或“半自动”的操作,解决首次访问时的人机验证,获取一个“干净”的、已通过验证的会话Cookie。然后,用requests.Session()小心翼翼地维护这个会话,在Cookie有效期内进行后续的低频操作。这本质上是在模拟一个“真人用户完成验证后,保持浏览器标签页不关闭”的行为。
重要提示:这种方法不能“破解”验证码本身。它是在验证码出现之前或之后,通过获取一个合法的会话状态来避免重复触发。对于必须在每次请求时都进行验证的场景(如每次搜索都弹验证码),此方法无效。
2.3 Requests库在此场景下的角色
requests库是我们模拟HTTP请求的核心工具。它的Session对象是我们维持Cookie会话的载体。我们需要用它来完成:
- 发送GET/POST请求,访问目标页面。
- 处理响应,提取表单所需的隐藏字段、令牌(如
csrf_token)。 - 提交包含验证码答案(如果需要手动输入)的登录或验证表单。
- 自动保存服务器返回的Set-Cookie头信息,并在后续请求中自动携带。
- 处理重定向、超时、异常状态码(如429)。
3. 实操准备:环境、工具与目标分析
在开始写代码之前,充分的准备工作能避免很多弯路。
3.1 环境搭建与库安装
确保你有一个Python环境(3.6以上版本推荐)。使用pip安装必要的库:
pip install requests如果目标网站页面结构复杂,我们可能需要lxml或BeautifulSoup4来解析HTML,提取表单数据或验证码图片地址。这里也一并安装:
pip install beautifulsoup4 lxml3.2 关键工具:浏览器开发者工具
你的主要“侦察”工具就是浏览器的开发者工具(F12打开)。我们需要重点关注网络(Network)标签页。
- 清空记录:开始前,先清除浏览器缓存和Cookie,然后打开开发者工具的Network面板,勾选“Preserve log”(保留日志)。
- 模拟完整流程:手动在浏览器中完成一次从访问首页 -> 可能触发验证 -> 解决验证 -> 登录或到达目标页面的全过程。
- 记录关键请求:在网络面板中,你会看到所有的HTTP请求。你需要找到以下几个关键请求:
- 初始页面请求:通常是第一个GET请求,返回的HTML里可能包含验证码组件或登录表单。
- 验证码获取请求:如果验证码是图片,会有一个单独的GET请求来获取图片资源。
- 验证请求:当你点击“验证”或提交表单时发出的POST请求。这个请求的请求头(Headers)和请求体(Payload)是我们需要重点复现的对象。
- 审查请求详情:点击那个关键的POST请求,查看:
- Headers:特别是
Cookie(请求头中的)、Content-Type、User-Agent、Referer等。User-Agent用于伪装成真实浏览器,Referer告诉服务器你从哪个页面跳转过来,有时是必填项。 - Payload:如果是表单提交,查看
Form Data;如果是JSON格式,查看Request Payload。这里会包含你输入的验证码答案、用户名、密码以及一些隐藏的令牌(如csrfmiddlewaretoken,g-recaptcha-response等)。
- Headers:特别是
3.3 目标网站行为分析
不是所有网站都适用同一种方法。你需要分析:
- 验证码类型:是谷歌reCAPTCHA(v2或v3),是Cloudflare的交互式挑战,还是简单的图片验证码?前两者完全依赖前端JavaScript和浏览器环境,纯
requests难以直接处理,可能需要配合selenium等自动化浏览器工具先获取初始Cookie。后者(图片验证码)则有可能通过requests下载图片,人工或调用OCR识别后提交。 - 会话保持逻辑:验证通过后,服务器是设置了一个长期的认证Cookie,还是只是一个短期的“已通过验证”的标记?这决定了你获取的Cookie的有效期。
- 风控强度:网站是否检查其他指纹,如IP地址的信用、HTTP请求头的完整性和一致性(缺少常见的
Accept-Encoding,Accept-Language等头可能会被怀疑)?
4. 分步实现:获取与使用Cookie的完整流程
我们以一个假设的、使用简单图片验证码的登录场景为例,拆解整个流程。对于复杂的JS验证,思路类似,但获取验证码响应(g-recaptcha-response)的步骤需要借助浏览器自动化。
4.1 第一步:创建会话与获取初始页面
首先,我们创建一个requests.Session()对象,并配置一些基本的请求头,让自己看起来更像一个普通的浏览器。
import requests from bs4 import BeautifulSoup # 创建一个会话对象,它会自动管理Cookie session = requests.Session() # 设置通用的请求头,模仿Chrome浏览器 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/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', } session.headers.update(headers) # 目标网站的登录页面URL login_url = 'https://example.com/login' # 发起第一个GET请求,获取登录页面 try: response = session.get(login_url, timeout=10) response.raise_for_status() # 如果状态码不是200,抛出HTTPError异常 print(f"初始页面获取成功,状态码:{response.status_code}") except requests.exceptions.RequestException as e: print(f"获取初始页面失败:{e}") exit()这一步之后,session对象已经保存了服务器可能返回的一些初始Cookie(例如,可能是一个会话IDsessionid)。
4.2 第二步:解析页面,提取关键信息与验证码
接下来,我们需要从返回的HTML中解析出提交登录表单所需的所有字段。常见的包括用户名、密码的输入框名,以及一个非常重要的CSRF令牌。同时,如果验证码是图片,我们需要找到图片的URL。
# 使用BeautifulSoup解析HTML soup = BeautifulSoup(response.text, 'lxml') # 1. 查找CSRF令牌(名称可能是csrf_token, csrfmiddlewaretoken, authenticity_token等) # 它通常隐藏在表单的一个hidden类型的input标签里 csrf_token = None csrf_input = soup.find('input', {'name': 'csrf_token'}) or \ soup.find('input', {'name': 'csrfmiddlewaretoken'}) or \ soup.find('input', {'name': '_token'}) if csrf_input: csrf_token = csrf_input.get('value') print(f"找到CSRF令牌:{csrf_token}") else: # 有些网站可能将令牌放在meta标签里 meta_csrf = soup.find('meta', {'name': 'csrf-token'}) if meta_csrf: csrf_token = meta_csrf.get('content') print(f"从meta标签找到CSRF令牌:{csrf_token}") else: print("警告:未找到明显的CSRF令牌,可能需要检查页面结构。") # 2. 查找验证码图片URL captcha_url = None captcha_img = soup.find('img', id='captcha-image') or \ soup.find('img', alt=lambda x: x and '验证码' in x) # 根据实际情况调整选择器 if captcha_img: captcha_url = captcha_img.get('src') # 处理可能的相对路径 if captcha_url.startswith('/'): from urllib.parse import urljoin captcha_url = urljoin(login_url, captcha_url) print(f"找到验证码图片URL:{captcha_url}") else: print("未找到图片验证码,可能是其他类型验证(如reCAPTCHA)。") # 3. 如果找到验证码图片,下载并展示(供人工识别) captcha_answer = None if captcha_url: try: # 注意:下载验证码图片时也要使用同一个session,以保持Cookie一致 captcha_response = session.get(captcha_url, timeout=10) captcha_response.raise_for_status() # 将图片保存到本地,方便查看 with open('captcha.png', 'wb') as f: f.write(captcha_response.content) print("验证码图片已保存为 'captcha.png',请打开查看。") # 这里暂停,等待人工输入。在实际自动化中,可以此处集成OCR识别。 captcha_answer = input("请输入看到的验证码:").strip() except requests.exceptions.RequestException as e: print(f"下载验证码图片失败:{e}") exit()实操心得:CSRF令牌是安全关键字段,绝大多数表单提交都必须有它,且每次访问页面都会变化。务必确保从当前响应的HTML中提取最新的令牌,而不是使用硬编码或过期的值。
4.3 第三步:构建并提交登录/验证表单
现在,我们有了CSRF令牌、验证码答案(如果是人工输入),以及你的账号信息。接下来构建POST请求的数据负载(payload)。
# 准备登录的账号信息(请勿将真实密码硬编码在代码中,应从安全的地方读取) username = 'your_username' password = 'your_password' # 强烈建议使用环境变量或配置文件 # 构建POST数据 login_data = { 'username': username, 'password': password, } # 加入CSRF令牌 if csrf_token: # 根据实际表单字段名添加 login_data['csrf_token'] = csrf_token # 加入验证码答案 if captcha_answer: # 根据实际表单字段名添加,可能是 'captcha', 'verification_code' 等 login_data['captcha'] = captcha_answer # 登录表单提交的URL(可能与登录页面相同,也可能是另一个端点,需从浏览器网络请求中确认) submit_url = login_url # 假设是同一个URL,常见情况 # 关键:Referer头通常需要设置为登录页面的URL,这是一个重要的反爬点 session.headers.update({'Referer': login_url}) try: print("正在提交登录表单...") login_response = session.post(submit_url, data=login_data, timeout=15) print(f"登录请求完成,状态码:{login_response.status_code}") # 检查登录是否成功 # 方法1:检查状态码和重定向。登录成功常返回302重定向到首页或用户中心。 # 方法2:检查响应内容中是否包含登录成功的标识(如“欢迎”,“退出登录”链接等)。 # 方法3:检查session.cookies中是否包含了关键的登录态Cookie(如`sessionid`, `auth_token`等)。 if login_response.status_code == 200: if '退出登录' in login_response.text or '我的账户' in login_response.text: print("登录成功(基于页面内容判断)!") else: print("登录请求返回200,但页面内容未显示成功,可能需要进一步检查。") # 可以将响应内容保存下来分析 with open('login_response.html', 'w', encoding='utf-8') as f: f.write(login_response.text) elif login_response.status_code == 302: print(f"登录成功,发生了重定向。重定向目标:{login_response.headers.get('Location')}") else: print(f"登录可能失败,状态码:{login_response.status_code}") except requests.exceptions.RequestException as e: print(f"提交登录请求时发生错误:{e}")如果登录成功,session对象里就已经保存了服务器返回的所有Cookie,包括那个代表你已登录状态的“通行证”。
4.4 第四步:验证Cookie有效性并用于后续请求
登录成功后,最关键的一步是验证我们获取的Cookie是否真的能让我们绕过验证,访问受保护的资源。
# 假设登录后跳转或可访问的用户主页URL profile_url = 'https://example.com/user/profile' try: # 使用同一个session发起请求,它会自动携带上一步获得的Cookie profile_response = session.get(profile_url, timeout=10) profile_response.raise_for_status() # 判断是否成功访问 if profile_response.status_code == 200: # 进一步检查内容,确认不是被跳转回登录页 if '请输入验证码' in profile_response.text or '登录' in profile_response.text and '密码' in profile_response.text: print("警告:Cookie可能无效或被拒绝,页面跳转回了验证/登录页。") else: print("成功!已使用获取的Cookie绕过验证,访问到受保护页面。") # 此时,你可以开始你的数据采集或测试任务了 # 例如:解析profile_response.text,提取所需信息 # 或者用session继续访问其他API接口 else: print(f"访问受保护页面失败,状态码:{profile_response.status_code}") except requests.exceptions.RequestException as e: print(f"访问受保护页面时发生错误:{e}")4.5 第五步:会话维持与请求策略
拿到了有效的Cookie,不代表可以高枕无忧。你需要像一个真人用户一样小心地使用这个会话。
- 保持会话活性:如果一段时间不活动,服务器可能会使会话过期。可以定期(例如每10分钟)用
session访问一个简单的、无需验证的页面(如网站首页)来“保活”。 - 模拟人类行为:在发起一系列请求时,在请求之间添加随机延时(例如
time.sleep(random.uniform(1, 3))),避免固定的、机器式的请求频率。 - 错误处理与重试:网络请求总可能失败。要为关键请求(如登录、获取数据)添加重试逻辑,但重试次数不宜过多,且重试前最好等待更长时间,避免触发风控。
- Cookie持久化:如果这个Cookie有效期很长(比如几天),你可以将其保存到文件或数据库,下次程序启动时直接加载,避免重复登录验证。
import pickle # 保存cookies with open('session_cookies.pkl', 'wb') as f: pickle.dump(session.cookies, f) # 加载cookies with open('session_cookies.pkl', 'rb') as f: loaded_cookies = pickle.load(f) session.cookies.update(loaded_cookies) # 注意:加载后最好验证一下Cookie是否仍然有效
5. 高级策略与复杂场景应对
上面的流程是针对简单图片验证码的。现实中的挑战往往更复杂。
5.1 应对谷歌reCAPTCHA v2/v3
纯requests库几乎无法直接处理reCAPTCHA,因为它严重依赖浏览器环境和JavaScript执行。常见的策略是:
- 使用浏览器自动化工具:如
selenium配合undetected-chromedriver,完全模拟真人操作浏览器,手动或通过第三方服务解决验证码,然后从浏览器中提取Cookie,再注入到requests.Session()中。这相当于“借道”浏览器获取通行证。 - 寻找替代接口:有些网站的API接口可能没有前端那么严格的人机验证。尝试分析网站的手机端(m.xxx.com)或APP的API,有时它们的验证会简单一些。
- 使用已登录的Cookie:如果你本身就拥有一个已登录的账号,可以直接从浏览器的Cookie存储中导出(需谨慎,涉及账号安全),然后加载到
requests中使用。但这通常需要定期更新。
5.2 处理Cloudflare等5秒盾
Cloudflare的“正在检查您的浏览器”或“5秒盾”也是一种基于浏览器指纹和JS挑战的防护。同样,纯requests难以通过。可以尝试:
- 使用
cloudscraper库:这是一个专门为绕过Cloudflare反爬而设计的库,它内置了模拟浏览器JS执行环境的能力。其接口与requests高度兼容。import cloudscraper scraper = cloudscraper.create_scraper() response = scraper.get('https://protected-site.com') # 如果成功,scraper的cookies里就包含了通过挑战后的Cookie - 结合浏览器自动化:和应对reCAPTCHA一样,用
selenium先通过挑战,再提取Cookie。
5.3 应对“429 Too Many Requests”错误
这是你行为像机器人,触发了服务器速率限制的最直接信号。一旦遇到429,必须立刻停止或大幅放缓请求。
- 指数退避重试:遇到429时,不要立即重试。等待一段时间,且每次重试前等待时间加倍。例如,第一次等2秒,第二次等4秒,第三次等8秒。
import time max_retries = 3 base_delay = 2 for attempt in range(max_retries): try: response = session.get(url) response.raise_for_status() break # 成功则跳出循环 except requests.exceptions.HTTPError as e: if e.response.status_code == 429: wait_time = base_delay * (2 ** attempt) # 指数退避 print(f"遇到429,第{attempt+1}次重试,等待{wait_time}秒...") time.sleep(wait_time) else: raise e # 其他错误直接抛出 else: print("重试多次后仍失败,请检查请求频率或IP是否被限制。") - 降低请求频率:从根本上减少单位时间内的请求数量,在请求间加入更长的、更随机的延迟。
- 使用代理IP池:如果你的请求量确实较大,分散请求到不同的IP地址是必要的。但务必使用合法、可靠的代理服务,并遵守其使用条款。
6. 常见问题排查与实战心得
在实际操作中,你肯定会遇到各种各样的问题。这里记录一些典型的排查思路和心得。
6.1 问题:登录请求总是失败,返回错误页面或状态码400/403。
- 排查点1:CSRF令牌:这是最常见的坑。确保你提交的CSRF令牌是从当前GET请求返回的页面中提取的,并且字段名完全匹配。有些网站会使用动态变化的字段名。
- 排查点2:请求头:检查你的请求头是否完整。特别是
Content-Type,如果是表单提交,通常是application/x-www-form-urlencoded;如果是JSON提交,则是application/json。Referer头也经常被检查。 - 排查点3:隐藏字段:表单里可能还有其他隐藏的
input字段,比如next(跳转地址)、time(时间戳)等。你需要把所有这些隐藏字段都原封不动地加入到POST数据中。 - 排查点4:Cookie的同步:确保在获取验证码图片和提交登录表单时,使用的是同一个
session对象。这样服务器才能把两次请求关联到同一个会话。 - 排查点5:验证码过期:图片验证码通常有很短的有效期(如1-2分钟)。如果你下载图片后等太久才输入,验证码可能已经失效。尽量缩短操作间隔。
6.2 问题:登录成功后,访问其他页面又被要求验证。
- 排查点1:Cookie作用域:检查Cookie的
Domain和Path属性。requests的Session会处理这些,但如果你手动处理Cookie,需要确保Cookie对目标URL是有效的。 - 排查点2:会话过期:可能你获取的会话本身有效期就很短,或者服务器有额外的活跃度检测。尝试在两次请求之间访问一下网站首页,保持会话活性。
- 排查点3:IP变动:如果你的网络环境导致出口IP地址发生了变化(例如切换了Wi-Fi或使用了动态代理),服务器可能会认为是一次新的可疑会话,从而要求重新验证。
- 排查点4:其他风控:网站可能结合了用户行为分析(鼠标移动、点击模式等),纯
requests请求缺乏这些行为指纹,可能被高级风控系统识别并拦截。这种情况下,可能需要更复杂的模拟工具。
6.3 问题:如何判断我获取的Cookie里哪个是关键登录态Cookie?
登录后,打印出session.cookies查看。通常关键Cookie的名字会包含session,auth,token,login等字样,并且其value通常是一长串无规律的哈希字符串。你可以通过对比登录前后的session.cookies变化,找出新增的Cookie,那很可能就是登录态Cookie。
6.4 实战心得:稳健性与道德
- 添加完备的日志:记录每个关键步骤的URL、状态码、请求/响应大小,甚至将出错的响应HTML保存到文件。这是后期排查问题的唯一依据。
- 设置合理的超时和重试:网络是不稳定的。为所有网络请求设置
timeout参数,并使用try...except包裹,进行优雅的错误处理。 - 尊重
robots.txt:在开始任何爬取或自动化操作前,先检查目标网站的robots.txt文件(通常在网站根目录,如https://example.com/robots.txt)。遵守其中关于爬虫频率和禁止访问目录的规定。 - 控制请求速率:这是最重要的道德和技术准则。将你的请求频率控制在远低于人类操作的水平(例如,每分钟几次,且带有随机间隔)。你的目标是完成自动化任务,而不是对网站进行压力测试。
- 明确法律责任:确保你的行为符合目标网站的服务条款,并且你采集的数据用途是合法的。绕过验证码进行大规模数据爬取可能违反法律或网站条款。
获取Cookie绕过人机验证,是一项在合规前提下提升自动化效率的技术。它考验的是你对HTTP协议、Web会话机制以及目标网站具体实现细节的理解。没有一成不变的方法,核心思路永远是:观察(浏览器行为)-> 模拟(使用requests)-> 调试(处理异常)-> 优化(模拟得更像真人)。希望这份详细的拆解,能帮你更从容地应对那些恼人的验证码关卡。