Skip to main content
Web scraping

Published Aug 16, 2024 · Updated Oct 14, 2026

Web Scraping With JavaScript: Methods, Tools, and Working Examples

Learn how to scrape static and JavaScript-heavy websites with Node.js, Cheerio, JSON endpoints, Playwright, and a tested method-selection workflow.

Web Scraping With JavaScript: Methods, Tools, and Working Examples

Web scraping with JavaScript can be as small as one fetch() call and a Cheerio parser, or as involved as a browser crawler with queues, sessions, retries, and proxy routing. The correct method depends on where the data exists—not simply on whether the website uses JavaScript.

This guide builds a working Node.js scraper for static HTML, diagnoses a dynamic page, collects its underlying JSON, and then handles the same page with Playwright. It also explains when Puppeteer, Crawlee, a managed scraping API, or a proxy belongs in the stack.

Quick Answer

Use Node.js fetch() and Cheerio when the data is present in the initial HTML response. If the visible page is empty in that response, inspect its scripts and Fetch/XHR traffic for an authorized JSON endpoint. Use Playwright or Puppeteer when the result requires JavaScript execution, browser state, scrolling, clicking, or visual verification. Add Crawlee when one script becomes a scheduled multi-page crawl, and choose a managed scraping API when you do not want to operate the retrieval and browser infrastructure yourself.

Key Takeaways

  • Start with the response, not a browser. Server-rendered pages and public JSON endpoints are usually cheaper and simpler to collect than fully rendered pages.
  • A JavaScript-heavy website may still expose clean JSON. Inspect Fetch/XHR requests before committing every URL to Playwright.
  • Cheerio parses markup but does not execute page JavaScript. An HTTP 200 can still produce zero useful elements when the browser builds the page later.
  • Playwright and Puppeteer control browsers. Use them when rendering, cookies, interaction, screenshots, or browser-only behavior is genuinely part of the requirement.
  • Client-side and Node.js scraping are different. Browser scripts face same-origin and CORS controls; a server-side Node.js collector is usually the practical JavaScript architecture.
  • A crawler framework solves orchestration. Crawlee adds queues, concurrency, retries, sessions, storage, and HTTP/browser crawler choices; it does not make source-specific validation unnecessary.
  • A proxy is a separate network layer. It can provide an exit location or distribute authorized collection, but it does not render JavaScript, parse data, grant permission, or guarantee access.
  • Validate records, not response codes. Confirm the page, entity, required fields, source URL, and collection time before accepting an observation.

JavaScript Web Scraping Methods Compared

MethodUse it whenMain advantageMain limitation
Supported API, feed, or exportThe publisher provides the required data through an approved interfaceStable schema and clearer access modelMay have quotas, narrower coverage, or licensing conditions
Node.js `fetch()` + CheerioRequired data is in the initial HTMLLightweight, transparent, and easy to scale carefullyDoes not execute page JavaScript
Embedded JSON extractionData is serialized into a script or hydration payload in the responseAvoids a browser while preserving structured dataFormat is source-specific and may change
Direct JSON or GraphQL requestThe page fetches the required data from an endpoint you are permitted to useStructured output with less rendering overheadAuthentication, signatures, terms, and endpoint changes may apply
Playwright or PuppeteerJavaScript, browser state, interaction, or rendered evidence is requiredReproduces browser behavior and exposes network eventsHigher CPU, memory, bandwidth, and operational cost
Crawlee or another crawler frameworkThe job needs queues, deduplication, concurrency, retries, sessions, and storageTurns individual requests into an operated crawlMore framework than a one-page script needs
Managed scraping APIA provider should operate some or all of retrieval, rendering, retries, or extractionLess infrastructure to build and maintainLess low-level control and a different billing model

The usual decision order is:

bash

This is a cost-and-complexity order, not an access-control ladder. Do not use a lower-level endpoint to bypass authentication, authorization, a paywall, or another technical restriction.

Client-Side JavaScript vs Node.js Web Scraping

JavaScript can run in a web page or outside the browser through Node.js. Those environments have different constraints.

Code running in a normal browser page is subject to the same-origin policy. Cross-origin reads normally require the destination to allow them through CORS. Browser code is also visible to the user, so it is not a safe place for private API keys or proxy credentials.

Node.js runs the scraper on your machine or server. It can make HTTP requests, use local files and databases, schedule work, keep credentials in environment variables, and launch browser automation. It is not exempt from the destination's rules or applicable law, but it is usually the right execution environment for a maintainable JavaScript scraper.

