Loading repository data…
Loading repository data…
PKHarsimran / repository
Website-downloader is a powerful and versatile Python script designed to download entire websites along with all their assets. This tool allows you to create a local copy of a website, including HTML pages, images, CSS, JavaScript files, and other resources. It is ideal for web archiving, offline browsing, and web development.
Turn any website you're authorized to copy into a fast, browsable offline mirror — with one command.
A modern, hackable alternative to wget --mirror and HTTrack — built in pure Python, without dragging in a heavy crawler framework.
📖 Read the Wiki — full CLI reference, cookbook, and troubleshooting · 📦 Install from PyPI
Open example_backup/index.html in your browser — the whole site works from disk: pages, styles, scripts, images, fonts, and media, all with links rewritten for offline browsing.
git clone https://github.com/PKHarsimran/website-downloader.git
cd website-downloader
python -m venv .venv
.venv\Scripts\activate # macOS/Linux: source .venv/bin/activate
pip install -e .
website-downloader --url https://example.com --destination example_backup --max-pages 100
The classic script entry point still works too:
python website-downloader.py --url https://example.com --destination example_backup
Those tools are great — until you hit a modern website. This project exists for the gap between "one-liner that misses half the assets" and "write your own Scrapy project."
| website-downloader | wget --mirror | HTTrack | Scrapy | |
|---|---|---|---|---|
Modern assets: srcset, data-src, poster, CSS @import, JS asset strings | ✅ | partial | partial | build it yourself |
| JavaScript rendering (React, Vue, Next.js) | ✅ Playwright | ❌ | ❌ | plugin |
Incremental re-mirroring (ETag / Last-Modified) | ✅ | timestamps only | ✅ | manual |
| Cookies + custom headers for authorized portals | ✅ | ✅ | ✅ | ✅ |
| Selective CDN mirroring with a domain allowlist | ✅ | ❌ | partial | manual |
| Zip + WARC export | ✅ | WARC ✅ | ❌ | manual |
| Windows-safe paths (long paths, reserved names, query hashing) | ✅ | ❌ | partial | manual |
| Small, readable Python codebase you can extend | ✅ | ❌ (C) | ❌ (C) | framework |
--page-threads, ~3–4× faster on multi-page sites), threaded asset downloads, and optional lxml parsing (pip install -e ".[fast]").--update skips unchanged pages and assets using ETag/Last-Modified, perfect for recurring archives.--render-js).sitemap.xml (including nested sitemap indexes) for complete discovery.--respect-robots, --delay, retry with backoff, and per-asset size caps.flowchart TD
A["Start with a URL and CLI options"] --> B["Create session with cookies, headers, retries"]
B --> C{"Use sitemap?"}
C -- "Yes" --> D["Load sitemap URLs into the page queue"]
C -- "No" --> E["Queue the starting URL"]
D --> F["Fetch next page"]
E --> F
F --> G{"Update cache says unchanged?"}
G -- "Yes" --> H["Reuse saved local file"]
G -- "No" --> I{"Render JavaScript?"}
I -- "No" --> J["Download HTML with requests"]
I -- "Yes" --> K["Render page with Playwright"]
J --> L["Parse HTML with BeautifulSoup"]
K --> L
H --> L
L --> M["Find page links and asset links"]
M --> N{"Same-site page?"}
N -- "Yes" --> O["Queue page for crawling"]
N -- "No" --> P{"Asset allowed?"}
P -- "Yes" --> Q["Download asset"]
P -- "No" --> R["Keep original reference or skip"]
O --> S["Rewrite links for offline browsing"]
Q --> S
R --> S
S --> T["Save mirror folder"]
T --> U{"Export requested?"}
U -- "Zip/WARC" --> V["Write portable archive"]
U -- "No" --> W["Open index.html locally"]
V --> W
In plain English:
sitemap.xml, custom headers, cookies, and robots rules.--max-pages limit.--update, unchanged resources are skipped using cache metadata.--zip-output or --warc-output, the result is also exported as an archive.Start with the core install, then add extras only when you need them:
| Install | Use when you want |
|---|---|
pip install -e . | Normal static-site crawling with requests and BeautifulSoup. |
pip install -e ".[fast]" | Faster HTML parsing with lxml (used automatically when installed). |
pip install -e ".[render]" | Playwright-powered JavaScript rendering with --render-js or --headless. |
pip install -e ".[ux]" | Rich-powered terminal progress with --progress. |
pip install -e ".[dev]" | Tests, formatting, linting, and local contributor work. |
Mirror a small public site:
website-downloader ^
--url https://example.com ^
--destination example_backup ^
--max-pages 50
Speed up a large mirror with parallel page fetching:
website-downloader ^
--url https://example.com ^
--max-pages 500 ^
--page-threads 4
--page-threads defaults to 1 so crawls stay polite; raise it only for sites that can handle concurrent requests. --render-js always uses a single page worker.
Download selected CDN assets:
website-downloader ^
--url https://example.com ^
--destination example_backup ^
--download-external-assets ^
--external-domains cdn.example.com fonts.gstatic.com
Mirror an authorized site with cookies:
website-downloader ^
--url https://intranet.example.com ^
--destination intranet_backup ^
--cookie-file example-cookie.txt
Cookie files use normal cookie header syntax:
sessionid=abc123; csrftoken=xyz789
Send custom headers such as bearer tokens:
website-downloader ^
--url https://docs.example.com ^
--destination docs_backup ^
--header "Authorization: Bearer <token>" ^
--header "X-Environment: staging"
Use a sitemap as the crawl seed:
website-downloader ^
--url https://example.com ^
--destination example_backup ^
--sitemap
Point at a custom sitemap URL or local sitemap file:
website-downloader --url https://example.com --sitemap https://example.com/sitemap.xml
Use safer crawl limits:
website-downloader ^
--url https://example.com ^
--max-pages 50 ^
--threads 4 ^
--delay 0.25 ^
--respect-robots ^
--max-asset-bytes 25000000 ^
--user-agent "WebsiteDownloader/0.2"
Update an existing mirror without re-downloading unchanged resources:
website-downloader ^
--url https://example.com ^
--destination example_backup ^
--update
Export a portable zip and WARC archive:
website-downloader ^
--url https://example.com ^
--destination example_backup ^
--zip-output example_backup.zip ^
--warc-output example_backup.warc
Some modern sites do not expose their real links and assets until JavaScript runs. For those, install the optional Playwright extra:
pip install -e ".[render]"
playwright install chromium
website-downloader --url https://example.com --render-js --max-pages 20
--headless is also available as a friendly alias for --render-js.
--render-js and --headless are optional because Playwright is heavier than the default requests + BeautifulSoup path. Use them when a normal crawl only captures an empty app shell or misses important client-rendered links.
Install the optional UX extra for a Rich-powered terminal dashboard:
pip install -e ".[ux]"
website-downloader --url https://example.com --progress
If rich is not installed, the crawler falls back to normal logging instead of failing.
| Flag | What it does | Best for |
|---|---|---|
--page-threads | Fetches HTML pages concurrently (default 1). | Faster mirroring of large sites that tolerate concurrent requests. |
--render-js / --headless | Uses Playwright before parsing the page. | React, Vue, Angular, Next.js, and other client-rendered sites. |
--cookie-file | Sends saved browser/session cookies. | Authorized portals, staging sites, docs behind login. |
--header | Adds custom request headers. | Bearer tokens, staging headers, API gateway headers. |
--update | Reuses cache metadata and skips unchanged resources when the server supports it. | Recurring mirrors and archives. |
--sitemap | Seeds the crawl from sitemap.xml or a supplied sitemap. | Faster, more complete discovery. |
--progress | Shows a Rich terminal progress dashboard when installed. | Long crawls where visibility matters. |
--zip-output | Exports the mirror folder as a zip. | Sharing, attaching, or storing snapshots. |
--warc-output | Writes a simple WARC response archive. | Archival workflows and future replay tooling. |
| Source | Rewritten for offline use |
|---|---|
| Page links | <a href> for same-site pages |
| Images and media | src, data-src, poster, srcset |
| Stylesheets and icons | <link href> for fetchable resource types |
| Metadata images | og:image, twitter:image |
| Inline styles | style="background: url(...)" |
| CSS files | url(...) and @import |
| JavaScript files | Common static asset strings like /img/logo.png |
| External assets | Optional CDN copies under cdn/<domain>/... |
When external scripts or stylesheets are localized, the tool removes integrity and crossorigin where needed because those attributes often break offline copies.
example_backup/
index.html
about.html
assets/
site.css
app.js
img/
logo.png
hero.webp
fonts/
inter.woff2
cdn/
cdn.example.com/
library.js
Open index.html in your browser to browse the mirrored copy.
pip install -e ".[dev]"
pytest
black . --check
isort . --check-only
ruff check .
Using PyCharm? Open the repo folder, point it at a Python 3.10+ virtualenv, run pip install -e ".[dev]" in the terminal, and use the pytest runner on