大家好我是威哥。上个月刚把公司那个卡了三个月的爬虫项目搞定对方是国内某头部电商平台用了一套堪称变态的JS加密反爬前后换了三波人都没搞下来。我接手之后前前后后踩了不下20个坑熬了好几个通宵终于把整个加密逻辑逆向出来现在稳定每天爬取百万级数据。说实话JS加密反爬这东西说难也难说简单也简单。难的是很多人没有一套系统的方法论上来就瞎找瞎试浪费大量时间简单的是只要你掌握了正确的流程和技巧90%的JS加密反爬都能搞定。这篇文章我会把我这半年来踩过的所有坑、总结的所有经验都毫无保留地分享出来从最基础的抓包分析到断点调试、逆向加密逻辑再到模拟请求、绕过反爬检测一步步带你搞定JS加密反爬。看完这篇文章你至少能搞定市面上80%的JS加密反爬。先上完整破解流程图先给大家一张我整理的标准破解流程我现在逆向任何网站都是按照这个流程来的效率至少提升3倍。是否抓包分析请求识别所有动态加密参数定位加密函数入口断点调试逆向核心逻辑提取并补全JS加密代码Python侧模拟加密生成参数构造完整请求测试请求成功?加入异常处理稳定爬取排查指纹/环境/反调试检测一、第一步抓包分析别上来就找JS文件很多新手一上来就打开Sources面板翻JS文件这是最大的误区。你连哪些参数是加密的都不知道找半天也是白找。1. 先搞清楚哪些参数是动态的打开Chrome开发者工具的Network面板刷新页面找到你要爬取的接口。连续发两次相同的请求对比两次的请求参数和请求头把所有变化的参数都记下来。常见的加密参数有这些sign/signature/signData最常见的签名参数token/accessToken身份验证令牌timestamp/t时间戳nonce/nonceStr随机字符串callback/cbJSONP回调函数名请求头里的X-Sign/X-Token/Authorization我踩过的第一个大坑很多人只看请求体里的参数完全忽略了请求头和Cookie里的动态参数。我上次那个电商项目就是因为忽略了请求头里的X-Signature模拟请求一直返回403整整卡了我两天。2. 过滤无关请求精准定位目标接口Network面板里会有大量的图片、CSS、JS请求一定要学会过滤。在过滤框里输入XHR或者Fetch只看异步请求。如果还是太多可以用域名过滤比如输入api.xxx.com只看目标域名的请求。小技巧右键点击目标请求选择Save all as HAR with content把所有请求保存下来后面分析的时候会非常方便。二、第二步定位加密函数这是最关键的一步找到加密参数之后接下来就是要找到生成这个参数的JS函数。这一步是整个逆向过程中最耗时也最容易卡壳的地方我给大家分享三个我最常用的方法按优先级排序。1. 全局搜索关键词最快最直接这是我首选的方法90%的情况下都能快速定位到加密函数。在Sources面板里按CtrlShiftF打开全局搜索输入加密参数名或者相关关键词。比如你要找sign参数的生成位置可以搜这些关键词signsign:signsignencrypt(md5(sha256(踩坑提醒很多大厂会把函数名混淆成a、b、c这种单字母这时候搜函数名是没用的要搜参数名或者加密算法的特征字符串。比如MD5加密的结果是32位十六进制字符串你可以搜32或者hex。2. XHR断点万能定位法如果全局搜索搜不到就用XHR断点这个方法几乎万能。在Sources面板的XHR/fetch breakpoints里点击输入目标接口的URL的一部分比如/api/goods/list。刷新页面当请求发送的时候代码会自动断在XMLHttpRequest.send()方法的位置。然后看右边的Call Stack调用栈从下往上找就能找到生成加密参数的地方。我踩过的第二个大坑很多网站会把AJAX请求封装成一个公共函数调用栈里会有很多层无关的代码。不要在封装函数里浪费时间一直往上翻直到找到业务代码的位置。3. 事件监听断点对付特殊情况如果上面两种方法都不行就用事件监听断点。比如点击按钮触发的请求可以在Event Listener Breakpoints里展开Mouse勾选click然后点击按钮代码就会断在点击事件的处理函数里。三、第三步断点调试逆向加密核心逻辑定位到加密函数之后接下来就是断点调试一步步看加密逻辑是怎么执行的。这一步需要耐心不要急慢慢走。1. 常用断点技巧普通断点点击行号左边的 gutter添加一个普通断点代码执行到这里会暂停。条件断点右键点击行号选择Add conditional breakpoint输入条件只有当条件满足的时候才会暂停。比如url /api/goods/list非常适合过滤无关请求。日志断点右键点击行号选择Add logpoint输入要打印的变量代码执行到这里不会暂停只会在控制台打印变量的值。这个技巧太好用了我现在调试基本都用日志断点不用频繁暂停执行。2. 一步步分析加密逻辑断点断下来之后用这几个快捷键控制执行F10单步执行不进入函数F11单步执行进入函数ShiftF11跳出当前函数F8继续执行直到下一个断点在执行的过程中注意观察Scope面板里的变量值看看每个变量是怎么变化的。比如你可以看到timestamp是怎么生成的nonce是怎么生成的最后是怎么拼接成字符串然后经过什么加密算法得到sign的。常见的加密算法识别结果是32位十六进制字符串大概率是MD5结果是40位十六进制字符串大概率是SHA1结果是64位十六进制字符串大概率是SHA256结果是等号结尾的字符串大概率是Base64编码结果是长字符串开头有U2FsdGVkX1大概率是AES加密CryptoJS默认的AES加密结果开头就是这个3. 对付JS混淆的小技巧现在很多网站都会对JS代码进行混淆常见的混淆手段有字符串数组移位自执行函数变量名和函数名混淆成单字母控制流平坦化字符串加密对付简单的混淆直接用js-beautify格式化一下就行。对付复杂的混淆可以用AST解混淆工具比如babel-plugin-transform-obfuscation或者在线的解混淆网站。我最常用的方法把混淆的JS代码复制到Chrome控制台里执行然后打印混淆后的函数就能看到明文的代码了。比如混淆后的函数是a你在控制台输入console.log(a.toString())就能看到这个函数的原始代码。四、第四步提取并补全JS代码用Python模拟加密逆向出加密逻辑之后接下来就是把加密代码提取出来然后用Python模拟执行生成正确的加密参数。1. 两种模拟方式的对比我一般用两种方式模拟加密各有优缺点方式优点缺点适用场景Python重写加密逻辑性能好不需要依赖Node.js容易出错复杂加密重写耗时简单加密MD5、SHA256、简单自定义加密execjs调用JS代码简单快速不容易出错性能稍差需要依赖Node.js复杂加密AES、RSA、严重混淆的加密对于大部分场景我推荐用execjs调用JS代码简单快速不容易出错。只有当性能要求特别高的时候才用Python重写。2. execjs调用JS代码实战首先安装execjs和Node.jspipinstallPyExecJS# 然后安装Node.js官网下载安装就行然后把逆向出来的加密函数复制出来保存为encrypt.js// 这是逆向出来的加密函数functiongetSign(timestamp,nonce,data){conststrtimestampnonceJSON.stringify(data);returnCryptoJS.MD5(str).toString().toUpperCase();}// 记得把依赖的CryptoJS也加进来// 这里省略CryptoJS的代码自己去官网下载然后在Python里调用这个函数importexecjsimporttimeimportrandomimportjson# 加载JS代码withopen(encrypt.js,r,encodingutf-8)asf:js_codef.read()ctxexecjs.compile(js_code)# 生成参数timestampstr(int(time.time()*1000))nonce.join(random.choices(abcdefghijklmnopqrstuvwxyz0123456789,k16))data{page:1,size:20}# 调用JS函数生成signsignctx.call(getSign,timestamp,nonce,data)print(ftimestamp:{timestamp})print(fnonce:{nonce})print(fsign:{sign})踩坑提醒很多JS代码依赖浏览器环境比如window、document、navigator这些对象在Node.js里是没有的。这时候需要手动模拟这些对象比如// 在JS代码开头添加这些模拟代码constwindow{};constdocument{cookie:};constnavigator{userAgent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36};五、第五步构造完整请求绕过反爬检测很多人以为加密参数对了就万事大吉了结果模拟请求还是返回403或者空数据。这是因为还有很多其他的反爬检测比如请求头检测、指纹检测、环境检测等等。1. 请求头一定要和浏览器完全一致这是最容易被忽略的一点。很多网站会检测请求头的顺序和内容如果和浏览器不一样就会直接拒绝请求。注意事项不要用requests默认的User-Agent一定要用浏览器的User-Agent所有的请求头都要带上包括Accept、Accept-Encoding、Accept-Language、Referer等等请求头的顺序一定要和浏览器一致。requests默认的请求头顺序和浏览器不一样这是一个巨坑解决方法右键点击目标请求选择Copy as cURL (bash)然后用curlconverter.com转换成Python代码这样生成的请求头和顺序和浏览器完全一致。2. 绕过浏览器指纹检测现在越来越多的网站会检测浏览器指纹比如canvas指纹、webgl指纹、字体指纹等等。如果用requests直接请求会被检测出来是爬虫。解决方案用undetected-chromedriver代替普通的selenium它可以绕过大部分指纹检测用playwright的无头模式它的指纹更接近真实浏览器不要用requests请求需要指纹检测的接口直接用无头浏览器执行JS获取数据3. 其他常见的反爬检测Cookie检测很多网站会在Cookie里设置一些动态参数每次请求都会更新。一定要保持Cookie的连贯性不要每次请求都用新的Cookie。时间戳检测时间戳不能和服务器时间相差太大一般不要超过5分钟。随机数检测随机数不能重复每次请求都要生成新的随机数。请求频率检测不要请求太快加随机延迟控制请求频率。六、我踩过的那些坑和解决方案JS代码动态加载每次都不一样解决方案不要直接下载JS文件用正则表达式从HTML里提取加密函数或者用无头浏览器直接执行页面上的JS。反调试一打开开发者工具就断在debugger解决方案在Chrome开发者工具的设置里勾选Disable JavaScript然后刷新页面再取消勾选。或者用never pause here功能右键点击debugger语句的行号选择Never pause here。加密算法里有随机数每次生成的sign都不一样解决方案只要模拟和JS代码一样的随机数生成方式就行不用管随机数是什么只要服务器能验证通过就行。AES加密的密钥是动态获取的解决方案先请求获取密钥的接口拿到密钥之后再加密数据。RSA加密公钥是动态的解决方案先从服务器获取公钥然后用公钥加密数据。最后说几句其实JS加密反爬这东西真的没有什么高深的技术就是一个熟能生巧的过程。我刚开始学的时候连断点都不会用一个简单的MD5加密都搞了好几天。但只要你掌握了正确的方法多逆向几个网站慢慢就熟练了。我现在逆向一个普通的JS加密基本上半小时就能搞定。复杂一点的最多也就一两天。关键是要有耐心不要遇到一点困难就放弃。最后还是要提醒大家爬虫一定要合规。不要爬取敏感数据不要给对方服务器造成过大压力。控制好请求频率尊重robots协议。技术是用来解决问题的不是用来搞破坏的。