1. 项目概述从“看准网”到数据洞察最近在做一个企业信息分析的项目需要批量获取一些公司的基本信息、评价和薪资数据。市面上这类平台不少但“看准网”以其相对丰富的UGC用户生成内容和结构化数据成为了一个典型的目标。我们的目标很明确不是简单地截图或者手动复制而是希望以程序化的方式稳定、高效地获取其网站上以JSON格式返回的企业信息数据。这本质上是一个典型的Web数据采集场景但“看准网”作为有一定规模的平台其前端交互和数据接口必然不会毫无防护。直接请求网页然后解析HTML即所谓的“爬虫”虽然直接但面临几个问题页面结构复杂且可能频繁变动渲染依赖JavaScript导致简单请求拿不到完整数据以及最直接的——很多关键数据是通过异步接口Ajax以JSON格式动态加载的。因此“逆向工程”其前端JavaScript代码找到并理解这些JSON接口的请求构造方式就成了必由之路。这个过程就是大家常说的“JS逆向”。对于数据分析师、市场研究员或开发者来说掌握这套方法意味着你能将公开的、非结构化的网页信息转化为结构化的、可分析的数据资产。无论是竞品监控、行业研究还是人才市场分析这都是一项极具价值的基础能力。接下来我就以一个实际的“看准网”企业详情页为例拆解整个逆向分析、接口定位、参数破解到最终数据获取的全过程并分享其中踩过的坑和总结的技巧。2. 逆向分析的核心思路与准备工作2.1 目标定义与法律边界在开始任何技术操作之前明确目标和遵守规则是首要的。我们的目标是学习Web前端与后端数据交互的机制通过技术手段解析公开接口用于个人学习、研究或合法的数据分析。必须严格遵守目标网站的robots.txt协议尊重版权和数据所有权不得进行恶意爬取、侵犯隐私或用于任何商业侵权用途。高频、大规模的请求会对对方服务器造成压力可能触及法律红线因此务必控制请求频率添加合理的延时并识别哪些数据是明确禁止抓取的。2.2 工具链准备浏览器开发者工具是主战场工欲善其事必先利其器。现代浏览器的开发者工具DevTools是我们进行JS逆向的“瑞士军刀”。以Chrome或Edge为例以下几个面板至关重要元素Elements面板用于查看网页的DOM结构但在这个案例中不是重点。网络Network面板这是核心中的核心。它记录了页面加载过程中所有的网络请求包括文档、图片、样式表以及最重要的——XHR/Fetch请求即Ajax请求通常返回JSON数据。我们需要在这里筛选出包含目标数据的请求。源代码Sources面板用于查看和调试前端JavaScript代码。当我们从Network面板找到目标请求后通常需要在这里设置断点跟踪请求参数是如何被构造出来的。控制台Console面板用于执行JavaScript代码片段测试我们解密出来的参数生成函数。应用Application面板可以查看本地存储、Session Storage、Cookie等有时签名或令牌会存储在这里。此外可以准备一些辅助工具抓包工具如 Fiddler、Charles它们可以提供更强大和灵活的网络请求监控、断点、重放和修改功能尤其对于HTTPS请求的证书管理很方便。代码格式化工具线上找到的JS代码经常是被压缩混淆的变量名变成a,b,c没有换行。浏览器Sources面板自带格式化按钮{}或者使用本地编辑器插件将代码变得可读是分析的第一步。Node.js环境用于在本地模拟和执行我们逆向出来的JavaScript加密函数。注意整个分析过程应在目标网站的正常使用环境下进行。不要尝试使用任何非法的代理或绕过地域限制的工具访问网站这违反了使用条款且可能带来安全风险。2.3 分析流程总览面对一个看似复杂的网站遵循一个清晰的流程可以事半功倍手动操作触发数据加载在浏览器中正常访问目标页面例如某个公司的看准网主页滚动页面或点击“查看更多评价”、“薪资详情”等按钮触发我们感兴趣的数据的异步加载。监控网络请求打开Network面板清空现有记录然后触发数据加载动作。在请求列表中筛选XHR或Fetch类型寻找包含疑似数据关键词如companyreviewsalary且响应体为JSON格式的请求。定位关键请求通过预览Preview响应内容确认找到的目标请求。记录下这个请求的Request URL接口地址、Request Method请求方法通常是GET或POST以及Headers请求头特别是Cookie,User-Agent,Referer等。分析请求参数查看该请求的Query String ParametersGET请求或Form DataPOST请求。找出那些看起来是随机的、加密的或长度很长的参数这些往往是需要逆向破解的重点。常见的可疑参数名包括token,sign,_signature,ts,nonce等。搜索与断点调试在Sources面板中全局搜索CtrlShiftF这些可疑参数名或其部分值。找到相关的JavaScript代码块后在其附近设置断点重新触发请求让代码执行暂停在断点处。然后通过“单步执行”F10、“步入”F11等方式跟踪参数的计算过程。还原加密/签名逻辑通过调试理清参数是如何生成的。可能是对某些固定字符串加上时间戳再进行MD5也可能是更复杂的HMAC-SHA256或者是前端生成的RSA加密。最终目标是在本地如Node.js环境用代码复现这个生成逻辑。构造请求获取数据用编程语言如Python模拟浏览器携带正确的请求头和我们逆向生成的参数去请求接口拿到结构化的JSON数据。3. 看准网企业信息接口逆向实战拆解我们以获取某个公司例如假设公司ID为12345的基本信息为例。实际操作中你需要替换为真实的公司ID。3.1 定位数据接口首先访问https://www.kanzhun.com/gsx12345/这样的公司主页。页面会加载公司Logo、简介、评分等。我们猜测这些信息是通过一个API接口获取的。打开Network面板筛选XHR/Fetch。刷新页面或滚动观察出现的请求。很快你会发现一个名为c的请求其响应Preview里正是结构化的公司信息JSON。点击这个c请求查看详细信息Request URL:https://www.kanzhun.com/api/com/rec/cRequest Method:GETQuery String Parameters:k: 12345 ka: open-company-rank kd: 12345 _: 1727836500000 一个13位的时间戳 rt: s b: 1Headers: 包含常见的User-Agent,Cookie等。这个接口看起来相对简单参数大多是明文。k和kd大概率是公司IDka是场景标识_是时间戳用于防止缓存rt和b可能是固定值或版本标识。初步判断这个接口可能没有复杂的动态签名。3.2 深入复杂接口以评价列表为例公司基本信息往往防护较弱。真正的挑战通常在列表数据如评价列表、面试列表、薪资列表。这些接口通常会有反爬机制。触发并定位在公司页面点击“公司评价”标签页并滚动加载更多。在Network面板中你会看到一个新的请求比如叫review.json或more_reviews。分析请求假设我们找到一个请求https://www.kanzhun.com/api/com/review/。Method:POST(列表数据常用POST)Query String: 可能为空或很简单。Request Headers: 需要特别注意是否有自定义Header如X-Requested-With,X-Signature等。Form Data / Payload: 这是分析的重点。可能会看到如下数据companyId: 12345 pageNum: 2 pageSize: 10 sortType: 0 cipherText: AbCdEfGhIjKlMnOpQrStUvWxYz0123456789 一个很长的、像Base64的字符串这里的cipherText就是典型的加密参数。明文参数公司ID、页码等被加密后放在了这里。我们的任务就是找出生成这个cipherText的算法。3.3 逆向加密参数cipherText这是JS逆向的核心环节。搜索与定位在Sources面板中全局搜索cipherText。你可能会在某个被压缩的JS文件如main.xxxxxx.js中找到多处引用。通过查看上下文找到它被赋值的地方例如data.cipherText encrypt(JSON.stringify(params))。格式化与断点点击{}格式化该JS文件。在encrypt函数被定义或调用的行号上设置断点。调试跟踪回到网页触发下一次评价列表的加载比如翻到下一页。代码执行会在断点处暂停。在右侧的Call Stack调用堆栈可以看到函数调用链。在Scope作用域中可以查看当前函数的局部变量、闭包变量等。**单步步入F11**进入encrypt函数内部。分析加密逻辑在encrypt函数内部你可能会看到类似这样的流程将传入的明文参数对象转换成JSON字符串。对这个字符串进行某种编码或处理。调用CryptoJS.AES.encrypt或window.btoa或一个自定义的加密函数。可能还涉及到从window对象或某个全局变量中获取一个密钥key或向量iv。最终返回加密后的密文。关键点提取加密算法是AES、DES还是简单的Base64观察函数中调用的库或原生方法。模式与填充如果是AES是CBC模式还是ECB模式填充是PKCS7还是其他密钥Key和向量IV它们从哪里来可能是硬编码在JS文件里的一个字符串也可能是通过另一个接口动态获取的或者是通过Date.now()等函数计算出来的一个值。这是逆向中最需要耐心寻找的部分。其他参数是否在加密前拼接了固定字符串或时间戳实操心得JS代码可能被“混淆”Obfuscation处理变量和函数名被替换成无意义的字符并添加了控制流平坦化等反调试技巧。这大大增加了阅读难度。此时不要试图理解全部代码我们的目标是“黑盒”还原通过断点观察输入明文参数和输出cipherText以及关键的中间变量如密钥从而推断出加密逻辑。浏览器的“Watch”功能可以持续监控某个变量的值非常有用。3.4 在Node.js中复现加密函数假设我们通过调试确定加密逻辑是对JSON.stringify(params)后的字符串使用 AES-CBC-PKCS7 模式进行加密密钥是kanzhun_2024_secret的MD5值的前16字节IV是全零。下面我们在Node.js环境中复现// 首先安装依赖npm install crypto-js const CryptoJS require(crypto-js); function generateCipherText(params) { // 1. 参数转JSON字符串 const plainText JSON.stringify(params); // 2. 准备密钥和IV (根据逆向结果) // 假设密钥是固定字符串的MD5前16位 const secretKey kanzhun_2024_secret; const key CryptoJS.MD5(secretKey).toString().substr(0, 16); // 取16进制字符串前16字符作为字节 // 将16进制字符串转换为CryptoJS需要的WordArray格式 const keyBytes CryptoJS.enc.Utf8.parse(key); // IV假设为16字节的0 const iv CryptoJS.enc.Utf8.parse(\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0); // 3. AES-CBC 加密 const encrypted CryptoJS.AES.encrypt(plainText, keyBytes, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); // 4. 输出Base64格式的密文 return encrypted.toString(); } // 测试 const testParams { companyId: 12345, pageNum: 2, pageSize: 10, sortType: 0 }; const cipher generateCipherText(testParams); console.log(生成的 cipherText:, cipher); // 将生成的cipherText与浏览器Network里抓到的进行对比如果一致则逆向成功。注意事项CryptoJS库的密钥和IV通常需要是WordArray类型。如果逆向发现密钥是字节数组需要注意转换。加密模式CBC/ECB、填充方式Pkcs7/ZeroPadding必须完全一致否则解密端服务器会失败。有时密钥或IV不是固定的而是根据时间戳、Cookie或其他动态信息计算得出这就需要我们把那部分计算逻辑也一并复现。4. 完整数据采集流程构建与代码实现当我们成功逆向出关键参数的生成算法后就可以用Python或其他语言构建一个完整的采集脚本了。这里以Python为例使用requests库。4.1 请求头Headers的模拟浏览器发送的请求带有完整的Headers服务器可能会校验其中一些。至少需要模拟以下关键头信息import requests import time import json from your_encrypt_module import generate_cipher_text # 导入我们逆向实现的加密函数 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, Accept-Encoding: gzip, deflate, br, Content-Type: application/x-www-form-urlencoded; charsetUTF-8, # 对于POST表单 Origin: https://www.kanzhun.com, Referer: https://www.kanzhun.com/gsx12345/, # 动态替换为公司页 Connection: keep-alive, # Cookie至关重要它包含了登录态如果需要和会话信息。 Cookie: 你的有效Cookie字符串可以从浏览器复制 }关于Cookie的获取与维护手动复制首次可以从已登录的浏览器开发者工具的Network面板中复制任意一个请求的Cookie值。但Cookie会过期。模拟登录更稳定的方式是逆向登录接口用代码实现用户名密码登录或验证码登录自动获取并维护Cookie。这又是一个独立的、更复杂的逆向课题。Session对象使用requests.Session()可以自动管理Cookie像浏览器一样保持会话。4.2 构造请求与解析数据对于简单的GET接口如公司基本信息def get_company_basic(company_id): url https://www.kanzhun.com/api/com/rec/c params { k: company_id, ka: open-company-rank, kd: company_id, _: int(time.time() * 1000), # 生成13位时间戳 rt: s, b: 1 } resp requests.get(url, paramsparams, headersheaders) if resp.status_code 200: data resp.json() # 直接解析JSON # 检查响应结构通常有一个code或status字段表示成功与否 if data.get(code) 0 or data.get(status) 1: return data.get(data, {}) # 返回数据部分 else: print(f请求失败消息{data.get(message)}) return None else: print(fHTTP请求失败状态码{resp.status_code}) return None对于需要加密参数的POST接口如评价列表def get_company_reviews(company_id, page_num1, page_size10): url https://www.kanzhun.com/api/com/review/ # 1. 构造明文参数 plain_params { companyId: company_id, pageNum: page_num, pageSize: page_size, sortType: 0 } # 2. 调用逆向得到的加密函数生成 cipherText cipher_text generate_cipher_text(plain_params) # 这是我们在Node.js中实现的函数可能需要用Python重写或通过子进程调用 # 3. 构造POST表单数据 form_data { cipherText: cipher_text # 有时还有其他固定参数 } # 4. 发送请求 resp requests.post(url, dataform_data, headersheaders) # 5. 解析响应 if resp.status_code 200: data resp.json() if data.get(rescode) 1: # 看准网常用rescode1表示成功 review_list data.get(data, {}).get(list, []) return review_list else: print(f获取评价失败{data.get(msg)}) return [] else: print(fHTTP请求失败{resp.status_code}) return []4.3 数据存储与调度获取到JSON数据后可以根据需要存储为文件或入库。import pandas as pd def save_to_csv(data_list, filename): if not data_list: print(无数据可保存) return df pd.DataFrame(data_list) df.to_csv(filename, indexFalse, encodingutf-8-sig) print(f数据已保存至 {filename}) # 调度示例 if __name__ __main__: company_id 12345 # 获取基本信息 basic_info get_company_basic(company_id) if basic_info: print(f公司名称{basic_info.get(companyName)}) # 获取前3页评价 all_reviews [] for page in range(1, 4): print(f正在获取第{page}页评价...) reviews get_company_reviews(company_id, page_numpage) all_reviews.extend(reviews) time.sleep(2) # 非常重要设置延时避免请求过快被封IP save_to_csv(all_reviews, fcompany_{company_id}_reviews.csv)5. 常见问题、反爬策略与应对技巧在实际操作中你几乎一定会遇到各种阻碍。下面是一些典型问题及应对思路。5.1 请求被拒绝状态码403/418问题直接返回403 Forbidden或者418 I‘m a teapot一种幽默的反爬响应。可能原因与解决缺少或错误的Headers检查User-Agent,Referer,Origin,Cookie是否齐全且格式正确。有些网站会校验Host头。IP频率限制你的IP在短时间内发送了太多请求。解决方案是使用代理IP池并在请求间添加随机延时如time.sleep(random.uniform(1, 3))。TLS指纹或JA3指纹识别高级反爬能识别出你是requests库而不是真实浏览器。可以使用curl_cffi或tls_client等库来模拟浏览器的TLS指纹或者直接使用playwright/selenium这类浏览器自动化工具但效率较低。5.2 返回数据为空或错误问题请求成功200但返回的JSON中data为空或者code不为成功状态。可能原因与解决加密参数错误这是最常见的原因。cipherText生成逻辑有误。需要重新检查加密的每一个步骤JSON序列化后的字符串是否完全一致注意空格、键顺序密钥/IV是否正确加密模式和填充是否匹配。一个调试技巧是用浏览器触发一次成功请求在加密函数断点处把明文字符串和生成的密文都记录下来然后在你的本地代码中用同样的明文加密对比结果。参数缺失或错误检查POST的Form Data或GET的Query String是否缺少了某些看似不起眼但必需的参数如_时间戳t令牌等。Cookie失效登录态已过期。需要重新获取Cookie或实现自动登录刷新。请求已过期某些签名参数包含时间戳服务器会校验其有效性。确保你生成签名的时间戳与发送请求的时间间隔不能太长比如超过60秒。5.3 代码被混淆难以调试问题JS文件中的变量名都是_0x1a2b3c这种格式逻辑被控制流平坦化打乱。解决技巧Hook关键函数在Console中注入代码拦截关键函数如JSON.stringify,encodeURIComponent,CryptoJS.AES.encrypt等打印其输入输出。例如var _stringify JSON.stringify; JSON.stringify function(...args) { console.log(JSON.stringify called with:, args); var result _stringify.apply(this, args); console.log(Result:, result); return result; }搜索特征常量即使变量名混淆加密算法中使用的常量如AES的S盒、初始向量值、固定的盐值可能还是明文字符串。尝试搜索这些字符串。使用AST还原工具对于复杂的混淆可以尝试使用ast-explorer在线工具或deobfuscator这类开源项目进行初步的代码还原但成功率不一。5.4 签名算法动态变化问题今天逆向成功的算法明天就失效了。返回signature error。应对策略定期检查将采集脚本的失败监控纳入日常。一旦失败立即手动检查接口变化。关键逻辑外部化将加密/签名函数单独写在一个配置文件中。当算法变更时只需更新这个文件而不必改动主流程代码。寻找更稳定的接口有时网站会有多个接口返回相同数据某些老接口或备用接口的防护可能较弱。5.5 验证码Captcha拦截问题当请求频率稍高弹出滑动验证码或点选验证码。应对策略必须合规首要方法是降速大幅增加请求间隔模拟真人操作。这是最有效且合规的方法。验证码识别服务对于必须绕过的场景需谨慎评估法律风险可以考虑使用商业的验证码识别API如打码平台但这会增加成本且可能违反网站条款。维护会话池准备多个账号和对应的Cookie池轮流使用分散单个账号的请求压力。最后也是最关键的心得逆向工程是一场与网站开发者的“博弈”。它锻炼的是你的耐心、观察力和逻辑推理能力。没有一成不变的方法核心思路永远是“观察 - 假设 - 验证”。保持对网络协议和前端技术的基础理解善用开发者工具从小处着手先搞定最简单的接口建立信心再逐步攻克更复杂的难题。整个过程务必保持对数据的尊重和对法律的敬畏将技术用于学习和提升而非滥用。