# Website Content Crawler - Clean Text, Markdown & RAG Chunks (`topapi/website-content-crawler`) Actor

Crawl any website or URL list for $1.20 per 1,000 pages: one row per page with clean text, Markdown, HTML, metadata, links, headings, tables and RAG chunks. Plain HTTP first, headless Chrome only for JavaScript pages. Sitemaps, llms.txt, PDF/DOCX text, Bing/DuckDuckGo search, change monitor.

- **URL**: https://apify.com/topapi/website-content-crawler.md
- **Developed by:** [Top Api](https://apify.com/topapi) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 4 total users, 3 monthly users, 96.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 page crawleds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

### What does Website Content Crawler do?

**Website Content Crawler** crawls any website, or a list of URLs, and turns every page into one clean dataset row: the main content as **Markdown** and **plain text**, plus metadata, links, headings, tables and optional **RAG chunks**. Built for teams that feed documentation, knowledge bases, help centers and blogs into LLMs, vector databases, chatbots and search, without writing a crawler or cleaning HTML by hand.

Website Content Crawler - website to Markdown - web crawler for RAG - LLM-ready text extractor

<a href="https://console.apify.com/actors/9f4xxNlF6tJ1O9kKZ"><img src="https://api.apify.com/v2/key-value-stores/qNQ9zP0xaj2yxGeMY/records/website-content-crawler-source.png" alt="Website Content Crawler - a documentation page that becomes one Markdown row" style="width:70%"></a>

- **Clean content, not page chrome:** navigation, footers, cookie banners, dialogs and scripts are removed; Mozilla Readability (or Extractus / Defuddle) picks the main content
- **Markdown, text, HTML:** headings, lists, links and GFM tables survive the conversion; links are made absolute
- **Fast plain HTTP, browser only when needed:** pages that are empty without JavaScript are rendered in headless Chrome automatically; every row says how it was read (`renderedWith`)
- **Crawl a whole site or just a list:** sub-pages under the start URL by default, or the whole hostname, the whole domain, every link, or only your URLs; sitemaps and `/llms.txt` as extra seeds
- **RAG chunks with heading paths:** set a chunk size and every page arrives split, each chunk carrying the headings above it
- **Search, then crawl:** give a query and the top Bing or DuckDuckGo results are crawled like start URLs
- **PDF and DOCX text:** linked documents (or document URLs you paste) come back as text rows
- **Monitor mode:** on a schedule, get only pages that are new or whose text changed since the last run
- **Export anywhere:** JSON, CSV, Excel, XML; LangChain, LlamaIndex, Pinecone, Qdrant, Make, Zapier, n8n; Apify API and MCP

### What data can you get from a website?

<table>
<tr>
<td>🔗 Page URL and final URL (`crawl.loadedUrl`)</td>
<td>📝 Main content as Markdown (`markdown`)</td>
<td>📄 Main content as plain text (`text`)</td>
</tr>
<tr>
<td>🏷️ Title and meta description</td>
<td>👤 Author, publisher, copyright</td>
<td>📅 Published and modified dates</td>
</tr>
<tr>
<td>🌐 Language code and canonical URL</td>
<td>🖼️ Open Graph tags, image and favicon</td>
<td>🧩 JSON-LD structured data (`metadata.jsonLd`)</td>
</tr>
<tr>
<td>🔗 Every link with its text (`links`)</td>
<td>📑 Heading outline (`headings`)</td>
<td>📊 Tables as Markdown (`tables`)</td>
</tr>
<tr>
<td>🎞️ Images and embedded videos</td>
<td>🔢 Word count and reading time</td>
<td>🧮 Content hash for change tracking</td>
</tr>
<tr>
<td>✂️ RAG chunks with heading paths (`chunks`)</td>
<td>📎 PDF / DOCX text and file link (`file`)</td>
<td>📸 Screenshot link (`screenshotUrl`)</td>
</tr>
</table>

On a 100-page crawl of docs.apify.com every row had a title, description, language, canonical URL, Markdown, text, links and word count (100%); headings 99%, images 59%. Pages from nine search results also carried author (56%), published date (56%), modified date (78%), publisher (89%) and JSON-LD (78%), because those sites publish them.

### How to crawl a website with Website Content Crawler

1. [Create a free Apify account](https://console.apify.com/sign-up) - the free plan includes monthly credit, no card needed.
2. Open [Website Content Crawler](https://apify.com/topapi/website-content-crawler) and click **Try for free**.
3. Paste the address of the docs, blog or site section into **Start URLs** and set **Max pages**.
4. Click **Start**. Pages stream into the dataset as they are crawled.
5. Open the **Storage** tab and **Export** as JSON, CSV or Excel, or send the dataset to LangChain, LlamaIndex or a vector database.

### ⬇️ Input

The only setting you need is **Start URLs**; everything else has a working default. Settings are grouped the same way in the form.

| Parameter | Type | Description |
|---|---|---|
| **🔍 What to crawl** | | |
| `startUrls` | List | Pages where the crawl starts. Sub-pages under the same path are crawled too. A linked text file of URLs also works. |
| `searchQueries` | List | Optional search queries; the top results of each are crawled. |
| `searchEngine` | Select | `bing` (default) or `duckduckgo`. When it returns fewer results than asked, the other engine tops up the list. |
| `maxResultsPerSearch` | Integer | Results crawled per query (1-50). Default `3`. |
| `useSitemaps` / `sitemapUrls` | Boolean / List | Seed the crawl from robots.txt sitemaps, `/sitemap.xml`, indexes and `.gz` files, or from sitemap files you name. |
| `useLlmsTxt` | Boolean | Also crawl `/llms.txt`, `/llms-full.txt` and the Markdown links in them. |
| **🎯 Crawl scope** | | |
| `crawlMode` | Select | Pages under the start URL path (default), whole hostname, whole domain, every link, or only the start URLs. |
| `includeUrlGlobs` / `excludeUrlGlobs` | Globs | Crawl or skip links matching patterns such as `https://example.com/blog/**`. |
| `maxCrawlDepth` | Integer | Links followed from the start URLs. Default `20`; `0` = start URLs only. |
| `maxCrawlPages` | Integer | Pages crawled per run. Default `200`; `0` = no limit. |
| `maxPagesPerStartUrl`, `linkSelector` | Integer / Text | Per-start-URL cap; CSS selector of links to follow (default `a[href]`). |
| **✂️ Refine results** | | |
| `onlyCrawlPagesModifiedSince` | Text | `2026-09-01` or `7 days`: only pages changed since then (304 answers, sitemap `lastmod`, page dates). |
| `minWords`, `articlesOnly`, `mustHaveDate` | | Skip short pages, non-article pages, or pages without a publish date. |
| `respectRobotsTxtFile` | Boolean | Skip URLs disallowed by robots.txt. Default `false`. |
| `keepUrlFragments`, `ignoreCanonicalUrl` | Boolean | Treat `#fragments` as pages; keep pages whose canonical URL was already delivered. |
| **🖥️ JavaScript rendering** | | |
| `renderJavaScript` | Select | `auto` (default: browser only for pages that are empty over HTTP), `never`, `always`. |
| `maxBrowserPages` | Integer | Cap on browser-rendered pages. `0` = 20% of Max pages (5 to 500). |
| `dynamicContentWaitSecs`, `waitForSelector`, `softWaitForSelector`, `maxScrollHeightPixels`, `clickElementsCssSelector`, `removeCookieWarnings`, `blockMedia`, `expandIframes` | | Browser behaviour, same names as Apify's Website Content Crawler. |
| **📄 Content extraction** | | |
| `htmlTransformer` | Select | `readableTextIfPossible` (default), `readableText`, `extractus`, `defuddle`, `none`. |
| `removeElementsCssSelector` / `keepElementsCssSelector` | Text | Elements removed before extraction, or the only elements kept. |
| `aggressivePrune` | Boolean | Drop text lines already seen on earlier pages (menus, footers). |
| **📦 Output** | | |
| `outputFormats` | Multi-select | `markdown`, `text` (default), `html`, `raw`. |
| `chunkSize` / `chunkOverlap` | Integer | RAG chunk size in characters (`0` = off) and overlap (default `200`). |
| `saveHtmlAsFile`, `saveFiles`, `saveScreenshots` | Boolean | Store cleaned HTML, linked documents, screenshots in the key-value store. |
| `maxResults`, `compact`, `customMapFunction` | | Delivered-page cap; core fields only; a JavaScript function that reshapes each row. |
| **🔁 Monitoring** | | |
| `monitorMode`, `monitorStoreName` | Boolean / Text | Only pages that are new or changed since the last run. Pair with a schedule. |
| **⚙️ Advanced** | | |
| `proxyConfiguration` | Proxy | Default: direct, with Apify Proxy for any site that refuses. Pick a group to proxy everything. |
| `customHttpHeaders`, `device`, `maxConcurrency`, `requestTimeoutSecs`, `maxRequestRetries`, `minFileDownloadSpeedKBps`, `ignoreHttpsErrors`, `storeSkippedUrls`, `debugMode` | | Network and debugging. |

#### Input JSON

```json
{
  "startUrls": [{ "url": "https://docs.apify.com/academy/scraping-basics-javascript" }],
  "maxCrawlPages": 100,
  "outputFormats": ["markdown", "text"],
  "chunkSize": 1500
}
```

#### Start at the section you want, not the homepage

The crawler follows links that sit under the start URL's path. **Do:** `https://example.com/docs/` to get the docs. **Don't:** `https://example.com/` when you only want the docs - it crawls marketing pages first. For a whole site, set **Which links to follow** to *Whole hostname*.

#### Use search queries for questions, start URLs for sites

A search query crawls only its top results (3 by default) and does not follow their links. Use it to collect fresh pages about a topic; use start URLs to index a known site.

#### Leave rendering on Auto

Most sites serve their content in the HTML, which is read in a fraction of a second. Auto sends only pages that come back empty to the browser. Choose *Always* for sites whose text appears only after their scripts run, on every page.

### ⬆️ Output

Every crawled page becomes one dataset item. Here is the table view of a real run on docs.apify.com:

<a href="https://console.apify.com/actors/9f4xxNlF6tJ1O9kKZ"><img src="https://api.apify.com/v2/key-value-stores/qNQ9zP0xaj2yxGeMY/records/website-content-crawler-output.png" alt="Website Content Crawler output - crawled pages with title, description, word count and Markdown" style="width:70%"></a>

And one row as JSON (shortened):

```json
{
  "url": "https://docs.apify.com/academy/scraping-basics-javascript",
  "crawl": { "loadedUrl": "https://docs.apify.com/academy/scraping-basics-javascript", "depth": 0, "httpStatusCode": 200, "renderedWith": "http", "transformer": "readableText" },
  "metadata": {
    "title": "Web scraping basics for JavaScript devs | Academy | Apify Documentation",
    "description": "Learn how to use JavaScript to extract information from websites in this practical course, starting from the absolute basics.",
    "languageCode": "en",
    "canonicalUrl": "https://docs.apify.com/academy/scraping-basics-javascript"
  },
  "markdown": "# Web scraping basics for JavaScript devs | Academy | Apify Documentation\n\n**Learn how to use JavaScript to extract information...",
  "text": "Learn how to use JavaScript to extract information from websites in this practical course...",
  "links": [{ "url": "https://docs.apify.com/academy/scraping-basics-javascript/devtools-inspecting", "text": "DevTools: Inspecting", "rel": null, "isInternal": true }],
  "headings": [{ "level": 2, "text": "What we'll do" }],
  "wordCount": 939,
  "readingTimeSec": 237,
  "contentHash": "168358ce2b473827c01e88cafd1720cf"
}
```

#### Fields

| Field | Type | What it is | Fill (100-page run) |
|---|---|---|---|
| `url` | string | URL as queued | 100% |
| `crawl` | object | `loadedUrl`, `loadedTime`, `referrerUrl`, `depth`, `httpStatusCode`, `contentType`, `contentLengthBytes`, `renderedWith`, `durationMs`, `transformer` | 100% |
| `metadata` | object | `title`, `description`, `author`, `keywords`, `languageCode`, `canonicalUrl`, `publishedAt`, `modifiedAt`, `publisher`, `copyright`, `favicon`, `image`, `type`, `tags`, `openGraph`, `jsonLd`, `headers` | title/description 100% |
| `markdown` / `text` | string | Main content | 100% |
| `html` / `htmlUrl` / `rawHtml` | string | Cleaned or original HTML, when requested | on request |
| `links` | array | `url`, `text`, `rel`, `isInternal` | 100% |
| `headings` / `tables` | array | Outline; tables as GFM Markdown | 99% / 3% |
| `images` / `videos` | array | Content images (`url`, `alt`); embedded videos | 59% / 1% |
| `wordCount`, `readingTimeSec`, `contentHash` | number / string | Size, reading time, change hash | 100% |
| `chunks` | array | `index`, `text`, `startChar`, `endChar`, `headingPath` | when `chunkSize` > 0 |
| `searchResult` | object | `query`, `title`, `description`, `url`, `rank`, `engine` | search results only |
| `file` | object | `type`, `pages`, `bytes`, `kvUrl` for PDF / DOCX | documents only |
| `isNew`, `changed`, `previousHash`, `previousCrawledAt` | | Monitor mode | monitor runs |

### How can I use website content data?

- 🤖 **AI and RAG teams** - index product docs, help centers and wikis into a vector database; read `markdown` or `chunks` with `headingPath` for citations
- 💬 **Chatbot builders** - keep a support bot's knowledge current with a weekly monitor run that returns only changed pages
- 🔎 **SEO and content teams** - audit titles, descriptions, canonical URLs, headings and word counts across a site
- 📚 **Researchers** - collect articles on a topic with search queries and keep their publish dates and authors
- 🧾 **Compliance and legal teams** - archive terms, policies and PDFs and detect when their text changes (`contentHash`)
- 🛠️ **Developers** - get any page as Markdown in one API call for an agent or tool

### How much does it cost to crawl a website?

Website Content Crawler uses pay-per-event pricing; Apify platform usage is included in the event prices. There is no start fee.

| Event | Price | When it is charged |
|---|---:|---|
| Page crawled | **$0.0012** ($1.20 per 1,000 pages) | Once per page or document written to the dataset, read over HTTP or in the browser |
| Search query | $0.0033 | Once per search query that returns results |
| Screenshot | $0.001 | Once per stored screenshot (only with Save screenshots) |

| Pages | Cost |
|---:|---:|
| 10 | $0.012 |
| 100 | $0.12 |
| 1,000 | $1.20 |
| 10,000 | $12.00 |

Pages that fail to load, are blocked, redirect to a page already delivered, or have no text are not written to the dataset and are not charged. A search that returns nothing is not charged. Set **Max pages** (or a maximum cost per run in the run options) to cap a run's cost.

Measured runs on the default 1,024 MB: 10 pages in 6 s, 100 pages in 34 s, 975 pages in 15 minutes (a docs site with heavy API-reference pages); JavaScript pages rendered in the browser took about 2 seconds each.

### Integrations, API, scheduling & MCP

- **Schedules:** run Website Content Crawler daily or weekly from **Schedules**; turn on **Monitor mode** to get only new and changed pages.
- **AI frameworks:** load the dataset with the Apify integrations for [LangChain](https://docs.apify.com/platform/integrations/langchain), [LlamaIndex](https://docs.apify.com/platform/integrations/llama-index), [Pinecone](https://docs.apify.com/platform/integrations/pinecone) and [Qdrant](https://docs.apify.com/platform/integrations/qdrant).
- **No-code:** Make, Zapier, n8n, Google Sheets, Slack and webhooks via [Apify integrations](https://docs.apify.com/platform/integrations).
- **API:** call it from Node.js or Python with `apify-client` - code on the [API tab](https://apify.com/topapi/website-content-crawler/api/python).
- **MCP / AI agents:** connect Claude, Cursor or any MCP client through the [MCP tab](https://apify.com/topapi/website-content-crawler/api/mcp) and ask for a site's content in plain language; use `compact` for small rows.

### More data tools by TopApi

<table>
<tr>
<td>📈 <a href="https://apify.com/topapi/similarweb-scraper">SimilarWeb Scraper</a> - traffic, rank and competitors of any website</td>
<td>🎬 <a href="https://apify.com/topapi/youtube-shorts-scraper">YouTube Shorts Scraper</a> - views, channels and transcripts</td>
<td>🎵 <a href="https://apify.com/topapi/tiktok-video-scraper">TikTok Video Scraper</a> - stats and transcript by URL</td>
</tr>
<tr>
<td>⭐ <a href="https://apify.com/topapi/google-maps-reviews-scraper">Google Maps Reviews Scraper</a> - ratings and owner replies</td>
<td>💼 <a href="https://apify.com/topapi/indeed-jobs-scraper">Indeed Jobs Scraper</a> - salary, company and description</td>
<td>🔎 <a href="https://apify.com/topapi/facebook-search-posts-scraper">Facebook Search Posts Scraper</a> - posts by keyword and date</td>
</tr>
<tr>
<td>™️ <a href="https://apify.com/topapi/uspto-trademark-scraper">USPTO Trademark Scraper</a> - status, owners and new filings</td>
<td>🐦 <a href="https://apify.com/topapi/x-profile-scraper">Twitter Profile Scraper</a> - X followers, bio and badge</td>
<td>🧭 <a href="https://apify.com/topapi">All TopApi actors</a></td>
</tr>
</table>

### Limits & empty runs

- **Pages per run:** **Max pages** (default 200) stops the crawl; the run log says how many URLs were left in the queue. Pages skipped as duplicates still count toward it.
- **Blocked sites:** a page that answers with a bot challenge or CAPTCHA is reported as blocked and skipped, not solved. Sites that block datacenter IPs may work with a RESIDENTIAL proxy group.
- **JavaScript pages:** at most 20% of Max pages are rendered in the browser by default (`maxBrowserPages`); the rest of an app-only site needs **Render JavaScript: Always**.
- **Large pages:** pages over 5 MB are read up to 5 MB; documents up to 50 MB. A page too large for one dataset item is trimmed and its full Markdown is linked in `fullContentUrl`.
- **Empty dataset:** every start URL failed (wrong URL, site down, blocked) or every page was filtered out. `RUN_SUMMARY` in the key-value store lists pages by reason; turn on **Store skipped URLs** for the URLs.
- **Not supported:** pages behind a login (no cookie input), AI summaries.

### FAQ

#### How many pages can Website Content Crawler crawl in one run?

As many as **Max pages** allows (set `0` for no limit). A 975-page crawl of docs.apify.com took 15 minutes on 1,024 MB; give the run more memory for big sites, it gets more CPU and finishes faster.

#### Can I run Website Content Crawler on a schedule and get only changed pages?

Yes. Save your input as a task, add a schedule and turn on **Monitor mode**. Each run delivers only pages that are new or whose text changed (`isNew`, `changed`, `previousHash`); unchanged pages are not delivered. Use one **Monitor store name** per site or task.

#### Does Website Content Crawler handle JavaScript-heavy websites?

Yes. With **Render JavaScript: Auto**, a page that comes back empty over HTTP (an app shell) is rendered in headless Chrome; `crawl.renderedWith` shows `browser` for those rows. **Always** renders every page.

#### Can Website Content Crawler split pages into chunks for RAG?

Yes. Set **RAG chunk size** (e.g. 1500 characters) and each row gets `chunks`, each with its text, character offsets and the heading path above it.

#### Can Website Content Crawler extract text from PDF and DOCX files?

Yes. Paste a PDF or DOCX URL as a start URL, or turn on **Crawl linked documents** to follow document links; the text comes back like a page and the file is stored when requested.

#### Can I use Website Content Crawler with LangChain or LlamaIndex?

Yes. Apify's LangChain and LlamaIndex integrations load the dataset directly; map `markdown` (or `text`) to the document content and `url` / `metadata.title` to metadata.

#### Can I use Website Content Crawler with the Apify API?

Yes. Start runs and read datasets with `apify-client` for [Node.js](https://apify.com/topapi/website-content-crawler/api/javascript) or [Python](https://apify.com/topapi/website-content-crawler/api/python); the API tab has ready code.

#### Can I use Website Content Crawler through an MCP server?

Yes. Open the [MCP tab](https://apify.com/topapi/website-content-crawler/api/mcp), connect your MCP client and ask for a page or a site; `compact` keeps rows small for agents.

#### Do I need an API key or a login to crawl a site with Website Content Crawler?

No. It reads pages the way a visitor's browser does. Search queries need no search API key either.

#### Why did my Website Content Crawler run return no pages?

Most often the start URL redirected to another domain or path, so its links fell outside the default scope - widen **Which links to follow**. Otherwise the site blocked the request (`blocked` in `RUN_SUMMARY`) or the filters removed every page.

#### Is it legal to crawl websites with Website Content Crawler?

Crawling publicly available, non-personal content is generally legal; what you do with it matters. Documentation, articles and blogs are usually protected by copyright, so do not republish content without the owner's permission. See Apify's post on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

#### Your feedback

We keep improving Website Content Crawler. Found a bug or missing a field? Open an issue on the [Issues tab](https://apify.com/topapi/website-content-crawler/issues/open).

# Actor input Schema

## `startUrls` (type: `array`):

Pages where the crawl starts, e.g. https://docs.example.com/guide. Sub-pages under that path are crawled too. A linked text file of URLs also works.

## `searchQueries` (type: `array`):

Optional. Each query is searched and its top results are crawled, e.g. "web scraping python tutorial". Charged per query that returns results.

## `searchEngine` (type: `string`):

Engine for the search queries. When it returns fewer results than asked (or nothing), the other engine tops up the list.

## `maxResultsPerSearch` (type: `integer`):

How many top results of each search query are crawled (1-50).

## `useSitemaps` (type: `boolean`):

Also find pages in the site's sitemaps (robots.txt Sitemap lines, /sitemap.xml, sitemap indexes, .gz files). Only in-scope URLs are added.

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

Optional sitemap files to read, e.g. https://example.com/sitemap-posts.xml. Only in-scope URLs are added.

## `useLlmsTxt` (type: `boolean`):

Also crawl /llms.txt and /llms-full.txt at the root of each start URL's site, and the Markdown links listed in them.

## `crawlMode` (type: `string`):

Default: pages under each start URL's path, e.g. start /blog crawls /blog/post-1. Pick a wider scope or only the start URLs.

## `includeUrlGlobs` (type: `array`):

Links matching any pattern are crawled even outside the scope above, e.g. https://example.com/blog/\*\*

## `excludeUrlGlobs` (type: `array`):

Links matching any pattern are never crawled, e.g. https://example.com/**/tag/**

## `maxCrawlDepth` (type: `integer`):

Links followed from the start URLs (depth 0). 1 = start pages plus the pages they link to. 0 = start URLs only.

## `maxCrawlPages` (type: `integer`):

Stop after this many pages are crawled. 0 = no limit. Each delivered page is billed, so this also caps the cost.

## `maxPagesPerStartUrl` (type: `integer`):

Optional cap per start URL, so one big site cannot use the whole page budget. 0 = no cap.

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

CSS selector of the links to follow. Default a\[href] (every link).

## `onlyCrawlPagesModifiedSince` (type: `string`):

ISO date (2026-09-01) or relative ("7 days"). Uses the server's 304 answer, sitemap lastmod, Last-Modified and the page's own dates.

## `minWords` (type: `integer`):

Skip pages whose extracted text has fewer words, e.g. 150 for articles only. 0 = keep all.

## `articlesOnly` (type: `boolean`):

Keep only pages marked as articles (og:type article, article:published_time or Article / BlogPosting JSON-LD). Links on other pages are still followed.

## `mustHaveDate` (type: `boolean`):

Keep only pages that state a publish date.

## `respectRobotsTxtFile` (type: `boolean`):

Skip URLs the site's robots.txt disallows.

## `keepUrlFragments` (type: `boolean`):

Treat example.com/#a and example.com/#b as different pages (some single-page sites route by fragment).

## `ignoreCanonicalUrl` (type: `boolean`):

By default a page whose canonical URL was already delivered is skipped as a duplicate. Turn on for sites with wrong canonical tags.

## `renderJavaScript` (type: `string`):

Auto renders only pages that are app shells over HTTP. Always renders every page (slower). Never uses plain HTTP only.

## `maxBrowserPages` (type: `integer`):

Cap on browser-rendered pages per run. 0 = automatic: 20% of Max pages (at least 5, at most 500), or every page in Always mode.

## `dynamicContentWaitSecs` (type: `integer`):

Browser only: wait up to this long for the network to go quiet before reading the page.

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

Browser only: wait until this CSS selector exists; the page fails if it never appears.

## `softWaitForSelector` (type: `string`):

Browser only: wait for this CSS selector, but continue if it never appears.

## `maxScrollHeightPixels` (type: `integer`):

Browser only: scroll down to load lazy content, up to this height. 0 = no scrolling.

## `clickElementsCssSelector` (type: `string`):

Browser only: click elements matching this selector to expand collapsed sections.

## `removeCookieWarnings` (type: `boolean`):

Remove cookie-consent banners (OneTrust, Cookiebot, Didomi, Quantcast and others) from the content.

## `blockMedia` (type: `boolean`):

Browser only: do not load images, video and fonts (faster, cheaper). Turn off for screenshots with images.

## `expandIframes` (type: `boolean`):

Browser only: include the content of iframes in the page.

## `htmlTransformer` (type: `string`):

Readability with fallback keeps the whole cleaned page when Readability finds no clear article. Extractus and Defuddle are alternative extractors.

## `readableTextCharThreshold` (type: `integer`):

Readability's minimum article length; also the text length under which an HTTP page counts as an app shell.

## `removeElementsCssSelector` (type: `string`):

Elements removed before extraction: navigation, footers, scripts, dialogs and banners by default.

## `keepElementsCssSelector` (type: `string`):

Optional: keep only elements matching this selector, e.g. article, main. Everything else is dropped first.

## `aggressivePrune` (type: `boolean`):

Drop text lines already seen on earlier pages of the run (menus, footers, repeated banners). May remove a repeated line that matters.

## `outputFormats` (type: `array`):

Content formats on each row. Markdown and text by default; HTML = the cleaned content HTML; Raw = the page's original HTML.

## `chunkSize` (type: `integer`):

Split each page's Markdown into chunks of up to this many characters, with their heading path. 0 = no chunks. Typical: 1000-2000.

## `chunkOverlap` (type: `integer`):

Characters repeated from the end of one chunk at the start of the next.

## `saveHtmlAsFile` (type: `boolean`):

Store each page's cleaned HTML as a file and put its link in htmlUrl (no size limit).

## `saveFiles` (type: `boolean`):

Also crawl in-scope PDF, DOCX and other document links: text is extracted from PDF and DOCX, the file is stored and linked in file.kvUrl.

## `saveScreenshots` (type: `boolean`):

Render every page in the browser and store a screenshot, linked in screenshotUrl. Charged as screenshot per stored screenshot.

## `maxResults` (type: `integer`):

Stop after this many pages are delivered. 0 = no extra limit (Max pages still applies).

## `compact` (type: `boolean`):

Core fields only (URL, title, description, date, Markdown, word count, hash) - sized for AI agents and MCP.

## `customMapFunction` (type: `string`):

Optional JavaScript (item, { $, html }) => item to reshape each row; $ is Cheerio on the page HTML. Return null to drop a page (not charged).

## `monitorMode` (type: `boolean`):

Deliver only pages not delivered before or whose text changed (isNew / changed / previousHash). Unchanged pages are not delivered.

## `monitorStoreName` (type: `string`):

Named key-value store that remembers delivered pages. Use one name per monitored site or task.

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

Default: each site is fetched directly and moved to Apify Proxy only if it refuses. Pick a group (e.g. RESIDENTIAL) to send every request through it.

## `customHttpHeaders` (type: `object`):

Extra headers sent with every request, e.g. {"Accept-Language": "de-DE"}.

## `device` (type: `string`):

Desktop or mobile browser headers (and viewport in the browser).

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

Pages fetched in parallel. Capped at 8 per GB of run memory (more starves the CPU).

## `requestTimeoutSecs` (type: `integer`):

Timeout for one page request.

## `maxRequestRetries` (type: `integer`):

Retries after a network error, 5xx or 429 (other HTTP version, then a fresh proxy session).

## `minFileDownloadSpeedKBps` (type: `integer`):

Abort a download that stays slower than this after 10 seconds. 0 = no limit.

## `ignoreHttpsErrors` (type: `boolean`):

Accept invalid or self-signed TLS certificates.

## `storeSkippedUrls` (type: `boolean`):

Save every URL that was not delivered, grouped by reason, to the SKIPPED_URLS record of the run's key-value store.

## `debugMode` (type: `boolean`):

Add a debug object to each row (app-shell verdict, transformer, timings) and save SKIPPED_URLS.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://docs.apify.com/academy/scraping-basics-javascript"
    }
  ],
  "searchEngine": "bing",
  "maxResultsPerSearch": 3,
  "useSitemaps": false,
  "useLlmsTxt": false,
  "crawlMode": "startUrlPath",
  "includeUrlGlobs": [],
  "excludeUrlGlobs": [],
  "maxCrawlDepth": 20,
  "maxCrawlPages": 10,
  "maxPagesPerStartUrl": 0,
  "linkSelector": "a[href]",
  "minWords": 0,
  "articlesOnly": false,
  "mustHaveDate": false,
  "respectRobotsTxtFile": true,
  "keepUrlFragments": false,
  "ignoreCanonicalUrl": false,
  "renderJavaScript": "auto",
  "maxBrowserPages": 0,
  "dynamicContentWaitSecs": 10,
  "maxScrollHeightPixels": 5000,
  "clickElementsCssSelector": "[aria-expanded=\"false\"]",
  "removeCookieWarnings": true,
  "blockMedia": true,
  "expandIframes": true,
  "htmlTransformer": "readableTextIfPossible",
  "readableTextCharThreshold": 100,
  "removeElementsCssSelector": "nav, footer, script, style, noscript, svg, img[src^='data:'],\n[role=\"alert\"],\n[role=\"banner\"],\n[role=\"dialog\"],\n[role=\"alertdialog\"],\n[role=\"region\"][aria-label*=\"skip\" i],\n[aria-modal=\"true\"]",
  "aggressivePrune": false,
  "outputFormats": [
    "markdown",
    "text"
  ],
  "chunkSize": 0,
  "chunkOverlap": 200,
  "saveHtmlAsFile": false,
  "saveFiles": false,
  "saveScreenshots": false,
  "maxResults": 0,
  "compact": false,
  "monitorMode": false,
  "monitorStoreName": "website-content-crawler-monitor",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "customHttpHeaders": {},
  "device": "desktop",
  "maxConcurrency": 10,
  "requestTimeoutSecs": 30,
  "maxRequestRetries": 3,
  "minFileDownloadSpeedKBps": 128,
  "ignoreHttpsErrors": false,
  "storeSkippedUrls": false,
  "debugMode": false
}
```

# Actor output Schema

## `pages` (type: `string`):

The dataset: one item per crawled page with text, Markdown, metadata, links and outline.

## `summary` (type: `string`):

Pages crawled, delivered and skipped by reason, egress and charged events.

# 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 = {
    "startUrls": [
        {
            "url": "https://docs.apify.com/academy/scraping-basics-javascript"
        }
    ],
    "maxCrawlPages": 10,
    "respectRobotsTxtFile": true,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("topapi/website-content-crawler").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 = {
    "startUrls": [{ "url": "https://docs.apify.com/academy/scraping-basics-javascript" }],
    "maxCrawlPages": 10,
    "respectRobotsTxtFile": True,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("topapi/website-content-crawler").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 '{
  "startUrls": [
    {
      "url": "https://docs.apify.com/academy/scraping-basics-javascript"
    }
  ],
  "maxCrawlPages": 10,
  "respectRobotsTxtFile": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call topapi/website-content-crawler --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,topapi/website-content-crawler"
        }
    }
}
```

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/9f4xxNlF6tJ1O9kKZ/builds/eKOnOWMnbWx2XqPns/openapi.json