RequirementBrowser-page JavaScriptNode.js
Read another origin without that origin allowing CORSUsually noTechnically possible, subject to permission and policy
Keep server credentials away from end usersNoYes, when handled correctly
Write JSON or CSV directly to local/server storageRestricted browser mechanismsYes
Run scheduled workers and queuesNot a practical defaultYes
Control Chromium, Firefox, or WebKit through PlaywrightNoYes
Parse current page DOMYesYes, after fetching or rendering it

Do not “solve” a CORS error by telling users to disable browser security. Move an authorized collection job to a controlled server-side environment instead.

How to Tell Which Scraping Method a Website Needs

Before choosing a library, determine where the required data appears.

1. Inspect the initial response

Use View Source, cURL, or a Node.js request. Search the returned document for the value or element you need. If it is present, use an HTTP client and parser.

Do not rely only on the browser's Elements panel. That panel shows the live DOM after scripts may have changed it; View Source or the raw response shows what the server originally returned.

2. Look for embedded structured data

Some pages include JSON-LD, application state, or framework hydration data inside <script> elements. Extracting that data can be more stable than reading presentation-oriented classes, provided the source permits the collection and you validate the schema.

Examples include:

  • <script type="application/ld+json">;
  • a serialized state object assigned to a global variable;
  • Next.js or another framework's hydration payload;
  • JSON stored in data-* attributes.

Do not use regular expressions as a general-purpose HTML parser. Parse the document, locate the relevant script element, and then parse its text as JSON when it is valid JSON.

3. Inspect Fetch/XHR requests

Open the browser's Network panel, filter for Fetch/XHR, reload the page, and perform the action that reveals the data. Check the request URL, method, query parameters, request payload, response type, pagination, and required session state.

A JSON endpoint can be the best method when it is documented or otherwise permitted for your workflow. It can also be brittle: an internal endpoint may change without notice, require signed parameters, or be limited by the website's terms. Never copy private tokens, session cookies, or another user's credentials into a scraper.

Playwright can observe the same traffic. Its network APIs expose requests, responses, routing, and page.waitForResponse().

4. Identify required browser behavior

Use a real browser when the outcome depends on:

  • client-side JavaScript rendering;
  • clicking, typing, scrolling, or selecting controls;
  • browser-managed cookies, local storage, or navigation state;
  • a canvas, visual layout, screenshot, or responsive presentation;
  • a browser-supported authentication flow you are authorized to automate;
  • a network call whose required state is legitimately created by the page.

A dynamic website does not automatically require a browser for every page. Hybrid crawlers often use a browser to diagnose or establish authorized state, then use direct HTTP for suitable high-volume requests.

Tested JavaScript Scraping Tutorial

The following examples use Quotes to Scrape, a public practice website designed for scraping exercises. We tested four observations against its static and infinite-scroll pages.

Test environment and results

ObservationMethodActual result
Static HTML pageNode.js `fetch()` + Cheerio10 complete quote records written to JSON
Infinite-scroll page before renderingNode.js `fetch()` + CheerioHTTP 200, zero `div.quote` elements, and an `/api/quotes` reference in the response
JSON endpointNative `fetch()` over two bounded pages20 complete quote records written to JSON
Infinite-scroll page after renderingPlaywright Chromium10 records initially and 20 after one verified scroll

The final runs used Node.js 24.15.0, Cheerio 1.2.0, Playwright 1.64.0, and Playwright's bundled Chromium 156.0.8078.4 on October 14, 2026. These results demonstrate the method-selection process on this practice site; they are not a universal speed or reliability benchmark.

Set up the project

Use a maintained Node.js release with stable global fetch(). Node's global fetch() documentation identifies it as stable. Run:

bash

The Python package and browser installation steps found in some Playwright tutorials do not apply here; this project uses the Node.js package.

Method 1: Scrape Static HTML With fetch() and Cheerio

Node.js provides fetch() for HTTP requests. Cheerio parses the returned markup and supports CSS selectors, but it does not run scripts or reproduce a browser.

Save this complete file as static-html.mjs:

javascript

Run it:

bash

Observed output:

bash

This example checks the HTTP status, enforces a timeout, validates required fields, retains the source URL, and writes structured output. That is more reliable than logging whatever a selector happens to return.

Method 2: Diagnose a JavaScript-Rendered Page

