资讯动态

puppeteer-extra 反检测插件源码解析:chrome.csi 计时 API 伪装(evasion)的原理与实战

发布时间:2026/10/18 2:28:43 来源:尧图企业网站定制
网页爬虫浏览器控制测试【免费下载链接】puppeteer-extra Teach puppeteer new tricks through plugins.项目地址https://gitcode.com/gh_mirrors/pu/puppeteer-extra点击查看免费下载导读puppeteer-extra-plugin-stealth是 puppeteer-extra 生态中用于降低无头浏览器被识别概率的官方插件其内部按功能拆分为一个个独立的 evasion反检测模块。本文聚焦其中的chrome.csievasion一个专门在无头环境下伪装已废弃的 Chrome 专用计时 APIchrome.csi()的模块。通过阅读本文你将理解该模块为什么存在、如何用 Web Performance API 复刻一个功能完整且不留痕迹的 mock、它如何接入 stealth 主插件并被测试验证以及它与姊妹模块chrome.loadTimes之间的关联。chrome.csi一个已被废弃、却依然被检测的浏览器计时 API在真实的 Chrome 浏览器中window.chrome.csi()是一个 Chrome 特有的非标准函数用于向页面脚本返回一组与页面加载时间相关的数据。它在功能上类似于performance.timing属于 Chrome Speed Index 的一部分但早已被标记为废弃。然而正如 关联文档 中所强调的这是一个deprecated (but unfortunately still existing)已废弃但很不幸仍然存在的 API。正因为它仍然存在无头浏览器中缺失该 API 就会成为可被站点脚本识别的指纹破绽所以 stealth 插件需要把它补上。值得注意的是 Chromium 内部早已切换为基于 Web Performance API 的实现因此 evasion 采用了同样的思路Internally chromium switched the implementation to use the WebPerformance API, so we can do the same to create a fully functional mock——即用标准 Web 性能接口构造一个与真实 Chrome 行为一致的全功能 mock。插件定位stealth 默认启用列表中的一员chrome.csi是 stealth 主插件默认启用的一系列 evasion 之一。在 主插件入口 的availableEvasions集合中与它并列的还有chrome.app、chrome.loadTimes、chrome.runtime、navigator.webdriver、navigator.plugins、user-agent-override等共 16 个模块。const availableEvasions new Set([ chrome.app, chrome.csi, // ← 本文主角 chrome.loadTimes, chrome.runtime, defaultArgs, // ... 其余 evasion 模块 ])因此使用puppeteer.use(require(puppeteer-extra-plugin-stealth)())时chrome.csi会被自动加载。也可以通过 stealth 插件的enabledEvasions集合主插件入口单独增删例如pluginStealth.enabledEvasions.delete(chrome.csi)即可在整套 stealth 中排除它。如果你只想单独使用这一个模块也可以像 stealth 主插件文档描述的那样直接按路径 require 该独立插件const puppeteer require(puppeteer-extra) puppeteer.use(require(puppeteer-extra-plugin-stealth/evasions/chrome.csi)())源码实现逐段解析chrome.csievasion 的全部核心逻辑位于 index.js仅有 70 余行。它继承自puppeteer-extra-plugin包导出的PuppeteerExtraPlugin基类插件名固定为stealth/evasions/chrome.csi构造器接受可选的opts默认{}与文档中 opts(optional, default{}) 的描述一致。1. 挂载时机onPageCreated evaluateOnNewDocumentasync onPageCreated(page) { await withUtils(page).evaluateOnNewDocument(utils { // ... 注入逻辑 }) }插件通过onPageCreated钩子接入 puppeteer-extra 的插件生命周期。从 插件基类实现 可以看到基类在浏览器每次targetcreated且 target 类型为 page时调用onPageCreated(page)。而evaluateOnNewDocument保证注入脚本在每一个新文档创建时、任何页面脚本执行之前运行这是 stealth 类插件的标准注入方式。2. 兜底创建 window.chrome还原真实属性描述符if (!window.chrome) { // Use the exact property descriptor found in headful Chrome // fetch it via Object.getOwnPropertyDescriptor(window, chrome) Object.defineProperty(window, chrome, { writable: true, enumerable: true, configurable: false, // note! value: {} // Well extend that later }) }无头模式下window.chrome本身可能不存在。源码注释明确说明这里还原的是真实headfulChrome 中通过Object.getOwnPropertyDescriptor(window, chrome)拿到的精确属性描述符——writable: true、enumerable: true、configurable: false。特别是configurable: false这一细节保证站点脚本即便尝试delete window.chrome或重定义该属性也无法得手与真实 Chrome 行为保持一致。同样的兜底写法也出现在chrome.app、chrome.loadTimes、chrome.runtime等姊妹 evasion 中参见 chrome.app/index.js是 stealth 系列处理chrome命名空间的通用模式。3. 有头模式短路已存在即不干扰// That means were running headful and dont need to mock anything if (csi in window.chrome) { return // Nothing to do here }如果csi已经存在于window.chrome上说明当前运行的是有头headfulChrome原生 API 已经可用插件立即返回、不做任何改动。这是 stealth 各 evasion 共同的最小干预原则只在缺失时才补。4. 前置条件检查依赖 Navigation Timing v1// Check that the Navigation Timing API v1 is available, we need that if (!window.performance || !window.performance.timing) { return } const { timing } window.performancemock 的数据全部来自window.performance.timingNavigation Timing Level 1 / PerformanceTiming API。若该 API 不可用则放弃注入保证脚本在异常环境下也不会抛错。5. 核心 mock用 PerformanceTiming 拼装 csi() 的返回值window.chrome.csi function() { return { onloadT: timing.domContentLoadedEventEnd, startE: timing.navigationStart, pageT: Date.now() - timing.navigationStart, tran: 15 // Transition type or something } } utils.patchToString(window.chrome.csi)注入的chrome.csi()返回四个字段与真实 Chrome 的返回值一一对应字段取值来源语义onloadTtiming.domContentLoadedEventEndDOMContentLoaded 事件结束时间戳代表页面内容加载完成的时刻startEtiming.navigationStart导航开始的时间戳pageTDate.now() - timing.navigationStart从导航开始到当前时刻的页面已运行时长毫秒tran15硬编码过渡类型transition type源码注释 Transition type or something 表明这是一个按真实行为取值的常量这四个字段全部基于同一份performance.timing快照计算因此各字段之间的相对关系与真实 Chrome 完全一致属于功能完整fully functional的 mock而不是一个只填充假数字的占位对象。6. patchToString让 mock 函数看起来是原生代码最后一行utils.patchToString(window.chrome.csi)是 stealth 模块的防指纹关键一步。默认情况下页面脚本可以通过chrome.csi.toString()的返回值区分原生函数返回function () { [native code] }与 JS 定义的函数返回完整源码从而识破 mock。patchToString定义在 共享工具库 中其原理是用utils.preloadCache()预先缓存Function.toString的原生字符串function toString() { [native code] }防止后续代理被反制通过utils.makeNativeString(name)把该字符串中的toString替换成目标函数名生成形如function csi() { [native code] }的伪造结果用Proxy包裹Function.prototype.toString当toString以被 mock 的对象为上下文ctx obj时返回伪造串其余情况放行给原生实现整个 Proxy 再经utils.stripProxyFromErrors包裹避免 Proxy 自身在错误堆栈中暴露参见 共享工具库。7. 注入管道的底层机制withUtilswithUtils(page).evaluateOnNewDocument(...)来自 withUtils.js。由于浏览器上下文无法直接使用 Node 端定义的函数对象它先把_utils中的工具函数通过utils.stringifyFns序列化为字符串与主函数字符串、参数一起作为原始数据传入page.evaluateOnNewDocument在浏览器端再用eval还原出可用的utils对象并调用utils.init()即预加载原生函数缓存。这保证了上面提到的patchToString、makeNativeString等工具能在页面上下文中正常工作这也是所有 stealth evasion 共用同一套工具库的原因。测试验证行为与指纹双断言配套的 index.test.js 用 ava 框架验证了 mock 的功能正确性与原生感test(stealth: will add functional chrome.csi function mock, async t { const puppeteer addExtra(vanillaPuppeteer).use( Plugin({ runOnInsecureOrigins: true /* for testing */ }) ) const browser await puppeteer.launch({ headless: true }) const page await browser.newPage() const results await page.evaluate(() { const { timing } window.performance const csi window.chrome.csi() return { csi: { exists: window.chrome csi in window.chrome, toString: chrome.csi.toString() }, dataOK: { onloadT: csi.onloadT timing.domContentLoadedEventEnd, startE: csi.startE timing.navigationStart, pageT: Number.isInteger(csi.pageT), tran: Number.isInteger(csi.tran) } } }) t.deepEqual(results, { csi: { exists: true, toString: function () { [native code] } }, dataOK: { onloadT: true, pageT: true, startE: true, tran: true } }) })测试断言了三个关键点存在性csi in window.chrome为true即无头环境下缺失的 API 被补齐原生感chrome.csi.toString()返回function () { [native code] }证明patchToString生效mock 在指纹层面与原生函数无异数据正确性onloadT与startE分别精确等于performance.timing的对应字段pageT与tran均为整数pageT为毫秒差tran为硬编码常量。测试中传入的runOnInsecureOrigins: true仅在测试场景使用注释标明 for testing。从 chrome.csi/index.js 的构造器看该插件本身并未像 chrome.runtime/index.js 那样定义runOnInsecureOrigins之类的默认选项后者会在非 https 来源上跳过注入因此该选项对chrome.csi的注入分支没有实际影响——这一点从源码结构可以推断。姊妹模块chrome.loadTimeschrome.csi与chrome.loadTimes是 stealth 中一对兄弟 evasion两者互相引用原文档中以 See:chrome.loadTimesevasion 相互标注。chrome.loadTimes/index.js 伪装的是另一个已废弃的 Chrome 专用计时 API但实现要复杂得多返回值分为protocolInfoconnectionInfo、npnNegotiatedProtocol、navigationType、wasFetchedViaSpdy等连接信息从performance.getEntriesByType(navigation)[0].nextHopProtocol推导 HTTP/2、QUIC 等协议与timingInforequestTime、startLoadTime、commitLoadTime、finishDocumentLoadTime、finishLoadTime、firstPaintTime等全部由performance.timing换算为秒两组字段依赖window.PerformancePaintTimingNavigation Timing v2 / Paint Timing作为额外前置条件对about:blank等未发生真实导航的页面提供ntEntryFallback兜底数据。两个模块共享同一套设计哲学在已存在则不动手、缺失则用标准性能 API 复刻的前提下尽可能把返回值的字段语义、数值关系和 toString 指纹都还原到位。若站点脚本同时探测chrome.csi与chrome.loadTimes二者需要一起启用才能保持一致这也是 stealth 默认全部启用这两个模块的原因。实战快速验证 chrome.csi 是否生效安装依赖后参考 stealth 插件文档 中的安装方式你可以用下面的脚本在无头模式下自行验证const puppeteer require(puppeteer-extra) const StealthPlugin require(puppeteer-extra-plugin-stealth) puppeteer.use(StealthPlugin()) ;(async () { const browser await puppeteer.launch({ headless: true }) const page await browser.newPage() await page.goto(https://example.com) const result await page.evaluate(() { if (!window.chrome || !(csi in window.chrome)) { return { exists: false } } const csi window.chrome.csi() return { exists: true, toString: window.chrome.csi.toString(), csi } }) console.log(result) // 预期输出: // { exists: true, // toString: function () { [native code] }, // csi: { onloadT: ..., startE: ..., pageT: ..., tran: 15 } } await browser.close() })()若输出中toString为function () { [native code] }且四个字段均存在说明 mock 已成功注入并伪装到位。局限与边界以下几点需要在实战中注意均基于仓库源码与文档可以确认的事实tran字段是硬编码常量15源码注释以 Transition type or something 的措辞表明这是对真实行为的经验性复刻而非从性能 API 推导而来该 evasion 的目标是补齐缺失的 API如果运行环境本身是有头 Chromecsi in window.chrome为真模块会直接短路不会对原生 API 做任何改动反检测是持续的猫鼠游戏单个 evasion 只解决一个指纹面实际部署时应配合 stealth 的完整 evasion 集合使用并留意 stealth 主插件文档中关于检测现状的说明stealth 插件文档。延伸阅读主插件入口与 evasion 清单puppeteer-extra-plugin-stealth/index.js共享工具库patchToString、makeNativeString、stripProxyFromErrorsevasions/_utils/index.js注入包装器_utils/withUtils.js姊妹模块文档与实现evasions/chrome.loadTimes/readme.md、evasions/chrome.loadTimes/index.js测试基座vanillaPuppeteer、addExtratest/util.js原文档API 说明与参考资料列表evasions/chrome.csi/readme.md赞分享网页爬虫浏览器控制测试【免费下载链接】puppeteer-extra Teach puppeteer new tricks through plugins.项目地址https://gitcode.com/gh_mirrors/pu/puppeteer-extra点击查看免费下载相关推荐puppeteer-extra-plugin-stealth 反检测插件解析navigator.hardwareConcurrency 硬件并发数伪装原理与实战puppeteer extra plugin stealth 反检测插件解析navigator.hardwareConcurrency 硬件并发数伪装原理与实网页爬虫浏览器控制测试puppeteer-extra 插件解析chrome.loadTimes 反检测插件原理与实战puppeteer extra 插件解析chrome.loadTimes 反检测插件原理与实战 chrome.loadTimes 是 Chrome 专属的浏览网页爬虫浏览器控制测试puppeteer-extra-plugin-stealth 的 navigator.permissions 反检测插件原理、源码与测试全解析puppeteer extra plugin stealth 的 navigator.permissions 反检测插件原理、源码与测试全解析 在 puppe网页爬虫浏览器控制测试创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