Python
Image Download
URL
File Handling
Programming Tutorial

How to save an image locally using Python whose URL address I already know?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Downloading an image from a known URL in Python is mostly a matter of making an HTTP request and writing the response bytes to disk. The reliable version of that workflow adds three extra concerns: checking the HTTP status, choosing a sensible filename, and streaming the file instead of loading everything into memory at once.

Use requests and Save the Response in Chunks

The requests library is a good default for this task because it makes the HTTP part explicit and easy to debug. A chunked download is usually better than writing response.content directly, especially for larger files.

python
1from pathlib import Path
2import requests
3
4url = "https://httpbin.org/image/png"
5output_path = Path("downloads/example.png")
6output_path.parent.mkdir(parents=True, exist_ok=True)
7
8with requests.get(url, stream=True, timeout=30) as response:
9    response.raise_for_status()
10
11    with output_path.open("wb") as file_handle:
12        for chunk in response.iter_content(chunk_size=8192):
13            if chunk:
14                file_handle.write(chunk)
15
16print(f"saved to {output_path.resolve()}")

This example is safe for typical cases because it creates the destination folder, fails fast on HTTP errors, and keeps memory use predictable.

Pick the Filename Deliberately

If the image URL already ends with a usable filename, you can save it directly. If not, derive the name yourself instead of trusting the URL blindly. Query strings, redirects, and CDN URLs often make the original path useless as a filename.

Here is a small helper that uses the last path segment when available:

python
1from pathlib import Path
2from urllib.parse import urlparse
3
4def filename_from_url(url: str, default: str = "downloaded-image") -> str:
5    path = urlparse(url).path
6    name = Path(path).name
7    return name or default
8
9print(filename_from_url("https://example.com/images/photo.jpg"))
10print(filename_from_url("https://example.com/render?id=42"))

You can combine that helper with the download code:

python
url = "https://httpbin.org/image/jpeg"
filename = filename_from_url(url, default="image.jpg")
output_path = Path("downloads") / filename

That gives you a predictable save location even when the input URL is messy.

Validate the Response Looks Like an Image

If the URL is wrong, some servers return an HTML error page with status 200, which means your code may save a .png file that is actually markup. Checking the Content-Type header can catch that.

python
1import requests
2
3url = "https://httpbin.org/image/png"
4
5with requests.get(url, stream=True, timeout=30) as response:
6    response.raise_for_status()
7    content_type = response.headers.get("Content-Type", "")
8
9    if not content_type.startswith("image/"):
10        raise ValueError(f"URL did not return an image: {content_type}")

This is not perfect because servers can lie about headers, but it prevents a surprising number of bad downloads.

A Minimal Version Without requests

If you want to avoid third-party packages, the standard library can also download a file. The tradeoff is that it gives you less ergonomic HTTP handling than requests.

python
1from pathlib import Path
2from urllib.request import urlopen
3
4url = "https://httpbin.org/image/png"
5output_path = Path("downloads/std-lib-image.png")
6output_path.parent.mkdir(parents=True, exist_ok=True)
7
8with urlopen(url) as response, output_path.open("wb") as file_handle:
9    file_handle.write(response.read())
10
11print(f"saved to {output_path.resolve()}")

This version is fine for simple scripts, but for production code requests is usually easier to extend with timeouts, retries, and header checks.

Add a User-Agent or Timeout When Servers Are Picky

Some sites reject requests that look like generic bots or hang longer than you expect. A timeout is almost always worth adding, and a User-Agent header can help with servers that treat anonymous clients differently.

python
1import requests
2
3headers = {
4    "User-Agent": "image-downloader/1.0"
5}
6
7with requests.get(
8    "https://httpbin.org/image/webp",
9    headers=headers,
10    stream=True,
11    timeout=30,
12) as response:
13    response.raise_for_status()
14    print(response.headers.get("Content-Type"))

These details matter more in scheduled jobs than in throwaway scripts.

Common Pitfalls

The most common mistake is writing response.content straight to disk without checking raise_for_status, which silently saves error pages when the download fails. Another frequent issue is trusting the URL to contain a good filename even though many image endpoints are query-based or redirect through CDNs. Developers also forget to open the output file in binary mode, which corrupts image data on some systems. Finally, large downloads should not be read all at once when a chunked stream is easy to implement.

Summary

  • Use requests.get(..., stream=True) and write the response in chunks.
  • Call raise_for_status() so HTTP failures do not become broken files on disk.
  • Choose the output filename deliberately instead of assuming the URL path is clean.
  • Check Content-Type if you want to reject non-image responses early.
  • The standard library works too, but requests is usually the better default for real scripts.

Course illustration
Course illustration

All Rights Reserved.