axios.get() 调用 API 时为何返回 403 错误?
结论
这不是 Axios 故障,也与 CORS 无关。响应头里的 server: cloudflare、cf-ray 和 content-type: text/html 说明,请求被 Cloudflare 或目标站点的防护规则拦截了。
浏览器能够正常访问,是因为它会携带浏览器指纹、Cookie,还可能已经通过 Cloudflare 验证。Node.js 中的 Axios 请求通常只有 User-Agent: axios/0.24.0 等少量请求头,更容易被识别为脚本流量,随后收到 403 Forbidden。
isAxiosError: true 只说明 Axios 把这次失败封装成了 Axios 错误,不代表拒绝请求的是 Axios。
为什么浏览器和 Axios 的结果不同
即使 URL 完全一样,浏览器和 Axios 发出的请求也不相同,差异可能包括:
User-Agent、Accept、Accept-Language等请求头。- 浏览器可能带有登录状态、会话 Cookie 或 Cloudflare 验证 Cookie。
- 浏览器可以执行 JavaScript 验证,Axios 不行。
- 服务端可能根据 IP、TLS 指纹、请求频率或数据中心网络评估请求风险。
- 该接口可能只供网站内部调用,并未向任意服务器端程序开放。
- 接口可能要求 API Key、签名或其他认证信息。
错误响应的正文类型是 text/html,不是预期的 JSON。这通常意味着返回的是安全验证页面或拦截页面。
请求中的这一项也应该删除:
Access-Control-Allow-Origin: *
Access-Control-Allow-Origin 是由服务器写入响应的 CORS 响应头,不是客户端用来申请跨域权限的请求头。Node.js 请求不受浏览器同源策略限制,所以添加它无法解决 403,反而可能让防护系统将请求判断为异常。
建议的解决顺序
1. 优先使用官方公开 API
先确认 Magic Eden 当前是否为该接口提供正式的开发者入口,并查看是否需要以下配置:
- API Key
Authorization请求头- 请求签名
- 指定的 API 域名
- 调用频率限制
- IP 白名单
示例:
const axios = require('axios');
const response = await axios.get('https://example.com/official-api/endpoint', {
params: {
// 按官方文档填写参数
},
headers: {
Accept: 'application/json',
Authorization: `Bearer ${process.env.MAGIC_EDEN_API_TOKEN}`
},
timeout: 10000
});
console.log(response.data);
不要把密钥直接写进源码,也不要将其提交到代码仓库。
2. 使用 params 生成查询字符串
当前 URL 将 JSON 查询条件手工编码进了字符串。虽然日志显示 Axios 最终完成了编码,但改用 params 更便于维护,也能减少重复编码或编码不一致:
const axios = require('axios');
const query = {
$match: {
txType: 'initializeEscrow',
blockTime: {
$gt: 1643376700
}
},
$sort: {
blockTime: -1
}
};
async function getActivities() {
try {
const response = await axios.get(
'https://api-mainnet.magiceden.io/rpc/getGlobalActivitiesByQuery',
{
params: {
q: JSON.stringify(query)
},
headers: {
Accept: 'application/json'
},
timeout: 10000
}
);
console.log(response.data);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('status:', error.response?.status);
console.error('content-type:', error.response?.headers?.['content-type']);
console.error('response:', error.response?.data);
} else {
console.error(error);
}
}
}
getActivities();
这样可以确保参数正确生成。不过,如果服务端明确拦截自动化请求,只修改编码方式仍然无法消除 403。
3. 对比浏览器和 Node.js 请求
在浏览器开发者工具的 Network 面板中找到成功的请求,再与 Axios 请求逐项比较:
- 实际请求 URL
- 是否发生重定向
Authorization- Cookie
Origin和Referer- 请求方法
- 查询参数编码
- 响应正文
- 调用频率
还可以把成功的请求复制为 cURL,然后在运行 Node.js 的同一台机器上执行。如果 cURL 也返回 403,问题通常与服务器 IP、防护策略或认证状态有关。如果 cURL 成功而 Axios 失败,再逐项检查两者的请求差异。
4. 仅在服务条款允许时补充普通请求头
有些站点会拒绝明显缺少常规请求头的请求。可以尝试提供合理的 User-Agent 和语言信息:
const response = await axios.get(url, {
headers: {
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.9',
'User-Agent': 'MySolanaTracker/1.0 (contact: admin@example.com)'
}
});
这种做法只适用于服务端允许程序调用,但要求客户端清楚标识自身的情况。伪造完整的浏览器请求头也无法保证通过 Cloudflare,因为防护判断还可能涉及 Cookie、JavaScript challenge、TLS 指纹和 IP 声誉。
5. 检查限流和出口 IP
如果接口以前能够调用,后来才开始返回 403,还需要检查:
- 是否在短时间内发送了大量请求。
- 是否使用了云服务器或代理出口 IP。
- 同一个 IP 是否被其他程序共享。
- 是否存在区域限制。
- 是否违反接口的调用频率或使用规则。
遇到限流时,应降低并发、增加指数退避,并遵守服务方公布的限制。不要通过轮换代理等方式规避访问控制。
注意事项
- 403 表示服务器主动拒绝访问,不是 Axios 无法解析响应。
- CORS 只约束浏览器中的前端 JavaScript,不约束 Node.js,因此设置
Access-Control-Allow-Origin解决不了这个问题。 - 不要直接把浏览器中的会话 Cookie 复制到生产程序。这类 Cookie 通常会过期,也可能包含账号凭据。
- 如果 Cloudflare challenge 需要浏览器能力,Axios 无法单独完成验证。
- 不建议依赖网站内部的
/rpc/...接口。未公开接口可能随时修改、增加认证要求或停止对外服务。 - 如果服务方没有提供公开的机器调用方式,可靠的处理办法是申请官方 API 权限或联系服务方,而不是继续模拟浏览器。
备注:内容仅供参考。
