某花顺Web登录逆向实战:从JS加密到Cookie获取的完整Python实现
1. 项目概述与核心价值最近在分析一些金融数据接口时不可避免地要面对一个“老朋友”——某花顺。无论是为了研究市场情绪、获取实时行情还是进行一些合规的数据分析绕过其Web端的登录验证直接获取到携带有效身份凭证的Cookie特别是那个关键的v值往往是第一步也是最关键的一步。这个项目就是一次对某花顺Web端登录流程的完整逆向工程实战。我将不仅带你走通整个流程理解其背后的加密逻辑和风控策略更重要的是我会附上经过脱敏和重构的完整Python源码让你能够真正动手实践理解每一个字节的来龙去脉。对于数据分析师、量化研究员或者是对Web安全、JS逆向感兴趣的朋友来说这个项目具有很高的实用价值。它解决的不仅仅是“登录”这个动作而是深入到了一个典型商业级Web应用的身份认证与状态维持机制内部。你将学到如何从零开始使用浏览器开发者工具进行网络抓包分析如何定位关键加密函数如何用Python模拟浏览器环境执行JavaScript代码以及如何处理复杂的动态参数。整个过程就像在解一个精心设计的密码谜题而最终的“钥匙”——那个有效的Cookie将为你打开一扇通往海量金融数据的大门。2. 逆向工程的整体思路与工具准备逆向一个现代Web应用的登录流程不能像无头苍蝇一样乱撞需要一个清晰、高效的策略。我的整体思路可以概括为“由外而内动静结合”。2.1 核心思路拆解首先“由外而内”指的是从网络请求这个最外层的表现入手。我们不需要一开始就去啃庞大的JavaScript源码。而是先打开浏览器的无痕模式避免已有Cookie干扰访问登录页面在开发者工具的Network面板中勾选“Preserve log”保留日志。然后进行一次完整的手动登录操作输入账号、密码、可能的验证码点击登录。这时Network面板会记录下从页面加载到登录成功之间所有的HTTP/HTTPS请求。我们的目标是找到那个最终决定登录成功与否的“关键请求”。通常这个请求的URL会包含login、doLogin、submit等关键字并且其请求方法多为POST响应会返回一个跳转或者明确的成功状态。找到它之后重点分析它的请求头Headers和请求体Payload。请求头里我们需要关注Cookie可能是初始化的、User-Agent、Content-Type等。请求体里则藏着我们提交的账号、密码但99%的情况下它们绝不是明文。接下来“动静结合”就派上用场了。“静”是指静态分析在Sources面板中搜索请求体里那些看起来像加密后的参数名比如password、encrypt、sign等找到它们被赋值的JavaScript代码。“动”是指动态调试在可能执行加密操作的代码行设置断点重新触发登录流程让代码执行暂停然后一步步跟踪Step Into/Over观察变量的值如何变化最终定位到核心的加密函数。2.2 必备工具链工欲善其事必先利其器。以下是本次逆向工程的核心工具它们构成了从分析到模拟的完整闭环浏览器与开发者工具Google Chrome或Microsoft Edge基于Chromium。它们的开发者工具F12打开是我们最主要的战场特别是Network网络、Sources源代码、Console控制台面板。抓包与调试工具Fiddler Classic / Charles作为独立的抓包代理可以更清晰地看到所有进出浏览器的流量包括HTTPS需安装证书。对于分析请求/响应序列、重发请求测试特别有用。浏览器开发者工具本身其Network面板的过滤、搜索功能非常强大。Sources面板的断点调试、代码美化Pretty Print功能是逆向JS的利器。Python模拟环境Requests用于发送HTTP请求的核心库。ExecJS或PyExecJS一个非常关键的库它允许你在Python环境中执行JavaScript代码。当我们找到前端加密函数后可以用它来在Python中直接调用避免用Python重写复杂的加密逻辑。BeautifulSoup4 / lxml用于解析HTML获取页面中隐藏的token、nonce等动态参数。浏览器自动化可选如Selenium或Playwright。在逆向初期用于模拟用户操作获取动态参数或绕过复杂交互在逆向后期如果加密逻辑过于复杂且与浏览器环境强绑定可作为保底方案直接驱动浏览器登录。但我们的目标是尽可能脱离浏览器因此它更多是辅助。注意使用这些工具进行逆向分析必须严格遵循目标网站的服务条款和robots.txt协议。本项目的目的是技术学习与研究所有操作应在法律和平台规则允许的范围内进行严禁用于攻击、爬取未经授权的数据或干扰服务正常运行。3. 登录流程关键步骤的逆向解析某花顺的登录流程是一个典型的“多步验证动态加密”体系。下面我将一步步拆解并解释每个环节的逆向方法。3.1 第一步获取登录页与初始化参数首先我们直接请求登录页的URL例如https://login.10jqka.com.cn/。这一步看似简单但至关重要。目标获取后续登录请求必需的“种子”参数。这些参数通常隐藏在页面的HTML表单input typehidden标签里或者由页面加载的JavaScript动态生成并写入全局变量。逆向操作用requests.get请求登录页。用BeautifulSoup解析返回的HTML查找所有name属性为csrf_token、lt、execution、_eventId等常见的隐藏字段。同时在开发者工具的Sources面板搜索整个页面的JS文件或内联脚本查找类似window.xxxToken、var nonce 这样的赋值语句。某花顺可能会生成一个名为vkey或rsaKey的变量用于后续的非对称加密。核心要点这些参数往往是临时的、一次性的并且与服务器端的会话Session绑定。必须在同一个会话即使用同一个requests.Session对象中使用本次获取的参数去提交登录请求。3.2 第二步密码加密逻辑定位与破解这是整个逆向的核心难点。在登录的POST请求中你几乎不可能看到明文的密码。我们需要找到密码在提交前被如何加密。目标定位加密密码的JavaScript函数并能在Python中复现该加密过程。逆向操作在Network中找到登录的POST请求查看其Form Data或Payload。假设你看到password: “a1b2c3d4e5...一串很长的密文”。在Sources面板按CtrlShiftF进行全局搜索。搜索关键词可以是password、encrypt、这个密文的前几个字符或者请求体中其他可疑参数名。搜索结果的代码往往是被压缩minified的。点击代码左下角的{}Pretty Print按钮进行美化。在可能加密的函数附近设置断点。回到登录页重新输入密码点击登录代码会在断点处暂停。使用Step Into (F11)逐步跟进观察哪个函数接收了你的明文密码并输出了一串密文。这个函数可能就是加密函数。注意观察其内部是否调用了RSA、AES、encode、hash等常见加密库或方法。某花顺很可能采用RSA非对称加密。这意味着页面会从一个接口或JS变量中获取一个RSA公钥然后用这个公钥对密码进行加密。你需要找到这个公钥的获取方式和加密的具体实现。Python复现策略最佳情况加密函数是纯JavaScript逻辑不依赖特殊的浏览器对象。我们可以直接用ExecJS将找到的JS加密函数代码可能需要稍作提取和封装和一个模拟的密码传入得到密文。import execjs # 假设我们从JS中提取出了加密函数保存为encrypt.js with open(encrypt.js, r, encodingutf-8) as f: js_code f.read() ctx execjs.compile(js_code) encrypted_password ctx.call(encryptPassword, 你的明文密码, 从页面获取的RSA公钥) print(encrypted_password)复杂情况加密函数依赖window、document或其他浏览器特有对象。我们需要在JS代码中模拟这些环境或者使用node.js环境通过execjs调用。更棘手的是如果加密涉及大量的前端框架代码或WebAssembly手动提取会异常困难。此时可以考虑使用**“补环境”** 技巧即用JavaScript代码模拟出一个浏览器核心对象欺骗加密代码正常运行。这需要较高的JS功底。3.3 第三步Cookie中v值的生成与维持成功登录后服务器会通过响应头Set-Cookie返回一系列Cookie。其中一个名为v也可能是其他名称如token的Cookie值是后续访问其他需要认证的接口如查询自选股、获取资讯的通行证。目标理解v值是什么以及如何维持其有效性。逆向分析登录成功后在Network面板查看响应头找到Set-Cookie字段记录下所有Cookie的名称和值。尝试用这些Cookie直接去访问一个需要登录的API。如果返回成功说明认证有效。v值通常是一个服务端签发的、有时效性的令牌Token可能是JWTJSON Web Token格式也可能是自定义格式。你可以将其进行Base64解码JWT通常由三部分组成用点.分隔每部分都是Base64Url编码可能会看到一些结构化信息如用户ID、过期时间等。关键问题是这个v值是否会刷新如何刷新观察在登录后的一段时间内浏览器发出的其他请求如心跳请求、获取用户信息的请求看其请求头中的Cookie是否变化或者是否有新的Set-Cookie返回。这关系到我们模拟的登录会话能维持多久。实操心得v值一旦获取在有效期内可以直接用于requests的请求头。使用requests.Session()对象可以自动管理Cookie非常方便。你需要关注的是Token的过期时间并设计一个刷新机制。如果发现v值定期变化可能需要逆向一个“心跳”或“刷新token”的接口。4. 完整模拟登录的Python代码实现基于以上的逆向分析我们可以构建一个完整的、可运行的Python登录类。以下代码是经过脱敏和结构优化的示例涵盖了主要逻辑。4.1 类结构与初始化import requests import execjs import re import time from urllib.parse import urljoin class ThsLoginSimulator: 某花顺Web端登录模拟器 def __init__(self): self.session requests.Session() # 设置一个合理的浏览器User-Agent这是绕过基础风控的第一步 self.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 }) self.base_url https://login.10jqka.com.cn # 示例基础URL self.rsa_public_key None # 存储从页面获取的RSA公钥 self.token None # 存储页面token等动态参数4.2 获取登录页动态参数def get_login_page_params(self): 获取登录页面中的隐藏参数和RSA公钥 login_url urljoin(self.base_url, /login) try: resp self.session.get(login_url) resp.raise_for_status() html_content resp.text # 1. 使用正则从HTML或JS中提取RSA公钥示例 # 假设公钥在某个JS变量中var rsaKey -----BEGIN PUBLIC KEY-----\n...; key_pattern rvar\srsaKey\s*\s*[\]([^\])[\] match re.search(key_pattern, html_content) if match: self.rsa_public_key match.group(1).replace(\\n, \n) # 处理换行符 print(f[] 成功获取RSA公钥 (长度: {len(self.rsa_public_key)})) else: # 可能公钥是通过另一个接口获取的需要进一步分析 print([-] 未在页面找到RSA公钥可能需要分析额外接口) # 这里可以添加分析其他JS文件或XHR请求的代码 # 2. 使用BeautifulSoup提取隐藏表单参数示例 from bs4 import BeautifulSoup soup BeautifulSoup(html_content, lxml) hidden_inputs soup.find_all(input, {type: hidden}) params {} for inp in hidden_inputs: if inp.get(name) and inp.get(value): params[inp[name]] inp[value] self.token params.get(csrf_token) # 假设有一个csrf_token print(f[] 获取到页面动态参数: {params}) return params except requests.RequestException as e: print(f[-] 获取登录页失败: {e}) return None4.3 执行密码加密假设我们已经将找到的JS加密函数保存到了本地文件encrypt.js中。def encrypt_password(self, plain_password): 使用ExecJS调用JS加密函数对密码进行加密 if not self.rsa_public_key: print([-] 请先获取RSA公钥) return None try: # 读取加密JS文件 with open(encrypt.js, r, encodingutf-8) as f: js_code f.read() # 创建JS执行环境 ctx execjs.compile(js_code) # 调用JS函数。函数名和参数需根据实际逆向结果调整 # 例如实际的JS函数可能是function encryptPwd(pwd, key){...} encrypted_pwd ctx.call(encryptPassword, plain_password, self.rsa_public_key) print(f[] 密码加密完成密文长度: {len(encrypted_pwd)}) return encrypted_pwd except Exception as e: print(f[-] 密码加密过程出错: {e}) # 可能是JS环境问题或函数调用错误 # 一种备选方案如果加密是标准RSA可以用Python的rsa库尝试 # import rsa # 但通常前端会有特定的填充方式(PKCS1_v1_5等)需要完全匹配 return None4.4 提交登录请求与处理响应def do_login(self, username, password): 执行登录操作 # 1. 获取动态参数 page_params self.get_login_page_params() if not page_params: return False # 2. 加密密码 encrypted_password self.encrypt_password(password) if not encrypted_password: return False # 3. 构造登录请求数据 login_api urljoin(self.base_url, /api/login) # 示例登录接口 payload { username: username, # 用户名有时是明文有时也需要处理 password: encrypted_password, csrf_token: self.token, # 从页面获取的动态token remember: true, # 根据实际表单调整 # ... 其他可能的固定或动态参数 } # 将页面获取的其他隐藏参数也加入 payload.update(page_params) try: print(f[*] 正在向 {login_api} 提交登录请求...) resp self.session.post(login_api, datapayload) resp.raise_for_status() # 4. 解析响应 result resp.json() # 假设返回JSON if result.get(status) 200 or result.get(success): print(f[] 登录成功! 用户: {username}) # 登录成功后session会自动管理服务器返回的Cookie # 我们可以检查一下关键的v值是否在Cookie中 cookies_dict self.session.cookies.get_dict() if v in cookies_dict: self.v_cookie cookies_dict[v] print(f[] 关键Cookie v 已获取: {self.v_cookie[:20]}...) # 打印所有Cookie供检查 print(f[] 当前会话Cookie: {cookies_dict}) return True else: error_msg result.get(message, 未知错误) print(f[-] 登录失败: {error_msg}) # 有时失败信息会提示验证码这里就需要引入验证码识别模块 return False except requests.RequestException as e: print(f[-] 登录请求失败: {e}) return False except ValueError as e: print(f[-] 响应解析失败 (可能不是JSON): {e}) print(f响应文本: {resp.text[:500]}) return False4.5 使用登录后的会话访问受保护接口def get_protected_data(self, api_url): 使用已登录的会话访问需要认证的API if not self.session.cookies: print([-] 请先登录) return None try: resp self.session.get(api_url) resp.raise_for_status() return resp.json() # 或 resp.text except requests.RequestException as e: print(f[-] 访问受保护接口失败: {e}) return None # 使用示例 if __name__ __main__: simulator ThsLoginSimulator() if simulator.do_login(your_username, your_password): # 登录成功尝试获取用户信息或数据 user_info simulator.get_protected_data(https://my.10jqka.com.cn/api/userinfo) if user_info: print(获取到用户信息:, user_info)5. 常见问题、风控策略与应对技巧在实际操作中你绝不会一帆风顺。网站的风控系统WAF、反爬虫会设置重重障碍。下面是我踩过坑后总结的一些常见问题及应对策略。5.1 请求被拒绝或返回奇怪的非200状态码问题现象403 Forbidden、412 Precondition Failed、429 Too Many Requests或者返回一个包含验证码挑战的HTML页面。原因分析请求头不完整或不标准缺少必要的头信息如Accept、Accept-Language、Accept-Encoding、Connection、Host等。风控系统会检查这些头是否看起来像“真人浏览器”。IP频率限制短时间内从同一IP发送过多请求。行为异常没有携带首次访问页面时获得的Cookie如sessionid或者请求参数顺序、格式与浏览器发送的不一致。解决方案完整复制请求头在浏览器开发者工具的Network面板中右键点击那个关键的登录POST请求选择“Copy - Copy as cURL (bash)”。然后将cURL命令粘贴到类似https://curlconverter.com/python/的转换工具中生成几乎完全一致的Pythonrequests代码。这是最快捷、最准确的方法。使用Session对象requests.Session()会自动处理Cookie的传递确保会话一致性。添加延迟与随机性在关键请求之间加入time.sleep(random.uniform(1, 3))模拟人类操作间隔。考虑使用代理IP池如果IP被限制需要准备多个高质量的HTTP/HTTPS代理IP进行轮换。5.2 加密函数无法在ExecJS中正常运行问题现象执行JS加密时报错提示某些浏览器对象如window、navigator、document未定义。原因分析前端加密代码可能依赖浏览器环境特有的全局对象或API。解决方案补环境在传递给ExecJS的JS代码开头手动定义这些缺失的对象。这是一个精细活。// 在encrypt.js文件开头添加 var window this; var navigator { userAgent: Mozilla/5.0... }; var document {}; // 更复杂的可能需要定义特定的函数或属性使用Node.js环境通过execjs指定Node.js作为运行时execjs.get(Node)因为Node.js的全局环境与浏览器不同有时能绕过一些依赖。但这并非万能。终极方案Selenium/Playwright执行加密如果加密逻辑极度复杂且严重依赖浏览器内核例如调用了Web Crypto API的特定实现可以退而求其次使用浏览器自动化工具在真实的浏览器环境中执行加密步骤获取结果后再用requests继续后续操作。虽然效率低但成功率高。5.3 验证码Captcha识别问题现象登录失败返回信息提示需要输入验证码。原因分析风控系统根据IP、行为、账号风险等因素触发验证码。解决方案降低触发概率使用更“干净”的IP如住宅代理完善请求头模拟更自然的行为流。集成打码平台这是最实用的方案。当收到验证码图片时将图片数据发送给第三方打码平台如超级鹰、图鉴等的API进行识别然后将识别结果填入请求参数。机器学习识别高级对于固定的简单验证码如四位数字可以尝试自己训练一个CNN模型进行识别但成本高且一旦验证码更新即失效。5.4 Cookie (v值) 过期或失效问题现象登录成功后用获取到的Cookie去访问其他接口返回“未登录”或“会话过期”。原因分析v值Token有有效期且可能不会自动刷新。解决方案分析Token有效期将v值进行Base64解码查看其中是否包含exp过期时间戳字段。计算其剩余有效时间。实现Token刷新机制观察浏览器在登录后是否有定期的、用于保持会话活跃的“心跳”请求如每隔几分钟向某个特定URL发送一个GET请求。逆向这个心跳接口在我们的代码中定时调用它。会话持久化与重登录将登录成功后的session.cookies用pickle模块保存到文件。下次程序启动时先加载Cookie尝试访问一个轻量级接口如果失败401/403则自动触发重新登录流程。逆向工程是一个与防御系统持续博弈的过程。某花顺的登录机制也可能随时升级。今天有效的方法明天可能就会失效。因此理解其核心原理如非对称加密、会话管理、风控策略远比记住一段具体的代码更重要。保持好奇心熟练使用开发者工具培养耐心追踪代码的能力你就能应对大部分类似的挑战。

相关新闻