如何仅用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()计算起止时间,确保数据一致性。
后续分析建议
- 在GTM中创建触发器监听
customEvent事件,将request_list中的数据发送到你的分析工具(如内部数据平台、BigQuery)。 - 聚合单页请求大小差异,再乘以3亿年会话量计算总数据量差距:
单页差异(KB) * 300,000,000 / 1024 / 1024 = TB级差距。
内容的提问来源于stack exchange,提问作者Olivier Pallatier
相关产品推荐
相关产品推荐

