node-fetch请求返回403但同参数curl请求返回200的问题问询
服务器区分两类请求的核心逻辑
- Accept-Encoding 头缺失:你使用的curl命令携带了
--compressed参数,会自动在请求头添加Accept-Encoding: gzip, deflate, br标识,告知服务端可以返回压缩内容。绝大多数站点的WAF规则会校验该头,缺省该头的请求会被直接判定为非浏览器请求,返回403。而你提供的node-fetch代码里没有添加该头,这是最可能的首因。 - TLS 指纹差异:不同客户端的TLS握手时的加密套件顺序、扩展字段等特征会生成唯一的JA3指纹,WAF可以通过该指纹区分node.js、curl、浏览器的请求。node.js默认的TLS加密套件顺序和Chrome、curl差异较大,很容易被识别拦截。
- HTTP 版本与伪头差异:node-fetch默认使用HTTP/1.1发送请求,你手动添加的
authority头是普通HTTP头,而非HTTP/2要求的:authority伪头。目前很多站点强制要求HTTP/2访问,使用HTTP/1.1发送的请求即使头字段完全匹配也会被拦截。
解决方案
方案1:补全缺失的压缩头(最简便,优先尝试)
在node-fetch的请求头中补全accept-encoding字段,同时开启自动解压配置:
await fetch('https://www.scottycameron.com/store/user/login/', { compress: true, // 开启自动解压响应内容 headers: { 'authority': 'www.scottycameron.com', 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'sec-gpc': '1', 'sec-fetch-site': 'none', 'sec-fetch-mode': 'navigate', 'sec-fetch-user': '?1', 'sec-fetch-dest': 'document', 'accept-language': 'en-US,en;q=0.9', // 补全curl自动添加的压缩头 'accept-encoding': 'gzip, deflate, br' } }).then(res => res.text())
方案2:使用支持HTTP/2的请求库修复指纹问题
如果方案1无效,说明站点校验了TLS指纹或HTTP版本,可更换为支持HTTP/2的fetch-h2库发送请求,模拟浏览器的HTTP/2请求特征:
- 安装依赖:
npm install fetch-h2 - 示例代码:
const { fetch } = require('fetch-h2'); const html = await fetch('https://www.scottycameron.com/store/user/login/', { headers: { 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'sec-gpc': '1', 'sec-fetch-site': 'none', 'sec-fetch-mode': 'navigate', 'sec-fetch-user': '?1', 'sec-fetch-dest': 'document', 'accept-language': 'en-US,en;q=0.9', 'accept-encoding': 'gzip, deflate, br' } }).then(res => res.text());
方案3:直接调用curl能力(百分百兼容)
如果前两个方案都无效,直接使用node-libcurl库调用原生curl的能力,请求特征和你测试成功的curl命令完全一致,可绕过所有特征校验:
- 安装依赖:
npm install node-libcurl - 示例代码:
const { Curl } = require('node-libcurl'); const curl = new Curl(); curl.setOpt('URL', 'https://www.scottycameron.com/store/user/login/'); curl.setOpt('HTTPHEADER', [ 'authority: www.scottycameron.com', 'upgrade-insecure-requests: 1', 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36', 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'sec-gpc: 1', 'sec-fetch-site: none', 'sec-fetch-mode: navigate', 'sec-fetch-user: ?1', 'sec-fetch-dest: document', 'accept-language: en-US,en;q=0.9' ]); curl.setOpt('ACCEPT_ENCODING', 'gzip, deflate, br'); // 对应curl的--compressed参数 curl.on('end', function(statusCode, data) { console.log(statusCode); // 正常返回200 console.log(data); // 输出预期的页面HTML内容 this.close(); }); curl.perform();
内容的提问来源于stack exchange,提问作者Spencer
相关产品推荐
相关产品推荐

