You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅用JavaScript统计Web请求性能数据,对比采集方案差异?

实现GTM容器请求性能数据采集方案

核心实现思路

通过Performance API监听资源加载性能数据,同时拦截fetch和XMLHttpRequest请求补充细节,最终将整理后的请求数据推送到dataLayer,用于对比两个GTM容器的性能差异。

完整代码实现

// 存储请求数据的数组
const requestData = [];

// 1. 使用PerformanceObserver捕获资源加载数据
const performanceObserver = new PerformanceObserver((entryList) => {
  entryList.getEntries().forEach(entry => {
    // 过滤仅保留目标GTM容器相关请求(替换为你的容器ID,比如GTM-XXXXXX、GTM-YYYYYY)
    const targetUrls = ['GTM-XXXXXX', 'GTM-YYYYYY'];
    const isTargetRequest = targetUrls.some(id => entry.name.includes(id)) || 
                           entry.name.includes('gtm.js') || 
                           entry.name.includes('google-analytics');
    if (!isTargetRequest) return;

    // 判断是否从缓存加载
    const isCached = entry.transferSize <= 1024 || (entry.fromMemoryCache && entry.initiatorType === 'script');

    // 简化URL,提取GTM容器ID
    let shortenedUrl = entry.name;
    const gtmIdMatch = entry.name.match(/GTM-[A-Z0-9]+/);
    if (gtmIdMatch) shortenedUrl = gtmIdMatch[0];

    // 统一请求类型命名
    let requestType = entry.initiatorType;
    if (requestType === 'script') requestType = 'javascript';
    else if (requestType === 'img') requestType = 'image';

    // 计算请求耗时(响应完成时间 - 请求开始时间)
    const requestTime = Math.round(entry.responseEnd - entry.requestStart);

    // 存入数据数组
    requestData.push({
      request_shortened_url: shortenedUrl,
      request_size: entry.transferSize, // 传输总大小(字节,含响应头)
      request_time: requestTime,
      request_type: requestType,
      request_cached: isCached
    });
  });
});

// 订阅资源类型的性能条目
performanceObserver.observe({ entryTypes: ['resource'] });

// 2. 拦截fetch请求,补充捕获动态发送的追踪请求
const originalFetch = window.fetch;
window.fetch = async (...args) => {
  const requestUrl = args[0] instanceof Request ? args[0].url : args[0];
  const targetUrls = ['GTM-XXXXXX', 'GTM-YYYYYY'];
  if (!targetUrls.some(id => requestUrl.includes(id)) && !requestUrl.includes('google-analytics')) {
    return originalFetch(...args);
  }

  const startTime = performance.now();
  const response = await originalFetch(...args);
  const endTime = performance.now();

  // 获取响应大小
  const contentLength = response.headers.get('content-length');
  const requestSize = contentLength ? parseInt(contentLength, 10) : 0;

  // 判断缓存状态
  const isCached = response.headers.get('x-cache') === 'HIT' || (response.type === 'opaque' && requestSize === 0);

  // 简化URL
  let shortenedUrl = requestUrl;
  const gtmIdMatch = requestUrl.match(/GTM-[A-Z0-9]+/);
  if (gtmIdMatch) shortenedUrl = gtmIdMatch[0];

  requestData.push({
    request_shortened_url: shortenedUrl,
    request_size: requestSize,
    request_time: Math.round(endTime - startTime),
    request_type: 'fetch',
    request_cached: isCached
  });

  return response;
};

// 3. 拦截XMLHttpRequest请求,兼容旧式动态请求
const originalXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function() {
  const xhr = new originalXHR();
  let startTime;

  xhr.addEventListener('loadstart', () => {
    startTime = performance.now();
  });

  xhr.addEventListener('load', () => {
    const requestUrl = xhr.responseURL;
    const targetUrls = ['GTM-XXXXXX', 'GTM-YYYYYY'];
    if (!targetUrls.some(id => requestUrl.includes(id)) && !requestUrl.includes('google-analytics')) return;

    const endTime = performance.now();
    const contentLength = xhr.getResponseHeader('content-length');
    const requestSize = contentLength ? parseInt(contentLength, 10) : 0;
    const isCached = xhr.getResponseHeader('x-cache') === 'HIT';

    let shortenedUrl = requestUrl;
    const gtmIdMatch = requestUrl.match(/GTM-[A-Z0-9]+/);
    if (gtmIdMatch) shortenedUrl = gtmIdMatch[0];

    requestData.push({
      request_shortened_url: shortenedUrl,
      request_size: requestSize,
      request_time: Math.round(endTime - startTime),
      request_type: 'xmlhttprequest',
      request_cached: isCached
    });
  });

  return xhr;
};

// 4. 页面加载完成后,去重并推送数据到dataLayer
window.addEventListener('load', () => {
  // 去重:避免PerformanceObserver和拦截器重复捕获同一请求
  const uniqueRequests = [...new Map(requestData.map(item => 
    [`${item.request_shortened_url}-${item.request_size}`, item]
  )).values()];

  window.dataLayer = window.dataLayer || [];
  dataLayer.push({
    'event': 'customEvent',
    'event_type': 'global_performance',
    'request_list': uniqueRequests
  });
});

关键细节说明

  • 请求过滤:代码中targetUrls变量可替换为你的两个GTM容器ID,确保只采集目标请求,排除无关资源干扰。
  • 缓存判断:结合transferSize(缓存资源传输极小)、浏览器私有属性fromMemoryCache(Chrome)和响应头x-cache(CDN缓存标记)多维度判断,提升准确性。
  • 数据去重:通过URL-大小作为唯一键去重,避免同一请求被PerformanceObserver和fetch/XHR拦截器重复捕获。
  • 耗时计算:Performance API使用responseEnd - requestStart,拦截器使用performance.now()计算起止时间,确保数据一致性。

后续分析建议

  1. 在GTM中创建触发器监听customEvent事件,将request_list中的数据发送到你的分析工具(如内部数据平台、BigQuery)。
  2. 聚合单页请求大小差异,再乘以3亿年会话量计算总数据量差距:单页差异(KB) * 300,000,000 / 1024 / 1024 = TB级差距。

内容的提问来源于stack exchange,提问作者Olivier Pallatier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 14:46:02