Web Scraper — CSS Selector & Data Extractor avatar

Web Scraper — CSS Selector & Data Extractor

Pricing

from $1.20 / 1,000 pages

Go to Apify Store
Web Scraper — CSS Selector & Data Extractor

Web Scraper — CSS Selector & Data Extractor

Web Scraper pulls any CSS selector off any page, returning text, HTML, attributes, tables or counts — one row per page (lists: an items array), no browser.

Pricing

from $1.20 / 1,000 pages

Rating

0.0

(0)

Developer

Murat Uzun

Murat Uzun

Maintained by Community

Actor stats

0

Bookmarked

9

Total users

4

Monthly active users

a day ago

Last modified

Share

What is CSS Selector Extractor (any page, any field)?

CSS Selector Extractor (any page, any field) is an Apify Actor that turns a list of URLs and a list of CSS selectors into structured data — no scraper to write, no browser to configure. Give it a page and tell it "get the text at .price" or "get the href at a.download-link" and it returns one clean row per page with exactly the values those selectors matched (on a list page, that row carries an items array with one object per product, post or listing). It is the generic "scrape this bit of this page" tool people reach for before writing a custom Cheerio or Playwright script, and it works on any static HTML page — product pages, blog posts, directories, documentation, internal tools — using plain HTTP requests, not a headless browser. Runs on Apify's schedule, webhook and API infrastructure, so checking the same selectors across a list of pages on a timer is a five-minute setup.

New in 0.2: scrape list pages (one object per product card, job row or search result with itemSelector), follow pagination or detail links (linkSelector + maxPages), read whole sites from their sitemap (sitemapUrls, gzip and nested indexes handled) and get page metadata + JSON-LD (includeMetadata) — all still one billed page per row, no browser. Existing inputs behave exactly as before.

What data does CSS Selector Extractor extract?

For every URL, the Actor runs each configured selector against the page and returns:

FieldTypeDescription
url, finalUrlstringThe input URL and the URL after following redirects
statusCode, contentTypenumber, stringHTTP status code and Content-Type header of the fetched page
fieldsobjectOne key per selector name — a string, an array of strings, a number for count, or row objects for table
matchCountsobjectOne key per selector name — how many DOM nodes that selector actually matched
htmlSizeBytesnumberSize of the fetched HTML in bytes
usedProxybooleanWhether this URL needed Apify Proxy to load
errorstringSet when the URL could not be fetched or parsed; every other field but url and scrapedAt stays null
scrapedAtstringTimestamp of the fetch (ISO 8601 UTC)
items, itemCountarray, numberItem mode only: one object per element matching itemSelector, and how many matched in total
metadataobjectWith includeMetadata: title, description, canonical URL, language, Open Graph tags and parsed JSON-LD
foundOnstringWith linkSelector: the page whose link led here (null for the start URLs)

