大多数关于设置 Axios 请求头的指南仅关注请求端,却从未关注响应内容。这是个错误。Axios 的响应头对象正是 API 告知你后续行为的关键:剩余请求次数、缓存副本是否仍有效,以及下一页的位置。若忽略这些信息,你的爬虫在第一天看似礼貌,但到第三天就会被限流。
Axios 将响应头暴露在 response.headers上。无论服务器在传输过程中如何处理大小写,这些键值均以小写形式呈现。这是初次阅读时最常见的意外情况。MDN 的《HTTP 条件请求》指南涵盖了 ETag 的规范, If-None-Match.
const res = await api.get("/orders");
// All read as lowercase keys, regardless of server casing
const limit = Number(res.headers["x-ratelimit-limit"]);
const remaining = Number(res.headers["x-ratelimit-remaining"]);
const resetAt = new Date(Number(res.headers["x-ratelimit-reset"]) * 1000);
console.log(`API budget: ${remaining}/${limit}, resets at ${resetAt.toISOString()}`);
为了实现缓存友好的轮询,请将 ETag ,并通过 If-None-Match。如果资源未发生变化,服务器将返回 304 状态码并附带空正文,你的代码将跳过解析:
let cachedEtag = null;
let cachedBody = null;
async function pollOrders() {
try {
const res = await api.get("/orders", {
headers: cachedEtag ? { "If-None-Match": cachedEtag } : {},
validateStatus: (s) => s === 200 || s === 304, // 304 is not an error
});
if (res.status === 200) {
cachedEtag = res.headers["etag"];
cachedBody = res.data;
}
return cachedBody;
} catch (err) {
/* network/HTTP errors only land here now */
}
}
分页 API 通常使用 Link 标头(RFC 8288)配合 rel="next" 和 rel="prev"。可手动遍历或使用解析器:
function nextLink(linkHeader) {
if (!linkHeader) return null;
const match = linkHeader.split(",").find((p) => /rel="next"/.test(p));
return match ? match.match(/<([^>]+)>/)[1] : null;
}
let url = "/orders?per_page=100";
while (url) {
const res = await api.get(url);
process(res.data);
url = nextLink(res.headers["link"]);
}
大多数 API 中值得监控的头部字段:
如果您的代码仅检查 response.data,你就忽略了 API 试图传达给你的半数信息。