Download and save PDF file with Python requests module
Asked Answered
S

6

137

I am trying to download a PDF file from a website and save it to disk. My attempts either fail with encoding errors or result in blank PDFs.

In [1]: import requests

In [2]: url = 'http://www.hrecos.org//images/Data/forweb/HRTVBSH.Metadata.pdf'

In [3]: response = requests.get(url)

In [4]: with open('/tmp/metadata.pdf', 'wb') as f:
   ...:     f.write(response.text)
---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)
<ipython-input-4-4be915a4f032> in <module>()
      1 with open('/tmp/metadata.pdf', 'wb') as f:
----> 2     f.write(response.text)
      3 

UnicodeEncodeError: 'ascii' codec can't encode characters in position 11-14: ordinal not in range(128)

In [5]: import codecs

In [6]: with codecs.open('/tmp/metadata.pdf', 'wb', encoding='utf8') as f:
   ...:     f.write(response.text)
   ...: 

I know it is a codec problem of some kind but I can't seem to get it to work.

Spires answered 29/12, 2015 at 2:0 Comment(0)
H
257

You should use response.content in this case:

with open('/tmp/metadata.pdf', 'wb') as f:
    f.write(response.content)

From the document:

You can also access the response body as bytes, for non-text requests:

>>> r.content
b'[{"repository":{"open_issues":0,"url":"https://github.com/...

So that means: response.text return the output as a string object, use it when you're downloading a text file. Such as HTML file, etc.

And response.content return the output as bytes object, use it when you're downloading a binary file. Such as PDF file, audio file, image, etc.


You can also use response.raw instead. However, use it when the file which you're about to download is large. Below is a basic example which you can also find in the document:

import requests

url = 'http://www.hrecos.org//images/Data/forweb/HRTVBSH.Metadata.pdf'
r = requests.get(url, stream=True)

with open('/tmp/metadata.pdf', 'wb') as fd:
    for chunk in r.iter_content(chunk_size):
        fd.write(chunk)

chunk_size is the chunk size which you want to use. If you set it as 2000, then requests will download that file the first 2000 bytes, write them into the file, and do this again, again and again, unless it finished.

So this can save your RAM. But I'd prefer use response.content instead in this case since your file is small. As you can see use response.raw is complex.


Relates:

Hypervitaminosis answered 29/12, 2015 at 2:2 Comment(1)
Cool, thank you for the additional information about response.raw.Spires
Z
50

In Python 3, I find pathlib is the easiest way to do this. Request's response.content marries up nicely with pathlib's write_bytes.

from pathlib import Path
import requests
filename = Path('metadata.pdf')
url = 'http://www.hrecos.org//images/Data/forweb/HRTVBSH.Metadata.pdf'
response = requests.get(url)
filename.write_bytes(response.content)
Zygospore answered 8/11, 2018 at 8:39 Comment(2)
Thank you for posting this. The original question was Python 2.7 but I've moved on and now use Python 3. I didn't know about about the pathlib library [new in version 3.4] and will incorporate it into my current projects.Spires
It give 544 and the file is broken, any ideas?Greiner
J
34

You can use urllib:

import urllib.request
urllib.request.urlretrieve(url, "filename.pdf")
Jointress answered 29/10, 2019 at 19:56 Comment(4)
This is best one, tbh.Tonality
This one is bestTribade
urlretrieve relies on global settings to determine request headers, making it unsuitable for some use cases.Nevermore
This one is really good. One thing to keep in mind is that without headers it might throw a 403 error. To avoid it, pass user-agent into headers.Roadstead
B
4

Please note I'm a beginner. If My solution is wrong, please feel free to correct and/or let me know. I may learn something new too.

My solution:

Change the downloadPath accordingly to where you want your file to be saved. Feel free to use the absolute path too for your usage.

Save the below as downloadFile.py.

Usage: python downloadFile.py url-of-the-file-to-download new-file-name.extension

Remember to add an extension!

Example usage: python downloadFile.py http://www.google.co.uk google.html

import requests
import sys
import os

def downloadFile(url, fileName):
    with open(fileName, "wb") as file:
        response = requests.get(url)
        file.write(response.content)


scriptPath = sys.path[0]
downloadPath = os.path.join(scriptPath, '../Downloads/')
url = sys.argv[1]
fileName = sys.argv[2]      
print('path of the script: ' + scriptPath)
print('downloading file to: ' + downloadPath)
downloadFile(url, downloadPath + fileName)
print('file downloaded...')
print('exiting program...')
Bowerbird answered 31/3, 2019 at 7:52 Comment(3)
Pawel, thank you for your answer. I was a Python novice when I first posted this question. Now I know the language very well. Your use case of writing a Python script to download a file from a command line can be covered by utilities like wget or curl. Also, your function downloadFile as posted seems to call itself. Did you intend to indent the second block of code? In stackoverflow you can correct that by out-denting that. I'd also like to suggest you have a look at Python's argparse library. You can use it to make nice command line utilities. It will take care of the parameters for you.Spires
I do like your use of a context manager (with open... as file:, etc) to handle the file writing. Your code is neatly written. You are on a good path to learning Python. Good luck!Spires
Thanks for the reply, @Jim! I've edited the post, and indeed I did not "intend to indent" :D the main part of the program. Thanks for your advices! :)Bowerbird
K
1

Generally, this should work in Python3:

import urllib.request 
..
urllib.request.get(url)

Remember that urllib and urllib2 don't work properly after Python2.

If in some mysterious cases requests don't work (happened with me), you can also try using

wget.download(url)

Related:

Here's a decent explanation/solution to find and download all pdf files on a webpage:

https://medium.com/@dementorwriter/notesdownloader-use-web-scraping-to-download-all-pdfs-with-python-511ea9f55e48

Kyd answered 21/6, 2020 at 11:42 Comment(0)
T
-5

regarding Kevin answer to write in a folder tmp, it should be like this:

with open('./tmp/metadata.pdf', 'wb') as f:
    f.write(response.content)

he forgot . before the address and of-course your folder tmp should have been created already

Trueman answered 1/4, 2017 at 23:52 Comment(1)
1- Kevin did not come up with the idea to write in tmp, it was like like in OP's question. 2- the /tmp directory is the tmp in Unix systems, located at /tmp, no .Saying

© 2022 - 2024 — McMap. All rights reserved.