Python requests module: How to download a large file smoothly

Updated: January 2, 2024 By: Guest Contributor Post a comment

Introduction

Dealing with large file downloads can be a daunting task, especially when ensuring stability and efficiency. The Python requests module provides a straightforward way to handle file downloads. This tutorial covers techniques for downloading large files smoothly using Python.

Getting Started with Requests

To get started with downloading files using the requests module, you first need to install the module if you haven’t already. You can install it using pip:

pip install requests

Once you have the requests module installed, you can start by downloading a small file to understand the basic concept:

import requests

url = 'http://example.com/file.pdf'
response = requests.get(url)

with open('downloaded_file.pdf', 'wb') as file:
    file.write(response.content)

Understanding Response Streaming

The previous example is straightforward, but it reads the entire file into memory before writing to disk. To handle large files more efficiently, you should stream the file download:

import requests

url = 'http://example.com/large_file.zip'
response = requests.get(url, stream=True)

with open('large_file.zip', 'wb') as file:
    for chunk in response.iter_content(chunk_size=128):
        file.write(chunk)

Setting the Chunk Size

Chunk size is important in managing memory usage and controlling the download progress:

chunk_size = 1 * 1024 * 1024  # 1 MB

with open('large_file.zip', 'wb') as file:
    for chunk in response.iter_content(chunk_size=chunk_size):
        if chunk:
            file.write(chunk)

Monitoring Progress

When downloading large files, it’s useful to display download progress. Here’s how to track progress using the requests module and the tqdm library:

import requests
from tqdm import tqdm

url = 'http://example.com/large_file.zip'
response = requests.get(url, stream=True)
total_size = int(response.headers.get('content-length', 0))

print(f'Downloading: {url}')

with open('large_file.zip', 'wb') as file,
     tqdm(desc='Downloading', total=total_size, unit='B', unit_scale=True, unit_divisor=1024) as bar:
    for chunk in response.iter_content(chunk_size=1024):
        if chunk:
            file.write(chunk)
            bar.update(len(chunk))

Error Handling

It’s also crucial to handle potential errors that may occur during the download process:

try:
    response.raise_for_status()
    # proceed with download
except requests.exceptions.HTTPError as err:
    print(f'HTTP Error: {err}')
except requests.exceptions.ConnectionError as errc:
    print(f'Error Connecting: {errc}')
except requests.exceptions.Timeout as errt:
    print(f'Timeout Error: {errt}')
except requests.exceptions.RequestException as errr:
    print(f'OOps: Something Else: {errr}')

Resume Partial Downloads

Sometimes, you may need to resume a download if it’s interrupted. Python requests can also handle this by setting the ‘Range’ header:

headers = {
    'Range': f'bytes={start_bytes}-'
}
response = requests.get(url, headers=headers, stream=True)
# continue the above download process with streaming

Using Sessions for Multiple Requests

If you’re downloading multiple files from the same server, using a requests session can improve efficiency by reusing the underlying TCP connection:

with requests.Session() as session:
    response = session.get(url, stream=True)
    # continue downloading as before

Handling Redirects

Sometimes, a URL might redirect to another location. Ensure your request follows redirects for a successful download:

response = requests.get(url, allow_redirects=True, stream=True)
# proceed with your download code

Turn Off SSL Verification for Trusted Sources

In cases where you are sure of the trustworthiness of the host and need to avoid SSL overhead, you can turn off SSL verification:

response = requests.get(url, verify=False, stream=True)
# Warning: Only disable SSL verification if you are sure of the source

Asynchronous Downloads with aiohttp

For an even more efficient method, consider using the asyncio library in conjunction with aiohttp for asynchronous downloads:

import aiohttp
import asyncio

async def download_file(session, url):
    async with session.get(url) as response:
        with open('async_large_file', 'wb') as fd:
            async for data in response.content.iter_any():
                fd.write(data)

async def main(url):
    async with aiohttp.ClientSession() as session:
        await download_file(session, url)

url = 'http://example.com/very_large_file.zip'
loop = asyncio.get_event_loop()
loop.run_until_complete(main(url))

Conclusion

Downloading large files in Python doesn’t have to be complicated. The requests module provides a powerful yet simple solution for handling file downloads. Through streamlining downloads, managing chunk sizes, tracking progress, handling errors, and potentially using sessions or asynchronous code, you can create a script that handles large file transfers both smoothly and efficiently.