4_ways_to_align_with_WebGL_fingerprinting_in_web_scraping

WebGL指纹是一种被反机器人系统广泛用于检测自动化活动的识别技术。它利用硬件层面的信号,因此难以绕过,也给依赖网页抓取开展业务的人带来了新的挑战。不过,仍有办法降低被识别的风险并采集数据,继续阅读即可了解具体做法。

什么是WebGL:详解

WebGL是Web Graphics Library的缩写,它是一个JavaScript API。浏览器借助它显示动态2D和3D图形,无需安装插件。为完成这些渲染任务,该API高度依赖GPU,即Graphics Processing Unit(图形处理单元)。如今的网站广泛使用WebGL来实现视觉效果、游戏和复杂的图形界面。事实上,WebGL 2正逐渐成为主流,因为它提供了更多功能。Chrome、Edge和Safari等现代浏览器对WebGL 2的支持率为95-99%,而较旧的浏览器和无头浏览器仍缺乏支持。

市面上的GPU多达数百种,即使同一制造商的不同型号之间也有很大差异,因此同一张图像的渲染结果也会不同。此外,浏览器、已安装的驱动程序,甚至硬件的差异,比如GPU与主板的连接方式,也会影响结果。这就好比走进一家家电商店,看到几十台展出的电视机,全都播放着同一则广告,但画面却略有不同。这正是WebGL指纹的基础。请注意,只有启用JavaScript时才能生成WebGL指纹,静态HTML页面无法执行相关操作。

WebGL指纹:分步概览

大致而言,整个过程如下:

  1. 网站创建一个<canvas>元素,该元素隐藏在页面的HTML中。它会初始化WebGL渲染环境。
  2. JavaScript通过调用gl.getParameter()提取GPU特征,例如供应商和型号。
  3. 指纹脚本让浏览器渲染一张测试图像,并使用readPixels()提取像素数据。

你可以访问 BrowserLeaks,点击WebGL Report,即可查看你的唯一指纹。效果如下:

下面是网站可能使用的脚本示例。你也可以将其用于测试目的,例如诊断、错误报告、兼容性测试等等。请记住,只在你自己拥有的设备上运行该脚本,或者在获得明确同意之后运行。请勿将该脚本用于追踪或识别匿名用户。请将结果视为敏感数据,未经许可不要与第三方分享。DataImpulse对该脚本的非法使用不承担责任。

步骤1:初始化WebGL环境


// Lightweight FNV-1a hash for strings/byte arrays (synchronous, simple)
function fnv1aHashFromBytes(bytes) {
  let h = 0x811c9dc5 >>> 0;
  for (let i = 0; i < bytes.length; i++) {
    h ^= bytes[i];
    // multiply by FNV prime (mod 2^32)
    h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
  }
  return ('00000000' + (h >>> 0).toString(16)).slice(-8);
}
function fnv1aHashFromString(s) {
  const bytes = new Uint8Array(s.length);
  for (let i = 0; i < s.length; i++) bytes[i] = s.charCodeAt(i) & 0xff;
  return fnv1aHashFromBytes(bytes);
}
// Create an (optionally hidden) canvas and return WebGLRenderingContext / WebGL2RenderingContext
function createWebGLContext(opts = {webgl2Preferred: true, preserveDrawingBuffer: false}) {
  const canvas = document.createElement('canvas');
  canvas.width = 256; canvas.height = 256;
  // optionally keep canvas off-DOM so it stays invisible
  canvas.style.display = 'none';
  document.body && document.body.appendChild(canvas); // append only if available
  let gl = null;
  if (opts.webgl2Preferred) {
    try { gl = canvas.getContext('webgl2', {preserveDrawingBuffer: opts.preserveDrawingBuffer}); } catch (e) {}
  }
  if (!gl) {
    try { gl = canvas.getContext('webgl', {preserveDrawingBuffer: opts.preserveDrawingBuffer}) || canvas.getContext('experimental-webgl'); } catch (e) {}
  }
  return {gl, canvas};
}

      
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
      

