What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Make Puppeteer faster by waiting only for the event that proves your data is ready, removing unnecessary requests and sleeps, reusing one browser process, and running a measured number of concurrent pages. The fastest configuration is site-dependent: concurrency must fit your CPU, memory, network, the target’s allowed rate, and the amount of work required to render each page.
- Use
domcontentloaded, a specific selector, or a specific response instead of waiting for all network activity. - Abort visual resources only when your extraction does not depend on them.
- Keep a browser alive and isolate jobs with pages or browser contexts.
- Use a bounded worker pool and measure tail latency, errors, bytes, and records per minute.
1. Measure the real bottleneck before changing Puppeteer
“Puppeteer is slow” can mean several different things: Chrome startup, page creation, DNS or network time, JavaScript rendering, extraction, or deployment throttling. Add timestamps around each stage for a representative URL set.
const started = performance.now();
const browser = await puppeteer.launch({headless: true});
console.log('launch_ms', performance.now() - started);
const page = await browser.newPage();
const pageReady = performance.now();
await page.goto(url, {waitUntil: 'domcontentloaded', timeout: 30000});
console.log('navigation_ms', performance.now() - pageReady);
const extracted = performance.now();
const rows = await page.$$eval('.product', nodes =>
nodes.map(node => ({
name: node.querySelector('.name')?.textContent?.trim(),
price: node.querySelector('.price')?.textContent?.trim()
}))
);
console.log('extraction_ms', performance.now() - extracted);
await browser.close();
Log median and tail (for example, p95) navigation and extraction time, timeout rate, bytes transferred, and successful records per minute. Change one variable at a time. Official Puppeteer material does not establish a universal percentage or “10x” speedup, so benchmark your own sites rather than promising one.
2. Wait for the earliest reliable readiness signal
Every wait should answer a question: “What proves the data I need is available?” Puppeteer supports navigation waits, selector waits, request waits, and response waits.
#1 Best Overall
| Readiness condition | Use it when | Why it is faster or safer |
|---|---|---|
domcontentloaded |
The required data is in the initial HTML | It does not wait for images, ads, analytics, or late connections |
| Specific selector | Client-side code inserts the records into a known element | Stops as soon as the evidence you need exists |
| Specific response | The data arrives from a known API request | Waits for the actual payload instead of unrelated page activity |
| Network idle | Network quiescence itself is the required condition | Useful in limited cases, but idle windows can be prolonged by polling, ads, analytics, or long-lived connections |
Initial-document extraction
await page.goto(url, {
waitUntil: 'domcontentloaded',
timeout: 30000
});
const title = await page.$eval('h1', el => el.textContent.trim());
Selector-based dynamic extraction
await page.goto(url, {waitUntil: 'domcontentloaded', timeout: 30000});
await page.waitForSelector('[data-testid="results"]', {
visible: true,
timeout: 15000
});
const records = await page.$$eval('[data-testid="result"]', nodes =>
nodes.map(node => node.textContent.trim())
);
Response-based extraction
await page.goto(url, {waitUntil: 'domcontentloaded', timeout: 30000});
const response = await page.waitForResponse(
response => response.url().includes('/api/products') &&
response.request().method() === 'GET',
{timeout: 15000}
);
const data = await response.json();
Start the response wait before the action that triggers it, usually with Promise.all. This avoids missing a fast response:
const [response] = await Promise.all([
page.waitForResponse(r => r.url().includes('/api/products')),
page.click('#load-more')
]);
const nextPage = await response.json();
3. Remove fixed sleeps instead of guessing delays
A fixed timeout makes every URL pay the worst-case delay and can still race a slow page. Replace a sleep with the selector, response, request, or navigation event that proves readiness. If the site has no reliable event, use the shortest bounded delay you can justify and keep a timeout around the operation; do not turn a large sleep into an unbounded wait.
4. Block only resources your scraper does not need
Request interception can reduce transfer, parsing, and rendering work when the result does not depend on visual assets. Images, fonts, media, and sometimes stylesheets are candidates. Validate the policy on each site:
- Blocking stylesheets can change layout-dependent selectors.
- Blocking scripts can remove the code that fetches or renders your data.
- Some pages use images, fonts, or media as part of a completed-state signal.
- Do not block authentication, API, or navigation requests that carry the records.
await page.setRequestInterception(true);
page.on('request', request => {
const type = request.resourceType();
if (['image', 'font', 'media'].includes(type)) {
request.abort();
} else {
request.continue();
}
});
await page.goto(url, {waitUntil: 'domcontentloaded', timeout: 30000});
There is no controlled, general benchmark that supports one universal percentage gain from interception. Compare successful records and timeout rates as well as elapsed time; a faster page that returns incomplete data is not an optimization.
5. Reuse the browser and isolate jobs
Launching a new browser for every URL adds process startup and memory overhead. Launch once, then create pages for independent jobs. Browser contexts provide separate user state when cookies, storage, or authentication must be isolated.
const browser = await puppeteer.launch({headless: true});
const context = await browser.createBrowserContext();
async function scrape(url) {
const page = await context.newPage();
try {
await page.goto(url, {waitUntil: 'domcontentloaded', timeout: 30000});
await page.waitForSelector('.record', {timeout: 10000});
return await page.$$eval('.record', nodes =>
nodes.map(node => node.textContent.trim())
);
} finally {
await page.close();
}
}
for (const url of urls) {
console.log(await scrape(url));
}
await context.close();
await browser.close();
Do not share a page between jobs that can navigate at the same time. Reuse a context only when sharing its cookies and storage is intentional; otherwise create a context per job or tenant.
6. Use bounded concurrency, not an unlimited tab loop
More pages improve throughput only until CPU, memory, network capacity, browser contention, target-site limits, or error rates become the bottleneck. A queue with a fixed worker count provides backpressure.
const workerCount = 4; // begin conservatively and measure
let next = 0;
const results = [];
async function worker() {
const page = await browser.newPage();
try {
while (true) {
const index = next++;
if (index >= urls.length) return;
const url = urls[index];
try {
await page.goto(url, {
waitUntil: 'domcontentloaded',
timeout: 30000
});
await page.waitForSelector('.record', {timeout: 10000});
results[index] = await page.$$eval('.record', nodes =>
nodes.map(node => node.textContent.trim())
);
} catch (error) {
results[index] = {url, error: String(error)};
}
}
} finally {
await page.close();
}
}
await Promise.all(Array.from({length: workerCount}, worker));
Raise the worker count gradually while watching CPU, resident memory, network throughput, timeout rate, and target-site responses. Back off when tail latency or failures rise. A per-host rate limiter may be necessary even when your machine can run more pages. Respect robots rules, terms of service, authentication requirements, privacy obligations, and explicit rate limits.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchRank #3
7. Keep headless configuration consistent
Puppeteer runs headless by default and controls Chrome or Firefox through the DevTools Protocol or WebDriver BiDi. Keep the mode and executable configuration explicit in production so local and deployed workers behave the same way.
const browser = await puppeteer.launch({
headless: true,
// executablePath: process.env.CHROME_PATH, // set when deployment requires it
args: []
});
Centralize launch defaults, navigation timeouts, selector timeouts, and viewport settings. Repeatedly discovering or installing a browser inside each job defeats the benefit of a persistent process.
8. Reduce work inside each page
Extract in one browser evaluation
Many round trips between Node.js and the page are slower than one evaluation that maps all records. Select only the fields you need and return plain data, not DOM nodes.
Use the site’s data response when permitted
If a page exposes the records in a response you are authorized to access, waiting for that response can avoid traversing a large rendered DOM. Keep the browser flow when JavaScript execution, session state, or policy requires it.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Control navigation scope
Set finite navigation and selector timeouts, close pages in finally blocks, and avoid opening a new tab for every pagination step. Reuse a page for sequential URLs only when state leakage is impossible; otherwise use separate contexts.
9. Fix deployment bottlenecks before adding workers
Puppeteer’s troubleshooting guidance documents a Google Cloud Run pattern in which CPU is disabled after an HTTP response. Background browser work can then appear to take minutes even though the code is still running. For that deployment pattern, keep CPU allocated for background work. Also profile launch, page creation, navigation, extraction, and teardown separately; adding concurrency cannot repair a worker that is being starved of CPU.
Chrome for Testing downloads are approximately 170 MB on macOS, 282 MB on Linux, and 280 MB on Windows in the current Puppeteer installation documentation. Those are download sizes, not scraping-speed benchmarks. Account for browser memory and disk startup costs when sizing containers.
10. Troubleshoot common speed and correctness failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Every URL takes roughly the same extra time | Fixed sleep or an unnecessarily long idle wait | Replace it with domcontentloaded, a selector, or a specific response wait |
| Requests never become idle | Analytics, polling, ads, or persistent connections | Stop using a network-idle condition unless quiescence is truly required |
| Records are missing after interception | A blocked script, stylesheet, API request, or image was required | Log resource types, allow the required request, and test selectors with interception disabled |
| Timeouts increase sharply with worker count | CPU, memory, network, or target rate limit is saturated | Reduce workers, add queue backpressure, and compare p95 latency and error rate |
| Background jobs stall on Cloud Run | CPU was disabled after the HTTP response | Configure the service to keep CPU allocated for the background pattern |
| Jobs contaminate one another | Shared cookies, local storage, or a page used concurrently | Use separate browser contexts or pages and close each job in a finally block |
| Browser startup dominates the run | A browser is launched for every URL | Launch once and reuse the process |
Or skip the browser setup
If your job is to produce a clean visual capture rather than extract records, ScreenshotNeo provides a single-request screenshot API. It accepts consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response reports the result in X-Page-Verdict and X-Billed headers.
The API supports PNG, JPEG, WebP, and PDF output, full-page lazy-image loading, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, custom CSS and JavaScript, clicks, selector or network-idle waits, request and resource blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.
Best Value
It also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. This is for screenshot and page-information workflows; it is not a substitute for a Puppeteer data-extraction pipeline.
cURL
See the ScreenshotNeo API documentation for all options.
curl -G "https://api.screenshotneo.com/v1/shot"
-d access_key=YOUR_API_KEY
--data-urlencode url=https://stripe.com
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
const bytes = Buffer.from(await res.arrayBuffer());
await require('node:fs').promises.writeFile('shot.webp', bytes);
ScreenshotNeo’s Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan. Create a free ScreenshotNeo account to try it.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

