企业信息平台逆向登录与风控对抗实战:从JS破解到Python实现
1. 项目概述与核心价值最近在做一个数据采集项目需要从某眼查这类企业信息平台获取一些公开数据。但凡做过类似爬虫的朋友都知道这类平台的风控机制是出了名的复杂尤其是登录环节简直就是一道“叹息之墙”。直接上Selenium模拟浏览器效率低不说还容易被识别为自动化工具分分钟给你弹滑块验证码或者直接封IP。所以逆向分析其登录流程和风控逻辑就成了获取稳定数据源的必经之路。这个项目标题“某眼查模拟登录解除风控全流程”说白了就是一场攻防演练。我们的目标不是攻击而是理解其防御机制从而以合规、高效的方式模拟一个“真人”的登录行为并绕过或适应其后续的数据访问风控。这涉及到前端JavaScript逆向、网络请求分析、加密参数破解、以及风控策略的识别与应对。整个过程就像在解一个层层嵌套的谜题每一步都需要耐心和技巧。对于从事数据工程、安全研究或逆向工程的朋友来说掌握这套流程不仅能搞定某眼查其方法论也适用于分析其他具有类似风控体系的Web应用。2. 逆向分析前的环境与工具准备工欲善其事必先利其器。逆向分析Web应用尤其是现代前端框架构建的复杂应用没有趁手的工具寸步难行。这里的工具链可以分为三大类浏览器开发者工具、抓包与调试工具、以及逆向辅助工具。2.1 核心工具链详解首先浏览器开发者工具是基石。Chrome DevTools 是最佳选择其Network网络面板用于记录所有HTTP/HTTPS请求重点关注XHR/Fetch请求登录和关键数据接口通常在这里。Sources源代码面板用于调试JavaScript可以设置断点、单步执行、查看调用栈。Console控制台用于执行JavaScript代码片段测试加密函数。其次抓包与调试工具用于更深入的分析。Fiddler Classic 或 Charles 这类代理工具可以拦截和修改所有经过系统的网络流量对于分析HTTPS请求的明文内容在安装并信任其CA证书后以及模拟弱网环境非常有用。对于更复杂的、协议可能被混淆的场景可以考虑使用 mitmproxy它支持Python脚本进行流量实时修改灵活性极高。最后逆向辅助工具能极大提升效率。当遇到JavaScript代码被压缩、混淆得面目全非时我们需要借助工具进行格式化、反混淆和静态分析。Chrome DevTools自带的Pretty Print美化打印功能是第一步。对于更高级的混淆可以使用 AST抽象语法树解析库如esprima和escope自己编写脚本进行反混淆或者使用现成的工具如de4js在线服务进行初步解混淆。此外一个强大的文本编辑器如VSCode用于搜索和比对代码以及一个能够执行Node.js环境用于本地测试加密函数都是必不可少的。2.2 环境配置与代理设置为了能稳定地拦截和分析某眼查的请求配置一个可靠的代理环境是关键。我推荐使用 Fiddler Everywhere 或 mitmproxy因为它们对现代Web应用的支持更好。以 Fiddler Everywhere 为例安装后首先需要在其设置中开启“解密HTTPS流量”并按照指引在电脑和浏览器上安装其根证书。这一步至关重要否则你看到的HTTPS请求内容都是加密的乱码。接着将浏览器的代理设置为 Fiddler 监听的地址通常是127.0.0.1:8866。之后所有浏览器流量都会经过 Fiddler。注意在开始分析敏感目标前最好关闭其他不必要的网络应用并创建一个干净的浏览器用户配置文件避免个人浏览数据干扰分析。同时准备好多个可用的IP地址资源例如优质的住宅代理IP池因为分析过程中可能会触发IP级别的风控。3. 登录流程的逆向分析与关键参数破解某眼查的登录页面看似简单一个手机号输入框一个密码/验证码区域但背后的逻辑非常复杂。通常它会包含动态加载的JavaScript里面藏着加密算法、Token生成逻辑和提交参数的构造方法。3.1 登录请求的捕获与初步分析打开无痕模式的浏览器配置好代理访问某眼查登录页。打开开发者工具的网络面板勾选“Preserve log”保留日志。在输入框里随意输入一个手机号不需要真实注册点击“获取验证码”或尝试登录。此时网络面板会刷出一系列请求。我们需要找到那个最终提交登录信息的POST请求。通过查看请求的URL通常包含login、submit等关键字、请求体Payload的类型通常是application/json或application/x-www-form-urlencoded以及响应结果可以定位到核心登录接口比如可能类似于/api/auth/login/v2这样的端点。点击这个请求查看其详细信息Headers 关注Cookie、User-Agent、Referer以及一些自定义的Header如X-Token、X-Sign等。这些往往是风控的一部分。Payload 这是重点。你会看到提交的数据除了明文的手机号其他字段如password、captcha、token、signature等很可能被加密或编码过。密码字段几乎不可能是明文传输。3.2 JavaScript逆向定位加密函数发现加密参数后下一步就是找到生成这些参数的JavaScript代码。在Network面板中该登录请求的“Initiator”发起者列会显示是哪个JS文件发起了这个请求。点击这个JS文件名会跳转到Sources面板对应的代码位置。由于代码通常被压缩成一行首先点击左下角的{}Pretty Print按钮进行格式化。格式化后代码有了结构但仍然变量名混淆如a, b, c, _0x1234。我们的目标是搜索加密参数名。在格式化后的JS文件中使用CtrlF搜索关键词。例如搜索password、encrypt、encode、sign或者搜索你在Payload里看到的那个加密后字符串的前几个字符。这通常能定位到关键的加密函数附近。找到疑似函数后最有效的方法是下断点。在加密函数被调用的行号左侧点击设置一个断点。然后回到网页再次触发登录动作比如再次点击登录按钮。执行流会暂停在断点处。此时在Console面板中你可以查看当前作用域的变量值。重点关注传入函数的参数是什么通常是明文密码或一个对象以及函数的返回值是什么。通过单步执行F10逐过程F11逐语句你可以一步步跟踪加密逻辑。你需要理清加密的输入是什么明文密码盐值时间戳使用了什么算法可能是自定义的Base64变种、AES、RSA、或简单的异或操作密钥从哪里来可能硬编码在JS里也可能由另一个接口动态返回3.3 关键参数sign的生成逻辑还原在许多风控体系中sign签名参数是重中之重用于防止请求被篡改。它的生成逻辑通常是将所有请求参数有时还包括固定盐值、时间戳按特定规则排序后拼接成一个字符串然后进行MD5、SHA256等哈希计算或者再进行一次自定义的加密。通过断点调试定位到生成sign的函数。你需要记录下参数集合 哪些字段参与了签名除了phone、password可能还有timestamp、nonce随机数、appVersion等。排序规则 通常是按照参数名的ASCII码升序排列。拼接规则 参数名和值如何拼接常见格式如key1value1key2value2。哈希/加密算法 拼接后的字符串经过什么处理可能是MD5(sign_str secret)其中secret是一个隐藏的密钥。将这套逻辑用Python或其他语言完整复现出来是模拟登录的核心。你可以将调试时捕获的真实参数代入你自己的复现函数计算出的sign值应该与原始请求中的完全一致。4. 风控机制的识别与动态对抗策略成功模拟登录请求只是第一步。某眼查的风控是立体的、动态的会在你后续的数据访问请求中持续生效。识别并适应这些风控才是项目稳定的保证。4.1 常见风控手段剖析行为指纹Browser Fingerprinting 网站会通过JavaScript收集你浏览器的大量特征如User-Agent、屏幕分辨率、时区、语言、Canvas指纹、WebGL指纹、字体列表等生成一个唯一标识。即使你更换IP如果指纹不变仍可能被关联识别。在逆向时你会发现一些接口会返回一段JS代码或一个Token这段代码执行后会上报指纹信息。请求参数验签与时效性 如前所述重要的数据查询接口往往也有签名机制而且签名可能有时效性如timestamp参与签名服务器会校验时间差。此外一些Token如登录后的sessionId或access_token也有生命周期需要定期刷新。请求频率与模式识别 这是最直观的风控。短时间内高频访问、访问模式过于规律如固定间隔秒杀式请求、访问非人类浏览的热点路径都会触发限制。限制方式从弹出验证码到直接封禁IP或账号不等。验证码挑战 当行为被判定为可疑时会触发验证码包括图形验证码、滑块验证码、点选验证码、甚至智能推理验证码。其中滑块验证码如某验的逆向难度较高涉及轨迹模拟、缺口识别等。4.2 动态Token与会话维持登录成功后服务器会返回一个Token可能在响应体里也可能在Set-Cookie头里。这个Token是后续所有请求的“通行证”。你需要分析Token的使用位置 是放在请求头Authorization: Bearer xxx里还是放在Cookie里抑或是作为一个普通的请求参数Token的刷新机制 Token有过期时间吗是否有专门的刷新接口刷新逻辑是怎样的通常在Token临近过期时调用刷新接口获取新Token并更新本地存储。会话一致性 保持整个会话过程中Cookie、LocalStorage、以及一些内存中的全局变量可能在JS中初始化的一致性。有时一个请求的响应会包含下一个请求必需的上下文信息。在Python模拟中你需要使用requests.Session()对象来自动管理Cookie并手动维护需要放在请求头中的Token。4.3 模拟真人行为模式这是对抗频率和模式风控的核心。你的爬虫行为不能像机器一样精准和快速。随机化延迟 在请求间加入随机等待时间例如time.sleep(random.uniform(1, 3))模拟人类阅读和点击的间隔。模拟浏览路径 不要直接访问目标数据接口。可以先访问首页再模拟点击进入某个分类然后再进行搜索或详情查看。这会在你的会话中留下“自然”的浏览记录。维护合理的请求头User-Agent最好使用常见的浏览器字符串池随机选择。Referer头要设置得合理让它看起来是从上一个页面跳转过来的。代理IP池的质量与使用策略 使用高质量的住宅代理IP并设计合理的IP切换策略。例如一个IP用于登录和维持会话另一个IP池用于数据抓取且每个IP的请求频率和总量都要控制。5. 完整模拟登录与数据采集代码实现基于以上的分析我们可以构建一个相对健壮的模拟登录和数据采集类。这里以Python的requests库为例展示核心框架。5.1 核心类结构与初始化首先我们设计一个类来封装所有功能包括登录、会话维持、请求签名和风控处理。import requests import time import random import hashlib import json from urllib.parse import urlencode class TianYanChaSpider: def __init__(self, username, password, proxy_poolNone): self.username username self.password password self.session requests.Session() self.base_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: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Origin: https://www.tianyancha.com, Referer: https://www.tianyancha.com/, } self.proxy_pool proxy_pool # 代理IP池格式 [{http: http://ip:port}, ...] self.current_proxy None self.access_token None self.token_expire_time 0 # 从逆向分析中得到的固定参数或密钥 self.app_secret 你逆向分析得到的SECRET_KEY self.app_version 9.0.0 # 示例版本 def _get_proxy(self): 从代理池中随机获取一个代理 if self.proxy_pool: return random.choice(self.proxy_pool) return None def _make_request(self, method, url, **kwargs): 封装请求加入代理、重试和风控延迟逻辑 proxy self._get_proxy() kwargs[proxies] proxy # 合并headers headers self.base_headers.copy() if headers in kwargs: headers.update(kwargs[headers]) kwargs[headers] headers # 加入随机延迟模拟人类操作 time.sleep(random.uniform(0.5, 2.0)) try: response self.session.request(method, url, **kwargs) # 这里可以加入状态码判断例如 429(请求过多)、403(禁止访问)时触发风控处理 if response.status_code 429: print(f触发频率限制等待10秒后重试...) time.sleep(10) return self._make_request(method, url, **kwargs) # 简单重试一次 return response except requests.exceptions.ProxyError: print(代理失败更换代理重试...) if self.proxy_pool: self.proxy_pool.remove(proxy) # 简单移除失效代理 return self._make_request(method, url, **kwargs)5.2 登录与签名函数实现接下来是实现最关键的登录和签名函数。这里的_generate_sign和_encrypt_password函数需要你用逆向分析得到的逻辑来填充。def _generate_sign(self, params): 生成请求签名。 params: 参与签名的参数字典 返回签名字符串。 # 1. 参数排序 (按key字母序) sorted_params sorted(params.items(), keylambda x: x[0]) # 2. 拼接成 key1value1key2value2 格式 sign_str .join([f{k}{v} for k, v in sorted_params]) # 3. 拼接密钥假设是app_secret sign_str self.app_secret # 4. 计算MD5具体算法需根据逆向结果调整可能是SHA256等 sign_md5 hashlib.md5(sign_str.encode(utf-8)).hexdigest() # 5. 可能还有二次处理如转大写 return sign_md5.upper() def _encrypt_password(self, plain_password): 加密密码。 这里是一个示例真实逻辑可能涉及RSA加密公钥或AES加密。 你需要通过逆向分析用Python复现完全相同的加密过程。 # 示例可能是Base64编码 盐值混淆 # 真实情况请替换为你的逆向结果 import base64 # 假设是简单的 base64 编码实际远比这复杂 encrypted base64.b64encode(plain_password.encode()).decode() return encrypted def login(self): 执行登录流程 login_url https://www.tianyancha.com/api/auth/login/v2 # 示例URL # 1. 获取必要的初始Token或盐值有时需要先请求一个接口获取加密公钥或随机数 init_params { phone: self.username, timestamp: int(time.time() * 1000), appVersion: self.app_version, } init_params[sign] self._generate_sign(init_params) # 可能先发一个GET请求获取public_key等 # init_resp self._make_request(GET, https://.../init, paramsinit_params) # public_key init_resp.json()[data][publicKey] # 2. 构造登录参数 timestamp int(time.time() * 1000) login_params { phone: self.username, password: self._encrypt_password(self.password), # 使用加密后的密码 timestamp: timestamp, appVersion: self.app_version, loginType: password, # 或 sms 验证码登录 # captcha: ..., # 如果需要图形验证码 } # 3. 生成登录请求的签名 login_params[sign] self._generate_sign(login_params) # 4. 发送登录请求 headers { Content-Type: application/json;charsetUTF-8, } resp self._make_request(POST, login_url, jsonlogin_params, headersheaders) if resp.status_code 200: result resp.json() if result.get(code) 0 or result.get(success): # 根据实际响应结构调整 data result.get(data, {}) self.access_token data.get(accessToken) # 假设返回的token有效期为2小时 self.token_expire_time time.time() 2 * 3600 print(f登录成功Token: {self.access_token[:20]}...) # 更新session的headers后续请求携带Token self.session.headers.update({Authorization: fBearer {self.access_token}}) return True else: print(f登录失败: {result.get(message)}) # 可能需要处理验证码 if 验证码 in result.get(message, ): self._handle_captcha() return False else: print(f登录请求异常状态码: {resp.status_code}) return False def _handle_captcha(self): 处理验证码此处为示意滑块验证码需要复杂逆向 print(触发验证码需要人工处理或接入打码平台。) # 对于图形验证码可以下载图片调用打码平台API识别。 # 对于滑块验证码需要识别缺口位置生成模拟滑动轨迹。 # 这是一个独立且复杂的话题此处不展开。5.3 数据采集与风控应对示例登录成功后就可以进行数据采集了。以搜索公司为例def search_company(self, keyword, page1): 搜索公司 if time.time() self.token_expire_time: print(Token已过期尝试刷新...) if not self._refresh_token(): print(刷新Token失败重新登录...) self.login() search_url https://www.tianyancha.com/api/search/v2 params { word: keyword, pageNum: page, pageSize: 20, timestamp: int(time.time() * 1000), } # 搜索接口可能也需要签名 params[sign] self._generate_sign(params) resp self._make_request(GET, search_url, paramsparams) if resp.status_code 200: return resp.json() else: print(f搜索请求失败: {resp.status_code}) return None def _refresh_token(self): 刷新Access Token refresh_url https://www.tianyancha.com/api/auth/refresh # 通常用refresh_token来刷新这里简化处理 # 实际情况可能需要调用特定接口 print(Token刷新逻辑需根据实际API实现) return False6. 常见问题排查与实战经验分享在实际操作中你会遇到各种各样的问题。下面是我踩过的一些坑和对应的解决方案。6.1 问题排查清单问题现象可能原因排查思路与解决方案登录请求返回“参数错误”或“签名无效”1. 签名算法还原错误。2. 参与签名的参数不全或顺序不对。3. 加密函数如密码加密有误。4. 缺少必要的请求头如自定义的X-Client。1.核对签名用抓包到的原始参数代入自己写的签名函数计算结果与抓包到的sign值逐字符比对。2.参数对比仔细对比自己构造的Payload和抓包到的Payload查看每个键值对是否完全一致包括看似无关的字段。3.断点调试在浏览器中仔细跟踪签名和加密函数的每一步记录中间变量的值与Python代码的中间结果对比。登录成功但后续接口返回“未登录”或“Token过期”1. Token未正确设置到后续请求中。2. Token过期时间判断有误。3. 会话Session未保持Cookie丢失。4. 某些接口需要额外的认证参数。1.检查请求头确认Authorization头或相应的Cookie已正确附加。使用requests.Session()可自动管理Cookie。2.检查Token生命周期登录响应中可能包含expires_in字段据此计算过期时间。3.全局会话确保所有请求都使用同一个Session对象。4.分析后续请求抓取一个成功的手动操作请求看除了Token外是否还有X-Request-ID等字段。请求频率稍高即返回429或弹出验证码触发了基于IP或账号的行为频率风控。1.降低请求频率增加随机延迟time.sleep(random.uniform(2, 5))。2.使用高质量代理切换不同的住宅代理IP模拟不同地理位置的用户。3.模拟人类行为在关键请求如翻页、查看详情之间插入访问首页、随机等待等操作。4.识别验证码类型准备好应对方案如接入打码平台。代码在本地运行正常部署到服务器失败1. 服务器环境缺少依赖如Node.js环境用于执行某些JS加密。2. 服务器IP被目标网站封禁。3. 时区、TLS版本等环境差异导致指纹不同。1.环境一致性使用Docker容器固化运行环境。2.检查服务器IP用服务器curl一下目标网站看是否可访问。3.完善请求头确保服务器上运行的脚本其User-Agent、Accept-Language等头与浏览器一致。6.2 核心实战经验与技巧逆向的核心是“对比”和“还原”不要试图完全理解所有混淆后的代码逻辑。你的目标是把输入变成输出的那个“黑盒”函数用Python还原出来。不断用真实数据从浏览器调试中获取输入你自己的函数对比输出是否与浏览器一致是最高效的方法。善用“Hook”技术对于难以定位的加密函数可以在浏览器控制台使用“Hook”技巧。例如重写window.crypto.subtle.encrypt或JSON.stringify等方法在它们被调用时打印出参数和结果能快速定位加密发生的位置。风控是持续对抗没有一劳永逸的方案。网站的风控策略会升级。你的代码需要有良好的日志系统记录每次请求的响应状态、触发风控的情况。当发现失败率升高时要及时分析日志调整策略如更换签名参数、更新加密算法、增加新的请求头。尊重robots.txt与法律边界本文技术讨论仅用于学习与交流。在实际应用中务必遵守目标网站的robots.txt协议尊重数据版权和个人隐私控制访问频率避免对目标网站服务器造成负担。用于商业用途或大规模抓取前请务必寻求法律意见。滑块验证码的应对这是一个深水区。简单方案是接入商业打码平台如超级鹰、图鉴将截图发送给他们返回滑动轨迹。如果想自己研究需要学习图像识别找缺口位置和轨迹生成算法模拟人类加速度曲线。这本身就是一个庞大的逆向工程课题。整个逆向分析的过程是对耐心、细心和逻辑思维能力的极大考验。每一个加密参数背后都可能藏着开发者精心设计的防御逻辑。破解它不仅需要技术更需要像侦探一样从纷繁的网络请求和混乱的代码中找到那条通往真相的路径。当你最终看到自己的程序稳定地获取到所需数据时那种成就感或许就是技术人最大的乐趣之一。