Node.js Puppeteer代理采集
Puppeteer通过--proxy-server指定123Proxy网关,再用page.authenticate发送代理账密。案例滚动到内容稳定,提取字段、去重并保存JSON。
语言Node.js 20+框架Puppeteer测试目标Quotes to Scrape / Infinite Scroll输出quotes-puppeteer.json
01
END-TO-END RESULT
采集无限滚动页面并按内容去重
测试目标Quotes to Scrape / Infinite Scroll
https://quotes.toscrape.com/scroll
运行时Node.js 20+Puppeteer
输出文件结构化数据quotes-puppeteer.json
代码直接连接隧道代理固定网关 proxy.123proxy.cn:36923。主机与端口无需替换,出口路由由当前产品配置决定;你只需设置控制台生成的完整代理用户名和密码。
02
CREDENTIALS
只把代理账密放进环境变量
PROXY_USER必须是控制台中的代理用户名,不是网站登录手机号。地区和SESSION场景应复制控制台生成的完整路由用户名。
export PROXY_USER="替换为控制台代理用户名"
export PROXY_PASS="替换为控制台代理密码"
# 隧道代理固定网关:proxy.123proxy.cn:36923$env:PROXY_USER="替换为控制台代理用户名"
$env:PROXY_PASS="替换为控制台代理密码"
# 隧道代理固定网关:proxy.123proxy.cn:36923
不要把真实代理密码提交到Git仓库、镜像、日志或前端代码中。
03
COMPLETE PROGRAM
scrape-scroll.js
以下代码包含完整入口和输出逻辑。安装注释、运行命令与依赖声明保留在文件顶部,复制为对应文件后即可执行。
// requirements:
// npm init -y
// npm install puppeteer
// run:
// node scrape-scroll.js
const fs = require("node:fs/promises");
const puppeteer = require("puppeteer");
const PROXY_HOST = "proxy.123proxy.cn";
const PROXY_PORT = 36923;
const PROXY_USER = process.env.PROXY_USER;
const PROXY_PASS = process.env.PROXY_PASS;
const TARGET_URL = "https://quotes.toscrape.com/scroll";
if (!PROXY_USER || !PROXY_PASS) {
throw new Error("set PROXY_USER and PROXY_PASS first");
}
async function main() {
const browser = await puppeteer.launch({
headless: true,
args: [
"--proxy-server=http://" + PROXY_HOST + ":" + PROXY_PORT,
],
});
const page = await browser.newPage();
await page.authenticate({
username: PROXY_USER,
password: PROXY_PASS,
});
await page.setUserAgent("123Proxy-Developer-Example/1.0");
page.setDefaultNavigationTimeout(45_000);
await page.goto(TARGET_URL, { waitUntil: "networkidle2" });
await page.waitForSelector("div.quote");
let previousCount = 0;
let stableRounds = 0;
for (let round = 0; round < 30 && stableRounds < 3; round += 1) {
const count = await page.$$eval(
"div.quote",
(nodes) => nodes.length,
);
stableRounds = count === previousCount ? stableRounds + 1 : 0;
previousCount = count;
await page.evaluate(() => {
window.scrollTo(0, document.body.scrollHeight);
});
await new Promise((resolve) => setTimeout(resolve, 900));
}
const records = await page.$$eval("div.quote", (nodes) =>
nodes.map((node) => ({
text: node.querySelector("span.text")?.textContent.trim(),
author: node.querySelector("small.author")?.textContent.trim(),
tags: [...node.querySelectorAll("a.tag")]
.map((tag) => tag.textContent.trim()),
source_url: location.href,
}))
);
const uniqueRecords = [
...new Map(
records.map((record) => [
record.author + "\u0000" + record.text,
record,
]),
).values(),
];
await fs.writeFile(
"quotes-puppeteer.json",
JSON.stringify(uniqueRecords, null, 2),
"utf8",
);
console.log(
"saved " + uniqueRecords.length + " records to quotes-puppeteer.json",
);
await browser.close();
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
04
ENGINEERING CHECKS
案例没有省略的关键逻辑
01Chromium启动参数只包含代理服务器,不暴露账密
02page.authenticate提供代理用户名和密码
03内容连续3轮不增长时结束滚动
04使用作者与正文组合键去重
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
常见问题
page.authenticate会影响性能吗?
Puppeteer官方文档说明该方法会启用请求拦截。生产任务应通过实际页面完成时间评估影响。
为什么不一直滚动到底?
无限页面可能没有明确结束标记。案例同时限制最大轮次和连续稳定轮次,避免任务永久运行。
多个浏览器worker如何接入?
所有worker连接unlimit.residential.123proxy.cn:10253,并使用控制台生成的代理账密;再由程序控制worker并发和目标站点速率。