Now request the practice site's infinite-scroll page without a browser. Save this as diagnose-rendering.mjs:

javascript

Run it:

bash

Observed output:

bash

The HTTP request succeeded, but the required records were absent. The response also revealed that the page loads data from /api/quotes. This is why “HTTP 200” and “successful scrape” are not equivalent.

Method 3: Collect a JSON Endpoint Directly

The practice page makes a permitted JSON request and passes a page number. This example requests two pages, checks the response shape, normalizes the records, and stops early if the endpoint reports no next page.

Save it as json-endpoint.mjs:

javascript

Observed output:

bash

The MAX_PAGES limit makes the example bounded. A production crawler should also deduplicate records, checkpoint progress, respect rate limits, and record per-page failures. See the pagination in web scraping guide for numbered pages, next links, cursors, Load More controls, and infinite scroll.

When direct endpoint collection is the wrong choice

Do not replay an internal request when:

  • an official API or licensed export is the required interface;
  • the endpoint requires authorization you do not have;
  • reproducing it would bypass an access control or technical restriction;
  • the data is private, sensitive, or outside your permitted scope;
  • the endpoint's terms prohibit the proposed use;
  • the browser presentation itself is the evidence you need.

Even when collection is authorized, private endpoints can change without versioning. Treat them as source-specific integrations with monitoring and maintenance—not permanent public APIs.

Method 4: Scrape Dynamic Content With Playwright

Playwright launches a real browser engine, executes page JavaScript, and lets Node.js interact with the rendered DOM. Use explicit conditions instead of arbitrary sleeps.

Save this complete file as dynamic-playwright.mjs:

javascript

Run it:

bash

Observed output:

bash

The script waits for an actual quote card, records the initial count, scrolls once, and waits until the count increases. It does not claim to collect the entire site. If another batch does not arrive, the script fails visibly instead of presenting the first batch as a successful infinite-scroll scrape.

serviceWorkers: "block" keeps service-worker behavior consistent when network inspection or interception is important. Playwright notes that service workers can otherwise hide requests from native routing and network events.

Method 5: Observe Network Responses With Playwright

A browser can help diagnose a dynamic page without forcing the final collector to parse the rendered DOM. Start the response wait before the action that triggers the request:

javascript

This is useful for understanding which response populates a component or follows an interaction. It does not automatically authorize replaying that request outside the browser.

For applications that use service workers, WebSockets, GraphQL, signed requests, or several dependent calls, the network model may be more complicated than one Fetch/XHR response. Preserve the session and verify the final data rather than assuming one intercepted response is complete.

Playwright, Puppeteer, Cheerio, or Crawlee?

These tools occupy different layers.

ToolWhat it doesChoose it whenDo not expect it to provide
CheerioParses HTML or XML and exposes selectorsYou already have markup and do not need JavaScript executionHTTP orchestration, browser state, or rendering
PlaywrightControls Chromium, Firefox, and WebKit browser enginesRendering, interaction, browser contexts, network observation, or cross-browser behavior mattersA complete crawl frontier and business-specific validation
PuppeteerControls supported Chrome/Chromium and Firefox builds through JavaScriptYour team prefers its API and browser coverage fits the taskWebKit or a complete crawler by itself
CrawleeProvides HTTP and browser crawlers, request queues, concurrency, sessions, retries, and storage patternsThe project has outgrown a one-file scriptA stable extraction schema for an unknown website

Cheerio is not a lighter Playwright; it is a parser. Playwright and Puppeteer are not full crawler architectures; they are browser automation libraries. Crawlee's JavaScript documentation exposes CheerioCrawler, PuppeteerCrawler, and PlaywrightCrawler, letting a project choose HTTP or browser workers while retaining common orchestration patterns.

For a wider category comparison, use the web scraping tools guide. It separates HTTP clients, parsers, crawler frameworks, browser automation, hosted browsers, document-oriented tools, scraping APIs, and proxy infrastructure.

When to Use a Managed Scraping API

A managed scraping API can operate some combination of HTTP retrieval, browser rendering, proxy selection, retries, and extraction behind an API request. It can be the right choice when the team values a smaller operations burden more than low-level control.

Build and operate your own JavaScript scraper when:

  • the team needs exact control over navigation, parsing, validation, or storage;
  • the sources have stable structures your engineers can maintain;
  • the existing system already provides scheduling and observability;
  • raw responses or browser traces must remain in your infrastructure;
  • the workload's economics favor your own collector.