这段代码会创建一个在后台运行的隐藏画布,并设置其分辨率。此外,若不支持WebGL 2,它会平滑降级为WebGL。

步骤2:获取硬件信息


function getWebGLHardwareInfo(gl) {
  const info = {};
  // Standard parameters
  info.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
  info.maxCombinedTextureImageUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
  info.maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
  info.maxVertexUniformVectors = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);
  info.maxFragmentUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
  info.shaderPrecision = {
    vertexHighFloat: gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT),
    fragmentHighFloat: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT)
  };
  // Try to get unmasked renderer/vendor (if browser exposes WEBGL_debug_renderer_info)
  const dbg = gl.getExtension('WEBGL_debug_renderer_info');
  if (dbg) {
    info.unmaskedVendor = gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL);
    info.unmaskedRenderer = gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL);
  } else {
    info.unmaskedVendor = null;
    info.unmaskedRenderer = null;
  }
  // Extensions list (useful fingerprinting signal)
  info.extensions = gl.getSupportedExtensions() || [];
  // Anisotropy (if available)
  const extAniso = gl.getExtension('EXT_texture_filter_anisotropic') ||
                   gl.getExtension('MOZ_EXT_texture_filter_anisotropic') ||
                   gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic');
  if (extAniso) {
    const maxAniso = gl.getParameter(extAniso.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
    info.maxAnisotropy = maxAniso;
  }
  return info;
}

      
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        

        
      

它会收集GPU的详细信息,例如供应商、型号、内存大小、最大纹理尺寸、着色器uniform限制、支持的WebGL扩展以及其他数据。

步骤3:检测着色器精度


function probeShaderPrecisions(gl) {
  const precisions = {};
  const shaderTypes = [
    {type: gl.VERTEX_SHADER, name: 'vertex'},
    {type: gl.FRAGMENT_SHADER, name: 'fragment'}
  ];
  const levels = [{p: gl.LOW_FLOAT, name: 'low'}, {p: gl.MEDIUM_FLOAT, name: 'medium'}, {p: gl.HIGH_FLOAT, name: 'high'}];
  for (const sh of shaderTypes) {
    precisions[sh.name] = {};
    for (const lv of levels) {
      const pf = gl.getShaderPrecisionFormat(sh.type, lv.p);
      precisions[sh.name][lv.name] = {rangeMin: pf.rangeMin, rangeMax: pf.rangeMax, precision: pf.precision};
    }
  }
  return precisions;
}

      
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
      

随后,网站会查询GPU对浮点运算和片段着色器的精度支持级别,并记录相应的数值范围。

脚本收集完硬件和软件信息后,会进入下一步:图像渲染。

步骤4:渲染测试场景


// Simple shader pair: position-only vertex shader and color-fragment that depends on coords
const VERT = `#version 300 es
in vec2 a_pos;
out vec2 v_uv;
void main() {
  v_uv = a_pos * 0.5 + 0.5;
  gl_Position = vec4(a_pos, 0.0, 1.0);
}`;
const FRAG = `#version 300 es
precision highp float;
in vec2 v_uv;
out vec4 outColor;
void main() {
  // deterministic function of coordinates that will exercise GPU math
  float r = sin(v_uv.x * 123.456) * 0.5 + 0.5;
  float g = cos(v_uv.y * 78.9) * 0.5 + 0.5;
  float b = fract((v_uv.x + v_uv.y) * 9876.54321);
  outColor = vec4(r, g, b, 1.0);
}`;

function createProgramWebGL2(gl, vertSrc, fragSrc) {
  function compile(src, type) {
    const s = gl.createShader(type);
    gl.shaderSource(s, src);
    gl.compileShader(s);
    if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
      const err = gl.getShaderInfoLog(s);
      gl.deleteShader(s);
      throw new Error('Shader compile error: ' + err);
    }
    return s;
  }
  const vs = compile(vertSrc, gl.VERTEX_SHADER);
  const fs = compile(fragSrc, gl.FRAGMENT_SHADER);
  const program = gl.createProgram();
  gl.attachShader(program, vs);
  gl.attachShader(program, fs);
  gl.linkProgram(program);
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    const err = gl.getProgramInfoLog(program);
    throw new Error('Program link error: ' + err);
  }
  gl.deleteShader(vs);
  gl.deleteShader(fs);
  return program;
}

