If you use Python regularly, you might have come across the wonderful
requests library. I use it almost everyday to read urls or make POST requests. In this post, we shall see how we can download a large file using the
requests module with low memory consumption.
To Stream or Not to Stream
When downloading large files/data, we probably would prefer the streaming mode while making the
get call. If we use the
stream parameter and set it to
True, the download will not immediately start. The file download will start when we try to access the
content property or try to iterate over the content using
If we set
False, all the content is downloaded immediately and put into memory. If the file size is large, this can soon cause issues with higher memory consumption. On the other hand – if we set
False, the content is not downloaded, but the headers are downloaded and the connection is kept open. We can now choose to proceed downloading the file or simply cancel it.
But we must also remember that if we decide to stream the file, the connection will remain open and can not go back to the connection pool. If we’re working with many large files, these might lead to some efficiency. So we should carefully choose where we should stream. And we should take proper care to close the connections and dispose any unused resources in such scenarios.
Iterating The Content
By setting the
stream parameter, we have delayed the download and avoided taking up large chunks of memory. The headers have been downloaded but the body of the file still awaits retrieval. We can now get the data by accessing the
content property or choosing to iterate over the content. Accessing the
content directly would read the entire response data to memory at once. That is a scenario we want to avoid when our target file is quite large.
So we are left with the choice to iterate over the content. We can use
iter_content where the content would be read chunk by chunk. Or we can use
iter_lines where the content would be read line by line. Either way, the entire file will not be loaded into memory and keep the memory usage down.
response = requests.get(url, stream=True)
handle = open(target_path, "wb")
for chunk in response.iter_content(chunk_size=512):
if chunk: # filter out keep-alive new chunks
The code should be self explanatory. We are opening the
stream set to
True. And then we are opening a file handle to the
target_path (where we want to save our file). Then we iterate over the content, chunk by chunk and write the data to the file.