Use a managed API when:

  • operating browser workers and retrieval infrastructure is not a core competency;
  • the API provides a required result or feature your team would otherwise build;
  • faster integration is worth the provider dependency and billing model;
  • the provider's compliance, retention, and data-processing terms fit the project.

This is not the same decision as choosing a proxy. The raw proxies versus scraping APIs guide explains who owns fetching, rendering, retries, parsing, and maintenance in each model.

How Proxies Fit Into JavaScript Web Scraping

A small scraper may work correctly over its direct connection. Add a proxy only when the authorized workload has a defined network requirement, such as:

  • observing public content from a required country or city;
  • distributing permitted collection across workers and routes;
  • keeping a related sequence on one sticky session;
  • separating customer, project, or test traffic;
  • reproducing a location-dependent application result.

Use residential proxies when the project needs broad country, city, or ISP coverage. Use mobile proxies when a real mobile-network context is part of the requirement. Neither is a universal upgrade over direct or datacenter access.

Configure a proxy in Playwright

Keep credentials outside the source file. Replace the chromium.launch() line in the browser example with:

javascript

Use the exact server, port, username, and password generated by the dashboard. Do not guess credential parameters or publish real credentials. The Playwright proxy guide covers complete Python and Node.js setups.

Browser contexts and proxy sessions are different. A context controls cookies and browser storage; the proxy service controls network routing and exit selection. Keep a stable proxy session for dependent steps and verify the observed exit location when geography matters.

For direct Node.js HTTP requests, the built-in fetch() implementation accepts an Undici-compatible dispatcher. Undici documents ProxyAgent for HTTP(S) proxy routing. Test the exact client and protocol combination you plan to deploy rather than assuming a browser proxy configuration also covers independent Node.js requests.

How to Make a JavaScript Scraper Reliable

A working selector is only the beginning. Production collection needs explicit controls around scope, failure, and data quality.

Validate the result

Check more than the status code:

  • expected page or entity identifier;
  • required fields and allowed types;
  • record count bounds;
  • source URL and retrieval timestamp;
  • requested and observed location, if location matters;
  • duplicate keys and pagination continuity;
  • login, CAPTCHA, error, and empty-state indicators.

Do not convert a failed collection into a business event. For example, a timeout on a careers page does not prove that every previous job was removed.

Bound concurrency and retries

Set a concurrency limit that the source and your infrastructure can handle. Retry only failures that are plausibly transient, use backoff, and cap the retry budget. A repeated 401, 403, validation failure, or changed selector usually needs diagnosis—not an infinite retry loop.

Honor Retry-After when applicable. Record the final failure reason instead of silently dropping the URL.

Reuse connections and browser contexts intentionally

HTTP clients and browsers can reuse connections. That improves efficiency, but it also affects cookies, sessions, proxy rotation boundaries, and per-connection behavior. Decide which state should persist between pages and which should be isolated.

For browser crawls, several pages can share a browser process while separate contexts isolate cookies. Close pages and contexts on both success and failure so leaks do not slowly exhaust workers.

Choose stable selectors

Prefer semantic attributes, structured fields, stable identifiers, and relationships tied to the data model. Long generated CSS paths are fragile. Selectors should fail loudly when the page no longer matches the expected schema.

Store provenance with the data

At minimum, retain:

  • source URL;
  • collection timestamp;
  • relevant request parameters or location;
  • parser/schema version;
  • required raw evidence or a permitted reference to it;
  • validation status and failure reason.

Use JSON for nested records and API-shaped data. Use CSV for flat tables that must interoperate with spreadsheet and analytics tools. The JSON versus CSV guide explains the tradeoffs.

Separate crawling from scraping

Scraping extracts fields from a response. Crawling discovers, schedules, prioritizes, revisits, and deduplicates URLs. A one-page tutorial can combine both, but a production system should make the responsibilities explicit. See What Is Web Crawling? for crawl scope, frontiers, canonicalization, and revisit policy.

Common JavaScript Scraping Problems