function renderTestAndHash(gl, canvas) {
  const isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext;
  if (!isWebGL2) {
    // For WebGL1 we'd need slightly different shaders (no #version, use attribute/varying)
    // but the concept is the same. For clarity this example uses WebGL2 if available.
    console.warn('Example uses WebGL2 for deterministic shader pipeline; falling back may require shader edits.');
  }

  // Create program & buffer
  const prog = createProgramWebGL2(gl, VERT, FRAG);
  gl.useProgram(prog);
  const posLoc = gl.getAttribLocation(prog, 'a_pos');

  const quad = new Float32Array([
    -1, -1,
     1, -1,
    -1,  1,
     1,  1
  ]);
  const vb = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vb);
  gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
  gl.enableVertexAttribArray(posLoc);
  gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

  // Render to the canvas
  gl.viewport(0, 0, canvas.width, canvas.height);
  gl.clearColor(0,0,0,1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  const t0 = performance.now();
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  gl.finish(); // ensure rendering completes
  const t1 = performance.now();

  const renderTime = t1 - t0; // render timing in ms

  // Read pixels
  const px = new Uint8Array(canvas.width * canvas.height * 4);
  gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, px);

  const pixelHash = fnv1aHashFromBytes(px);

  // Clean up (optional)
  gl.deleteBuffer(vb);
  gl.deleteProgram(prog);

  return {pixelHash, renderTime};
}

      
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        

        
      

脚本会让浏览器显示一个2D或3D场景,其中可能包含特定的阴影、渐变或效果。随后,它读取像素字节,获取像素颜色、图像纹理、着色和渐变等信息,计算这些数值的哈希,并测量渲染时间。

步骤5:将数据汇总为指纹


function collectWebGLFingerprint(opts = {}) {
  const {gl, canvas} = createWebGLContext({webgl2Preferred: true, preserveDrawingBuffer: true});
  if (!gl) throw new Error('No WebGL available');

  const hw = getWebGLHardwareInfo(gl);
  const precisions = probeShaderPrecisions(gl);

  // optional: probe more capabilities
  const supportsFloatTextures = !!gl.getExtension('OES_texture_float') || !!gl.getExtension('EXT_color_buffer_float');

  // Render test and get pixel hash + timing
  const {pixelHash, renderTime} = renderTestAndHash(gl, canvas);

  // Combine into a fingerprint string
  const parts = [
    navigator.userAgent || '',
    hw.unmaskedVendor || '',
    hw.unmaskedRenderer || '',
    hw.maxTextureSize,
    hw.maxCombinedTextureImageUnits,
    hw.maxVertexAttribs,
    supportsFloatTextures ? 'floatTex' : '',
    hw.extensions ? hw.extensions.join(',') : '',
    JSON.stringify(precisions),
    pixelHash,
    String(Math.round(renderTime)) // include rounded render time
  ];
  const raw = parts.join('|');
  const fingerprint = fnv1aHashFromString(raw);

  // Return a readable object
  return {
    fingerprint,
    raw,
    components: {
      ua: navigator.userAgent,
      hardware: hw,
      precisions,
      pixelHash,
      renderTime
    }
  };
}

// Example usage:
try {
  const fp = collectWebGLFingerprint();
  console.log('WebGL fingerprint:', fp.fingerprint);
  console.log('Components:', fp.components);
} catch (e) {
  console.error('Could not collect WebGL fingerprint:', e);
}

      
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

最后,脚本汇总前述步骤获得的全部数据,将其组合成一个长字符串并再次计算哈希,从而生成更紧凑、更实用的指纹ID。

整个过程在毫秒之内完成,且对你不可见。采集到指纹后,网站会将其存储并与内部数据库比对,或发送给反机器人服务分析。如果某个指纹与已知的机器人或可疑用户匹配,你就有被封禁的风险。

