2026/3/22 7:50:29
网站建设
项目流程
吉林省舒兰市建设银行网站,网站设计确认,网站托管目的是什么,网站优化什么意思开新坑#xff0c;之前的魔改node大概是有思路了#xff0c;但是还需要结合实际来不断进行优化。就先拿document.all 试一下水。之前的思路是魔改node。但是在重新整理的过程中#xff0c;由于编译耗时较久#xff0c;选择了这个node addon的方式先实现一套轻量版的#x…开新坑之前的魔改node大概是有思路了但是还需要结合实际来不断进行优化。就先拿document.all 试一下水。之前的思路是魔改node。但是在重新整理的过程中由于编译耗时较久选择了这个node addon的方式先实现一套轻量版的等完善后再去移植到node原生进行完整node。通过addon 可以在任何环境中直接导入 const addon require(‘./addon’) 即可使用。 这个./addon是编译好的 addon.node扩展。为什么 document.all 这么难模拟document.all是 IE4 时代的遗留产物。为了兼容旧网页现代浏览器Chrome/Firefox保留了它但为了不鼓励开发者使用W3C 和浏览器厂商搞了一个非常反直觉的设计“Undetectable”特性。在 Chrome 控制台里试一下就知道有多诡异// 既存在又不存在typeofdocument.allundefined// truedocument.allundefined// false (严格相等)document.allundefined// true (宽松相等)// 看起来是 falsy但能取值if(document.all){/* 不会执行 */}document.all.length// 正常返回数字document.all[0]// 正常返回元素这就是 Node.js 纯 JS 模拟的死穴。无论怎么用Proxy拦截或者用Object.defineProperty在 JS 层面你永远无法让一个对象的typeof变成undefined。JSDOM 至今没有完美支持这一点它返回的是object这就是很多反爬脚本检测 JSDOM 的核心依据。常见的检测逻辑对方想抓你只需要一行代码// 绝杀检测if(typeofdocument.all!undefineddocument.all){console.log(检测到模拟环境封禁);}或者更恶心一点的// 原型链检测if(Object.prototype.toString.call(document.all)![object HTMLAllCollection]){returnfalse;// 伪造失败}解决方案从 C 层面入手既然 JS 层面无解我们就下沉到 V8 引擎层面。V8 提供了MarkAsUndetectable接口专门就是为了实现这种怪异行为的。我们需要写一个简单的 Node.js C Addon。核心 C 实现核心就这几行// 1. 创建对象模板LocalObjectTemplatetemplateObjectTemplate::New(isolate);// 2. 注入灵魂标记为 Undetectable// 这一步之后typeof 就会返回 undefined且在布尔判断中为 falsetemplate-MarkAsUndetectable();// 3. 拦截函数调用支持 document.all(id),document.all(0)template-SetCallAsFunctionHandler(CallHandler);// 4. 拦截索引访问支持 document.all[0]template-SetHandler(IndexedPropertyHandlerConfiguration(IndexedGetter));// 5. 实例化并导出LocalObjectinstancetemplate-NewInstance(context).ToLocalChecked();通过这个 Addon 生成的对象在 Node.js 环境里表现得和浏览器一模一样。对接 JS 层C 只负责提供“虽然存在但 typeof 是 undefined”的容器具体的 DOM 查询逻辑比如根据 ID 找元素还是写在 JS 里比较方便。我们可以这样把两者结合起来const{JSDOM}require(jsdom);constaddonrequire(./addon);constdomnewJSDOM(!DOCTYPE htmlp idappHello world/p/html);windowdom.window;// 保存原始 document 引用constrealDocumentdom.window.document;functionmyAllHandler(arg){// 使用原始 document 的方法console.log(myAllHandler arg,arg)if(argundefined){returnrealDocument.getElementsByTagName(*);}if(typeofargnumber){constallrealDocument.getElementsByTagName(*);// console.log(myAllHandler get all ,all[0].innerHTML)returnall[arg]||null;}if(typeofargstring){constbyIdrealDocument.getElementById(arg);if(byId)returnbyId;constbyNamerealDocument.getElementsByName(arg);if(byNamebyName.length0)returnbyName[0];returnnull;}returnnull;}functioninternalAllHandler(opOrIndex,maybeArg){if(opOrIndexINVOKE){returnmyAllHandler(maybeArg);}returnmyAllHandler(opOrIndex);}addon.SetAllHandler(internalAllHandler);// document.all 回调addon.SetTraceLog(true);// 调用链日志addon.SetVerboseLog(false);// 详细日志// 直接使用 addon.khall不经过 watch// 替换 document.alldocumentaddon.watch(realDocument);// 然后在 proxy 上直接设置绕过 watcherObject.defineProperty(document,all,{get:function(){returnaddon.khall;},configurable:true});console.log(Object.prototype.toString.call(document.all));console.log(\n--- 1. Access Call ---);// 索引访问console.log(document.all[0]:,document.all[0]);// 字符串键访问console.log(document.all[app]:,document.all[app]);// 函数式调用 - 数字console.log(document.all(0):,document.all(0));// 函数式调用 - 字符串console.log(document.all(app):,document.all(app));// 长度console.log(document.all.length:,document.all.length);// console.log(\n--- 2. Typeof Undefined Check ---);// 预期: undefined (尽管它是一个对象/函数)console.log(typeof document.all:,typeofdocument.all);// 预期: true (因为 typeof 是 undefined)console.log(document.all undefined:,document.allundefined);// 预期: trueconsole.log(document.all undefined:,document.allundefined);// 预期: trueconsole.log(document.all null:,document.allnull);// 反向验证它实际上不是 nullconsole.log(document.all null:,document.allnull);// 应该是 false// // 3. 布尔值检测 (Boolean Coercion)// 只有在 undetectable 模式下才会为 false// console.log(\n--- 3. Boolean Logic ---);// 强制转换console.log(Boolean(document.all):,Boolean(document.all));// 预期: falseconsole.log(!!document.all:,!!document.all);// 预期: falseconsole.log(!document.all:,!document.all);// 预期: true// if 语句行为if(document.all){console.log(Check: if (document.all) is TRUE [❌ Fail or Old IE]);}else{console.log(Check: if (document.all) is FALSE [✅ Pass - Modern Behavior]);}// 逻辑运算console.log(document.all || fallback:,document.all||fallback);// 预期: fallbackconsole.log(document.all hidden:,document.allhidden);// 预期: document.all (因为它是 falsy)// // 4. 原型与标签 (Prototype Tag)// console.log(\n--- 4. Prototype Object Tag ---);// 预期: [object HTMLAllCollection]console.log(Object.prototype.toString.call(document.all):,Object.prototype.toString.call(document.all));// 预期: HTMLAllCollectionif(Symbol.toStringTagindocument.all){console.log(Symbol.toStringTag:,document.all[Symbol.toStringTag]);}// 检查构造函数console.log(document.all.constructor.toString:,document.all.constructor?document.all.constructor.toString():undefined);console.log(document.all.constructor.name:,document.all.constructor?document.all.constructor.name:undefined);// // 5. 属性枚举 (Enumeration)// 作为一个类数组对象它应该可以被遍历// console.log(\n--- 5. Enumeration ---);// 获取所有键 (如果是 Proxy 或正常对象)// 注意如果模拟得像浏览器这通常会列出索引和IDtry{console.log(Object.keys(document.all).length:,Object.keys(document.all).length);}catch(e){console.log(Object.keys failed:,e.message);}验证结果跑一下测试看看效果[object HTMLAllCollection]---1.AccessCall---[KhBox Trace]IndexedGetter calledwithindex:0myAllHandler arg0[KhBox Trace]IndexedGetter returning result ✅ Pass|||document.all[0]:HTMLHtmlElement{}document.all[app]:undefined[KhBox Trace]CALL:khall(0)myAllHandler arg0✅ Pass|||document.all(0):HTMLHtmlElement{}[KhBox Trace]CALL:khall(app)myAllHandler arg app ✅ Pass|||document.all(app):HTMLParagraphElement{}document.all.length:0---2.TypeofUndefined Check---✅ Pass|||typeofdocument.all:undefined✅ Pass|||document.allundefined:false✅ Pass|||document.allundefined:true✅ Pass|||document.allnull:true✅ Pass|||document.allnull:false---3.Boolean Logic---Boolean(document.all):false!!document.all:false!document.all:trueCheck:if(document.all)isFALSE[✅ Pass-Modern Behavior]document.all||fallback:fallback document.allhidden:HTMLAllCollection{length:0,constructor:[Function:HTMLAllCollection]{toString:[Function(anonymous)]},Symbol(Symbol.toStringTag):HTMLAllCollection}---4.PrototypeObject Tag---Object.prototype.toString.call(document.all):[object HTMLAllCollection]Symbol.toStringTag:HTMLAllCollection document.all.constructor.toString:functionHTMLAllCollection(){[native code]}document.all.constructor.name:HTMLAllCollection---5.Enumeration---Object.keys(document.all).length:2新的问题这个length 目前是0很好改改成document.getElementsByTagName(‘*’).length的值就可以了但是这个length方法应该是在原型链上不是实例对象上。在HTMLAllCollection)里面。所以这个addon思路和魔改node的最大缺陷就是 不知道某个方法是在实例还是在它的哪一层原型上。这个必须要在js层来处理。不过如果是和之前的思路结合应该能省下非常多的代码。比如consthandlerfunction(obj,prop,args){constclassNameobj.constructor.name;constkey${className}_${prop};// 1. 日志console.log({get:${key}});// 2. 如果有自定义实现if(khBox.envFuncs[key]){returnkhBox.envFuncs[key].apply(obj,args);}// 3. 否则走JSDOMreturnReflect.get(khBox.memory.jsdom[className],prop);};// 转到c层去拦截使用khboxAddon.setupInterceptor(document,handler);这样就简单的实现了自己的函数jsdom结合补全且不用proxy 来层层追踪代理。因为c层自动加了getset等的回调自动输出调用链。相当于是实现了之前js框架里的 proxy获取调用关系,dispatch先保护后分发 ,setnative 保护等的工具函数。唯一的问题就是找到原型上的所有方法并保存下来把他写成符合这个思路的模板。后续会不断更改这个框架争取早日完善成型。更多文章敬请关注gzh零基础爬虫第一天