DataForge
Turn any website into a fine-tuning dataset — in one command.
Fine-tuning needs data, and good domain data is trapped in documentation sites, knowledge bases and public archives. Getting it out usually means writing a throwaway scraper, a chunker, a prompt loop and an exporter — then doing it all again for the next domain.
DataForge is that pipeline, already built, tuned, and checkpointed end to end:
sitemap ──▶ crawl ──▶ clean + chunk ──▶ LLM generates Q&A ──▶ score ──▶ JSONL / Parquet / HF Hub
pip install llm-web-crawler
dataforge init-recipe fema.yaml # write a starter config
dataforge run fema.yaml # crawl, generate, score, export — unattended
You get a versioned, deduplicated, quality-scored dataset in ChatML JSONL,
Parquet and CSV, ready for Unsloth, Axolotl, TRL or HuggingFace datasets.
What you'll need
Before you run anything, here's the honest list of what's required vs. optional:
| Required? | Notes | |
|---|---|---|
| Python 3.11+ | ✅ Required | Or skip it entirely with a standalone binary — no interpreter needed. |
| An LLM to generate and score samples | ✅ Required (one of the below) | This is what actually writes the Q&A pairs. |
| — A hosted provider API key (OpenAI, Anthropic, Google, Groq, or Together) | One of these, or the local option below | dataforge config walks you through it; stored in .env. Content leaves your machine for generation. |
| — Ollama running locally, no key | or fully local | ollama serve && ollama pull llama3.2, then dataforge config → ollama. Nothing leaves your machine. This is the only local-inference path wired in today — LM Studio, Lemonade and other OpenAI-compatible local servers aren't supported yet (#22). |
| A HuggingFace account | ❌ Optional | Only if you set export.targets: [huggingface] to publish the finished dataset to the Hub. Needs HUGGINGFACE_TOKEN. |
| A Kaggle account | ❌ Optional | Only if you set export.targets: [kaggle]. Needs KAGGLE_USERNAME / KAGGLE_KEY. |
| Nothing else | — | Local JSONL/Parquet/CSV export (the default) needs no account at all — the whole pipeline runs against just an LLM provider. |
In short: one LLM (hosted key, or Ollama for zero-key/fully-local) is the only hard requirement. Everything else — which provider, which export target, whether you need a HuggingFace or Kaggle account — is a choice you make in the recipe, not a prerequisite to get started. Full setup for each option is in docs/INSTALLATION.md and docs/CONFIGURATION.md.
Why DataForge
| Configuration as code | A YAML recipe captures every decision. Commit it, review it in a PR, re-run it in CI — the same input produces the same dataset. |
| Unattended by default | dataforge run needs no prompts. Cron it, or drive it interactively with dataforge when exploring a new site. |
| Streaming pipeline | Generation starts on the first page instead of waiting for the last one, so the LLM and the crawler work at the same time. |
| Resumable, not restartable | Every page, chunk and sample is checkpointed to SQLite. Interrupt a 5,000-page crawl and resume exactly where it stopped — nothing is re-fetched or re-billed. |
| Any model | OpenAI, Anthropic, Google, Groq, Together, or fully local via Ollama — so sensitive content never has to leave your machine. |
| Leak-free by design | Train/validation/test splits are page-aware, not sample-aware, so paraphrases of the same source never land on both sides of a split. |
| Polite by construction | robots.txt honoured, per-domain rate limiting, URL sanitisation, and PII/copyright guidance built in. |
A worked example
Building a U.S. disaster-preparedness dataset from Ready.gov. The full
recipe ships with the project at
examples/ready-gov.yaml:
version: 1
name: ready-gov-preparedness
stream: true
source:
urls:
- https://www.ready.gov/sitemap.xml
language: en
exclude: ['/node/', '/press-release', 're:toolkit']
max_urls: 150
generation:
format: qa
goal: >
Accurate Q&A about U.S. disaster preparedness, grounded strictly in
official Ready.gov and FEMA guidance.
n_per_chunk: 3
quality:
threshold: 0.5
export:
targets: [local]
split:
train: 0.8
validation: 0.1
test: 0.1
group_by: page
dataforge run examples/ready-gov.yaml --dry-run # validate + preview the plan
dataforge run examples/ready-gov.yaml # execute
One sitemap becomes a few hundred curated pages, a few thousand chunks, and a scored, deduplicated, leak-free Q&A dataset — without answering a single prompt.
Get started
pip install llm-web-crawler
dataforge init-recipe my.yaml # write an annotated starter recipe
dataforge run my.yaml --dry-run # validate and print the plan, run nothing
dataforge run my.yaml # execute end-to-end, no prompts
Prefer to explore a site interactively first? dataforge launches the
guided wizard with a per-stage review. Full install options (uv, pip,
standalone binaries, local models via Ollama), every CLI command, all
environment variables and the complete recipe schema are in the docs:
- Installation — uv / pip / source / standalone binaries / Ollama
- Configuration — environment variables and full recipe reference
- Architecture — how discovery, streaming, quality and export fit together
- Development — project layout, testing, releasing
- Ethics & data residency — responsible use, PII, copyright, rate limits
- Third-party libraries — full dependency and license list
- Technical writeup — design rationale and references
What makes it different
Most scrapers stop at "here's the text." DataForge is built for the part that actually determines whether a fine-tune works:
- It knows a scraped dataset isn't i.i.d. Several Q&A pairs come from one chunk, several chunks from one page — so a naive random split leaks paraphrases across train/test and quietly inflates your eval score. DataForge splits by page and verifies the property before writing.
- It treats web failure modes as data, not noise.
429isn't404,Retry-Afterisn't ignored, androbots.txtCrawl-delayis honoured automatically — so a run against a real, imperfect site finishes instead of half-failing silently. - It doesn't make you choose between fast and resumable. Streaming mode overlaps crawling and generation for throughput; every stage still checkpoints to SQLite, so a killed process loses only the item in flight.
- The quality bar is layered, not a single score. Length heuristics, a source-reference filter, deduplication, and an LLM judge that fails closed — with a rejection breakdown so you can see what's actually being filtered out.
License
MIT — see LICENSE for details.
If you use DataForge-generated datasets in a publication or project, attribution is appreciated but not required:
Ian Too. DataForge (2026). https://github.com/ianktoo/data-forge
@software{dataforge2026,
author = {Ian Too},
title = {DataForge: LLM Data Pipeline},
year = {2026},
url = {https://github.com/ianktoo/data-forge},
license = {MIT}
}
Release files for llm-web-crawler 2.3.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| llm_web_crawler-2.3.3.tar.gz | 457.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| llm_web_crawler-2.3.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 587.0 kB
Release files / llm_web_crawler-2.3.3.tar.gz
| Download URL | llm_web_crawler-2.3.3.tar.gz |
|---|---|
| Size | 457.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8164ee7b33b6dd385044e961239caf5fdc375010243394a826f5d4b49a63e88f
|
|
BLAKE2b-256 checksum How to use checksums |
6dd42a11162851f1ffb725862cfbf7bfa1e77ded0f06a3842abfe446597085ee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / llm_web_crawler-2.3.3-py3-none-any.whl
| Download URL | llm_web_crawler-2.3.3-py3-none-any.whl |
|---|---|
| Size | 129.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
75c693f37b94194bab4dcfbfed07f21044ba6a99383720095fb364babac0d1fc
|
|
BLAKE2b-256 checksum How to use checksums |
56b49df74beeb938d8912fb7fcd7fa6d6b1d09c4d1655363ccfbf7a77cef221c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|