Playwright is the most capable browser automation framework in 2026 — but running it without proper mobile proxies means your scraper gets blocked within minutes on any serious target.
Datacenter IPs are fingerprinted at the ASN level before a single request is made. Residential proxies help but burn quickly under scraping load. Mobile proxies from real 4G/5G carriers are the only class of IP that survives sustained browser automation.
This guide walks through a complete production setup: SOCKS5 integration, automatic IP rotation via the ProxyGrow API, and patterns that keep your scraper alive on the hardest targets.
Get Mobile Proxies for Playwright Scraping
Real 4G/5G IPs from Ukraine, Romania, Latvia. Rotation API included.
Why Playwright Needs Mobile Proxies
Playwright controls a real Chromium, Firefox, or WebKit engine. The browser sends real TLS fingerprints, real HTTP/2 headers, real navigator values — and a real IP address.
If that IP address belongs to a datacenter ASN, no amount of browser fingerprint spoofing saves you. The platform knows before your page even loads.
Mobile proxies fix this at the network layer:
- ASN type: Kyivstar (AS15895), Orange Romania (AS8708), LMT Latvia (AS12578) — these are mobile carrier ASNs, not datacenter ranges
- CGNAT reputation: mobile IPs are shared among thousands of real users, so blanket blocks hurt real traffic and sites avoid them
- IP history: carrier IPs are not in commercial blacklists the way datacenter IPs are
Prerequisites
npm init -y
npm install playwright axios
npx playwright install chromium
You'll need:
- A ProxyGrow SOCKS5 proxy (host, port, username, password)
- Your rotation API URL (provided in your ProxyGrow credentials)
Basic Playwright + SOCKS5 Setup
Playwright accepts a proxy option directly in chromium.launch() or browser.newContext().
const { chromium } = require('playwright');
const PROXY_HOST = 'your-proxy-host.proxygrow.net';
const PROXY_PORT = 10500;
const PROXY_USER = 'your-username';
const PROXY_PASS = 'your-password';
(async () => {
const browser = await chromium.launch({
proxy: {
server: `socks5://${PROXY_HOST}:${PROXY_PORT}`,
username: PROXY_USER,
password: PROXY_PASS,
},
});
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://httpbin.org/ip');
const body = await page.textContent('body');
console.log('Current IP:', JSON.parse(body).origin);
await browser.close();
})();
Run this and you will see a Ukrainian, Romanian, or Latvian mobile carrier IP — not your real IP or a datacenter address.
IP Rotation via API Between Requests
ProxyGrow provides a rotation endpoint. Calling it triggers an IP change on the modem (new CGNAT allocation from the carrier). This is the correct way to rotate — not by switching proxy servers.
const { chromium } = require('playwright');
const axios = require('axios');
const PROXY_HOST = 'your-proxy-host.proxygrow.net';
const PROXY_PORT = 10500;
const PROXY_USER = 'your-username';
const PROXY_PASS = 'your-password';
const ROTATION_URL = 'https://your-proxy-host.proxygrow.net/rotate?token=YOUR_TOKEN';
async function rotateIP() {
try {
const response = await axios.get(ROTATION_URL, { timeout: 10000 });
console.log('IP rotated:', response.data);
// Wait for the new IP to stabilize
await new Promise(resolve => setTimeout(resolve, 3000));
} catch (err) {
console.error('Rotation failed:', err.message);
}
}
async function scrapeWithRotation(urls) {
const browser = await chromium.launch({
proxy: {
server: `socks5://${PROXY_HOST}:${PROXY_PORT}`,
username: PROXY_USER,
password: PROXY_PASS,
},
});
for (const url of urls) {
// Rotate IP before each target
await rotateIP();
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36',
viewport: { width: 390, height: 844 },
locale: 'en-US',
});
const page = await context.newPage();
try {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
const title = await page.title();
console.log(`[${url}] Title: ${title}`);
} catch (err) {
console.error(`[${url}] Failed: ${err.message}`);
} finally {
await context.close();
}
}
await browser.close();
}
const targets = [
'https://example.com/product/1',
'https://example.com/product/2',
'https://example.com/product/3',
];
scrapeWithRotation(targets);
Key design decisions here:
- A new
contextis created for each request so cookies and storage don't carry over - User agent is set to a real Android mobile UA — matching the mobile carrier IP
- Viewport is set to mobile dimensions — again, consistent with the carrier type
- IP rotation happens before each context, giving each request a fresh IP
Handling Anti-Bot Detection
Modern anti-bot systems (Cloudflare, DataDome, PerimeterX, Akamai) analyze dozens of signals beyond IP address. Here are the patterns that matter most.
Match the User Agent to the IP Type
If your IP is a mobile carrier but your UA says HeadlessChrome/124, you're flagged immediately. Real mobile users don't run headless browsers.
Use a realistic Android or iOS user agent:
const mobileUA = 'Mozilla/5.0 (Linux; Android 14; SM-S928B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.82 Mobile Safari/537.36';
Disable the Headless Flag Properly
Playwright has a --headless=new mode that reduces detection, but some sites check for the Headless string in navigator properties. Use the headless: false option for the most resistant setup, or suppress bot-specific navigator properties:
const context = await browser.newContext();
await context.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3] });
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
});
Add Human-Like Delays
Bots scrape at machine speed. Human users pause, scroll, and click inconsistently. Add jitter:
function randomDelay(min = 800, max = 3000) {
const ms = Math.floor(Math.random() * (max - min + 1)) + min;
return new Promise(resolve => setTimeout(resolve, ms));
}
await page.goto(url);
await randomDelay();
await page.mouse.move(200, 300);
await randomDelay(200, 600);
await page.mouse.move(400, 500);
await randomDelay();
Handle Cloudflare Challenges
Cloudflare's Turnstile and JS challenges require JavaScript execution and a browser that passes the environment check. With Playwright + mobile proxy:
- Set
headless: falseor useheadless: 'new' - Wait for the challenge to resolve before proceeding
- Rotate IP only between sessions, not in the middle of a session
await page.goto(url);
// Wait for CF to resolve — up to 15s
await page.waitForFunction(
() => !document.title.includes('Just a moment'),
{ timeout: 15000 }
).catch(() => console.log('CF challenge may still be active'));
Production Pattern: Proxy Pool with Queue
For large-scale scraping with multiple proxies:
const { chromium } = require('playwright');
const axios = require('axios');
const PROXIES = [
{ host: 'proxy1.proxygrow.net', port: 10500, user: 'user1', pass: 'pass1', rotateUrl: 'https://...' },
{ host: 'proxy2.proxygrow.net', port: 10501, user: 'user2', pass: 'pass2', rotateUrl: 'https://...' },
{ host: 'proxy3.proxygrow.net', port: 10502, user: 'user3', pass: 'pass3', rotateUrl: 'https://...' },
];
let proxyIndex = 0;
function getNextProxy() {
const proxy = PROXIES[proxyIndex % PROXIES.length];
proxyIndex++;
return proxy;
}
async function scrapeURL(url) {
const proxy = getNextProxy();
// Rotate IP on the selected proxy
await axios.get(proxy.rotateUrl, { timeout: 10000 }).catch(() => {});
await new Promise(r => setTimeout(r, 2500));
const browser = await chromium.launch({
proxy: {
server: `socks5://${proxy.host}:${proxy.port}`,
username: proxy.user,
password: proxy.pass,
},
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36',
viewport: { width: 393, height: 852 },
});
const page = await context.newPage();
await page.goto(url, { waitUntil: 'networkidle', timeout: 45000 });
const html = await page.content();
await browser.close();
return html;
}
Best GEOs for Playwright Scraping
| Target Site GEO | Recommended ProxyGrow GEO | Carrier |
|---|---|---|
| US/Global | Romania | Orange Romania (AS8708) |
| EU-specific | Latvia | LMT (AS12578) |
| CIS/Eastern EU | Ukraine | Kyivstar (AS15895) |
| Any EU ecommerce | Romania or Latvia | Orange / LMT |
Romanian and Latvian IPs are EU-based, which passes geo-restrictions on EU-targeted sites and avoids GDPR-based access blocks.
Playwright vs. Puppeteer vs. Selenium
For proxy-heavy automation:
| Feature | Playwright | Puppeteer | Selenium |
|---|---|---|---|
| Native SOCKS5 | Yes | Requires flag | Limited |
| Multi-browser | Yes | Chromium only | Yes |
| Context isolation | Yes (fast) | Manual | Session-based |
| Stealth plugins | Playwright-extra | puppeteer-extra | Webdriver flags |
| Mobile UA simulation | First-class | Manual | Manual |
Playwright wins for proxy scraping because its context model maps cleanly to per-request proxy isolation without spawning new browser processes.
Quick Setup Checklist
✔ Install playwright and axios
✔ Use SOCKS5 (not HTTP) for better protocol coverage
✔ Set mobile user agent matching the carrier GEO
✔ Set mobile viewport (390x844 or similar)
✔ Create a new context per scrape session
✔ Rotate IP via API between sessions, not mid-session
✔ Add random delays between actions
✔ Suppress navigator.webdriver property
✔ Match locale to the proxy GEO
Common Mistakes
Using HTTP proxy instead of SOCKS5
HTTP proxies can't forward all traffic types. SOCKS5 is protocol-agnostic and handles WebSocket connections, binary protocols, and TLS more cleanly.
Rotating IP in the middle of a session
If you trigger a rotation while a page load is in progress, the connection drops and requests fail. Always rotate before opening a new browser context.
Desktop UA with mobile IP
A Kyivstar or LMT IP sending desktop Chrome traffic is a statistical anomaly — most mobile carrier traffic is from mobile devices. Use a matching mobile UA.
Not waiting after rotation
After calling the rotation endpoint, the modem needs 2-4 seconds to complete the reconnection and receive a new IP from the carrier. Skip this wait and your first request goes out on the old IP.
Start Scraping with Real Mobile IPs
SOCKS5 + rotation API from Ukraine, Romania, Latvia. Order via @ProxyGrow on Telegram.