SymptomLikely causeWhat to check
HTTP 200 but selectors return nothingData is rendered later, the wrong page was returned, or selectors changedInspect the raw response, page title, embedded data, and Fetch/XHR traffic
`fetch()` works in Node but not in page JavaScriptBrowser same-origin or CORS policyRun authorized collection server-side; do not disable browser security
Playwright hangs at `networkidle`The page keeps background connections openWait for the specific element, response, URL, or count change that proves readiness
Script saves a login or error page as dataStatus-only validation accepted the wrong documentValidate entity markers, content type, title, and required fields
Infinite scroll returns only the first batchThe script did not trigger or verify another loadScroll or click, then wait for a response or record-count increase
Duplicate records appearPagination overlaps or retries repeat successful workUse a stable, source-scoped record key and checkpoint pages
`429 Too Many Requests`Request rate exceeds the source's policy or current capacityReduce concurrency, honor retry instructions, and review the supported interface
`401` or proxy `407`Destination or proxy authentication failedConfirm credential scope, formatting, and expiration; never fall back silently
Public IP or location is unchangedThe relevant client is not using the proxyConfigure the proxy in the same HTTP client or browser context and verify there
Browser memory keeps increasingPages, contexts, traces, or downloads are not being closedUse `try`/`finally`, bound parallel browsers, and monitor worker resources

Responsible and Compliant Collection

Use web scraping only for lawful, authorized purposes. Review applicable law, contractual terms, privacy obligations, intellectual-property rights, and the source's technical policies before collection. Minimize personal data and retain only what the project needs.

The standardized Robots Exclusion Protocol lets services publish crawler preferences, but the RFC explicitly states that those rules are not access authorization. A permissive robots.txt does not replace permission, and a restrictive one should not be treated as the only factor in a compliance review.

Do not use the techniques in this guide to bypass authentication, access private or paywalled material without permission, overload a service, evade a technical restriction, or collect sensitive personal data unlawfully. Proxidize services are governed by the Acceptable Use Policy.

Conclusion

The best JavaScript web scraping method is the smallest one that produces a valid, permitted result. Start with a supported API or the initial response. Parse static HTML with native fetch() and Cheerio. Inspect embedded data and network responses when the browser loads content separately. Add Playwright or Puppeteer only when the workflow genuinely requires JavaScript execution, browser state, interaction, or visual evidence.

When the job grows, add crawl orchestration, validation, storage, observability, and carefully bounded retries. Add a proxy for a defined network or location requirement—not as a substitute for correct extraction logic or permission.

Frequently asked questions

Yes. Server-side JavaScript running in Node.js can request HTML or JSON, parse documents, control browsers, write files, connect to databases, and run crawler frameworks. Browser-page JavaScript is more constrained by same-origin and CORS policies.

There is no single best library. Use Cheerio to parse HTML you already have, Playwright or Puppeteer when a real browser is required, and Crawlee when you need queues, concurrency, sessions, retries, and storage around HTTP or browser workers.

Not necessarily. Maintained Node.js releases include stable global fetch(), which is sufficient for many examples. Axios, Undici, and other clients may still be useful when their APIs or connection controls better fit the application.

Cheerio can parse the HTML string it receives, but it does not execute page JavaScript. It works on a dynamic website only when the required data is already present in the response, embedded in the markup, or retrieved separately from a suitable endpoint.

First inspect the initial HTML and network traffic. If a permitted JSON endpoint contains the data, request and validate that response directly. If rendering, browser state, or interaction is required, use Playwright or Puppeteer and wait for a meaningful element, response, or state change.

It depends on the project. Playwright supports Chromium, Firefox, and WebKit and provides browser contexts and network tooling. Puppeteer is a strong JavaScript option for supported Chrome/Chromium and Firefox workflows. Compare required browser coverage, APIs, team experience, and deployment constraints.

Use Node.js for most maintained collectors because it can protect server credentials, write to storage, run schedules, and avoid browser-page CORS restrictions. Use browser-page JavaScript only for appropriate same-origin tasks or an extension/application specifically designed for that environment.

Not always. Start with the simplest permitted route that works. A proxy is useful when the authorized task requires a specific exit location, route separation, sticky network session, or distributed traffic. It does not execute JavaScript or guarantee a successful response.

Inspect the network request that loads each batch. If direct collection is permitted, reproduce the endpoint's cursor or page parameter with a strict limit. Otherwise, use a browser to scroll or click, then wait for the record count or a relevant network response to change. Stop on a documented terminal condition and deduplicate records.

The answer depends on the jurisdiction, data, access method, contractual terms, privacy obligations, and intended use. Public visibility alone does not settle every legal or contractual question. Obtain legal advice for consequential or high-risk projects and do not bypass access controls or collect restricted data without authorization.

Ready to launch?

Proxies built for real operations.

For teams that depend on stability, not luck.