# CSS Selector Extractor for JavaScript Sites (Browser) (`webdatatools/css-selector-extractor-js`) Actor

Extract any CSS selector from pages that need JavaScript: a real Chromium browser renders each page, waits for content, scrolls or clicks "load more", then returns text, HTML, attributes, tables or counts. One row per page, pay per page. For static pages use the cheaper CSS Selector Extractor.

- **URL**: https://apify.com/webdatatools/css-selector-extractor-js.md
- **Developed by:** [Murat Uzun](https://apify.com/webdatatools) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 pages

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## CSS Selector Extractor for JavaScript Sites (Browser)

Extract any CSS selector from pages that need JavaScript. A real Chromium browser (Crawlee PlaywrightCrawler) opens each
page, waits for the content, can click "load more" and scroll infinite lists, then runs your selectors on the rendered
HTML. You get text, HTML, attributes, tables or counts, one dataset row per page. No login.

Pay per use: **$0.005 per run start** (browser launch, default 1 GB memory) plus **$0.003 per page** on the free plan, lower on higher Apify plans. Failed pages produce a row with `error`
set and the other fields `null`.

### When to use this Actor, and when not to

| Use this one (browser) when | Use the sibling [CSS Selector Extractor](https://apify.com/webdatatools/css-selector-extractor) (HTTP only) when |
|---|---|
| The data appears only after JavaScript runs (SPA, React/Vue pages, client-side lists) | The HTML already contains the data (view-source shows it) |
| You need "wait for element", "click load more" or infinite scroll | You only need the page as served |
| The page is empty or shows "Enable JavaScript" to a plain fetch | Speed and cost matter most |

The browser is slower and uses more memory (1024 MB default), so the sibling is cheaper and faster on static sites.
(Run durations were not recorded for these cloud tests, so no speed figure is quoted here.) Use the same JSON input in both: the field names and the output rows are the same.

### Quick start

Default input loads `https://quotes.toscrape.com/js/` and returns the quote text and author of each quote as items:

```json
{
  "urls": ["https://quotes.toscrape.com/js/"],
  "itemSelector": ".quote",
  "selectors": [
    { "name": "text", "selector": ".text", "type": "text" },
    { "name": "author", "selector": ".author", "type": "text" }
  ]
}
```

### Examples

**Follow pagination (3 pages):** add `linkSelector` and `maxPages`. Each followed page is one result and billed as one page.

```json
{
  "urls": ["https://quotes.toscrape.com/js/"],
  "itemSelector": ".quote",
  "selectors": [{ "name": "text", "selector": ".text", "type": "text" }],
  "linkSelector": "li.next a",
  "maxPages": 3
}
```

**Infinite scroll:** `scrollToBottom` scrolls until the page stops growing, up to `maxScrolls`.

```json
{
  "urls": ["https://quotes.toscrape.com/scroll"],
  "itemSelector": ".quote",
  "selectors": [{ "name": "text", "selector": ".text", "type": "text" }],
  "scrollToBottom": true,
  "maxScrolls": 5
}
```

**"Load more" button:** `clickSelector` clicks the element again and again until it is gone or hidden, up to `maxClicks`.

```json
{
  "urls": ["https://example.com/listing"],
  "selectors": [{ "name": "items", "selector": ".product", "type": "count" }],
  "clickSelector": "button.load-more",
  "maxClicks": 10
}
```

**Wait for content:** `waitForSelector` waits up to `waitTimeoutSecs` for the element before reading the page. If it never
appears, the page is still read and the row's `renderNotes` says so.

### Browser options

| Field | Default | What it does |
|---|---|---|
| `waitForSelector` | none | Element that must appear before the page is read |
| `waitTimeoutSecs` | 15 | How long to wait for it (1 to 60) |
| `waitUntil` | `load` | `domcontentloaded` (fastest), `load`, or `networkidle` (slowest) |
| `scrollToBottom` | false | Scroll repeatedly for infinite lists |
| `maxScrolls` | 10 | Scroll steps per page (1 to 100) |
| `clickSelector` | none | Button or link to click for "load more" |
| `maxClicks` | 5 | Clicks per page (1 to 50) |
| `blockResources` | true | Blocks images, fonts and media while rendering (cheaper, faster) |

Order of steps on each page: wait for `waitForSelector`, then `clickSelector` clicks, then `scrollToBottom`, then the
selectors run on the final HTML.

### The shared options

`urls`, `selectors` (up to 25; `text`, `html`, `attr`, `count`, `table`), `firstMatchOnly`, `maxMatchesPerSelector`,
`trimWhitespace`, `itemSelector` and `maxItemsPerPage`, `linkSelector`, `sameDomainOnly`, `urlPattern`, `urlTemplate` with
`pageRange`, `sitemapUrls`, `maxPages`, `includeMetadata`, `headers`, `cookies`, `proxyMode`, `proxyConfiguration`,
`inputDatasetId` and `inputField`. They work the same as in the sibling Actor.

Differences from the sibling:

- `maxConcurrency` defaults to 3 and is capped at 10 (each page is a browser tab).
- `proxyMode`: `always` routes every page through Apify Proxy. `auto` and `never` load pages directly. A blocked page
  (HTTP 403, 429, 503 or a short challenge page) is reported as an error row. This Actor does not switch to the proxy on
  its own for a blocked page; set `proxyMode` to `always` for those sites.

### Output

One row per page:

```json
{
  "url": "https://quotes.toscrape.com/js/",
  "finalUrl": "https://quotes.toscrape.com/js/",
  "statusCode": 200,
  "contentType": "text/html; charset=utf-8",
  "fields": null,
  "matchCounts": null,
  "htmlSizeBytes": 8985,
  "usedProxy": false,
  "scrapedAt": "2026-10-11T17:02:57.954Z",
  "error": null,
  "items": [{ "text": "...", "author": "Albert Einstein" }],
  "itemCount": 10
}
```

`fields` and `matchCounts` appear in page mode (no `itemSelector`); `items` and `itemCount` in item mode. `renderNotes`
appears only when a browser step did not finish as asked. `foundOn` appears on pages reached by following links.
A row over 9 MB is replaced by an error row with the fields left out.

### Using the API, Python and n8n

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('webdatatools/css-selector-extractor-js').call({ urls: ['https://quotes.toscrape.com/js/'], itemSelector: '.quote', selectors: [{ name: 'text', selector: '.text', type: 'text' }] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient(token)
run = client.actor("webdatatools/css-selector-extractor-js").call(run_input={"urls": ["https://quotes.toscrape.com/js/"], "itemSelector": ".quote", "selectors": [{"name": "text", "selector": ".text", "type": "text"}]})
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
```

In n8n, use the Apify node with this Actor's ID and the same JSON input.

### Cost

Each run pays $0.005 once at start (one event per GB of memory; the default 1 GB is one event), then $0.003 per page on the free plan; higher plans pay less per page. 100 pages on the free plan: $0.005 + $0.30. Failed pages are
still rows in the dataset and are billed like any other row. Set **Maximum cost per run** to cap spending. The Actor stops
at what the budget covers and says so in the status message.

### Feedback

Found a bug or a page that does not render? Leave a review at
https://apify.com/webdatatools/css-selector-extractor-js/reviews and include the input (without any cookies or headers) and
the URL that failed.

# Actor input Schema

## `urls` (type: `array`):

Enter one URL per row. Each page is opened in a real Chromium browser, JavaScript runs, and the selectors are read from the rendered HTML. Each page gets exactly one result row, even if loading fails. Rows are saved as pages finish; if the run timeout comes first, the run stops early and still ends SUCCEEDED.

## `selectors` (type: `array`):

Enter the fields to extract as a JSON array of {"name", "selector", "type"} objects, read from the rendered page. "type" is text, html, attr, count or table; "attr" also needs "attribute" (e.g. {"name":"canonicalUrl","selector":"link\[rel='canonical']","type":"attr","attribute":"href"}). "table" returns the matched <table> as an array of row objects keyed by its header row. Capped at 25 entries per run.

## `firstMatchOnly` (type: `boolean`):

Keep this on to return only the first element each selector matches. Turn it off to return every match (up to Max matches per selector) as an array.

## `maxMatchesPerSelector` (type: `integer`):

Enter the most matches to return for one selector when First match only is off, e.g. 20. Does not limit the reported match count, only how many values are returned.

## `trimWhitespace` (type: `boolean`):

Keep this on to collapse runs of whitespace and trim the ends of extracted text and attribute values. Turning it off returns text and attr values exactly as found in the page.

## `maxConcurrency` (type: `integer`):

Enter how many pages to render in parallel (each one is a browser tab). Default 3 fits the default 1024 MB memory; raise it only with more memory. Capped at 10.

## `waitForSelector` (type: `string`):

Optional. A CSS selector that must appear before the page is read, e.g. ".quote". If it does not appear within the wait timeout the page is read anyway and the row notes it in "renderNotes".

## `waitTimeoutSecs` (type: `integer`):

How long to wait for the "Wait for selector" element, e.g. 15.

## `waitUntil` (type: `string`):

When navigation counts as loaded. 'domcontentloaded' is fastest; 'load' (default) waits for images and scripts; 'networkidle' waits until the network is quiet (slow on busy pages).

## `scrollToBottom` (type: `boolean`):

Scroll to the bottom of the page repeatedly so infinite-scroll content loads. Stops when the page stops growing or after Max scrolls.

## `maxScrolls` (type: `integer`):

Most scroll steps per page when "Scroll to bottom" is on, e.g. 10.

## `clickSelector` (type: `string`):

Optional. A button or link to click repeatedly to load more content, e.g. "button.load-more". Clicking stops when the element is gone or hidden.

## `maxClicks` (type: `integer`):

Most clicks per page for "Click selector", e.g. 5.

## `blockResources` (type: `boolean`):

Blocks images, fonts and video while rendering: faster and cheaper (less traffic). Turn it off only if the data you need is loaded by those resources.

## `itemSelector` (type: `string`):

Optional. A CSS selector for repeated items, e.g. ".quote" or "div.product-card". When set, each matching element becomes one object in the row's "items" array, and every selector above is searched only inside that element. The row is still one per page.

## `maxItemsPerPage` (type: `integer`):

Optional. Most items to return per page in item mode (default 200). "itemCount" always reports the full number.

## `linkSelector` (type: `string`):

Optional. A CSS selector for links to follow, e.g. "a.next" for pagination or "a.product-link" for detail pages. Every followed page is extracted the same way and billed as one page. Capped by Max pages.

## `sitemapUrls` (type: `array`):

Optional. Sitemap or sitemap-index URLs, e.g. https://example.com/sitemap.xml. Every page URL listed (gzip and nested indexes handled) is added to the run, capped by Max pages.

## `urlPattern` (type: `string`):

Optional regular expression. Only followed links and sitemap URLs matching it are processed, e.g. "/products/".

## `urlTemplate` (type: `string`):

Optional. A URL with a {page} placeholder, e.g. "https://books.toscrape.com/catalogue/page-{page}.html". One URL per page number in Page range is added to the run (up to 10,000), after the URLs above. Each page is one result.

## `pageRange` (type: `object`):

Used with URL template: {"from": 1, "to": 10} and optionally "step" (e.g. 20 for offset-style URLs like ?start={page}). Inclusive; from defaults to 1.

## `maxPages` (type: `integer`):

Optional. Total page cap when following links or reading sitemaps (default 100). Plain URL lists are not capped by this.

## `sameDomainOnly` (type: `boolean`):

Only follow links on the same host as the page they were found on (default on).

## `includeMetadata` (type: `boolean`):

Add a "metadata" object to each row: title, description, canonical URL, language, Open Graph tags and parsed JSON-LD (schema.org) blocks.

## `headers` (type: `object`):

Optional. Extra HTTP headers sent with every page request, e.g. {"Accept-Language": "de-DE", "Authorization": "Bearer …"}. A "User-Agent" here replaces the default one. Values are never written to the log.

## `cookies` (type: `array`):

Optional. Cookies sent with every page request, as \["name=value", …] or the \[{"name": …, "value": …}] list a browser cookie-export extension produces (domain and path are ignored). Values are never written to the log; a raw Cookie header can also go in Request headers.

## `proxyMode` (type: `string`):

'auto' (default) and 'never' load every page directly; a blocked page is reported as an error row, not retried through the proxy. 'always' routes every page through Apify Proxy (paid by traffic). Use 'always' when the target blocks datacenter IPs.

## `proxyConfiguration` (type: `object`):

Which Apify Proxy group to use when 'Proxy mode' is 'always'. Leave Residential selected (the default). Not needed when running locally with your own IP.

## `inputDatasetId` (type: `string`):

Take the items from another Actor run's dataset, e.g. "aBcD1234". Combined with the list above.

## `inputField` (type: `string`):

Column holding the value, e.g. "url" (the default). Array columns are flattened.

## Actor input object example

```json
{
  "urls": [
    "https://quotes.toscrape.com/js/"
  ],
  "selectors": [
    {
      "name": "text",
      "selector": ".text",
      "type": "text"
    },
    {
      "name": "author",
      "selector": ".author",
      "type": "text"
    }
  ],
  "firstMatchOnly": true,
  "maxMatchesPerSelector": 20,
  "trimWhitespace": true,
  "maxConcurrency": 3,
  "waitTimeoutSecs": 15,
  "waitUntil": "load",
  "scrollToBottom": false,
  "maxScrolls": 10,
  "maxClicks": 5,
  "blockResources": true,
  "itemSelector": ".quote",
  "pageRange": {
    "from": 1,
    "to": 3
  },
  "proxyMode": "auto",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All pages from this run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://quotes.toscrape.com/js/"
    ],
    "selectors": [
        {
            "name": "text",
            "selector": ".text",
            "type": "text"
        },
        {
            "name": "author",
            "selector": ".author",
            "type": "text"
        }
    ],
    "firstMatchOnly": true,
    "maxMatchesPerSelector": 20,
    "trimWhitespace": true,
    "maxConcurrency": 3,
    "waitTimeoutSecs": 15,
    "waitUntil": "load",
    "maxScrolls": 10,
    "maxClicks": 5,
    "blockResources": true,
    "itemSelector": ".quote",
    "pageRange": {
        "from": 1,
        "to": 3
    },
    "proxyMode": "auto",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("webdatatools/css-selector-extractor-js").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": ["https://quotes.toscrape.com/js/"],
    "selectors": [
        {
            "name": "text",
            "selector": ".text",
            "type": "text",
        },
        {
            "name": "author",
            "selector": ".author",
            "type": "text",
        },
    ],
    "firstMatchOnly": True,
    "maxMatchesPerSelector": 20,
    "trimWhitespace": True,
    "maxConcurrency": 3,
    "waitTimeoutSecs": 15,
    "waitUntil": "load",
    "maxScrolls": 10,
    "maxClicks": 5,
    "blockResources": True,
    "itemSelector": ".quote",
    "pageRange": {
        "from": 1,
        "to": 3,
    },
    "proxyMode": "auto",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("webdatatools/css-selector-extractor-js").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://quotes.toscrape.com/js/"
  ],
  "selectors": [
    {
      "name": "text",
      "selector": ".text",
      "type": "text"
    },
    {
      "name": "author",
      "selector": ".author",
      "type": "text"
    }
  ],
  "firstMatchOnly": true,
  "maxMatchesPerSelector": 20,
  "trimWhitespace": true,
  "maxConcurrency": 3,
  "waitTimeoutSecs": 15,
  "waitUntil": "load",
  "maxScrolls": 10,
  "maxClicks": 5,
  "blockResources": true,
  "itemSelector": ".quote",
  "pageRange": {
    "from": 1,
    "to": 3
  },
  "proxyMode": "auto",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call webdatatools/css-selector-extractor-js --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,webdatatools/css-selector-extractor-js"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/osxMRYcqI8pEnRLba/builds/SruWrJmBDqlvgQdP1/openapi.json
