NODE.JS BROWSER AUTOMATION

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 Scrollhttps://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

不要把真实代理密码提交到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并发和目标站点速率。