Agencies and in-house marketing teams often need proof of what a page showed in a particular market: the landing page a campaign pointed to, the banner on a partner site, or the price on a product page on a given day. Puppeteer, which drives Chrome from Node.js, is a practical way to collect that proof on a schedule. Add a proxy and the same job can capture each market from one machine.
The pattern has two parts. You give Chrome the proxy address when it launches, then hand the username and password to each page. This guide covers both, plus separate contexts for each client, ways to keep data use down, and how to confirm every run went through the proxy.
Reporting jobs Puppeteer handles well
- Market screenshots: a weekly capture of key landing pages in each country you sell to.
- Placement evidence: a record of how an ad or sponsored listing appeared to visitors in a region.
- Change checks: a comparison of page text over time, flagging when a competitor's public offer or a partner's page changes.
Getting set up
Puppeteer needs Node.js 18 or newer. Install the package:
npm install puppeteerYour proxy order provides a host, port, username and password, written here as HOST, PORT, USER and PASS. Test them before writing any browser code, so a bad credential is not mistaken for a script bug:
curl --proxy http://USER:PASS@HOST:PORT https://api.ipify.orgWhen the proxy's IP comes back, carry on.
Launching Chrome with the proxy
The proxy goes in as a Chrome launch argument. Give it the host and port only. Chrome will not accept a username and password inside this argument:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({
headless: "new",
args: ["--proxy-server=http://HOST:PORT"]
});
const page = await browser.newPage();
await page.goto("https://api.ipify.org");
console.log(await page.content());
await browser.close();
})();For an order on IP-whitelist authentication, that is all you need. Login-based orders need one more step.
Answering the login with page.authenticate()
Puppeteer's page.authenticate() responds to the proxy's request for credentials on your behalf. Call it before the page navigates anywhere:
const puppeteer = require("puppeteer");
const HOST = "HOST", PORT = "PORT", USER = "USER", PASS = "PASS";
(async () => {
const browser = await puppeteer.launch({
headless: "new",
args: [`--proxy-server=http://${HOST}:${PORT}`]
});
const page = await browser.newPage();
await page.authenticate({ username: USER, password: PASS });
await page.goto("https://api.ipify.org", { waitUntil: "networkidle2" });
console.log(await page.evaluate(() => document.body.innerText));
await browser.close();
})();This is the standard Puppeteer approach. The authenticate call applies to one page at a time, so each new tab, meaning each new page object, needs its own call.
One context per client or market
Reporting jobs often cover several clients or markets in one run. Separate browser contexts keep cookies and cache apart, so nothing set while capturing one client's pages carries into another's:
const context = await browser.createBrowserContext();
const page = await context.newPage();
await page.authenticate({ username: USER, password: PASS });
await page.goto("https://example.com");
// ... do work ...
await context.close();With a rotating gateway, new connections through the same credentials already receive new addresses. With a set of sticky endpoints, give each context its own endpoint so each market keeps a steady address for the length of the capture. Older Puppeteer releases call the method createIncognitoBrowserContext rather than createBrowserContext; use whichever your installed version provides.
Cutting data costs on text-only checks
Proxy traffic is billed per gigabyte, and images, video and fonts make up much of a typical page. For jobs that only compare text or read the HTML, request interception can skip those files:
await page.setRequestInterception(true);
page.on("request", (req) => {
const blocked = ["image", "media", "font"];
if (blocked.includes(req.resourceType())) req.abort();
else req.continue();
});Leave this switched off for screenshot jobs, since the images are the point of the capture. For text and price checks it reduces data use considerably without changing what the script reads.
Confirm the exit IP in the job log
Write the browser's visible IP into the log at the start of each run, so every report can be traced to the market it claims to show:
await page.goto("https://api.ipify.org?format=json");
const body = await page.evaluate(() => document.body.innerText);
console.log("Proxy IP:", JSON.parse(body).ip);Our proxy checker can test those credentials independently. If the research itself is confidential, run the DNS leak test as well.
Which proxy type suits reporting jobs
For consumer-facing pages, where the capture must match what local households receive, residential proxies or residential premium proxies are the right fit. For mobile placements and carrier-specific content, use mobile proxies, which exit through mobile networks. Captures of your own staging site, or of public pages that accept server traffic, come out the same on datacenter proxies, which cost less per GB. The proxies page lists all four.
Ground rules for automated captures
Scheduled jobs can generate more traffic than you intend. Before adding markets or pages, check each site's robots.txt, respect its Terms of Service, space requests out and collect only public information you are entitled to use. Automation must never be used to get past a login or an anti-fraud check, and personal data needs a lawful basis before anyone collects it. Careful pacing keeps both the reports and the proxy addresses in good shape.
In summary: --proxy-server at launch, page.authenticate() on each page, one context per client, and an IP check at the top of every log.