A self-contained Python tool that crawls any website and returns structured business intelligence — services, pricing, contacts, hours, FAQs, team, testimonials, tech stack, and more.
Run one command, open the studio in your browser, paste a URL, and get JSON you can copy or download. No separate frontend repo, no npm, no Next.js.
┌─────────────────────────────────────────────────────────────┐
│ python run.py │
│ → FastAPI REST API (port 8000) │
│ → Streamlit Studio UI (port 8501) │
└─────────────────────────────────────────────────────────────┘
| Capability | Description |
|---|---|
| Website crawling | Async BFS on the same domain, robots.txt aware, URL exclusions for login/cart/etc. |
| Structured extraction | Deterministic extractors (JSON-LD, DOM patterns, trafilatura) — no LLM required for core data |
| Multi-strategy fetch | Static HTTP → Playwright headless → headed → stealth → session replay |
| Retrieval providers | Internal crawler, Firecrawl cloud fallback, or Auto (internal first, escalate on blocks) |
| Output modes | minimal, structured, full, debug — same crawl, different JSON shape |
| Optional LLM enrich | Fills gaps only (industry, tagline, empty service descriptions) when API keys are set |
The default path is deterministic, reproducible, and offline-after-crawl.
git clone <your-repo-url>
cd Crawler
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python -m playwright install chromium
cp .env.example .env # optional — edit keys if using Firecrawl / LLM
python run.pyThen open:
| Service | URL |
|---|---|
| Studio UI | http://127.0.0.1:8501 |
| REST API | http://127.0.0.1:8000 |
| OpenAPI docs | http://127.0.0.1:8000/docs |
Equivalent:
make devpython main.py https://stripe.com --out json/stripe.json
python main.py https://example.com --enrich ┌──────────────────┐
│ Streamlit UI │ ui/app.py
│ (Studio) │
└────────┬─────────┘
│ POST /crawl (or direct import)
▼
┌──────────────────┐
│ FastAPI │ api/
│ extract_service │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
retrieval/ core/ output/
auto | internal orchestrator formatter
| firecrawl │
└──────────────┴──────────────┘
▼
extraction/ (deterministic)
crawler/ + fetch/
Everything lives in one Python repo:
run.py— single entrypoint (API + UI)api/— FastAPI routes and sharedrun_extract()ui/— Streamlit studiocrawler/,fetch/— internal retrievalretrieval/— provider abstraction (internal / firecrawl / auto)extraction/— deterministic extractors + optionalai_enricheroutput/— mode formattersllm/— OpenAI / OpenRouter (enrichment only)main.py— Typer CLI for scripting
Copy .env.example to .env. Only Firecrawl and LLM keys are optional for basic crawls.
Crawler and extraction settings have sensible defaults.
| Variable | Default | Purpose |
|---|---|---|
RETRIEVAL_PROVIDER |
auto |
auto | internal | firecrawl |
ENABLE_FIRECRAWL |
false |
Must be true to use Firecrawl |
FIRECRAWL_API_KEY |
— | Firecrawl API key |
FIRECRAWL_TIMEOUT_SEC |
300 |
Crawl job timeout (poll until done) |
FIRECRAWL_POLL_INTERVAL_SEC |
2 |
Seconds between crawl status polls |
STRUCTURED_USE_LLM |
false |
Internal structured mode: LLM fills same schema as Firecrawl |
| Variable | Purpose |
|---|---|
ENRICH |
true to enable gap-fill via LLM |
OPENAI_API_KEY |
Use OpenAI (auto-detected) |
OPENROUTER_API_KEY |
Use OpenRouter (auto-detected) |
LLM_PROVIDER |
Force openai or openrouter |
OPENAI_MODEL / OPENROUTER_MODEL |
Model override |
| Variable | Default |
|---|---|
API_HOST |
0.0.0.0 |
API_PORT |
8000 |
UI_PORT |
8501 |
- Enter a URL, e.g.
https://stripe.com - Choose retrieval provider:
- Auto — internal crawl; Firecrawl if blocked / empty
- Internal — only this project's crawler
- Firecrawl — cloud scrape (needs API key)
- Choose output mode (see section 7)
- Expand Advanced settings for depth / max pages / concurrency
- Click Extract Website Intelligence
- Use Download JSON or copy from the response panel
The UI calls POST /crawl when the API is up, and falls back to in-process extraction if not.
curl -s -X POST http://127.0.0.1:8000/crawl \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"provider": "auto",
"output_mode": "structured",
"depth": 2,
"max_pages": 50
}' | jq .Same body at POST /api/v1/crawl.
# Internal crawler only
curl -X POST http://127.0.0.1:8000/api/v1/internal \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "mode": "structured"}'
# Firecrawl only
curl -X POST http://127.0.0.1:8000/api/v1/external \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "mode": "full"}'curl http://127.0.0.1:8000/healthGET / redirects to the Streamlit studio port.
| Mode | Use case |
|---|---|
minimal |
Fast preview — name, description, service names |
structured |
SaaS / booking integrations — strict schema |
full |
Complete canonical WebsiteIntelligence model |
debug |
Engineering — fetch strategies, blocks, timings |
{
"business_name": "Stripe",
"description": "Financial infrastructure platform for businesses.",
"services": ["Payments", "Billing", "Checkout"]
}{
"business_details": {
"business_name": "Example Spa",
"city": "Karachi",
"phone_number": "+92 300 1234567",
"primary_language": "en",
"currency": "PKR",
"time_zone": "Asia/Karachi",
"short_description": "Premium massage and wellness center."
},
"business_type": "Health & Wellness",
"operating_hours": {
"monday": { "open": "10:00", "close": "22:00" },
"tuesday": { "open": "10:00", "close": "22:00" }
},
"day_off": ["Sunday"],
"services": [
{
"category": "Massage",
"items": [
{
"service_name": "Swedish Massage",
"price": 4500,
"duration_minutes": 60
}
]
}
]
}Includes everything from deterministic extraction:
business,services,pricing,contacts,social,faq,team,testimonialstech_stack,seo,hours,locations,raw_pages(when enabled)crawl_metadata,blocked_pages,extraction_mode
{
"provider_used": "internal",
"fallback_triggered": false,
"crawl_success": true,
"extraction_ms": 842,
"pages": [
{
"url": "https://example.com/",
"category": "home",
"fetch_strategy": "static",
"block_reason": null
}
],
"blocked_pages": [],
"http_errors": [],
"llm_usage": null
}| Provider | When to use |
|---|---|
| Auto | Default — try internal multi-strategy crawl; escalate to Firecrawl on heavy blocks or empty results |
| Internal | Full control, no external cost, works offline after Playwright install |
| Firecrawl | Hard targets (Cloudflare, heavy JS SPAs) when you have an API key |
Enable Firecrawl in .env:
ENABLE_FIRECRAWL=true
FIRECRAWL_API_KEY=fc-...Many sites return 403 Forbidden to bots. This platform handles that in layers:
- Realistic browser User-Agent (Chrome on macOS by default)
- Static HTTP first — often enough for marketing sites
- Playwright escalation — headless → headed → stealth
- Challenge detection — saves HTML/screenshots under
debug/when enabled - Auto → Firecrawl — cloud renderer when internal crawl is blocked
- Run with Auto provider and Firecrawl configured
- Increase
MAX_PAGES/depthonly after a successful shallow crawl - Check
debug/artifacts for challenge pages (CAPTCHA, Cloudflare) - Use Debug output mode to see
blocked_pagesandfetch_strategyper URL
- Site may be JS-only with no server-rendered HTML → try Firecrawl or increase fetch strategies
- All pages blocked → see
blocked_pagesin debug output - Wrong domain / redirects → verify final URL in debug
crawl_pages
- Confirm
ENABLE_FIRECRAWL=trueand validFIRECRAWL_API_KEY - Check API quota and
FIRECRAWL_TIMEOUT_SEC
- Set
OPENAI_API_KEYorOPENROUTER_API_KEY - Enrichment is optional — deterministic output still returns
- Start with
python run.py(not Streamlit alone) - Or set
STUDIO_PREFER_API=falseto force in-process extraction
python -m playwright install chromiumCrawler/
├── run.py # ← start here
├── Makefile
├── main.py # CLI
├── ui/app.py # Streamlit studio
├── api/ # FastAPI
├── core/ # orchestrator
├── crawler/
├── fetch/
├── retrieval/ # internal | firecrawl | auto
├── extraction/
├── output/
├── llm/
├── json/ # CLI output samples
└── tests/
Placeholder — add
docs/screenshots/studio.pngafter your first run.
| Studio home | JSON response panel |
|---|---|
| screenshot | screenshot |
pytest -q
python -m uvicorn api.main:app --reload --port 8000
streamlit run ui/app.pyUse and modify for your own projects. Add a license file if you distribute publicly.