Node.js fetch与浏览器请求差异及API访问拦截问题排查
问题背景
我爬取公开数据API时,用Node.js的fetch发起请求偶尔会被拦截,但浏览器(包括Chrome无痕模式,无Cookie)请求同一API却不会,Instagram和Reddit的API都存在这个情况。已经确认sec-fetch-mode不是拦截原因,想知道两者还有哪些深层差异?
核心差异点
User-Agent 标识差异
Node.js的fetch默认会发送node.js/[版本号]这类User-Agent,而Chrome会发送包含浏览器版本、操作系统、渲染引擎的详细UA字符串(比如你提供的"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"),服务器能轻松通过UA识别出非浏览器请求。请求头的完整性与顺序
Chrome会自动附带一堆浏览器专属请求头,比如sec-ch-prefers-color-scheme、sec-ch-ua-platform这类sec-ch-*系列,还有priority、Referrer-Policy等;Node.js的fetch默认不带这些头。另外,浏览器发送请求头的顺序是固定的,Node.js的fetch头顺序可能和浏览器不一致,部分服务器会通过校验头顺序来拦截非合法请求。TLS握手与连接特性差异
浏览器用的是自身内置的TLS配置,包括支持的SSL/TLS版本、加密套件、会话复用逻辑;而Node.js的fetch用的是Node.js自带的TLS栈,两者的TLS握手指纹不一样,服务器可以通过这个识别出请求不是来自浏览器。请求行为模式差异
浏览器发起请求时,通常是伴随页面加载的一系列请求,间隔符合人类浏览节奏;Node.js的fetch可能是批量高频发起,或者单个请求的触发时机和浏览器不同,服务器的反爬机制会通过这种行为模式判断拦截。会话级标识差异
即使Chrome无痕模式没有持久Cookie,浏览器会话中也会生成一些临时标识或会话Cookie;Node.js的fetch默认不会携带这些,部分服务器会校验这类会话级标识来确认请求合法性。特殊请求头缺失
你提供的Chrome请求里有x-requested-with: XMLHttpRequest,这个头是浏览器AJAX请求时自动添加的,Node.js的fetch默认不带,部分服务器会检查这个头来确认请求是否来自浏览器的AJAX调用。
Chrome复制的Fetch请求代码
fetch("https://www.instagram.com/api/v1/users/web_profile_info/?username=[removed]", { "headers": { "accept": "*/*", "accept-language": "en-US,en;q=0.9", "cache-control": "no-cache", "pragma": "no-cache", "priority": "u=1, i", "sec-ch-prefers-color-scheme": "light", "sec-ch-ua": "\"Chromium\";v=\"124\", \"Google Chrome\";v=\"124\", \"Not-A.Brand\";v=\"99\"", "sec-ch-ua-full-version-list": "\"Chromium\";v=\"124.0.6367.91\", \"Google Chrome\";v=\"124.0.6367.91\", \"Not-A.Brand\";v=\"99.0.0.0\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-model": "\"\"", "sec-ch-ua-platform": "\"Windows\"", "sec-ch-ua-platform-version": "\"15.0.0\"", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "x-asbd-id": "129477", "x-csrftoken": "[removed]", "x-ig-app-id": "936619743392459", "x-ig-www-claim": "0", "x-requested-with": "XMLHttpRequest", "x-web-device-id": "[removed]", "cookie": "csrftoken=[removed]", "Referer": "https://www.instagram.com/[removed]/", "Referrer-Policy": "strict-origin-when-cross-origin" }, "body": null, "method": "GET" }).then(res => res.text()).then(console.log);
内容的提问来源于stack exchange,提问作者Leo Jiang

