【web补环境篇-0】document.all

开新坑,之前的魔改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.all==='undefined'// truedocument.all===undefined// false (严格相等)document.all==undefined// 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!=='undefined'&&document.all){console.log("检测到模拟环境,封禁!");}

或者更恶心一点的:

// 原型链检测if(Object.prototype.toString.call(document.all)!=="[object HTMLAllCollection]"){returnfalse;// 伪造失败}

解决方案:从 C++ 层面入手

既然 JS 层面无解,我们就下沉到 V8 引擎层面。V8 提供了MarkAsUndetectable接口,专门就是为了实现这种怪异行为的。

我们需要写一个简单的 Node.js C++ Addon。

核心 C++ 实现

核心就这几行:

// 1. 创建对象模板Local<ObjectTemplate>template=ObjectTemplate::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. 实例化并导出Local<Object>instance=template->NewInstance(context).ToLocalChecked();

通过这个 Addon 生成的对象,在 Node.js 环境里表现得和浏览器一模一样。

对接 JS 层

C++ 只负责提供“虽然存在但 typeof 是 undefined”的容器,具体的 DOM 查询逻辑(比如根据 ID 找元素)还是写在 JS 里比较方便。

我们可以这样把两者结合起来:

const{JSDOM}=require('jsdom');constaddon=require('./addon');constdom=newJSDOM(`<!DOCTYPE html><p id="app">Hello world</p></html>`);window=dom.window;// 保存原始 document 引用constrealDocument=dom.window.document;functionmyAllHandler(arg){// 使用原始 document 的方法console.log('myAllHandler arg',arg)if(arg===undefined){returnrealDocument.getElementsByTagName('*');}if(typeofarg==='number'){constall=realDocument.getElementsByTagName('*');// console.log('myAllHandler get all ',all[0].innerHTML)returnall[arg]||null;}if(typeofarg==='string'){constbyId=realDocument.getElementById(arg);if(byId)returnbyId;constbyName=realDocument.getElementsByName(arg);if(byName&&byName.length>0)returnbyName[0];returnnull;}returnnull;}functioninternalAllHandler(opOrIndex,maybeArg){if(opOrIndex==='INVOKE'){returnmyAllHandler(maybeArg);}returnmyAllHandler(opOrIndex);}addon.SetAllHandler(internalAllHandler);// document.all 回调addon.SetTraceLog(true);// 调用链日志addon.SetVerboseLog(false);// 详细日志// 直接使用 addon.khall,不经过 watch// 替换 document.alldocument=addon.watch(realDocument);// 然后在 proxy 上直接设置(绕过 watcher)Object.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.all===undefined);// 预期: trueconsole.log('document.all == undefined:',document.all==undefined);// 预期: trueconsole.log('document.all == null:',document.all==null);// 反向验证:它实际上不是 nullconsole.log('document.all === null:',document.all===null);// 应该是 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");// 预期: "fallback"console.log('document.all && "hidden":',document.all&&"hidden");// 预期: 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.Access&Call---[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.Typeof&Undefined Check---✅ Pass|||typeofdocument.all:undefined✅ Pass|||document.all===undefined:false✅ Pass|||document.all==undefined:true✅ Pass|||document.all==null:true✅ Pass|||document.all===null: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.all&&"hidden":HTMLAllCollection{length:0,constructor:[Function:HTMLAllCollection]{toString:[Function(anonymous)]},Symbol(Symbol.toStringTag):'HTMLAllCollection'}---4.Prototype&Object 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层来处理。

不过,如果是和之前的思路结合,应该能省下非常多的代码。比如

