
Website Content Crawler
scrape.ly/store/querystone/website-content-crawlerAIDeveloper tools
1 monthly user

Crawl websites over HTTP and run your own page function on each page to extract exactly the data you need. Follow links with globs and pseudo-URLs, set limits, and export structured results.
Turn public pages into JSON with the page function you already know how to write. The default fetch is HTTP. Turn on useChrome when the text only appears after scripts run.
Built for teams who collect public records from sites that put the data in the response, and for the pages that need a real document.
useChrome off, each page is downloaded with curl_cffi impersonating Chrome 131. No extra User-Agent is added on top of that impersonation.useChrome runs the document. That flag launches headless Chromium, waits for the navigation, and gives the page function the DOM after scripts. Other runs stay on HTTP.context.jQuery is real jQuery when Inject jQuery is on.useChrome on, maxScrollHeightPixels scrolls until new content stops or that distance is reached (0 skips scrolling). closeCookieModals clicks common consent controls so the dialog is dismissed before the page function runs.useChrome on, downloadMedia and downloadCss allow images, fonts, video, audio, and stylesheets, or block them. ignoreCorsAndCsp lets a navigation hook fetch another origin from the page.useChrome on, pre-navigation and post-navigation hooks receive the Chromium page. Without it, page is null.startUrls accepts page URLs and requestsFromUrl lists. linkSelector, globs, pseudoUrls, and excludes decide what gets enqueued. enqueueRequest adds a URL directly (globs, pseudo-URLs and excludes do not filter it, as in the original Web Scraper), and skipLinks stops selector links for that page.maxPagesPerCrawl, maxResultsPerCrawl, and maxCrawlingDepth stop the crawl. maxRequestRetries retries transport failures, HTTP 401/403/408/429/5xx, and a page function that throws. The last HTTP response is what the page function sees, including a non-200 status.proxyConfiguration uses Scrapely Proxy. If Scrapely Proxy is on and no group is set, the run uses the residential group. proxyRotation supports RECOMMENDED, PER_REQUEST, and UNTIL_FAILURE.respectRobotsTxtFile skips a disallowed link found by linkSelector. A URL you put in startUrls, requestsFromUrl, or enqueueRequest is still fetched.#error and #debug.DEVELOPMENT) keep a crawl inside a normal 1024 MB run.{
"startUrls": [
{
"url": "https://docs.python.org/3/tutorial/"
}
],
"globs": [
{
"glob": "https://docs.python.org/3/tutorial/*"
}
],
"linkSelector": "a[href]",
"pageFunction": "async function pageFunction(context) {\n const $ = context.jQuery;\n return { url: context.request.url, title: $('title').first().text() };\n}"
}
One item from a real run of the example input, shortened. Long text is cut and lists show their first entries.
{
"url": "https://docs.python.org/3/tutorial/introduction.html",
"pageTitle": "3. An Informal Introduction to Python — Python 3.15.0 documentation",
"h1": "3. An Informal Introduction to Python¶",
"first_h2": "3.1. Using Python as a Calculator¶",
"random_text_from_the_page": "2. Using the Python Interpreter"
}
You pay $0.0005 per result ($0.50 per 1,000 results) plus platform usage. A result is one record your page function returns with at least one field. Pages that return nothing, metadata-only records (null) and URLs that fail are not charged.
Worked example: 1,000 results ≈ $0.50 in event fees plus platform usage. On our test runs at the default 1024 MB, usage was about $0.0015 for the 19-page example input and about $0.010 for a 100-page crawl over HTTP (roughly $0.10 per 1,000 pages). With useChrome on, the remote browser adds browser credits: the same 19 pages cost about $0.045 in usage.
Set a maximum charge for the run if you want a hard cap. The run then stops cleanly once it is reached and keeps everything saved so far.
Required: startUrls, pageFunction, proxyConfiguration.
| Field | What it does |
|---|---|
startUrls | Objects with url, or requestsFromUrl for a text list. Optional method, headers, userData, payload, label, uniqueKey. |
pageFunction | Async JavaScript function. Return an object or an array of objects. |
proxyConfiguration | Scrapely Proxy or your own proxy URLs. Missing Scrapely Proxy groups become RESIDENTIAL. |
runMode | PRODUCTION or DEVELOPMENT. Development forces concurrency to 1 and skips page timeouts. |
linkSelector | CSS selector for links to enqueue. Empty means do not follow links. |
globs | Glob patterns. A link must match a glob or a pseudo-URL when either list is set. |
pseudoUrls | Pseudo-URLs such as https://example.com/[.*]. |
excludes | Glob patterns dropped before enqueue. |
keepUrlFragments | Keep #fragments in the unique key. |
respectRobotsTxtFile | Fetch robots.txt. Selector links that are disallowed are not fetched. Start URLs and enqueueRequest URLs are still fetched. |
injectJQuery | Expose context.jQuery. Default true. |
proxyRotation | RECOMMENDED, PER_REQUEST, or UNTIL_FAILURE. |
initialCookies | Cookies sent on matching requests. They are not written to the log. |
useChrome | Launch headless Chromium for each page. Off by default. |
ignoreSslErrors | Skip TLS certificate checks. |
ignoreCorsAndCsp | With useChrome, ignore CSP and cross-origin checks in Chromium. |
downloadMedia | With useChrome, allow images, fonts, video, and audio. Off blocks them. |
downloadCss | With useChrome, allow stylesheets. Off blocks them. |
closeCookieModals | With useChrome, click common cookie-consent controls after navigation. |
maxScrollHeightPixels | With useChrome, scroll up to this many pixels. 0 does not scroll. Default 5000. |
maxRequestRetries | Extra attempts after the first failure. |
maxPagesPerCrawl | Page cap. 0 means no cap. |
maxResultsPerCrawl | Record cap. 0 means no cap. |
maxCrawlingDepth | How many selector hops from the start URLs. enqueueRequest is not depth-capped. 0 means no cap. |
maxConcurrency | Parallel fetches, capped at 8. |
pageLoadTimeoutSecs | Timeout for the HTTP fetch, or for the Chromium navigation when useChrome is on. |
pageFunctionTimeoutSecs | Time limit for the page function, and for navigation hooks. |
waitUntil | Used by the Chromium navigation. The HTTP path already has the body, so it does not wait for network idle. |
preNavigationHooks / postNavigationHooks | Hook arrays. With useChrome, crawlingContext.page is the Chromium page. Otherwise page is null. |
customData | Copied to context.customData. |
debugLog | Keep context.log.debug lines. |
browserLog | Keep console messages from the document the page function reads. |
datasetName / keyValueStoreName | Named storages. Empty uses the run default. |
context inside the page function: jQuery (when injected), request, response (status, headers), customData, input, env, log, enqueueRequest, skipLinks, getValue, setValue, globalStore, saveSnapshot, waitFor.
Each object returned by pageFunction becomes one dataset record. An array becomes one record per element. null stores metadata only.
Always present:
#error — false on a stored page-function record. A URL that never returns a response is not written as an error row; the run fails instead.#debug.requestId#debug.url#debug.method#debug.retryCount — 0 on the first attempt.#debug.errorMessages — null when there were none, otherwise short status or exception names such as HTTP 429 or TimeoutError.#debug.loadedUrl — the final URL after redirects, when a response arrived.#debug.statusCode — the HTTP status, when a response arrived.Your own fields are whatever the page function read from the response. Empty strings, empty arrays, and empty objects are left off the record. A real false or 0 stays. A field named error is not copied. A plain-text body is exposed as text in the document; the words are the response body.
saveSnapshot() writes the HTML to the key-value store as SNAPSHOT-BODY (text/html), at most once every 2 seconds. It does not write a screenshot.
Every field below can be set in the run form, passed through the API, or saved as a task. Unset fields fall back to the defaults shown.
startUrlsarrayRequiredStart URLs
URLs to start crawling from.
maxPagesPerCrawlintegerOptionaldefault 0Max pages per run
Stop after loading this many pages. The real count can be slightly higher. 0 means no limit.
The code that runs on each page and returns its data.
pageFunctionstringRequiredPage function
JavaScript (ES6) function run in the browser on every page. Use it to extract data, interact with the page or queue more URLs. See the README for the context object.
injectJQuerybooleanOptionaldefault trueInject jQuery
If enabled, the scraper will inject the jQuery library into every web page loaded, before Page function is invoked. Note that the jQuery object will not be registered into global namespace in order to avoid conflicts with libraries used by the web page. It can only be accessed through context.jQuery in Page function.
Which links are followed from each page.
linkSelectorstringOptionalLink selector
CSS selector for the links to follow (anchor elements with an href, e.g. a[href]). Glob patterns and Pseudo-URLs filter which of them are queued. Leave empty to ignore page links.
globsarrayOptionalGlob Patterns
Glob patterns to match links in the page that you want to enqueue. Combine with Link selector to tell the scraper where to find links. Omitting the Glob patterns will cause the scraper to enqueue all links matched by the Link selector.
excludesarrayOptionalExclude Glob Patterns
Glob patterns to match links in the page that you want to exclude from being enqueued.
pseudoUrlsarrayOptionaldefault []Pseudo-URLs
Which links found by Link selector to queue. A pseudo-URL is a URL with regular expressions in [] brackets, e.g. http://www.example.com/[.*]. Leave empty to queue every matched link.
keepUrlFragmentsbooleanOptionaldefault falseURL #fragments identify unique pages
Indicates that URL fragments (e.g. http://example.com#fragment) should be included when checking whether a URL has already been visited or not. Typically, URL fragments are used for page navigation only and therefore they should be ignored, as they don't identify separate pages. However, some single-page websites use URL fragments to display different pages; in such a case, this option should be enabled.
maxCrawlingDepthintegerOptionaldefault 0Max crawling depth
How many links away from the start URLs to follow. Pages added with context.enqueuePage() don't count. 0 means no limit; to crawl only the start URLs, leave Link selector empty.
respectRobotsTxtFilebooleanOptionaldefault falseRespect the robots.txt file
If enabled, the crawler will consult the robots.txt file for the target website before crawling each page. At the moment, the crawler does not use any specific user agent identifier. The crawl-delay directive is also not supported yet.
maxResultsPerCrawlintegerOptionaldefault 0Max result records
The maximum number of records that will be saved to the resulting dataset. The scraper will stop when this limit is reached. If set to 0, there is no limit.
maxConcurrencyintegerOptionaldefault 50Max concurrency
Specified the maximum number of pages that can be processed by the scraper in parallel. The scraper automatically increases and decreases concurrency based on available system resources. This option enables you to set an upper limit, for example to reduce the load on a target web server.
maxRequestRetriesintegerOptionaldefault 3Max page retries
The maximum number of times the scraper will retry to load each web page on error, in case of a page load error or an exception thrown by Page function. If set to 0, the page will be considered failed right after the first error.
pageLoadTimeoutSecsintegerOptionaldefault 60Page load timeout
The maximum amount of time the scraper will wait for a web page to load, in seconds. If the web page does not load in this timeframe, it is considered to have failed and will be retried (subject to Max page retries), similarly as with other page load errors.
pageFunctionTimeoutSecsintegerOptionaldefault 60Page function timeout
The maximum amount of time the scraper will wait for Page function to execute, in seconds. It's a good idea to set this limit, to ensure that unexpected behavior in page function will not get the scraper stuck.
waitUntilarrayOptionaldefault ["networkidle2"]Navigation waits until
Contains a JSON array with names of page events to wait, before considering a web page fully loaded. The scraper will wait until all of the events are triggered in the web page before executing Page function. Available events are domcontentloaded, load, networkidle2 and networkidle0. For details, see waitUntil option in Puppeteer's Page.goto() function documentation.
useChromebooleanOptionaldefault falseUse Chrome
If enabled, each page is loaded in Chromium and the page function runs on the document after scripts. Scrolling, cookie-modal dismissal, image and stylesheet blocking, CORS and CSP bypass, and the browser page in navigation hooks apply only when this is on. Other runs stay on HTTP.
downloadMediabooleanOptionaldefault trueDownload media files
When Use Chrome is on and this is enabled, the browser downloads images, fonts, video, and audio. When it is disabled, those subresources are blocked. The HTTP path downloads the document only.
downloadCssbooleanOptionaldefault trueDownload CSS files
When Use Chrome is on and this is enabled, the browser downloads stylesheets. When it is disabled, stylesheets are blocked. The HTTP path downloads the document only.
closeCookieModalsbooleanOptionaldefault falseDismiss cookie modals
When Use Chrome is on, the crawler clicks common cookie-consent controls after navigation so the dialog is dismissed. When Use Chrome is off, there is no browser page to click.
maxScrollHeightPixelsintegerOptionaldefault 5000Maximum scrolling distance in pixels
When Use Chrome is on, the crawler scrolls down until no new content loads or this distance, in pixels, is reached. Set this to 0 to disable scrolling. When Use Chrome is off, the page is not scrolled.
ignoreSslErrorsbooleanOptionaldefault falseIgnore SSL errors
If enabled, the scraper will ignore SSL/TLS certificate errors. Use at your own risk.
ignoreCorsAndCspbooleanOptionaldefault falseIgnore CORS and CSP
When Use Chrome is on, Chromium ignores Content Security Policy and cross-origin restrictions, so a navigation hook can fetch another origin from the page. When this is off, those browser checks stay in place. The HTTP path does not run page scripts.
preNavigationHooksstringOptionalPre-navigation hooks
Async functions evaluated before navigation. Each function receives crawlingContext and gotoOptions. When Use Chrome is on, crawlingContext.page is the Chromium page before navigation. Otherwise page is null.
postNavigationHooksstringOptionalPost-navigation hooks
Async functions evaluated after navigation and before the page function. When Use Chrome is on, crawlingContext.page is the Chromium page. Otherwise page is null.
runModeenumOptionaldefault "PRODUCTION"Run mode
PRODUCTION applies page timeouts and runs requests concurrently. DEVELOPMENT runs one request at a time and does not apply page timeouts.
customDataobjectOptionaldefault {}Custom data
A custom JSON object that is passed to Page function as context.customData. This setting is useful when invoking the scraper via API, in order to pass some arbitrary parameters to your code.
datasetNamestringOptionalDataset name
Name or ID of the dataset that will be used for storing results. If left empty, the default dataset of the run will be used.
keyValueStoreNamestringOptionalKey-value store name
Name or ID of the key-value store that will be used for storing records. If left empty, the default key-value store of the run will be used.
debugLogbooleanOptionaldefault falseDebug log
Include debug messages in the run log, including your own context.log.debug() calls. Can be verbose.
browserLogbooleanOptionaldefault falseBrowser log
If enabled, the Actor log will include console messages produced by JavaScript executed by the web pages (e.g. using console.log()). Beware that this may result in the log being flooded by error messages, warnings and other messages of little value, especially with high concurrency.
proxyConfigurationobjectRequireddefault {"useScrapelyProxy":true}Proxy configuration
Proxy servers used to load pages.
proxyRotationenumOptionaldefault "RECOMMENDED"Proxy rotation
How proxies are rotated between requests. Recommended picks and rotates proxies automatically.
Every run writes its results to a dataset. Preview it in the dashboard, then export it or read it through the API.
No output schema
The developer has not described this actor's dataset fields. Run it once to see what it returns.
You pay for the events the actor reports, such as each result or page it produces. Platform usage is included in the event prices.
| Event | Charged when | Price |
|---|---|---|
| Result | One record with data returned by your page function and saved to the dataset. | $0.0005 |
Run this actor from your own code. Swap in your API token and the examples work as-is.
import { ScrapelyClient } from 'scrapely-client'
const client = new ScrapelyClient({ token: process.env.SCRAPELY_TOKEN })
// Start the actor and wait for the run to finish.
const run = await client.actor('querystone/web-scraper').call({
"startUrls": [
{
"url": "https://docs.python.org/3/tutorial/"
}
],
"pageFunction": "// The function accepts a single argument: the \"context\" object.\n// For a complete list of its properties and functions,\n// see the Input section of this actor's README.\nasync function pageFunction(context) {\n // jQuery is handy for finding DOM elements and extracting data from them.\n // To use it, make sure to enable the \"Inject jQuery\" option.\n const $ = context.jQuery;\n const pageTitle = $('title').first().text();\n const h1 = $('h1').first().text();\n const first_h2 = $('h2').first().text();\n const random_text_from_the_page = $('p').first().text();\n\n\n // Print some information to Actor log\n context.log.info(`URL: ${context.request.url}, TITLE: ${pageTitle}`);\n\n // Manually add a new page to the queue for scraping.\n await context.enqueueRequest({ url: 'http://www.example.com' });\n\n // Return an object with the data extracted from the page.\n // It will be stored to the resulting dataset.\n return {\n url: context.request.url,\n pageTitle,\n h1,\n first_h2,\n random_text_from_the_page\n };\n}",
"linkSelector": "a[href]",
"globs": [
{
"glob": "https://docs.python.org/3/tutorial/*"
}
],
"excludes": [
{
"glob": "/**/*.{png,jpg,jpeg,pdf}"
}
],
"pseudoUrls": [],
"respectRobotsTxtFile": true,
"waitUntil": [
"networkidle2"
],
"preNavigationHooks": "// We need to return array of (possibly async) functions here.\n// The functions accept two arguments: the \"crawlingContext\" object\n// and \"gotoOptions\".\n[\n async (crawlingContext, gotoOptions) => {\n // ...\n },\n]\n",
"postNavigationHooks": "// We need to return array of (possibly async) functions here.\n// The functions accept a single argument: the \"crawlingContext\" object.\n[\n async (crawlingContext) => {\n // ...\n },\n]",
"runMode": "PRODUCTION",
"customData": {},
"proxyConfiguration": {
"useScrapelyProxy": true
}
})
// Read the dataset the run produced.
const { items } = await client.dataset(run.defaultDatasetId).listItems()
console.log(items)Tokens live in your account settings. Keep them server-side: a token carries the full permissions of your account.
Connect the Scrape.ly MCP server once and your agent can run this actor as a tool. Pick your tool:
Fetch scrape.ly/agents.md and run querystone/web-scraperPaste into Claude Code, Codex, Cursor or any AI agent. It signs you in once in the browser.
{
"mcpServers": {
"scrapely": {
"url": "https://api.scrape.ly/mcp?tools=actors,runs,storage,docs&actors=querystone/web-scraper"
}
}
}No key in the file: your client opens the browser to sign you in the first time it connects.
Inputs the developer prepared for common jobs. Open one to see its input, then copy it to your account to run it as-is or adjust it.
Page titles and data extracted with a page function.
Same crawl with images and CSS downloads turned off.
Scrape.ly gives you the infrastructure, the tooling, and the team to extract, process, and operationalize web data at any scale. Setup takes minutes. The advantage lasts forever.