Crawl any documentation site and return heading-bounded, LLM-ready chunks with source metadata.
Turn any documentation site into a vector-database-ready knowledge base in one click.
Crawls documentation sites with Playwright, strips HTML navigation boilerplate, splits content on heading boundaries, and outputs clean Markdown or JSON chunks with embedded source metadata — ready to drop into Pinecone, Chroma, pgvector, or any LangChain / LlamaIndex / Haystack pipeline.
| Generic scraper output | What you get here |
|---|---|
| Raw HTML with nav bars, sidebars, cookie banners | Clean text extracted from the main content area only — no noise |
| One blob per page | Heading-boundary chunks that map naturally to retrieval units |
| No metadata | Every chunk carries source_url, page_title, section breadcrumb, token_count, content_hash |
| Re-processes every page on every run | Optional DynamoDB delta tracking skips unchanged pages — up to 80% cost reduction on re-runs |
| Duplicate content from versioned docs | SHA-256 deduplication silently drops identical chunks regardless of URL |
Tested against the Crawlee.dev documentation (production run, 2026-10-18):
from apify_client import ApifyClient
chunks = ApifyClient("YOUR_APIFY_TOKEN").actor("YOUR_USERNAME/apify-rag-docs-scraper").call(
run_input={"startUrls": [{"url": "https://YOUR_DOCS_SITE.com/docs/"}], "chunkSize": 500, "outputFormat": "json-chunks"}
)["defaultDatasetId"]
items = list(ApifyClient("YOUR_APIFY_TOKEN").dataset(chunks).iterate_items())
# items[0] → {"text": "...", "metadata": {"source": "...", "title": "...", "section": "...", "token_count": 487}}
See developer_quickstart/colab_rag_example.ipynb for a full end-to-end walkthrough: actor run → dataset pull → ChromaDB insert → semantic search query.
markdown (default), json-chunks (Pinecone/Chroma/pgvector ready), text (bare text)Document, LlamaIndex TextNode, Haystack Document, raw JSONLchunkSize (tokens) to control granularity; works with any embedding model/v2/) with one input fieldmarkdown (default) — generic RAG, vector-DB agnostic{
"markdown": "## Quick Start\n\nCrawlee is available as a set of NPM packages...",
"source": "https://crawlee.dev/docs/introduction",
"title": "Introduction | Crawlee for JavaScript",
"section": "Introduction > Quick Start",
"chunk_index": 0,
"chunk_total": 12,
"token_count": 312,
"content_hash": "c4a7e891f23b5d10",
"scraped_at": "2026-10-18T14:45:22.341Z"
}
json-chunks — Pinecone / Chroma / pgvector{
"text": "Quick Start\n\nCrawlee is available as a set of NPM packages...",
"metadata": {
"source": "https://crawlee.dev/docs/introduction",
"title": "Introduction | Crawlee for JavaScript",
"section": "Introduction > Quick Start",
"chunk_index": 0,
"chunk_total": 12,
"token_count": 312,
"last_modified": null,
"content_hash": "c4a7e891f23b5d10",
"scraped_at": "2026-10-18T14:45:22.341Z"
}
}
langchain — drop-in LangChain Document{
"page_content": "Quick Start\n\nCrawlee is available as a set of NPM packages...",
"metadata": {
"source": "https://crawlee.dev/docs/introduction",
"heading_path": "Introduction > Quick Start",
"chunk_index": 0,
"chunk_total": 12,
"token_count": 312,
"content_hash": "c4a7e891f23b5d10"
}
}
| Parameter | Type | Default | Description |
|---|---|---|---|
startUrls | array | — | Required. Root URL(s) to crawl |
urlPatterns | string[] | [] | Glob patterns to restrict crawl scope (e.g. /docs/**). Empty = all same-domain URLs |
maxDepth | integer | 5 | Link levels to follow (0 = start pages only) |
outputFormat | enum | markdown | markdown, json-chunks, text, langchain, llamaindex, haystack, raw_jsonl |
chunkSize | integer | 512 | Target token count per chunk. 500 works well for most embedding models |
chunkStrategy | enum | heading_boundary | heading_boundary, fixed_tokens, paragraph, page |
chunkOverlapTokens | integer | 50 | Token overlap between adjacent chunks (for fixed_tokens strategy) |
deduplicateContent | boolean | true | SHA-256 dedup — skip chunks already seen in this run |
versionFilter | string | "" | Only crawl URLs containing this path fragment (e.g. /v3/) |
useDeltaTracking | boolean | false | DynamoDB incremental crawling — re-runs skip unchanged pages |
pdfHandling | enum | skip | skip, extract_text, aws_textract |
maxRequestsPerCrawl | integer | 200 | Hard page cap per run. 0 = unlimited |
excludeSelectors | string[] | [] | Extra CSS selectors to strip (e.g. .announcement-bar) |
This actor uses Pay-Per-Result pricing — you pay per chunk pushed to the dataset.
| Docs site size | Typical chunks | Estimated cost |
|---|---|---|
| 50 pages | 250–400 chunks | ~$1.25–$2.00 |
| 100 pages | 500–800 chunks | ~$2.50–$4.00 |
| 500 pages | 2,500–4,000 chunks | ~$12.50–$20.00 |
Cut costs by up to 80%: Enable useDeltaTracking: true so re-runs only process pages that changed since the last run. Stable documentation sites rarely change more than 10–20% of pages per day.
Five ready-to-run tasks are available in the Apify Store for the most popular documentation niches:
json-chunks format for payment botsRun python scripts/create_published_tasks.py (after apify push) to create these tasks programmatically.
AWS features — Textract OCR for PDFs and DynamoDB delta tracking — are fully optional. Set these environment variables in Apify Console → Actor Settings → Environment Variables:
| Variable | Required for |
|---|---|
AWS_ACCESS_KEY_ID | Delta tracking + Textract |
AWS_SECRET_ACCESS_KEY | Delta tracking + Textract |
AWS_S3_BUCKET | Textract only (temp staging) |
The DynamoDB table is created automatically on first use (PAY_PER_REQUEST billing, 90-day TTL).
The repository ships a standalone MCP server (mcp-server/) that wraps the Apify actor as a live tool. Once configured, Claude Desktop, Cursor, or any MCP-compatible AI client can call it directly during a conversation — no API calls or copy-pasting required.
| Tool | Description |
|---|---|
scrape_documentation_for_rag | Crawl a documentation URL and return semantically chunked, LLM-ready text with full source metadata. Accepts url, outputFormat, chunkSize, maxPages, maxDepth, urlPattern, and versionFilter. |
# Node 18+ required
git clone https://github.com/LernowaAdmin/rag-docs-scraper.git
cd rag-docs-scraper/mcp-server
npm install
npm run build # compiles TypeScript → dist/index.js
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"rag-docs-scraper": {
"command": "node",
"args": ["/absolute/path/to/rag-docs-scraper/mcp-server/dist/index.js"],
"env": {
"APIFY_TOKEN": "apify_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
Restart Claude Desktop. The tool appears automatically in the tool palette.
Edit ~/.cursor/mcp.json:
{
"mcpServers": {
"rag-docs-scraper": {
"command": "node",
"args": ["/absolute/path/to/rag-docs-scraper/mcp-server/dist/index.js"],
"env": {
"APIFY_TOKEN": "apify_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
Reload Cursor (Cmd/Ctrl + Shift + P → "Reload Window").
For hosted deployments, the server supports SSE over HTTP:
cd mcp-server
MCP_TRANSPORT=http MCP_PORT=3001 APIFY_TOKEN=apify_api_... node dist/index.js
Then point your MCP client at http://your-host:3001/sse.
The mcp-server/Dockerfile builds a production-ready image:
cd mcp-server
docker build -t rag-docs-mcp .
docker run -p 3001:3001 -e APIFY_TOKEN=apify_api_... -e MCP_TRANSPORT=http -e MCP_PORT=3001 rag-docs-mcp
Apify exposes every actor's API as an MCP-compatible tool via their MCP gateway. Add this to your client config for a zero-install option:
{
"mcpServers": {
"apify": {
"type": "sse",
"url": "https://mcp.apify.com/sse",
"headers": {
"Authorization": "Bearer apify_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
See developer_quickstart/mcp_client_config_example.json for all three variants side by side.
The actor crawls only one page — why aren't more pages enqueued?
A redirect often changes the URL structure after navigation. Check the actual URL in the logs (it may be /en/docs/ instead of /docs/) and update startUrls and urlPatterns to match the post-redirect path.
Chunks are empty or very short on a JavaScript-rendered site.
The page content may not be rendered when Playwright first loads it. Add the main content selector to excludeSelectors's inverse — or simply let the actor wait by setting navigationTimeoutSecs to a higher value (default: 30 s).
I'm seeing nav menu text in every chunk.
Add the offending element's CSS selector to excludeSelectors (e.g. [".custom-nav", "#site-sidebar"]). The actor already strips the most common patterns automatically.
Versioned docs produce duplicate chunks.
Use versionFilter to restrict crawling to one version (e.g. /v3/), or leave deduplicateContent: true to drop identical chunks regardless of which version URL they came from.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y rag-docs-scraper-mcp-serverMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-lernowaadmin-rag-docs-scraper": {
"command": "npx",
"args": [
"-y",
"rag-docs-scraper-mcp-server"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referencerag-docs-scraper-mcp-servernpmRAG Documentation Scraper works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.