免责声明:本文仅讨论公开可访问数据的采集。在美国,《计算机欺诈和滥用法》(CFAA)可能适用于未经授权的访问;在欧盟,GDPR 对个人数据的处理有严格规定。请始终遵守目标网站的 robots.txt、服务条款和适用法律,优先使用官方 API。
Node.js 中的 got-scraping:为什么你的原始请求会被拦截
如果你用过 got 或 axios 做过规模化爬虫,大概率遇到过这种情况:前 100 个请求正常返回,然后突然全是 403 或 Cloudflare 验证码。原因不在你的代码逻辑,而在于请求的「指纹」暴露了自动化身份。
现代反爬系统不只看 IP 地址。它们检查至少三个层面:
- HTTP 头部一致性 — 真实 Chrome 浏览器发送的头部有固定顺序,且包含
sec-ch-ua、sec-fetch-dest、accept-language等一系列客户端提示头部。got默认发送的user-agent和少量头部立刻暴露非浏览器身份。 - TLS 指纹(JA3/JA4) — Node.js 的 TLS 握手特征与 Chrome 不同,高级反爬系统可以通过 TLS 扩展顺序识别。
- IP 信誉 — 数据中心 IP 段(AWS、GCP、DigitalOcean)被大量爬虫使用,反爬服务对这些段有标记。
got-scraping 是 Apify 开发的 got 扩展库,专门解决前两个问题。它内置 header-generator 模块,能生成与真实浏览器一致的头部集,并支持 HTTP/2。配合住宅代理解决 IP 信誉问题后,三层指纹都能被有效伪装。
header-generator:生成连贯的浏览器头部集
为什么头部顺序很重要
HTTP/1.1 规范并不强制头部顺序,但真实浏览器有固定的发送顺序。例如 Chrome 总是先发送 Host、Connection,然后是 sec-ch-ua 系列,最后是 accept。反爬系统如果发现 user-agent 在 accept 之前,或者缺少 sec-fetch-mode,就会标记为可疑。
header-generator 不是简单地随机拼头部。它从真实浏览器采样数据,生成一个连贯的头部集——如果你指定 Chrome 120 on Windows,它会生成 Chrome 120 对应的所有客户端提示头部、正确的 accept 值、匹配的 sec-ch-ua-platform,且顺序与真实 Chrome 一致。
headerGeneratorOptions 配置
核心配置项包括:
- browsers — 指定浏览器类型,如
['chrome']、['firefox']。建议只用chrome,因为它是市场份额最大的浏览器,不容易引起怀疑。 - devices —
'desktop'或'mobile'。移动端头部集与桌面端不同(如sec-ch-ua-mobile值)。 - operatingSystems —
'windows'、'macos'、'linux'、'android'、'ios'。必须与浏览器和设备类型匹配。
一个常见错误是混合不兼容的组合,比如 chrome + ios(iOS 上的 Chrome 使用 WebKit 引擎,头部特征不同)。header-generator 内部有约束逻辑,但最好显式指定合理组合。
got-scraping 的惯用接口:got.extend 与 useHeaderGenerator
got-scraping 的设计遵循 got 的扩展模式。你不直接调用全局 got,而是通过 got.extend() 创建一个预配置实例。这是 got 推荐的做法——got 官方文档明确建议为不同用途创建独立实例。
基础实例创建
const { gotScraping } = require('got-scraping');
// 创建带 header-generator 的 got 实例
const client = gotScraping.extend({
headerGeneratorOptions: {
browsers: ['chrome'],
devices: ['desktop'],
operatingSystems: ['windows'],
},
http2: true, // 启用 HTTP/2,真实 Chrome 默认使用
});
// 每个请求自动获得连贯的浏览器头部
const response = await client.get('https://httpbin.org/headers');
console.log(JSON.parse(response.body).headers);
注意 http2: true。现代浏览器默认使用 HTTP/2 连接目标网站。如果你的爬虫用 HTTP/1.1 请求一个支持 HTTP/2 的网站,这本身就是一个异常信号。got-scraping 基于 got 的 HTTP/2 实现,能正确处理 ALPN 协商。
proxyUrl 选项:HTTP 与 SOCKS5
got-scraping 通过 proxyUrl 选项支持代理。可以在实例级别设置,也可以在单个请求级别覆盖:
// 实例级别:所有请求走同一代理
const client = gotScraping.extend({
headerGeneratorOptions: {
browsers: ['chrome'],
devices: ['desktop'],
operatingSystems: ['windows'],
},
proxyUrl: 'http://user-country-US:pass@gate.proxyhat.com:8080',
});
// 请求级别:覆盖代理(例如切换国家)
const resp = await client.get('https://httpbin.org/ip', {
proxyUrl: 'http://user-country-DE:pass@gate.proxyhat.com:8080',
});
对于 SOCKS5 代理,使用端口 1080:
proxyUrl: 'socks5://user-country-US:pass@gate.proxyhat.com:1080'
SOCKS5 在某些场景下比 HTTP 代理更可靠,因为它不修改 HTTP 头部,且支持 UDP。但 HTTP 代理在大多数爬虫场景中已经足够,且调试更方便。
通过 ProxyHat 住宅代理路由请求
为什么头部对了还需要住宅 IP
假设你已经用 got-scraping 生成了完美的 Chrome 头部,TLS 也通过 HTTP/2 对齐了。但如果你从一个 AWS us-east-1 的 IP 发送请求,目标网站的反爬服务(如 Cloudflare、DataDome、PerimeterX)仍然会标记你——因为真实用户不会从数据中心 IP 访问电商网站。
住宅代理的 IP 来自真实 ISP 分配给家庭用户的地址段。当请求从这些 IP 发出时,反爬系统看到的是一个「普通家庭网络用户」,而不是「服务器上的爬虫脚本」。这就是为什么头部伪装和 IP 伪装必须同时做——只做一层不够。
ProxyHat 的住宅代理网关位于 gate.proxyhat.com:8080(HTTP)和 gate.proxyhat.com:1080(SOCKS5)。你可以在用户名中嵌入地理定位和会话标识:
user-country-US— 美国出口 IPuser-country-DE-city-berlin— 德国柏林出口 IPuser-session-abc123— 粘性会话,同一 session ID 保持同一 IP
查看可用地理位置请访问 ProxyHat 代理位置页面。
住宅 vs 数据中心 vs 移动代理对比
| 类型 | IP 来源 | 信任度 | 速度 | 成本 | 适用场景 |
|---|---|---|---|---|---|
| 住宅代理 | 真实 ISP 家庭网络 | 高 | 中等(50-200ms) | 中等 | SERP 爬取、电商价格监控、社交媒体调研 |
| 数据中心代理 | 云服务商 IP 段 | 低 | 快(10-50ms) | 低 | 无反爬的 API 调用、低风险页面 |
| 移动代理 | 4G/5G 运营商网络 | 极高 | 较慢(100-500ms) | 高 | 社交账号管理、高信任度要求场景 |
可运行示例:轮换住宅端点与重试钩子
下面是一个完整的 Node.js 脚本,展示如何用 got-scraping 配合 ProxyHat 住宅代理实现带重试的轮换爬取。每个请求生成唯一的 session ID,确保 IP 轮换。
const { gotScraping } = require('got-scraping');
const crypto = require('crypto');
// ProxyHat 凭据
const PROXYHAT_USER = 'your_username';
const PROXYHAT_PASS = 'your_password';
const GATEWAY = 'gate.proxyhat.com';
const HTTP_PORT = 8080;
// 生成带 session 和 geo 的代理 URL
function buildProxyUrl({ country = 'US', session } = {}) {
const sid = session || crypto.randomUUID().substring(0, 8);
const username = `${PROXYHAT_USER}-country-${country}-session-${sid}`;
return `http://${username}:${PROXYHAT_PASS}@${GATEWAY}:${HTTP_PORT}`;
}
// 创建 got-scraping 实例,带重试钩子
const client = gotScraping.extend({
headerGeneratorOptions: {
browsers: ['chrome'],
devices: ['desktop'],
operatingSystems: ['windows'],
},
http2: true,
retry: {
limit: 3,
statusCodes: [429, 503, 502, 504],
backoffLimit: 5000,
},
hooks: {
beforeRequest: [
(options) => {
// 每个请求自动生成新 session → 新 IP
if (!options.proxyUrl || !options.context?.sticky) {
options.proxyUrl = buildProxyUrl({
country: options.context?.country || 'US',
});
}
},
],
afterResponse: [
(response, retryWithMergedOptions) => {
// 检测 CAPTCHA 页面
const body = response.body;
if (typeof body === 'string' && body.includes('cf-challenge')) {
// 换一个新 session 重试
retryWithMergedOptions({
proxyUrl: buildProxyUrl({ country: 'US' }),
});
}
return response;
},
],
},
});
// 批量请求示例
async function scrapeUrls(urls) {
const results = [];
for (const url of urls) {
try {
const resp = await client.get(url, {
context: { country: 'US' },
});
results.push({ url, status: resp.statusCode, ok: true });
} catch (err) {
results.push({ url, status: err.response?.statusCode, ok: false });
}
}
return results;
}
// 运行
const targets = [
'https://httpbin.org/ip',
'https://httpbin.org/headers',
'https://httpbin.org/user-agent',
];
scrapeUrls(targets).then(console.log);
关键设计点:
- beforeRequest 钩子在每个请求前生成新的
proxyUrl,通过随机 session ID 实现自动 IP 轮换。 - retry 配置对 429 和 503 自动重试,最多 3 次,退避上限 5 秒。
- afterResponse 钩子检测 Cloudflare challenge 页面,触发带新 IP 的重试。
- context 对象用于在请求间传递自定义参数(如国家代码),不污染 HTTP 头部。
生产模式:并发控制、Cookie 与 Crawlee 集成
用 p-limit 控制并发
无限制并发是爬虫被封锁的头号原因。即使每个请求都有不同的 IP,同一秒发出 500 个请求到同一域名也会触发行为分析。使用 p-limit 限制并发:
const pLimit = require('p-limit');
const limit = pLimit(10); // 最多 10 个并发请求
async function scrapeWithConcurrency(urls) {
const tasks = urls.map(url =>
limit(() => client.get(url).then(
r => ({ url, ok: true, status: r.statusCode }),
e => ({ url, ok: false, status: e.response?.statusCode })
))
);
return Promise.all(tasks);
}
经验值:对大多数网站,5-20 个并发是安全区间。SERP 爬取建议更低(3-5),因为搜索引擎对自动化访问特别敏感。更多 SERP 爬取策略参见 ProxyHat SERP 跟踪用例。
Cookie Jar:多步流程中的会话保持
某些场景需要多个请求共享 Cookie(如登录后的分页爬取)。got 内置 tough-cookie 支持:
const { gotScraping } = require('got-scraping');
const { CookieJar } = require('tough-cookie');
const cookieJar = new CookieJar();
const sessionClient = gotScraping.extend({
headerGeneratorOptions: {
browsers: ['chrome'],
devices: ['desktop'],
operatingSystems: ['windows'],
},
cookieJar,
proxyUrl: buildProxyUrl({
country: 'US',
session: 'login-flow-001', // sticky session 保持同一 IP
}),
});
// 第一步:获取 CSRF token
const page1 = await sessionClient.get('https://example.com/login');
// 第二步:提交登录(复用 Cookie 和 IP)
const page2 = await sessionClient.post('https://example.com/login', {
form: { username: '...', password: '...' },
});
// 第三步:访问受保护页面
const page3 = await sessionClient.get('https://example.com/dashboard');
这里 session: 'login-flow-001' 确保三个请求通过同一住宅 IP 发出。如果每个请求换 IP,目标网站会因 IP 跳变而中断会话。
升级到 Crawlee 的 CheerioCrawler
当爬虫规模增长到需要 URL 队列、自动重试、去重和结果存储时,直接用 got-scraping 管理会变得复杂。Apify 的 Crawlee 框架内置了 got-scraping,通过 CheerioCrawler 提供 HTML 解析和爬取编排:
const { CheerioCrawler } = require('crawlee');
const crawler = new CheerioCrawler({
requestHandler: async ({ $, request }) => {
const title = $('title').text();
const links = $('a[href]').map((_, el) => $(el).attr('href')).get();
console.log(`${request.url}: ${title} (${links.length} links)`);
},
// got-scraping 配置透传
proxyConfiguration: {
newUrlFunction: (sessionId) =>
buildProxyUrl({ country: 'US', session: sessionId }),
},
maxConcurrency: 10,
maxRequestRetries: 3,
});
await crawler.run(['https://example.com']);
CheerioCrawler 自动使用 got-scraping 发送请求,你只需关注解析逻辑。代理轮换通过 proxyConfiguration 的 newUrlFunction 实现,每个请求获得独立的 session ID。
何时必须用无头浏览器
got-scraping 能解决头部和 HTTP 协议层的伪装,但无法执行 JavaScript。以下场景必须升级到 Playwright 或 Puppeteer:
- 客户端渲染的 SPA — 页面内容通过 JS 动态加载,HTTP 请求只返回骨架 HTML。
- Cloudflare Turnstile / reCAPTCHA — 需要真实浏览器环境执行挑战脚本。即使如此,也应配合住宅代理使用。
- 需要交互的流程 — 点击、滚动、等待动态元素加载。
无头浏览器的成本是 10-50 倍的内存和 CPU 开销。一个 Chrome 实例约占 150-300 MB 内存,而 got-scraping 请求只需约 2 MB。在容器化部署中,10 个 Chrome 实例需要约 3 GB 内存,而 100 个 got-scraping 并发连接只需约 200 MB。
最佳策略是分层爬取:先用 got-scraping 爬取能直接获取 HTML 的页面,只对需要 JS 渲染的页面降级到无头浏览器。Crawlee 的 PlaywrightCrawler 和 CheerioCrawler 可以在同一个项目中混用。
伦理与合规要点
技术能力不等于合法使用权。在开始任何爬虫项目前:
- 检查 robots.txt — 虽然法律效力存在争议,但它是网站表达意愿的明确信号。
- 遵守服务条款 — 许多网站的 ToS 明确禁止自动化访问,违反可能导致账号封禁或法律行动。
- 控制请求频率 — 即使没有明确的 rate limit,也应保持合理间隔(如每秒不超过 1-2 请求/域名)。
- 优先使用官方 API — 如果目标网站提供 API,用 API 而非爬虫。API 更稳定、更合法、更不易被封。
- 不采集个人数据 — GDPR 定义的个人数据(姓名、邮箱、IP、位置等)的处理需要法律依据。
更多爬虫最佳实践参见 ProxyHat 网页爬虫用例和 ProxyHat 官方文档。
关键要点
got-scraping 的价值在于「连贯性」 — 它不是简单地加一个 User-Agent,而是生成与真实浏览器完全一致的头部集(顺序、客户端提示、accept 值),配合 HTTP/2 对齐 TLS 层。这是它区别于手动设置头部的核心优势。
- 三层伪装缺一不可 — 头部(header-generator)+ 协议(HTTP/2)+ IP(住宅代理)。只做一层会被多层检测系统识破。
- got.extend 是惯用法 — 创建预配置实例,通过 hooks 注入代理轮换和重试逻辑,而非在每个请求中重复配置。
- session ID 实现 IP 控制 — 随机 session = 自动轮换;固定 session = 粘性会话。ProxyHat 的用户名参数化设计让这变得简单。
- 并发控制是生产底线 — 用 p-limit 或 Crawlee 的 maxConcurrency 限制并发,5-20 是安全区间。
- 无头浏览器是最后手段 — 只在需要 JS 渲染或交互式挑战时使用,资源开销是 got-scraping 的 10-50 倍。
- 合规优先 — robots.txt、ToS、GDPR/CFAA 不是可选项。优先用官方 API。
准备好开始了吗?查看 ProxyHat 定价方案,选择适合你规模的住宅代理套餐,然后复制上面的代码示例即可运行。






