image download
requests library
Python programming
web scraping
tutorial

How to download image using requests

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Downloading an image with Python requests is easy for a quick script, but a reliable downloader needs a bit more structure than get and write. Timeouts, streaming, content validation, and safe file handling matter if you want the code to behave well when servers are slow, responses are wrong, or files are larger than expected.

The Simplest Working Download

For a small trusted image, the basic pattern is:

python
1import requests
2
3url = "https://httpbin.org/image/png"
4response = requests.get(url, timeout=20)
5response.raise_for_status()
6
7with open("image.png", "wb") as file_handle:
8    file_handle.write(response.content)

This is enough for throwaway scripts, but it loads the entire response into memory. That is fine for tiny files and not ideal for large images or large batches.

Stream the Response Instead of Buffering Everything

For a better default, request the response as a stream and write it in chunks.

python
1import requests
2
3
4def download_image(url: str, output_path: str) -> None:
5    with requests.get(url, stream=True, timeout=30) as response:
6        response.raise_for_status()
7
8        with open(output_path, "wb") as file_handle:
9            for chunk in response.iter_content(chunk_size=8192):
10                if chunk:
11                    file_handle.write(chunk)
12
13
14download_image("https://httpbin.org/image/jpeg", "photo.jpg")

This keeps memory usage low and scales much better when you are downloading many files or working with high-resolution images.

Validate That the Response Is Actually an Image

Some endpoints return an HTML error page, rate-limit message, or JSON payload while still producing a successful status code. A quick content-type check catches a lot of bad data early.

python
1import requests
2
3
4def download_checked(url: str, output_path: str) -> None:
5    with requests.get(url, stream=True, timeout=30) as response:
6        response.raise_for_status()
7
8        content_type = response.headers.get("Content-Type", "")
9        if not content_type.startswith("image/"):
10            raise ValueError(f"Expected image content, got {content_type!r}")
11
12        with open(output_path, "wb") as file_handle:
13            for chunk in response.iter_content(chunk_size=8192):
14                if chunk:
15                    file_handle.write(chunk)

That one validation step is worth it in scraping, ETL, and dataset-building jobs where silent bad downloads become expensive later.

Add Retries for Temporary Failures

Transient network failures happen. A requests.Session with bounded retry logic is a good improvement for batch jobs.

python
1import requests
2from requests.adapters import HTTPAdapter
3from urllib3.util.retry import Retry
4
5
6def build_session() -> requests.Session:
7    retry = Retry(
8        total=3,
9        connect=3,
10        read=3,
11        backoff_factor=0.5,
12        status_forcelist=[429, 500, 502, 503, 504],
13        allowed_methods=["GET"],
14    )
15
16    adapter = HTTPAdapter(max_retries=retry)
17    session = requests.Session()
18    session.mount("https://", adapter)
19    session.mount("http://", adapter)
20    return session

You can then reuse the session:

python
1session = build_session()
2
3with session.get("https://httpbin.org/image/png", stream=True, timeout=30) as response:
4    response.raise_for_status()
5    print(response.headers.get("Content-Type"))

Retries should be limited. Too many retries just make a failing job slower and harder to diagnose.

Use Safe Output Paths

For repeated downloads, do not assume the URL path is already a safe filename. Derive a usable name and write to a temporary file first if partial downloads would be a problem.

python
1from pathlib import Path
2
3
4def safe_target_name(url: str) -> str:
5    name = Path(url.split("?", 1)[0]).name
6    return name or "downloaded_image"

An atomic-write pattern is even safer:

python
1from pathlib import Path
2
3
4def atomic_write_download(session: requests.Session, url: str, output_path: Path) -> None:
5    temp_path = output_path.with_suffix(output_path.suffix + ".part")
6
7    with session.get(url, stream=True, timeout=30) as response:
8        response.raise_for_status()
9
10        with open(temp_path, "wb") as file_handle:
11            for chunk in response.iter_content(8192):
12                if chunk:
13                    file_handle.write(chunk)
14
15    temp_path.replace(output_path)

That prevents half-written files from being mistaken for valid completed downloads.

Optional Post-Download Validation

If the next step assumes the file is a valid image, verify it after download. One common choice is Pillow.

python
1from PIL import Image
2
3
4def verify_image(path: str) -> None:
5    with Image.open(path) as image:
6        image.verify()

This is useful when building image datasets, media ingestion pipelines, or upload workflows where corrupt files should fail immediately.

Common Pitfalls

The most common mistake is forgetting to set a timeout, which can leave the script hanging indefinitely. Another is using response.content for large files and creating avoidable memory pressure. Developers also trust status codes too much and skip content-type checks, which lets HTML or JSON responses slip into image directories unnoticed. Finally, writing directly to the final filename can leave broken partial files behind when a download is interrupted.

Summary

  • Use requests.get(..., timeout=...) with raise_for_status() as the baseline.
  • Prefer streaming with iter_content for real downloads.
  • Check Content-Type so you do not store non-image responses as images.
  • Add bounded retries when downloads happen in batches or unstable networks.
  • Use safe filenames and temporary files to avoid partial-download corruption.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.