How to use CSS Selector Extractor

  1. Fill URLs with one page per line — product pages, articles, directory listings, anything with static HTML.
  2. Fill Selectors with a JSON array of {"name", "selector", "type"} objects. type is one of text (element text), html (element's outer HTML), attr (an attribute value — add "attribute":"href" or similar) or count (how many nodes matched, no value extraction).
  3. Leave First match only on to get one value per selector, or turn it off to get every match as an array (capped by Max matches per selector).
  4. Click Start and export the dataset as JSON, CSV, Excel or HTML from the Output tab — one row per page, ready to join, chart or feed into another system.

Example input

{
"urls": ["https://example.com", "https://apify.com"],
"selectors": [
{ "name": "title", "selector": "h1", "type": "text" },
{ "name": "canonicalUrl", "selector": "link[rel='canonical']", "type": "attr", "attribute": "href" }
],
"firstMatchOnly": true,
"trimWhitespace": true
}

Example output

{
"url": "https://news.ycombinator.com/",
"finalUrl": "https://news.ycombinator.com/",
"statusCode": 200,
"contentType": "text/html; charset=utf-8",
"fields": {
"firstTitle": "Show HN: A CSS selector scraper",
"firstLink": "https://example.com/show-hn",
"storyCount": 30
},
"matchCounts": {
"firstTitle": 30,
"firstLink": 30,
"storyCount": 30
},
"htmlSizeBytes": 34611,
"usedProxy": false,
"error": null,
"scrapedAt": "2026-09-14T01:57:46.921Z"
}

A URL that fails to load or parse still produces exactly one row, with error set and every other field null — a bad URL or a typo'd selector never crashes the run or leaves a URL missing from the dataset.

Scrape list pages, follow pagination and read sitemaps

List pages (item mode). Set itemSelector to the repeated element; every selector is then searched inside each item, and the row carries an items array — up to 1,000 items per page for the price of one page.

{
"urls": ["https://books.toscrape.com/"],
"itemSelector": "article.product_pod",
"selectors": [
{ "name": "title", "selector": "h3 a", "type": "attr", "attribute": "title" },
{ "name": "price", "selector": ".price_color", "type": "text" }
],
"linkSelector": "li.next a",
"maxPages": 3
}

Output: 3 rows (the start page plus two "next" pages), each with itemCount: 20 and items like { "title": "A Light in the Attic", "price": "£51.77" }. Rows reached by a followed link carry foundOn.

Pagination and detail pages. linkSelector follows any links you point it at — a.next for pagination, a.product-link for detail pages — staying on the same domain unless sameDomainOnly is off, filtered by an optional urlPattern regex, and never beyond maxPages (default 100).

Whole sites from a sitemap. Give sitemapUrls (e.g. https://example.com/sitemap.xml) instead of, or together with, urls. Sitemap indexes, nested sitemaps and .xml.gz files are expanded automatically; urlPattern keeps only the pages you want (e.g. /products/).

Numbered pages without a next link. urlTemplate with pageRange generates the page URLs up front: "https://books.toscrape.com/catalogue/page-{page}.html" with {"from": 1, "to": 2} fetches pages 1 and 2 (20 prices each in our cloud test). Use step for offset-style URLs such as ?start={page} with {"from": 0, "to": 100, "step": 20}. Generated URLs are not capped by maxPages (only by the 10,000 limit and your max total charge).

HTML tables in one field. A selector with "type": "table" returns the matched <table> (or the first table inside the matched element) as an array of row objects keyed by its header row — e.g. { "name": "customers", "selector": "#customers", "type": "table" } on the w3schools tables page gave 6 rows like { "Company": "Alfreds Futterkiste", "Contact": "Maria Anders", "Country": "Germany" }. Blank headers become column_N, repeated ones get _2, and colspan cells are repeated so columns stay aligned. Up to 1,000 rows per table.

Headers and cookies. headers (e.g. {"Accept-Language": "de-DE"}) and cookies (e.g. [{"name": "session", "value": "…"}] or ["theme=dark"]) go with every page request — for pages behind a login cookie, an API key header or a locale switch. In our cloud test an echo endpoint received Accept-Language: de-DE, a custom header and Cookie: session=abc123; theme=dark. Header and cookie values are never written to the run log, but they are stored in the run input like any other field.

Metadata and JSON-LD. includeMetadata: true adds metadata with title, description, canonical URL, language, Open Graph tags and every parsed schema.org JSON-LD block — product prices, ratings, article dates and organization details without writing a selector.

Use it from code, n8n or Make

Call the Actor over the Apify API and use the rows in your own pipeline. Replace YOUR_APIFY_TOKEN with your token from the Apify Console.

cURL (synchronous: the response is the dataset rows):

curl -X POST "https://api.apify.com/v2/acts/webdatatools~css-selector-extractor/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls":["https://books.toscrape.com/"],"selectors":[{"name":"title","selector":"h1","type":"text"}]}'

Python (pip install apify-client):

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("webdatatools/css-selector-extractor").call(run_input={
"urls": ["https://books.toscrape.com/"],
"selectors": [{"name": "title", "selector": "h1", "type": "text"}],
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
print(row["url"], row["fields"])

JavaScript (npm install apify-client):

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('webdatatools/css-selector-extractor').call({
urls: ['https://books.toscrape.com/'],
selectors: [{ name: 'title', selector: 'h1', type: 'text' }],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);

n8n and Make: use the HTTP Request module (n8n) or the HTTP "Make a request" module (Make) with the cURL call above as a POST with a JSON body. The response is an array of rows, one per page, ready for the next step.

At a glance: this Actor and the other scrapers in the Store

CSS Selector Extractorapify/cheerio-scraperapify/web-scraperapify/ai-web-scraper
How you describe the dataJSON list of {name, selector, type}JavaScript pageFunctionJavaScript pageFunctionNatural-language prompt
Price$0.002 per page (lower on higher plans)Free, platform usage onlyPlatform usage (browser)$0.009 per page
Tables into rowsBuilt in (type: "table")Write codeWrite codeNot compared

Prices as listed on each Actor's Store page on 11 October 2026.

Input parameters

ParameterTypeDefaultDescription
urlsarray["https://example.com"]One URL per line to fetch and run the selectors against
selectorsarray (JSON)[{"name":"title","selector":"h1","type":"text"}]{name, selector, type, attribute?} objects; type is text, html, attr, count or table; capped at 25 entries
firstMatchOnlybooleantrueReturn only the first match per selector; off returns every match as an array
maxMatchesPerSelectorinteger20Most values returned per selector when firstMatchOnly is off (1-500)
trimWhitespacebooleantrueCollapse whitespace runs and trim ends of extracted text/attr values
maxConcurrencyinteger5Parallel page fetches (1-20)
proxyModestringautoauto tries every URL directly first and only switches to Apify Proxy after a block is detected; always and never force one path
proxyConfigurationobjectResidential proxyWhich Apify Proxy group proxyMode uses when a request needs the proxy
itemSelectorstring—Repeated element for list pages; selectors are searched inside each match and returned as items
maxItemsPerPageinteger200Most items returned per page in item mode (1-1000); itemCount always reports the full number
linkSelectorstring—Links to follow (pagination or detail pages); each followed page is one more row
sitemapUrlsarray—Sitemap or sitemap-index URLs whose pages are added to the run
urlPatternstring—Regular expression that followed links and sitemap URLs must match
maxPagesinteger100Total page cap when following links or reading sitemaps (plain URL lists are not capped by it)
sameDomainOnlybooleantrueOnly follow links on the same host as the page they were found on
includeMetadatabooleanfalseAdd title, description, canonical, language, Open Graph and JSON-LD per page
urlTemplatestring—URL with a {page} placeholder; one URL per number in pageRange is added (up to 10,000)
pageRangeobject—{"from": 1, "to": 10, "step": 1} for urlTemplate; inclusive, from defaults to 1
headersobject—Extra HTTP headers for every page request; a User-Agent here replaces the default
cookiesarray—["name=value"] or [{"name", "value"}] (browser cookie export); sent as one Cookie header

Pricing

CSS Selector Extractor uses pay-per-event pricing: $0.002 per page on the free plan, down to $0.0012 per page on higher Apify plans, plus a negligible actor-start fee. Extracting five selectors from 100 URLs costs about twenty cents, whether one selector matches or five do — the price is per page, not per field, and in item mode a page with 200 products is still one page. Set Maximum cost per run and the Actor stops at what the budget covers instead of overspending — including pages found by following links or reading sitemaps.

CSS Selector Extractor vs. writing your own scraper

A one-off Node or Python script with Cheerio or BeautifulSoup can do the same extraction, but it means writing fetch/retry/timeout logic, a proxy fallback, whitespace cleanup and a dataset writer from scratch for every new site. This Actor already handles retries, direct-first proxy escalation on a block, and a guaranteed one-row-per-page output shape — describe the fields once as a small JSON list and get a typed dataset back, schedulable and API-callable without touching code.

Using CSS Selector Extractor with AI agents and MCP

CSS Selector Extractor is pay-per-event with limited permissions — the two requirements for an Actor to be callable through the Apify MCP server at mcp.apify.com. An agent that needs one specific value off a page (a price, a title, a status, a download link) can call this Actor directly with a urls list and a selectors array instead of fetching and parsing HTML itself, and gets back a typed row with the value already extracted, trimmed, and counted.

Short timeouts never lose results. Every row is saved to the dataset as soon as its page is processed. When the run's timeout is close (the last 10 s, or 5 % of a longer run, at most 2 min) the Actor starts no new pages and the run ends SUCCEEDED — not TIMED-OUT — with a status message such as Stopped early before the run timeout: 495 of 2000 page(s) done…. URLs it never reached get no row (and cost nothing); read the run's statusMessage to tell a complete result from a partial one, and re-run the remaining URLs.

Sizing a run. Speed depends on the target site: a fast site gives roughly 10–20 pages per second at the default maxConcurrency of 5 (495 docs.apify.com pages in about 30 s in our test), a slow or proxied one far less. Give large lists a timeout of several minutes, or raise maxConcurrency. maxTotalChargeUsd caps the cost — the Actor processes only as many pages as the budget covers and says so in the status message.

FAQ

Does this use a headless browser? No. It fetches HTML with plain HTTP requests and parses it with Cheerio, which is far faster and cheaper than a browser but means it cannot extract content that is only added by client-side JavaScript after the initial page load.

What happens if my selector matches nothing? That is not an error — the field's value is null and its matchCounts entry is 0. Only a fetch failure (bad URL, timeout, non-2xx status) or an unparseable response sets the row's error.

What does type: "count" do? It returns how many elements the selector matched as a number, ignoring firstMatchOnly — useful for "how many products/comments/results are on this page" without extracting any text.

Why is my attr field null even though the element exists? Either attribute was left out of that selector's definition, or the matched element simply doesn't have that attribute set — both return null rather than throwing.

Why did a site return blocked or empty content? Some sites answer bot traffic with a challenge page even at HTTP 200. With proxyMode set to auto (the default), the Actor detects that content and retries the rest of the run through Apify Proxy automatically — no proxy spend unless a block is actually detected.

Is this legal to run? Extracting publicly visible page content for your own use is common practice, but you are responsible for complying with the target site's Terms of Service and applicable law (e.g. robots.txt, copyright, personal-data rules) for your specific use case.

Can it crawl a whole site? Yes — give a sitemap in sitemapUrls, or a linkSelector such as a[href] with maxPages. For full-text Markdown of every page rather than specific fields, use Website to Markdown.

What happens to a page bigger than 10 MB? It is not read in full or parsed. Its row carries error with the reason, and statusCode still shows what the server answered. Rate-limited (429) and server-error (5xx) answers are retried with exponential backoff, honouring Retry-After, and each URL gets at most 45 seconds across all of its attempts.

What are the limitations? No JavaScript rendering, up to 25 selectors per run, up to 1,000 rows per table field, up to 500 matches returned per selector even when firstMatchOnly is off, and up to 1,000 items per page in item mode. A failing URL still produces a row, with the reason in error.

Need to know when the section you extract here changes? Page & Price Watch watches one CSS-selected section of a public page (or its readable text or price) on a schedule and emails you only when that value changes, so rotating banners and timestamps do not trigger alerts.

Support and feedback

Found a page shape or selector edge case it should handle better? Open an issue on the Issues tab.

Part of the webdatatools web-intelligence suite — every Actor is pay-per-event, reads public data without a login, and returns one clean row per entity:

Browse the whole suite at webdatatools, or call ten of these Actors straight from Claude, Cursor or Cline with the webdatatools MCP server.

Feedback

Did it help? A quick review helps a lot: https://apify.com/webdatatools/css-selector-extractor/reviews

Website & domain intelligence

Content for AI, LLMs and RAG

Search, video and social

Leads, jobs and company data

Developer, app and research data