我使用 Python
get
库的
requests
函数下载文件。为了存储文件,我想像网络浏览器的“保存”或“另存为...”对话框那样确定文件名。
简单吧?我可以 从
Content-Disposition
HTTP 标头获取它,可在响应对象上访问:
import re
d = r.headers['content-disposition']
fname = re.findall("filename=(.+)", d)
但更仔细地看这个话题,它并不that容易:
根据 RFC 6266 section 4.3,以及 section 4.1 中的语法,该值可以是不带引号的标记(例如
the_report.pdf
)或带引号的字符串,也可以包含空格(例如"the report.pdf"
)和转义序列。此外,
当“文件名”和“文件名*”都出现在单个标头字段值中时,[我们]应该选择“文件名*”并忽略“文件名”。
filename*
的值,虽然,比 filename
的值更复杂。
此外,RFC 似乎允许在
=
周围留出额外的空格。
因此,对于 RFC 中列出的示例,我想要以下结果:
Content-Disposition: Attachment; filename=example.html
文件名:example.html
Content-Disposition: INLINE; FILENAME= "an example.html"
文件名:an example.html
Content-Disposition: attachment;
filename*= UTF-8''%e2%82%ac%20rates
文件名:€ rates
Content-Disposition: attachment;
filename="EURO rates";
filename*=utf-8''%e2%82%ac%20rates
文件名:这里也是€ rates
(不是EURO rates
,因为filename*
优先)现在,我可以很容易地调整正则表达式来解释
=
周围的可变空白,但是让它处理所有其他变化,同样,会变得相当笨拙。 (通过引用和转义,我什至不确定 RegEx 是否可以涵盖所有情况。也许他们可以,因为不涉及大括号嵌套。)
所以 我是否必须实现一个成熟的解析器,或者我可以通过几次调用 HTTP 库(也许是
requests
本身)来根据 RFC 6266 确定文件名?由于 RFC 6266 是 HTTP 标准的一部分,我可以想象一些专门针对 HTTP 的库已经涵盖了这一点。 (所以我也询问了软件推荐SE。)
rfc6266
库似乎可以满足您的需求。它可以解析原始标头、requests
响应和 urllib2
响应。它在PyPI.
一些例子:
>>> import rfc6266, requests
>>> rfc6266.parse_headers('''Attachment; filename=example.html''').filename_unsafe
'example.html'
>>> rfc6266.parse_headers('''INLINE; FILENAME= "an example.html"''').filename_unsafe
'an example.html'
>>> rfc6266.parse_headers(
'''attachment; '''
'''filename*= UTF-8''%e2%82%ac%20rates''').filename_unsafe
'€ rates'
>>> rfc6266.parse_headers(
'''attachment; '''
'''filename="EURO rates"; '''
'''filename*=utf-8''%e2%82%ac%20rates''').filename_unsafe
'€ rates'
>>> r = requests.get('http://example.com/€ rates')
>>> rfc6266.parse_requests_response(r).filename_unsafe
'€ rates'
不过请注意:这个库确实 not 像标题中的非标准空格。
在 2022 年,原始答案 中推荐的 Python 模块rfc6266 似乎已被放弃,并且不能真正与较新版本的 Python 一起使用。
好消息是有一个名为pyrfc6266
的替换模块(几个之一,但这个确实有效!)可以安装以下内容:
pip install pyrfc6266
和使用相同的方式:
import pyrfc6266
pyrfc6266.parse_filename('attachment; filename="foo.html"')
或
import requests
import pyrfc6266
response = requests.get('http://httpbin.org/response-headers?Content-Disposition=attachment;%20filename%3d%22foo.html%22')
pyrfc6266.requests_response_to_filename(response)
如果你真的不需要 utf-8 格式的结果
def getFilename(s):
fname = re.findall("filename\*?=([^;]+)", s, flags=re.IGNORECASE)
print fname[0].strip().strip('"')
但如果 utf-8 是必须的
def getFilename(s):
fname = re.findall("filename\*=([^;]+)", s, flags=re.IGNORECASE)
if not fname:
fname = re.findall("filename=([^;]+)", s, flags=re.IGNORECASE)
if "utf-8''" in fname[0].lower():
fname = re.sub("utf-8''", '', fname[0], flags=re.IGNORECASE)
fname = urllib.unquote(fname).decode('utf8')
else:
fname = fname[0]
# clean space and double quotes
print fname.strip().strip('"')
# example
getFilename('Attachment; filename=example.html')
getFilename('INLINE; FILENAME= "an example.html"')
getFilename("attachment;filename*= UTF-8''%e2%82%ac%20rates")
getFilename("attachment; filename=\"EURO rates\";filename*=utf-8''%e2%82%ac%20rates")
getFilename("attachment;filename=\"_____ _____ ___ __ ____ _____ Hekayt Bent.2017.mp3\";filename*=UTF-8''%D8%A7%D8%BA%D9%86%D9%8A%D9%87%20%D8%AD%D9%83%D8%A7%D9%8A%D8%A9%20%D8%A8%D9%86%D8%AA%20%D9%84%D9%80%20%D9%85%D8%AD%D9%85%D8%AF%20%D8%B4%D8%AD%D8%A7%D8%AA%D8%A9%20Hekayt%20Bent.2017.mp3")
结果
example.html
an example.html
€ rates
€ rates
اغنيه حكاية بنت لـ محمد شحاتة Hekayt Bent.2017.mp3