LLM-ready batch crawling for Nordic company data.
NordCrawl crawls CSV/JSON URL lists, extracts structured Swedish company data, and can upsert the results directly into Supabase.
If this saves you time, star the repo — it helps the project grow and tells me to keep polishing it.
- Instantly useful: real Swedish company extraction, not a toy demo
- Great first impression: rich CLI output, demo assets, examples, and docs
- Production-minded: retries, dedupe, rate limiting, and validation
- Easy adoption:
pip install nordcrawlafter release and clean CLI entry points
- Batch crawling with configurable concurrency
- Per-domain rate limiting
- Retry logic with exponential backoff
- Swedish company data extraction
- company name
- org number
- phone
- address
- Consistent JSON schema via Pydantic
- CSV/JSON import and export
- Supabase upserts with dedupe by URL
- CLI-first workflow with a clean Python API
The package name is nordcrawl.
After publishing, install it with:
pip install nordcrawlThe screenshot above is based on a real crawl run with Rich output.
- Demo input:
examples/urls.json - Verified output:
examples/results.json - More details:
examples/README.md
- Why people star projects like this
- Features
- PyPI
- Demo
- Quick start
- CLI
- Python API
- Input formats
- Supabase table
- Example output
- Roadmap
- Contributing
- License
pip install -r requirements.txtCopy the example file and fill in your Supabase keys:
cp .env.example .envpython3 main.py crawl-urls \
https://www.ikea.com/se/sv/ \
https://www.hemkop.se/ \
https://www.ica.se/ \
--output results.jsonpython3 main.py crawl data/test_urls.json --output results.json --format jsonpython3 main.py crawl data/test_urls.json --save-dbpython3 main.py stats --limit 50python3 main.py search "IKEA" --limit 20python3 main.py export company_data.csv --format csvimport asyncio
from src import BatchCrawler, SupabaseManager
async def main() -> None:
crawler = BatchCrawler(max_concurrent=5, rate_limit_delay=2.0)
results = await crawler.crawl_urls([
"https://www.ikea.com/se/sv/",
"https://www.hemkop.se/",
"https://www.ica.se/",
])
print(results)
asyncio.run(main())url
https://www.example.se
https://www.example2.se[
"https://www.example.se",
{"url": "https://www.example2.se"}
]NordCrawl expects a table named company_data with these columns:
urlcompany_nameorg_numberemailphoneaddresspostal_codecitywebsitecrawled_atstatusraw_html_hashextraction_confidencecreated_atupdated_at
The migration is included here:
supabase/migrations/20240325000001_create_company_data_table.sql{
"url": "https://www.example.se",
"company_name": "Exempel AB",
"org_number": "556123-4567",
"email": "info@example.se",
"phone": "+46701234567",
"address": "Storgatan 1",
"postal_code": "123 45",
"city": "Stockholm",
"status": "completed",
"extraction_confidence": 0.85
}Local supabase start requires Docker access. If you do not want to run Supabase locally:
- Apply the migration in your remote Supabase project.
- Set
SUPABASE_URLandSUPABASE_KEYin.env. - Run the crawler with
--save-db.
.
├── main.py
├── requirements.txt
├── src/
│ ├── api/
│ ├── crawlers/
│ ├── database/
│ ├── extractors/
│ └── models/
├── supabase/
│ └── migrations/
└── tests/
- Better selector coverage for Swedish business websites
- Richer address parsing
- Supabase auth-aware dashboard
- Web UI for crawl jobs
- More sample datasets and benchmarks
- Questions and ideas: open a GitHub Discussion
- Bug reports: use the bug template in
.github/ISSUE_TEMPLATE/bug_report.md - Feature requests: use the feature template in
.github/ISSUE_TEMPLATE/feature_request.md
PRs are welcome. Please read the contributing guide before opening a pull request.
- Fork the repo
- Create a branch
- Add tests
- Open a pull request
Apache-2.0
If you like the project, star it on GitHub to help it grow.