DocsIntegration guides
Puppeteer and Playwright
Run headless Chrome through your Pine IPs, with one browser per IP and a fingerprint that matches it.
Puppeteer
Chrome takes the proxy’s host and port as a launch flag, but not the login. Send the username and password with page.authenticate() before the first navigation:
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
args: ["--proxy-server=http://HOST:PORT"],
});
const page = await browser.newPage();
await page.authenticate({ username: "USERNAME", password: "PASSWORD" });
await page.goto("https://api.ipify.org?format=json");
console.log(await page.evaluate(() => document.body.innerText));
await browser.close();
Call authenticate on every new page you open. Pages that skip it get 407 Proxy Authentication Required.
Playwright
Playwright takes the whole proxy, login included, at launch:
import { chromium } from "playwright";
const browser = await chromium.launch({
proxy: { server: "http://HOST:PORT", username: "USERNAME", password: "PASSWORD" },
});
const page = await browser.newPage();
await page.goto("https://api.ipify.org?format=json");
console.log(await page.textContent("body"));
await browser.close();
You can also set proxy per context with browser.newContext({ proxy }), which runs several IPs from a single browser.
One browser per IP
Chrome’s proxy flag applies to the whole browser. To use several IPs with Puppeteer, launch one browser for each:
const hosts = ["HOST_1:PORT", "HOST_2:PORT", "HOST_3:PORT"];
await Promise.all(hosts.map(async (host) => {
const browser = await puppeteer.launch({ args: [`--proxy-server=http://${host}`] });
const page = await browser.newPage();
await page.authenticate({ username: "USERNAME", password: "PASSWORD" });
await page.goto("https://books.toscrape.com/");
console.log(host, await page.title());
await browser.close();
}));
For accounts that log in, keep each account on the same IP every time. Because Pine IPs are static, the same host always gives the same exit address.
Match the fingerprint to the browser
Headless Chrome on a Linux server still reports the User-Agent you give it. Pick a profile for the same OS, so the TCP/IP fingerprint agrees with it:
await page.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
"(KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
);
// Windows is the default profile, so the username needs no suffix.
await page.authenticate({ username: "USERNAME", password: "PASSWORD" });
For a Mac User-Agent, authenticate as USERNAME-os-macos instead. TCP/IP fingerprints lists every profile.
Common issues
| You see | What to check |
|---|---|
net::ERR_PROXY_CONNECTION_FAILED |
The host or port in --proxy-server is wrong, or the IP isn’t active on your account. |
407 Proxy Authentication Required |
page.authenticate() ran after goto, or not at all on a new page. |
| A 407 even though the login is in the flag | Chrome ignores credentials in --proxy-server. Pass only http://HOST:PORT and send the login with page.authenticate(). |
net::ERR_TUNNEL_CONNECTION_FAILED |
The proxy couldn’t reach the site. Try the same URL with cURL through the proxy, as shown in Connection errors. |