consthandler=function(obj,prop,args){constclassName=obj.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层自动加了get,set等的回调,自动输出调用链。相当于是实现了之前js框架里的 proxy(获取调用关系),dispatch(先保护后分发) ,setnative (保护)等的工具函数。

唯一的问题就是找到原型上的所有方法并保存下来,把他写成符合这个思路的模板。

后续会不断更改这个框架,争取早日完善成型。

更多文章,敬请关注gzh:零基础爬虫第一天

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1142553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PDF-Extract-Kit技术解析:OCR识别精度提升的秘诀

PDF-Extract-Kit技术解析&#xff1a;OCR识别精度提升的秘诀 1. 引言&#xff1a;PDF智能提取的技术挑战与创新 在数字化办公和学术研究中&#xff0c;PDF文档已成为信息传递的核心载体。然而&#xff0c;PDF格式的多样性&#xff08;如扫描件、图文混排、公式表格等&#xf…

DLSS Swapper:游戏画质与性能的智能调校大师

DLSS Swapper&#xff1a;游戏画质与性能的智能调校大师 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 在追求极致游戏体验的道路上&#xff0c;每个玩家都面临着画质与性能的艰难抉择。当你的显卡在3A大作中苦苦挣扎…

Proteus 8 Professional下载环境下LCD显示电路仿真实践

用Proteus做LCD仿真&#xff0c;我为什么不再急着焊电路板&#xff1f;还记得第一次在实验室里连HD44780 LCD的时候吗&#xff1f;接好线&#xff0c;烧录程序&#xff0c;通电——屏幕一片漆黑。换数据线顺序&#xff0c;调对比度电位器&#xff0c;改初始化代码……折腾一整天…

智能解锁工具完整指南:5款强力付费墙绕过方案深度解析

智能解锁工具完整指南&#xff1a;5款强力付费墙绕过方案深度解析 【免费下载链接】bypass-paywalls-chrome-clean 项目地址: https://gitcode.com/GitHub_Trending/by/bypass-paywalls-chrome-clean 你是否曾经在阅读精彩内容时被付费墙无情阻挡&#xff1f;在这个信息…

WeMod专业版功能免费解锁技术解析与实战指南

WeMod专业版功能免费解锁技术解析与实战指南 【免费下载链接】Wemod-Patcher WeMod patcher allows you to get some WeMod Pro features absolutely free 项目地址: https://gitcode.com/gh_mirrors/we/Wemod-Patcher 在游戏辅助工具市场中&#xff0c;WeMod凭借其强大…

DLSS Swapper终极指南:轻松掌控游戏画质与性能

DLSS Swapper终极指南&#xff1a;轻松掌控游戏画质与性能 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 你是否遇到过新版本DLSS反而导致游戏闪退或画质下降的情况&#xff1f;DLSS Swapper就是解决这一问题的专业工…

PDF-Extract-Kit成本优化:如何节省80%的PDF处理费用

PDF-Extract-Kit成本优化&#xff1a;如何节省80%的PDF处理费用 在当前AI与文档自动化处理需求激增的背景下&#xff0c;PDF内容提取已成为科研、教育、金融等多个领域的刚需。然而&#xff0c;市面上主流的商业PDF解析服务&#xff08;如Adobe Document Cloud、Google Docume…

3个关键点解决TranslucentTB安装难题:从失败到完美运行的实战经验

3个关键点解决TranslucentTB安装难题&#xff1a;从失败到完美运行的实战经验 【免费下载链接】TranslucentTB 项目地址: https://gitcode.com/gh_mirrors/tra/TranslucentTB 作为一名长期使用TranslucentTB美化Windows任务栏的深度用户&#xff0c;我深知安装过程中的…

零基础入门I2C硬件连接:双线制通信机制小白指南

从零开始搞懂I2C&#xff1a;双线通信如何让多个芯片“和平共处”&#xff1f;你有没有遇到过这种情况——手头的MCU引脚快被占满了&#xff0c;可还想再接个温湿度传感器、OLED屏或者EEPROM&#xff1f;明明只是低速数据交互&#xff0c;却因为SPI要四根线、UART只能点对点&am…

MoeKoeMusic完全免费开源音乐播放器:解锁VIP特权的最佳选择

MoeKoeMusic完全免费开源音乐播放器&#xff1a;解锁VIP特权的最佳选择 【免费下载链接】MoeKoeMusic 一款开源简洁高颜值的酷狗第三方客户端 An open-source, concise, and aesthetically pleasing third-party client for KuGou that supports Windows / macOS / Linux :elec…

PDF-Extract-Kit实战案例:法律文书智能分析系统搭建

PDF-Extract-Kit实战案例&#xff1a;法律文书智能分析系统搭建 1. 引言 1.1 法律文书处理的现实挑战 在司法、律所和企业法务等场景中&#xff0c;每天都会产生大量PDF格式的法律文书&#xff0c;包括判决书、合同、起诉状、证据材料等。这些文档通常结构复杂&#xff0c;包…

核心要点:硬件I2C时序匹配工业设备的方法

硬件I2C如何“读懂”工业设备的节奏&#xff1f;—— 一场关于时序匹配的实战解析你有没有遇到过这样的情况&#xff1a;明明代码写得没问题&#xff0c;引脚也接对了&#xff0c;可I2C就是偶尔通信失败&#xff0c;甚至总线锁死&#xff1f;换根线就好了&#xff0c;或者把速度…

PDF-Extract-Kit数字签名:验证PDF文档真实性

PDF-Extract-Kit数字签名&#xff1a;验证PDF文档真实性 1. 引言&#xff1a;为何需要验证PDF文档的真实性&#xff1f; 在当今数字化办公和学术交流日益频繁的背景下&#xff0c;PDF文档已成为信息传递的核心载体。然而&#xff0c;随着伪造、篡改文档的风险不断上升&#x…

Multisim示波器使用:手把手教程(从零实现)

Multisim示波器实战指南&#xff1a;从零搭建电路到精准测波形你有没有过这样的经历&#xff1f;在实验室里&#xff0c;面对一台复杂的示波器&#xff0c;手忙脚乱地调了半天&#xff0c;结果波形还是抖个不停&#xff1b;或者刚接上探头&#xff0c;信号就变了样——这可能是…

Springboot3整合myBatisplus报错:Bean named ‘ddlApplicationRunner‘ is expected to be of type ‘org.sprin

1、错误&#xff1a; 我用Springboot3.1.7整合myBatisplus3.5.3时&#xff0c;出现下面的错误&#xff1a; Bean named ‘ddlApplicationRunner’ is expected to be of type ‘org.springframework.boot.Runner’ but was actually of type ‘org.springframework.beans.facto…

NVIDIA Profile Inspector完整使用指南:解锁显卡隐藏性能的终极教程

NVIDIA Profile Inspector完整使用指南&#xff1a;解锁显卡隐藏性能的终极教程 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector NVIDIA Profile Inspector是一款强大的显卡驱动配置工具&#xff0c;能够…

LeagueAkari完全攻略:英雄联盟玩家的智能助手终极指南

LeagueAkari完全攻略&#xff1a;英雄联盟玩家的智能助手终极指南 【免费下载链接】LeagueAkari ✨兴趣使然的&#xff0c;功能全面的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/LeagueAkari 还在为繁…

5分钟掌握LeagueAkari:英雄联盟终极智能辅助工具完全指南

5分钟掌握LeagueAkari&#xff1a;英雄联盟终极智能辅助工具完全指南 【免费下载链接】LeagueAkari ✨兴趣使然的&#xff0c;功能全面的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/LeagueAkari 还在为…

解锁显卡隐藏潜能:NVIDIA Profile Inspector超详细配置攻略

解锁显卡隐藏潜能&#xff1a;NVIDIA Profile Inspector超详细配置攻略 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector 还在为游戏卡顿、画面撕裂而烦恼吗&#xff1f;想要让老显卡焕发第二春&#xff…

PDF-Extract-Kit企业应用:人力资源文档自动化处理

PDF-Extract-Kit企业应用&#xff1a;人力资源文档自动化处理 1. 引言 1.1 业务场景与痛点分析 在现代企业的人力资源管理中&#xff0c;每天都会产生大量非结构化文档&#xff0c;包括简历、劳动合同、员工档案、绩效考核表、培训记录等。这些文档大多以PDF或扫描图片形式存…