WebGL伪装的挑战

要让流量特征显得自然并不容易,原因如下。

  1. GPU渲染行为的差异对人眼来说可能不可见,但对网站而言却显而易见,而且它们在不同页面之间保持一致。清除cookie或使用无痕模式都无济于事。
  2. 渲染依赖物理硬件提供的数据,而这些数据不在JavaScript修改或浏览器扩展能够影响的范围内。
  3. 你可以在每次加载页面时修改WebGL参数;但如果你声称GPU是Intel,实际渲染表现却像NVIDIA,网站就会发现这种不一致,足以触发反机器人系统。
  4. 大多数现代检测系统都会捕捉到篡改WebGL API的企图。

这是否意味着WebGL指纹无法绕过?并非如此,有几种技术确实有效。不过,先明确一个前提。

WebGL指纹伪装合法吗?

你有权保护隐私,因此不能一概认为伪装WebGL指纹违法。它也可用于测试。但若借助伪装实施欺诈或违反网站服务条款,则可能违法。你应当始终阅读服务条款,并避免任何可能损害他人隐私的违法行为。 以下信息仅供教育目的提供。DataImpulse不鼓励你采取任何行动,也不对可能的后果承担责任。

伪装WebGL指纹的有效方法

诸如 Playwright 或Selenium之类的流行工具,借助Playwright Stealth或Undetected Chrome Driver等工具,可以略微修改屏幕尺寸等参数。不过,你需要小心,以确保伪造的变量和渲染出的图像彼此匹配。这种方法不适用于大规模项目,也难以抓取采用最新机器人检测算法的网站。WebGL Fingerprint Defender或CanvasBlocker之类的浏览器扩展也是如此。它们拦截WebGL API调用并返回经过篡改的数据,但渲染出的图像仍可能与之不匹配。

  • 选择GPU虚拟化

你可以让浏览器呈现的GPU和/或驱动程序看起来不同于真实的物理GPU。你可以采用几种方法,例如,用Switch Shader之类的软件渲染器替换真实的GPU,或使用配备各种GPU驱动程序的虚拟机。不过,如果预算紧张或技术基础不足,采用这些方法前请三思。此外,要扩展抓取项目也颇具挑战。

  • 请求静态API

如果你抓取公开数据,且目标网站提供返回JSON的公开或未公开API,这种方法便可行。对于受保护的动态网站,此方法不可行;但在其他情况下值得考虑,因为API端点通常防护较弱。

  • 禁用WebGL

禁用WebGL会阻断所有主要的WebGL指纹信号(供应商/渲染器、扩展、精度、像素哈希、渲染时长)。

如何禁用WebGL

Chrome

方法1 – 通过Chrome Flags:

在地址栏中输入 chrome://flags,按Enter键,然后选择 “Disable WebGL”–disable-webgl。将该标志设置为”Disabled”,然后重新启动Chrome以使更改生效。

方法2 – 通过命令行:

右键点击Google Chrome图标,进入”属性”。查找 “目标” 这一行。该行通常以类似 chrome.exe. 的内容结尾。在该行的最末尾,添加一个空格,然后输入 –disable-webgl。点击”应用” > “确定”。

Firefox

在地址栏中输入 about:config 并按Enter键。如果出现警告,接受即可。查找 webgl.disable。双击并将其值设置为true。

Safari

打开Safari偏好设置,进入”高级”选项卡,然后选择”开发“(如果可用)。在实验性功能中找到WebGL via Metal选项。取消勾选该复选框,然后重新启动浏览器。

不过要谨慎,因为禁用WebGL后,许多网站可能无法正常运行。

利用WebGL指纹的漏洞

你还可以利用一些小技巧。例如,脚本常会缓存结果并重复使用,而非从头计算。你可以检测这一行为,然后在缓存结果被复用前改变环境,以避免与先前记录的身份关联,或迫使网站重新计算该值。

检测器:


// Helper: run the fingerprinting routine and return {time, result}
async function runFingerprintTrial(triggerFn) {
  const t0 = performance.now();
  const result = await triggerFn(); // should return fingerprint/hash if available
  const time = performance.now() - t0;
  return { time, result };
}

