Skip to content

Downloader

Downloads pages from a gallery with optional per-page translation and PDF export.

from nhentai import NHentai, Downloader, NekoTranslator, Engine, Language

gallery = NHentai("639456")

# Download only
dl = Downloader(gallery, output_dir="./downloads", workers=8)
paths = dl.download()

# Download + translate + PDF
translator = NekoTranslator()
dl = Downloader(
    gallery,
    output_dir="./downloads",
    translator=translator,
    translate_lang=Language.INDONESIAN,
    translate_engine=Engine.DEEPL,
)
pdf = dl.download(make_pdf=True)

If a page fails to translate, the original image is saved instead (silent fallback).

Proxy Support

The downloader supports proxy lists for enhanced connectivity. Proxy lists can be provided as local files or URLs.

Using Local Proxy List File

dl = Downloader(
    gallery,
    output_dir="./downloads",
    proxy_list=["http://127.0.0.1:8080", "socks5://10.0.0.1:1080"]
)

Using URL-based Proxy List

dl = Downloader(
    gallery,
    output_dir="./downloads",
    proxy_list=["https://example.com/proxies.txt"]
)

The downloader will automatically fetch proxy lists from URLs and use them for requests.


nhentai.downloader.Downloader(gallery: NHentai, output_dir: str | Path = '.', workers: int = 4, translator: Any | None = None, translate_lang: Language | str = Language.ENGLISH, translate_engine: str | Any = 'deepl', timeout: int = 30, proxy_list: list[str] | None = None)

Downloads pages from an :class:NHentai gallery, with optional translation and PDF export.

Parameters:

Name Type Description Default
gallery NHentai

Parsed gallery object.

required
output_dir str | Path

Root directory for downloads. Gallery pages go into <output_dir>/<gallery_id>/.

'.'
workers int

Number of parallel download threads.

4
translator Any | None

Optional :class:NekoTranslator. If provided, each page is translated before saving.

None
translate_lang Language | str

Target language for translation.

ENGLISH
translate_engine str | Any

Engine to use for translation.

'deepl'
timeout int

Per-request timeout in seconds.

30
Source code in nhentai/downloader.py
def __init__(
    self,
    gallery: NHentai,
    output_dir: str | Path = ".",
    workers: int = 4,
    translator: Any | None = None,
    translate_lang: Language | str = Language.ENGLISH,
    translate_engine: str | Any = "deepl",
    timeout: int = 30,
    proxy_list: list[str] | None = None,
):
    self.gallery = gallery
    self.output_dir = Path(output_dir)
    self.workers = max(1, workers)
    self.translator: Any | None = translator
    self.translate_lang = Language(translate_lang) if not isinstance(translate_lang, Language) else translate_lang
    self.translate_engine = translate_engine
    self.timeout = timeout
    self.proxy_list = proxy_list or []

download(make_pdf: bool = False, progress_callback: Callable[[int, int, str], None] | None = None) -> list[Path]

Download all pages. Returns a list of saved file paths (or a single PDF path if make_pdf=True).

Parameters:

Name Type Description Default
make_pdf bool

Compile pages into a PDF after downloading. Requires Pillow.

False
progress_callback Callable[[int, int, str], None] | None

Optional callback to report progress: (current, total, status).

None

Raises:

Type Description
DownloadError

If a page fails to download.

RuntimeError

If make_pdf=True but Pillow is not installed.

Source code in nhentai/downloader.py
def download(self, make_pdf: bool = False, progress_callback: Callable[[int, int, str], None] | None = None) -> list[Path]:
    """
    Download all pages. Returns a list of saved file paths (or a single PDF path if ``make_pdf=True``).

    :param make_pdf: Compile pages into a PDF after downloading. Requires Pillow.
    :param progress_callback: Optional callback to report progress: (current, total, status).
    :raises DownloadError: If a page fails to download.
    :raises RuntimeError: If ``make_pdf=True`` but Pillow is not installed.
    """
    import concurrent.futures

    gallery_target_dir = self.output_dir / str(self.gallery.id)

    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        urls = self.gallery.get_image_urls()
        results: list[Path | None] = [None] * len(urls)
        total = len(urls)

        def _update_progress(current: int, status: str):
            if progress_callback:
                progress_callback(current, total, status)

        with concurrent.futures.ThreadPoolExecutor(max_workers=self.workers) as pool:
            futures = {
                pool.submit(self._download_page, url, tmp_path, i + 1, _update_progress): i
                for i, url in enumerate(urls)
            }
            for future in concurrent.futures.as_completed(futures):
                idx = futures[future]
                try:
                    results[idx] = future.result()
                except Exception as exc:
                    # If one fails, we might want to cancel others or just let them finish/error
                    raise DownloadError(f"Failed to download page {idx + 1}: {exc}") from exc

        paths = [p for p in results if p is not None]

        if make_pdf:
            self.output_dir.mkdir(parents=True, exist_ok=True)
            pdf_path = self._make_pdf(paths, self.output_dir)
            return [pdf_path]

        # If not PDF, move from temp to final destination
        gallery_target_dir.mkdir(parents=True, exist_ok=True)
        final_paths = []
        for p in paths:
            target = gallery_target_dir / p.name
            shutil.move(str(p), str(target))
            final_paths.append(target)
        return final_paths

nhentai.downloader.DownloadError

Bases: Exception

Raised when a page download fails.