如何使用NodeJS Express过滤iOS Safari的重复预加载GET请求
iOS Safari 预加载请求识别与访问统计优化方案
针对基于Express搭建的访问统计场景,不需要依赖IP去重这种高误差方案,浏览器本身已经提供了标准的请求头标识,可以精准区分预加载请求和用户真实访问请求。
核心识别规则:基于标准请求头判断
Safari触发的地址栏自动填充预加载请求,会携带专门的请求头标识,这是准确率最高的判断依据,不会出现误判:
- 优先判断
Sec-Purpose头:现代版本iOS Safari发起的所有预加载、预取请求,都会携带Sec-Purpose: prefetch头,用户主动触发的页面导航请求不会携带这个头。在Express中可以通过req.get('Sec-Purpose')直接取值,只要值包含prefetch字段即可判定为预加载请求。 - 兼容旧版本判断
X-Purpose头:部分较早版本的iOS Safari没有采用标准的Sec-Purpose头,会使用非标准的X-Purpose: prefetch头传递预加载标识,做兼容判断时需要同时覆盖这个头。 - 辅助校验
Sec-Fetch-Mode头:用户主动触发的顶层页面导航请求,该头的值固定为navigate;预加载请求不会携带这个值,通常为cors或no-cors,可以作为辅助校验规则,避免特殊场景下的漏判。
你可以直接在Express中封装一个轻量中间件实现识别逻辑,参考代码如下:
// 预加载请求判断方法 function checkIsSafariPrefetch(req) { const secPurpose = req.get('Sec-Purpose') || ''; const xPurpose = req.get('X-Purpose') || ''; // 命中标准预加载头直接返回结果 if (secPurpose.includes('prefetch') || xPurpose.includes('prefetch')) { return true; } return false; } // 访问统计中间件 app.use((req, res, next) => { // 标记预加载请求,跳过访问量统计逻辑 if (checkIsSafariPrefetch(req)) { req.isPrefetchRequest = true; return next(); } // 仅对真实请求执行访问日志记录、PV统计逻辑 collectPageVisitData(req); next(); });
为什么不推荐单纯用IP+时间窗去重
这种方案的统计误差会非常大,不适合作为核心过滤规则:
- 误判率高:同一办公网、校园网、公共WiFi下的大量用户会共享同一个出口IP,短时间内多个不同真实用户访问同一页面是高频场景,单纯按IP时间窗去重会把大量真实访问判定为重复请求,导致统计结果远低于实际值。
- 漏判率高:如果用户在地址栏输入URL后停顿时间较长,超过你设置的时间窗口才选中自动填充的建议,预加载请求和真实请求的时间差会超出窗口阈值,预加载请求会被误算为真实访问,达不到过滤效果。
兜底优化规则
如果需要覆盖极少数不携带标准预加载头的特殊场景,可以加一层低误判的兜底逻辑:
- 预加载请求到达时,以「客户端IP + User-Agent + 请求路径」为三元组,写入一个有效期10秒的短缓存标记,不要直接拦截请求,正常返回页面内容即可,避免影响用户真实跳转时的加载体验。
- 后续请求到达时,如果匹配到缓存中的三元组标记,且请求的
Sec-Fetch-Mode值不是navigate,再判定为预加载请求跳过统计;如果匹配到标记但Sec-Fetch-Mode为navigate,说明是用户真实触发的跳转,正常执行统计逻辑即可。 - 不要对预加载请求返回4xx/5xx状态码,也不要故意延迟响应,否则会导致用户真实跳转时出现加载慢、页面渲染异常的问题。
内容的提问来源于stack exchange,提问作者Matt Wise
相关产品推荐
相关产品推荐

