You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-**MCP server**: expose crawl tools to AI agents via the Model Context Protocol. 🔌
44
+
-**Library API**: use `from crawler_to_md import crawl` in your own code. 📦
45
+
-**Plugin system**: extend formatters, filters, processors, and fetchers via Python entry points. 🔧
39
46
- Docker support. 🐳
40
47
41
48
## 📋 Requirements
42
49
43
50
Python 3.10 or higher is required.
44
51
45
-
Project dependencies are managed with `pyproject.toml`. Install them with:
52
+
Project dependencies are managed with `pyproject.toml`. Install the core package with:
46
53
47
54
```shell
48
-
pip install .
55
+
pip install crawler-to-md
56
+
```
57
+
58
+
### Optional extras
59
+
60
+
Heavy or niche features ship as optional extras so the core stays lightweight:
61
+
62
+
| Extra | Install | Enables |
63
+
|---|---|---|
64
+
|`readability`|`pip install crawler-to-md[readability]`| Boilerplate extraction via trafilatura (`--extract readability`) |
65
+
|`render`|`pip install crawler-to-md[render]`| JS rendering via Playwright (`--render`). After install run `playwright install chromium`. |
66
+
|`rag`|`pip install crawler-to-md[rag]`| Token-aware RAG chunking via tiktoken (`--chunk-size`/`--chunk-overlap`); exact token counts in the run summary |
67
+
|`vector`|`pip install crawler-to-md[vector]`| Parquet vector export via pyarrow (`--export-vectors`) |
68
+
|`mcp`|`pip install crawler-to-md[mcp]`| MCP server (`crawler-to-md mcp`) |
crawler-to-md now uses subcommands. The legacy `crawler-to-md --url ...` invocation is fully preserved — when no subcommand is given the tool defaults to `crawl`.
crawler-to-md crawl --url <URL> [options] # crawl a site and export
86
+
crawler-to-md export --url <URL> [options] # re-export from an existing cache (no re-crawl)
87
+
crawler-to-md mcp # start the MCP server over stdio
57
88
```
58
89
59
-
Options:
90
+
### Config file
91
+
92
+
Place a `crawler-to-md.toml` file in your working directory and it is discovered automatically. Pass `--config path/to/file.toml` to use a different location. CLI flags always override config file values.
-`--urls-file`: Path to a file containing URLs to scrape, one URL per line. If '-', read from stdin. 📁
63
-
-`--output-folder`, `-o`: Where to save Markdown files (default: `./output`). 📂
64
-
-`--cache-folder`, `-c`: Where to store the database (default: `./cache`). 💾
65
-
-`--overwrite-cache`, `-w`: Overwrite existing cache database before scraping. 🧹
114
+
-`--urls-file`: Path to a file containing URLs to scrape, one URL per line. If `-`, read from stdin. 📁
115
+
-`--output-folder`, `-o`: Where to save output files (default: `./output`). 📂
116
+
-`--cache-folder`, `-c`: Where to store the SQLite database (default: `~/.cache/crawler-to-md`). 💾
66
117
-`--base-url`, `-b`: Filter links by base URL (default: URL's base). 🔎
67
-
-`--title`, `-t`: Final title of the markdown file. Defaults to the URL. 🏷️
118
+
-`--title`, `-t`: Title for the output files. Defaults to the URL. 🏷️
119
+
-`--config`: Path to a `crawler-to-md.toml` config file. Auto-discovered in CWD when omitted. ⚙️
120
+
121
+
#### Crawl control
122
+
123
+
-`--overwrite-cache`, `-w`: Overwrite existing cache database before scraping. 🧹
68
124
-`--exclude-url`, `-e`: Exclude URLs containing this string (repeatable). ❌
69
-
-`--export-individual`, `-ei`: Export each page as an individual Markdown file. 📝
70
-
-`--rate-limit`, `-rl`: Maximum number of requests per minute (default: 0, no rate limit). ⏱️
71
-
-`--delay`, `-d`: Delay between requests in seconds (default: 0, no delay). 🕒
125
+
-`--include-url`, `-I`: Include only URLs containing this string (repeatable). ✅
126
+
-`--rate-limit`, `-rl`: Maximum number of requests per minute (default: 0, no limit). ⏱️
127
+
-`--delay`, `-d`: Delay between requests in seconds (default: 0). 🕒
72
128
-`--proxy`, `-p`: Proxy URL for HTTP or SOCKS requests. 🌐
129
+
130
+
#### HTML filtering
131
+
73
132
-`--include`, `-i`: CSS-like selector (#id, .class, tag) to include before Markdown conversion (repeatable). ✅
74
133
-`--exclude`, `-x`: CSS-like selector (#id, .class, tag) to exclude before Markdown conversion (repeatable). 🚫
75
134
76
-
One of the `--url` or `--urls-file` options is required.
135
+
#### Robustness
136
+
137
+
-`--timeout`: Per-request timeout in seconds (default: `15`). ⏳
138
+
-`--max-retries`: Maximum retries on transient failures — timeouts, 429, 5xx — with exponential backoff (default: `3`). 🔁
139
+
-`--max-pages`: Stop after scraping this many pages (`0` = unlimited). 📏
140
+
-`--max-depth`: Maximum link-discovery depth (`-1` = unlimited). 🌊
141
+
-`--max-time`: Maximum wall-clock crawl time in seconds (`0` = unlimited). ⏰
142
+
143
+
#### Concurrency
144
+
145
+
-`--concurrency N`: Number of parallel fetches. `1` (default) uses the synchronous engine; `N > 1` enables the async `httpx` engine with a bounded semaphore. Per-host politeness (rate limit, delay) is preserved in async mode. ⚡
146
+
147
+
#### Intelligence
148
+
149
+
-`--ignore-robots`: Disable robots.txt compliance (robots.txt is **honored by default**). 🤖
150
+
-`--user-agent`: Custom User-Agent string sent on every request. 🪪
151
+
-`--sitemap`: Seed the crawl frontier from the host's `/sitemap.xml` before crawling. 🗺️
-`--allow-types application/pdf`: Additional MIME types to ingest via MarkItDown — e.g. `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document` (repeatable). 📎
158
+
159
+
#### Output formats
160
+
161
+
-`--export-individual`, `-ei`: Export each page as an individual Markdown file. 📝
162
+
-`--frontmatter` / `--no-frontmatter`: Prepend YAML frontmatter (url, title, fetched_at, word_count, token_count) to individual Markdown files. On by default. 🗂️
163
+
-`--no-markdown`: Skip generation of the compiled Markdown file. 🚫
164
+
-`--no-json`: Skip generation of the compiled JSON file. 🚫
165
+
-`--export-jsonl`: Export pages as JSON Lines — one `{url, content, metadata}` record per line. 🗃️
166
+
-`--export-llms`: Export `llms.txt` (page index) and `llms-full.txt` (full content) in the emerging LLM-friendly format. 🤖
167
+
-`--chunk-size N`: Split pages into RAG chunks of `N` tokens (0 = disabled). Requires `pip install crawler-to-md[rag]`. 🧩
168
+
-`--chunk-overlap N`: Token overlap between consecutive chunks (used when `--chunk-size > 0`). 🔗
169
+
-`--export-vectors`: Export pages to a Parquet file for downstream vector indexing. Requires `pip install crawler-to-md[vector]`. 📊
170
+
171
+
#### Run summary
172
+
173
+
Every run prints an end-of-run summary to stdout:
174
+
175
+
```
176
+
Run summary
177
+
Links discovered : 142
178
+
Pages scraped : 98
179
+
Pages stored : 98
180
+
Content bytes : 1048576
181
+
Total tokens : 210340 (estimated)
182
+
Duration : 34.21s
183
+
```
184
+
185
+
Token counts are exact when the `rag` extra is installed (tiktoken), or estimated otherwise.
186
+
187
+
### `export` — re-export without re-crawling
188
+
189
+
Re-run any combination of export formats from an existing cache database without hitting the network again. Accepts all the same output flags as `crawl`.
Use `crawler_to_md` as a library — no CLI side effects, no `sys.argv` parsing.
219
+
220
+
```python
221
+
from crawler_to_md import crawl
222
+
223
+
result = crawl("https://docs.example.com", max_pages=50, concurrency=4)
224
+
225
+
for page in result.pages:
226
+
print(page.url, len(page.content))
227
+
228
+
print(f"Scraped {result.stats.pages_scraped} pages in {result.stats.duration:.1f}s")
229
+
```
230
+
231
+
`crawl()` returns a `CrawlResult` object with:
232
+
233
+
-`result.pages` — list of scraped pages (url, content, metadata)
234
+
-`result.stats` — aggregate run statistics (pages scraped, bytes, tokens, duration)
235
+
-`result.exports` — paths of any files written (if export options are passed)
236
+
237
+
## 🔧 Plugin System
238
+
239
+
crawler-to-md exposes an entry-point-based plugin architecture for all four pipeline stages. See **[docs/plugins.md](docs/plugins.md)** for the full reference.
All built-in output formats ship as first-party formatters (`markdown`, `json`, `jsonl`, `llms`, `individual`, `chunks`, `vectors`) and are wired live through the registry. Register your own by declaring an entry point in your package's `pyproject.toml`:
251
+
252
+
```toml
253
+
[project.entry-points."crawler_to_md.formatters"]
254
+
my-format = "my_package.exporters:MyFormatter"
255
+
```
256
+
257
+
Once your package is installed, the plugin is discovered automatically. See `tests/sample_plugin.py` for a worked end-to-end example.
258
+
259
+
> **Note**: The filter, processor, and fetcher plugin protocols and registries are defined and tested, but are not yet consumed by the crawl loop — see [Known Limitations](#-known-limitations--follow-ups).
81
260
82
261
## 🐳 Docker Support
83
262
@@ -100,6 +279,11 @@ docker run --rm \
100
279
crawler-to-md --url <URL>
101
280
```
102
281
282
+
## ⚠️ Known Limitations / Follow-ups
283
+
284
+
-**Filter/processor/fetcher plugin protocols** are fully defined and tested but are not yet consumed by the crawl loop. Formatters are wired live; the other three stages are available for extension and will be integrated in a future release.
285
+
-**Sitemap parsing** uses the stdlib `xml.etree.ElementTree`. For untrusted or adversarially crafted sitemaps, consider using `defusedxml` as a drop-in replacement to guard against XML DoS attacks (billion-laughs).
286
+
103
287
## 🤝 Contributing
104
288
105
289
Contributions are welcome! Feel free to submit pull requests or open issues. 🌟
0 commit comments