打开开发者工具准备看网络请求,页面“啪”一下停在一个 debugger 语句上,怎么点 Resume 都没用,停一次、继续一次、又停一次,循环往复,网页像是被下咒一样卡死在调试器里。这个场景搞前端分析、调接口参数、看响应数据的时候太常见了,很多资讯类、财经类站点为了防爬,会在前端脚本里埋“无限 debugger”。雪球网就是典型例子。网上关于怎么处理的技术贴其实不少,但大多都是“正派”路线——教你顺着调用栈去定位、加条件断点、一点点修复逻辑,费时间不说,遇到堆了十几层定时器和eval的混淆代码真的会想砸电脑。
我花了不少时间折腾过这类防护,最后的经验是:不必去和 debugger 硬碰硬。开发工具本身和浏览器运行机制里其实留了不少后门,只要思路换一下,可以做到不去管它的具体触发点,直接从机制上把它废掉。整套流程固定下来之后,处理速度非常快,而且不依赖某个特定站点的代码结构。那这篇就把我常用的几招“邪修”方法完整记录下来。
1. 先说清楚无限debugger是怎么让你卡住的
1.1 debugger语句的底层机制
debugger是JavaScript里的一个关键字,本身不复杂:代码执行到这一句时,JS引擎会主动通知调试器暂停执行。如果没有外部调试器挂在那里,这句话等同于不存在,用户正常打开网页不会有任何感觉。问题就出在,只要你开着DevTools,调试器是激活状态,那么每碰到一次debugger就会中断一次。
“无限”这两个字的关键在于触发方式。最朴素的一种写法是在JS里批量埋点:
javascript复制function blockDebug() {
debugger;
}
setInterval(blockDebug, 100);
每隔100毫秒来一次,每次你点击“继续执行”后,下一次又来了。还有更刁钻的写法,把debugger塞进eval字符串里动态执行:
javascript复制setInterval(function() {
(function() {}).constructor('debugger')();
}, 100);
这种方式写出来的debugger不是直接出现在源码层面,而是运行时通过Function构造函数动态生成的,你就算在源代码里全文搜索“debugger”都可能搜不到,因为源码里压根没有这个关键字,只有一段字符串拼接逻辑。这也是为什么很多人觉得这玩意儿诡异的原因——从静态代码里看,什么可疑的东西都不明显,一跑起来就不断暂停。
1.2 触发位置的主要几种类型
根据我拆过的一些案例,无限debugger的出现位置大致能归成三类,先分类再看解决方案会清晰很多:
| 类型 | 典型实现方式 | 调试时的表现 |
|---|---|---|
| 定时器触发型 | setInterval/setTimeout循环调用含debugger的函数 |
点击继续后马上再次暂停,间隔固定 |
| 递归调用型 | 函数内部调用自身,执行到debugger后继续递归 |
暂停点在同一个函数内来回跳,调用栈越积越深 |
| 动态构造型 | eval或Function构造器执行字符串拼接出的debugger |
静态代码里搜不到debugger,只能运行时定位 |
理解触发类型很重要,因为不同位置的写法适合用不同方案来绕。比如纯定时器型的,最快方式是直接在运行入口把定时器Hook掉;而动态构造型的,则需要往Function构造器那一层动手。
1.3 明确一下做这些事情的边界
开始说具体操作之前,先聊一句边界问题。这篇文章讨论的是开发调试过程中怎么摆脱无限debugger的干扰,用于你自己在合法场景下分析网页、排查脚本问题、学习前端工程实现。请别把它用去搞批量抓取、恶意攻击或者绕过人家服务端的安全控制。真正的爬虫对抗大头在服务端,前端这点debugger只是最基础的一道门槛,过了它后面还有接口签名、频率控制、行为识别一堆东西等着,心思花在这上面意义也不大。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么说常规处理方式又慢又累
不少技术帖教的“正派”解法是定位到debugger所在的那一行,然后通过加条件断点、修改逻辑的方法让它不生效。听起来合理,实操的时候非常痛苦。
一是代码定位难。现在的生产环境JS基本都是压缩混淆过的,变量名是一两个字母,代码一长串挤在一行里,函数嵌套得跟千层饼一样。哪怕你通过调用栈找到了暂停位置,看到的也是一坨人肉没法读的东西,想从里面精准地找出是哪个逻辑触发了这个debugger,有时候跟大海捞针差不多。
二是循环触发让人身体被掏空。无限debug
