Node.js Axios代理采集
Axios通过原生proxy选项连接隧道住宅网关,Cheerio解析分页HTML。案例复用HTTP连接,只对429、5xx与临时网络错误执行有限退避,并输出JSON Lines。
语言Node.js 20+框架Axios + Cheerio测试目标Quotes to Scrape输出quotes-axios.jsonl
01
END-TO-END RESULT
使用原生代理配置采集分页网页
测试目标Quotes to Scrape
https://quotes.toscrape.com/
运行时Node.js 20+Axios + Cheerio
输出文件结构化数据quotes-axios.jsonl
代码直接连接隧道住宅代理固定网关 residential.123proxy.cn:33000。主机与端口无需替换,出口路由由当前产品配置决定;你只需设置控制台生成的完整代理用户名和密码。
02
CREDENTIALS
只把代理账密放进环境变量
PROXY_USER必须是控制台中的代理用户名,不是网站登录手机号。地区和SESSION场景应复制控制台生成的完整路由用户名。
export PROXY_USER="替换为控制台代理用户名"
export PROXY_PASS="替换为控制台代理密码"
# 隧道住宅代理固定网关:residential.123proxy.cn:33000$env:PROXY_USER="替换为控制台代理用户名"
$env:PROXY_PASS="替换为控制台代理密码"
# 隧道住宅代理固定网关:residential.123proxy.cn:33000
不要把真实代理密码提交到Git仓库、镜像、日志或前端代码中。
03
COMPLETE PROGRAM
scrape-axios.mjs
以下代码包含完整入口和输出逻辑。安装注释、运行命令与依赖声明保留在文件顶部,复制为对应文件后即可执行。
// requirements:
// npm init -y
// npm install axios cheerio
// save as scrape-axios.mjs, then run:
// node scrape-axios.mjs
import fs from "node:fs/promises";
import http from "node:http";
import https from "node:https";
import axios from "axios";
import * as cheerio from "cheerio";
const PROXY_HOST = "residential.123proxy.cn";
const PROXY_PORT = 33000;
const PROXY_USER = process.env.PROXY_USER;
const PROXY_PASS = process.env.PROXY_PASS;
const START_URL = "https://quotes.toscrape.com/";
const MAX_PAGES = 3;
if (!PROXY_USER || !PROXY_PASS) {
throw new Error("set PROXY_USER and PROXY_PASS first");
}
const client = axios.create({
timeout: 30_000,
proxy: {
protocol: "http",
host: PROXY_HOST,
port: PROXY_PORT,
auth: {
username: PROXY_USER,
password: PROXY_PASS,
},
},
headers: {
"User-Agent": "123Proxy-Developer-Example/1.0",
"Accept-Language": "en-US,en;q=0.8",
},
httpAgent: new http.Agent({ keepAlive: true, maxSockets: 10 }),
httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 10 }),
});
function delay(milliseconds) {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
async function getWithRetry(url, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
return await client.get(url);
} catch (error) {
const status = error.response?.status;
const retryable =
status === 429
|| (status >= 500 && status <= 599)
|| error.code === "ECONNRESET"
|| error.code === "ETIMEDOUT";
if (!retryable || attempt === maxAttempts) throw error;
const retryAfter = Number(error.response?.headers?.["retry-after"]);
const waitMs = Number.isFinite(retryAfter)
? Math.min(retryAfter * 1000, 30_000)
: 500 * (2 ** (attempt - 1));
await delay(waitMs + Math.floor(Math.random() * 250));
}
}
throw new Error("unreachable");
}
function parsePage(html, sourceUrl) {
const $ = cheerio.load(html);
const records = $("div.quote")
.map((_, node) => ({
text: $(node).find("span.text").text().trim(),
author: $(node).find("small.author").text().trim(),
tags: $(node).find("a.tag")
.map((__, tag) => $(tag).text().trim())
.get(),
source_url: sourceUrl,
}))
.get();
const nextHref = $("li.next > a").attr("href");
return { records, nextHref };
}
async function main() {
const records = [];
let url = START_URL;
for (let page = 0; page < MAX_PAGES && url; page += 1) {
const response = await getWithRetry(url);
const parsed = parsePage(response.data, url);
records.push(...parsed.records);
url = parsed.nextHref ? new URL(parsed.nextHref, url).href : null;
await delay(500);
}
const jsonLines = records.map((record) => JSON.stringify(record)).join("\n");
await fs.writeFile("quotes-axios.jsonl", jsonLines + "\n", "utf8");
console.log(
"saved " + records.length + " records to quotes-axios.jsonl",
);
}
main().catch((error) => {
console.error(error.response?.status || error.code, error.message);
process.exitCode = 1;
});
04
ENGINEERING CHECKS
案例没有省略的关键逻辑
01proxy对象分别设置协议、主机、端口和代理账密
02固定住宅网关为residential.123proxy.cn:33000
03HTTP与HTTPS Agent启用keepAlive并限制连接数
04仅重试429、5xx、连接重置与超时
05解析相对分页URL并输出一行一个JSON对象
05
SWITCH PROXY PRODUCT
替换网关,不改采集逻辑
代理产品网关域名端口适合任务
隧道代理
proxy.123proxy.cn36923混合池 / 纯住宅池隧道住宅代理
residential.123proxy.cn33000国家地区与SESSION不限量动态住宅
unlimit.residential.123proxy.cn10253不限流量住宅任务两类静态代理
控制台分配的固定代理IP返回端口直连IP,无网关域名隧道住宅代理的国家与SESSION写在完整代理用户名中
网关固定为 residential.123proxy.cn:33000。例如使用美国SESSION时,直接把控制台生成的完整用户名作为 PROXY_USER,代码无需解析或重组。
06
PRIMARY REFERENCES
框架文档与测试目标
07
TROUBLESHOOTING
常见问题
PROXY_USER应该填写什么?
填写控制台生成的完整代理用户名。需要国家或SESSION时,地区后缀与SESSION参数都属于完整用户名,不要在Axios代码中再次拼接。
为什么没有设置HTTP_PROXY环境变量?
案例使用Axios原生proxy配置,使代理行为与当前采集客户端绑定,避免进程环境代理影响其他请求。
如何使用隧道代理?
把固定地址改为proxy.123proxy.cn:36923,并使用隧道代理控制台生成的代理用户名与密码;其他采集逻辑保持不变。
429是否应该立即重试?
不应该。优先读取Retry-After,否则使用指数退避与随机抖动,并降低目标域名的并发和QPS。