A web crawler CLI tool for preparing content for LLM ingestion and vector databases
Project description
LLM Web Crawler
A powerful command-line tool for crawling websites and preparing content for LLM ingestion and vector databases (like pgvector).
Features
- 🕷️ Smart Crawling: Configurable depth, rate limiting, same-domain restriction
- 🧹 Content Cleaning: Removes navigation, ads, boilerplate - keeps only main content
- ✂️ Intelligent Chunking: Splits text into ~1000 token chunks with sentence boundary preservation
- 📦 LLM-Ready Output: JSON format optimized for pgvector and other vector databases
- 🚀 JavaScript Support: Uses Playwright for JavaScript-heavy websites
- 📊 Rich Metadata: Extracts titles, descriptions, headings, and canonical URLs
Installation
Prerequisites
- Python 3.10 or higher
- pip
Install Dependencies
# Install the package in development mode
pip install -e .
# Install Playwright browsers
playwright install chromium
Usage
Basic Usage
# Crawl a single page
crawler https://example.com
# Crawl with depth 2
crawler https://example.com --depth 2 --output data.json
Advanced Options
# Custom chunk size
crawler https://example.com --chunk-size 5000
# Rate limiting (2 seconds between requests)
crawler https://example.com --depth 3 --rate-limit 2.0
# Limit maximum pages
crawler https://example.com --depth 5 --max-pages 100
# Include subdomains
crawler https://example.com --depth 2 --include-subdomains
# Verbose output
crawler https://example.com --depth 2 --verbose
# Pretty JSON output
crawler https://example.com --output data.json --pretty
Output Format
The crawler generates JSON output optimized for vector database ingestion:
{
"crawl_metadata": {
"start_url": "https://example.com",
"crawl_started_at": "2026-01-16T19:45:00Z",
"crawl_completed_at": "2026-01-16T19:47:30Z",
"max_depth": 2,
"total_pages_crawled": 15,
"total_chunks": 127,
"crawler_version": "1.0.0"
},
"chunks": [
{
"chunk_id": "550e8400-e29b-41d4-a716-446655440000",
"content": "This is the extracted text content...",
"char_count": 3847,
"estimated_tokens": 962,
"position": 0,
"heading_context": "Introduction > Getting Started",
"page_metadata": {
"url": "https://example.com/docs/intro",
"canonical_url": "https://example.com/docs/intro",
"title": "Getting Started - Documentation",
"description": "Learn how to get started",
"crawled_at": "2026-01-16T19:45:23Z",
"depth": 1,
"status_code": 200
}
}
]
}
Integration with pgvector
Creating a Table
CREATE TABLE documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
chunk_id TEXT UNIQUE NOT NULL,
content TEXT NOT NULL,
embedding vector(1536), -- For OpenAI embeddings
url TEXT,
title TEXT,
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops);
Loading Data
import json
import psycopg2
from openai import OpenAI
# Load crawler output
with open('output.json') as f:
data = json.load(f)
# Connect to database
conn = psycopg2.connect("your_connection_string")
cur = conn.cursor()
# Generate embeddings and insert
client = OpenAI()
for chunk in data['chunks']:
# Generate embedding
response = client.embeddings.create(
model="text-embedding-3-small",
input=chunk['content']
)
embedding = response.data[0].embedding
# Insert into database
cur.execute("""
INSERT INTO documents (chunk_id, content, embedding, url, title, metadata)
VALUES (%s, %s, %s, %s, %s, %s)
""", (
chunk['chunk_id'],
chunk['content'],
embedding,
chunk['page_metadata']['url'],
chunk['page_metadata']['title'],
json.dumps(chunk['page_metadata'])
))
conn.commit()
Command-Line Options
| Option | Short | Default | Description |
|---|---|---|---|
--depth |
-d |
1 | Maximum crawl depth |
--chunk-size |
-c |
4000 | Target chunk size in characters |
--output |
-o |
output.json | Output JSON file path |
--rate-limit |
-r |
1.0 | Delay between requests (seconds) |
--max-pages |
-m |
None | Maximum pages to crawl |
--same-domain |
True | Restrict to same domain | |
--include-subdomains |
False | Include subdomains | |
--respect-robots |
True | Respect robots.txt directives | |
--use-sitemap |
False | Use sitemap from robots.txt for URL discovery | |
--user-agent |
LLMCrawler/1.0 | Custom user agent for robots.txt matching | |
--verbose |
-v |
False | Show detailed progress |
--pretty |
False | Pretty-print JSON output |
Robots.txt Support
The crawler respects robots.txt directives by default, ensuring ethical crawling behavior.
Features
- Automatic Parsing: Fetches and parses robots.txt from target domains
- Disallow Rules: Respects
Disallowdirectives for your user agent - Crawl Delay: Honors
Crawl-delaydirectives (overrides--rate-limitif higher) - Sitemap Discovery: Extracts sitemap URLs for comprehensive crawling
- Custom User Agent: Match specific rules with
--user-agent
Example: Respecting robots.txt
# Crawl while respecting robots.txt (default behavior)
crawler https://shopify.dev/docs --depth 2 --verbose
# Use sitemap for URL discovery
crawler https://shopify.dev --use-sitemap --max-pages 100
# Ignore robots.txt (use responsibly)
crawler https://example.com --ignore-robots
# Custom user agent for specific rules
crawler https://example.com --user-agent "MyBot/1.0"
Example robots.txt handling
For a robots.txt like:
User-agent: *
Disallow: /beta/
Disallow: /api/shipping-partner-platform/
Sitemap: https://example.com/sitemap.xml
Crawl-delay: 2
The crawler will:
- Skip URLs matching
/beta/and/api/shipping-partner-platform/ - Use 2-second delay between requests (if higher than
--rate-limit) - Optionally fetch URLs from the sitemap with
--use-sitemap
How It Works
1. URL Management
- Maintains a queue of URLs to visit
- Tracks depth for each URL
- Deduplicates URLs (normalizes before comparison)
- Respects domain restrictions
2. Content Extraction
- Uses Playwright for JavaScript rendering
- Employs Trafilatura for main content extraction
- Removes navigation, ads, footers, and boilerplate
- Extracts metadata (title, description, canonical URL)
- Preserves document structure (headings)
3. Smart Chunking
- Splits text at sentence boundaries
- Maintains ~1000 token chunks (4000 characters)
- Adds overlap between chunks for context
- Preserves heading hierarchy
- Estimates token count (1 token ≈ 4 characters)
4. Rate Limiting
- Enforces delay between requests
- Prevents overloading target servers
- Configurable via
--rate-limitoption
Architecture
crawler/
├── src/crawler/
│ ├── cli.py # CLI interface
│ ├── crawler.py # Playwright-based crawler
│ ├── content_extractor.py # Content cleaning & extraction
│ ├── chunker.py # Smart text chunking
│ ├── url_manager.py # URL queue management
│ └── models.py # Data models
└── tests/ # Test suite
Examples
Crawl Documentation Site
crawler https://docs.python.org/3/ \
--depth 2 \
--chunk-size 4000 \
--output python_docs.json \
--rate-limit 1.5 \
--verbose
Crawl Blog (Single Domain)
crawler https://blog.example.com \
--depth 3 \
--max-pages 50 \
--same-domain \
--output blog_content.json
Quick Single-Page Crawl
crawler https://example.com/article \
--depth 0 \
--output article.json \
--pretty
Troubleshooting
Playwright Installation Issues
# Reinstall Playwright browsers
playwright install --force chromium
Rate Limit Errors
Increase the --rate-limit value:
crawler https://example.com --rate-limit 3.0
JavaScript-Heavy Sites
The crawler uses Playwright by default, which handles JavaScript. If you encounter issues:
- Increase timeout (modify
timeoutinWebCrawler) - Add longer wait times for dynamic content
Memory Issues
For large crawls, limit pages:
crawler https://example.com --depth 5 --max-pages 1000
Development
Running Tests
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run with coverage
pytest --cov=crawler tests/
Code Formatting
# Format code
black src/
# Lint code
ruff check src/
License
MIT License
Contributing
Contributions welcome! Please open an issue or PR.
Support
For issues or questions, please open a GitHub issue.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llm_crawler-1.1.0.tar.gz.
File metadata
- Download URL: llm_crawler-1.1.0.tar.gz
- Upload date:
- Size: 31.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c035cb60f22c729300de13a7ef49e370ac4cb2de23c7f39c460a3f9e23e3ae71
|
|
| MD5 |
7ae46465f5d11934e4f82be7e303fa4a
|
|
| BLAKE2b-256 |
65d9126cba461799ef78b8284994ee62e64451d9e382531132424d9cdb893e41
|
File details
Details for the file llm_crawler-1.1.0-py3-none-any.whl.
File metadata
- Download URL: llm_crawler-1.1.0-py3-none-any.whl
- Upload date:
- Size: 31.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e94b8b2c1d3dcb93f4faa3e8feb709c95f76a38819dc118310f5bb8f43b5cf7
|
|
| MD5 |
f7dd1946df39688a2e625f1f709e0ff8
|
|
| BLAKE2b-256 |
ca259a2ee2c3279bb0e27e58139a72f3ea0501cdc1cfb780fb003caadf7ab552
|