使用Python requests模块下载并保存PDF文件

137

我正在尝试从网站下载PDF文件并保存到磁盘。我的尝试要么出现编码错误,要么导致空白的PDF文件。

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)
   ...: 

我知道这是某种编解码问题,但似乎无法解决。

6个回答

252

在这种情况下,你应该使用response.content

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

来自该文档

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

>>> r.content
b'[{"repository":{"open_issues":0,"url":"https://github.com/...
这意味着:response.text返回字符串对象,用于下载文本文件,例如HTML文件等。

response.content返回字节对象,用于下载二进制文件,例如PDF文件、音频文件、图像等。


你也可以使用response.raw代替。然而,当你要下载的文件很大时才使用它。下面是一个基本示例,你也可以在文档中找到:

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 是你想要使用的块大小。如果将其设置为 2000,那么请求将下载该文件的前 2000 字节,将它们写入文件中,然后再次执行这个过程,直到完成。

因此,这可以节省你的 RAM。但在这种情况下,我更喜欢使用 response.content,因为你的文件很小。正如你所看到的,使用 response.raw 是复杂的。


相关文章:


很棒,感谢您提供有关response.raw的额外信息。 - Jim

49

在Python 3中,我发现pathlib是最简单的方法来实现这一点。Request的response.content可以很好地与pathlib的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)

5
感谢您发布这篇文章。原始问题是关于Python 2.7的,但我现在已经使用Python 3了。我不知道有关pathlib库[在版本3.4中新增]的内容,将会将其融入到我的当前项目中。 - Jim
它给出了 544,文件已损坏,有什么想法吗? - ah bon
@ahbon,你是什么意思? - user6481870
也许这只是我的观察,但我觉得你甚至没有使用你用pathlib设置的变量“filename”,而你却在推荐它。 - tagoma
1
@tagoma - filename 是一个 Path 对象。在最后一行中,他们在 filename 对象上调用 write_bytes 方法:filename.write_bytes(response.content) - nigh_anxiety

32
您可以使用urllib:
import urllib.request
urllib.request.urlretrieve(url, "filename.pdf")

2
这是最好的一个,说实话。 - Dhaval Savalia
这个是最好的。 - roktim
2
"urlretrieve" 依赖于全局设置来确定请求头,因此在某些用例中不适用。 - crenshaw-dev
这个很不错。需要记住的一件事是,如果没有“headers”,它可能会抛出403错误。为了避免这种情况,user-agent传递到headers中。 - Dmitriy Zub

4
请注意,我是一个初学者。如果我的解决方案有误,请随时纠正和/或告知我。我也可能会学到一些新的东西。
我的解决方案: 根据需要将downloadPath更改为相应位置,以便将文件保存在您想要的位置。您也可以自由地使用绝对路径来满足您的需求。
将以下内容保存为downloadFile.py。
用法:python downloadFile.py 要下载的文件的URL 新文件名.扩展名 一定要记得添加扩展名! 用法示例: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...')

Pawel,感谢您的回答。我在第一次发布这个问题时是一个Python新手。现在我非常了解这门语言。像wget或curl这样的实用程序可以覆盖您编写Python脚本从命令行下载文件的用例。此外,您发布的downloadFile函数似乎调用了自身。您是否打算缩进第二个代码块?在stackoverflow上,您可以通过减少缩进来纠正它。我还想建议您查看Python的argparse库。您可以使用它来创建漂亮的命令行实用程序。它会为您处理参数。 - Jim
我很喜欢你使用上下文管理器(with open... as file:等)来处理文件写入。你的代码写得很整洁。你正在学习Python的正确道路上。祝你好运! - Jim
1
谢谢回复,@Jim!我已经编辑了帖子,确实我没有“打算缩进”:D程序的主要部分。感谢您的建议! :) - Duck Ling

1
一般来说,这应该在Python3中有效:

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

请记住,在Python2之后,urllib和urllib2不再正常工作。

如果在某些神秘的情况下requests无法正常工作(我遇到过这种情况),您也可以尝试使用

wget.download(url)

相关内容:

这里有一个不错的解释/解决方案,可以查找并下载网页上的所有pdf文件:

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


-5
关于Kevin的回答,将文件写入tmp文件夹,应该像这样:
with open('./tmp/metadata.pdf', 'wb') as f:
    f.write(response.content)

他忘记在地址前加上 . ,当然你的文件夹 tmp 应该已经被创建了


6
1- Kevin没有提出在tmp中写作的想法,这就像OP问题中的一样。 2- /tmp目录是Unix系统中的tmp,位于/tmp,没有. - realUser404

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接