async function detectCachingRobust(triggerFingerprinting, trials = 5) {
  const runs = [];
  for (let i = 0; i < trials; i++) {
    // small delay to reduce micro-scheduling bias
    if (i > 0) await new Promise(r => setTimeout(r, 30));
    runs.push(await runFingerprintTrial(triggerFingerprinting));
  }

  // compute median time
  const times = runs.map(r => r.time).sort((a,b) => a-b);
  const medianTime = times[Math.floor(times.length/2)];

  // compare first vs median of later runs
  const firstTime = runs[0].time;
  const laterMedian = times.slice(1).length ? times.slice(1)[Math.floor((times.length-1)/2)] : medianTime;

  // check result stability (if trigger returns a fingerprint/hash)
  const results = runs.map(r => r.result);
  const allSame = results.every(x => x === results[0]);

  // simple heuristic: if first run is much slower *and* results identical, likely cached
  const speedRatio = firstTime / (laterMedian || 1);
  const likelyCached = (speedRatio > 5) && allSame;

  return {
    runs,
    medianTime,
    firstTime,
    laterMedian,
    speedRatio,
    results,
    allSame,
    likelyCached
  };
}

      
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        

        
        
        
        
        

        
        
        
        
        
      

强制使用全新环境:


function newWebGLContext(w=256,h=256) {
  const c = document.createElement('canvas');
  c.width = w; c.height = h;
  const gl = c.getContext('webgl2') || c.getContext('webgl') || c.getContext('experimental-webgl');
  return {c, gl};
}

      
        
        
        
        
        
        
      

强制重新计算:


localStorage.removeItem('fp_token'); // only if you can identify it
// or flush likely storage (be careful: may break site)
localStorage.clear();
indexedDB.deleteDatabase('site-db-name'); // needs precise names

      
        
        
        
        
      

另一种方法是检测页面何时添加指纹脚本,并在此前运行伪装脚本。不过,这种做法很容易被检测到,也可能干扰页面行为,更适合测试或防御用途。


