For anything under a few megabytes, fetch it and write the bytes. Note the "wb": a download is bytes, not text.
from pathlib import Path import requests url = "https://www.python.org/static/img/python-logo.png" response = requests.get(url, timeout=30) response.raise_for_status() Path("python-logo.png").write_bytes(response.content) print(len(response.content), "bytes saved")
These examples need your own machine, since a browser page can only fetch from servers that allow it. Install requests first with pip install requests.
Big files: stream it
response.content holds the whole file in memory. For a large dataset or a video, ask for it in pieces and write each piece as it arrives:
import requests url = "https://example.com/big-dataset.zip" with requests.get(url, stream=True, timeout=30) as response: response.raise_for_status() with open("big-dataset.zip", "wb") as f: for chunk in response.iter_content(chunk_size=1024 * 1024): # 1 MB at a time f.write(chunk)
Memory use stays at about one chunk, whatever the file size.
No requests installed? The standard library can do it
import urllib.request urllib.request.urlretrieve( "https://www.python.org/static/img/python-logo.png", "python-logo.png", )
Fine for a quick script. It has no timeout setting and its errors are harder to read, so prefer requests in anything you'll run more than once.
Getting the file name from the URL
from pathlib import PurePosixPath from urllib.parse import urlparse url = "https://example.com/reports/2026/q3-sales.csv?download=1" name = PurePosixPath(urlparse(url).path).name print(name)
q3-sales.csv
urlparse drops the ?download=1 part first; splitting the URL on / by hand would have kept it in the file name.
If the download fails with CERTIFICATE_VERIFY_FAILED, don't reach for verify=False; the SSL error page explains the real fix.