// Robust script interception (best run at document_start)
(function() {
  if (window.__scriptInterceptorInstalled) return;
  window.__scriptInterceptorInstalled = true;

  // Your spoofing routine — define this to patch WebGL or other APIs.
  function injectSpoofing() {
    try {
      // Example: block debug info (replace with your real routine)
      const origGetExt = WebGLRenderingContext.prototype.getExtension;
      WebGLRenderingContext.prototype.getExtension = function(name) {
        if (name === 'WEBGL_debug_renderer_info') return null;
        return origGetExt.call(this, name);
      };
      // mark that spoofing ran
      window.__spoofingInjected = true;
    } catch (e) {
      // swallow errors — don't break page
      console.error('injectSpoofing error', e);
    }
  }

  // Helper: decide whether a script looks like fingerprinting (tweak to match)
  function looksLikeFingerprintScript(srcOrContent) {
    if (!srcOrContent) return false;
    const s = String(srcOrContent).toLowerCase();
    return s.includes('fingerprint') || s.includes('fingerprinting') || s.includes('fingerprintjs') || s.includes('fingerprint2');
  }

  // 1) Hook HTMLScriptElement.prototype.src property (setter)
  try {
    const proto = HTMLScriptElement.prototype;
    const srcDesc = Object.getOwnPropertyDescriptor(proto, 'src');
    if (srcDesc && srcDesc.configurable) {
      Object.defineProperty(proto, 'src', {
        configurable: true,
        enumerable: srcDesc.enumerable,
        get: function() { return srcDesc.get.call(this); },
        set: function(val) {
          try {
            // If the script's src looks suspicious, inject before setting
            if (looksLikeFingerprintScript(val) && !window.__spoofingInjected) {
              injectSpoofing();
            }
          } catch (e) { /* ignore */ }
          return srcDesc.set.call(this, val);
        }
      });
    }
  } catch (e) {
    // non-fatal
  }

  // 2) Hook setAttribute on script elements (covers element.setAttribute('src', ...))
  try {
    const origSetAttr = HTMLScriptElement.prototype.setAttribute;
    HTMLScriptElement.prototype.setAttribute = function(name, value) {
      try {
        if ((name + '').toLowerCase() === 'src' && looksLikeFingerprintScript(value) && !window.__spoofingInjected) {
          injectSpoofing();
        }
      } catch (e) {}
      return origSetAttr.call(this, name, value);
    };
  } catch (e) {}

  // 3) As a fallback, monitor append/insert to catch scripts that already had src set
  try {
    const origAppend = Node.prototype.appendChild;
    Node.prototype.appendChild = function(node) {
      try {
        if (node && node.tagName && node.tagName.toLowerCase() === 'script') {
          const src = node.src || node.getAttribute && node.getAttribute('src');
          if (looksLikeFingerprintScript(src) && !window.__spoofingInjected) {
            injectSpoofing();
          }
        }
      } catch (e) {}
      return origAppend.call(this, node);
    };

    const origInsertBefore = Node.prototype.insertBefore;
    Node.prototype.insertBefore = function(node, ref) {
      try {
        if (node && node.tagName && node.tagName.toLowerCase() === 'script') {
          const src = node.src || node.getAttribute && node.getAttribute('src');
          if (looksLikeFingerprintScript(src) && !window.__spoofingInjected) {
            injectSpoofing();
          }
        }
      } catch (e) {}
      return origInsertBefore.call(this, node, ref);
    };
  } catch (e) {}

  // 4) Small DOM observer as an extra safety net (lightweight)
  try {
    const obs = new MutationObserver((mutations) => {
      for (const m of mutations) {
        for (const n of m.addedNodes || []) {
          try {
            if (n && n.tagName && n.tagName.toLowerCase() === 'script') {
              const src = n.src || (n.getAttribute && n.getAttribute('src')) || n.textContent;
              if (looksLikeFingerprintScript(src) && !window.__spoofingInjected) {
                injectSpoofing();
                obs.disconnect();
                return;
              }
            }
          } catch (e) {}
        }
      }
    });
    obs.observe(document, { childList: true, subtree: true });
  } catch (e) {}

  // If the page already has script tags at start, scan them now
  try {
    const scripts = document.getElementsByTagName('script');
    for (let i = 0; i < scripts.length; i++) {
      const s = scripts[i];
      const src = s.src || s.getAttribute && s.getAttribute('src') || s.textContent;
      if (looksLikeFingerprintScript(src) && !window.__spoofingInjected) {
        injectSpoofing();
        break;
      }
    }
  } catch (e) {}
})();

      
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        

        
        
      

此外,不同页面的WebGL环境彼此隔离。你可以创建相互隔离的上下文,防止网站跨页面匹配你的指纹。

WebGL与Canvas

WebGL指纹和Canvas指纹常被混为一谈,二者确实密切相关,但并不完全相同。

Canvas指纹会分析浏览器如何通过HTML5 Canvas渲染2D图形。渲染结果主要取决于浏览器的图形栈和渲染引擎。因此,它更容易被伪装。

WebGL指纹会分析浏览器如何渲染2D和3D图形。渲染结果取决于硬件特性,因此该方法可提供更多信息,也更难绕过。

总结

了解WebGL指纹伪装的挑战后,你或许会觉得十分钟前一无所知时反而更轻松。然而,你的目标并不是隐身,而是让自己看起来像一个”普通用户”。为此,你的指纹需要真实可信,在同一会话内保持一致,并在不同会话之间有所变化。这完全可以做到。另一方面,只有从各个层面维持匿名性,并结合运用多账号浏览器, TLS伪装解决方案, 代理等工具,才能成功进行网页抓取。这些工具和方法相辅相成,可降低被发现的风险。DataImpulse提供有助于保护隐私和匿名性的方案:合法获取且已列入白名单的代理,可满足各种抓取需求,并提供7天24小时人工支持,让你可以持续采集数据。点击“立即试用”,或通过[email protected]联系我们即可开始。

Share article: