如何在Python中确定通过HTTP下载的内容的文件名?

27
我使用Python requests 库的get函数下载文件。为了存储文件,我想确定与Web浏览器“保存”或“另存为...”对话框相同的文件名。
很简单,我只需从响应对象上访问HTTP头中的Content-Disposition字段即可获取它
import re
d = r.headers['content-disposition']
fname = re.findall("filename=(.+)", d)

但是更仔细地看这个主题,它并不那么容易:

根据 RFC 6266第4.3节 中的语法以及 第4.1节,该值可以是未引用的标记(例如 the_report.pdf)或带有空格(例如 "the report.pdf")和转义序列的带引号字符串。此外,

当单个头字段值中同时存在“filename”和“filename*”时,[我们]应选择“filename*”,并忽略“filename”。

filename* 的值比 filename 的值要复杂得多

此外,RFC 似乎允许在 = 周围添加额外的空格。

因此,对于RFC中列出的示例,我希望得到以下结果:
  •   Content-Disposition: Attachment; filename=example.html
    
    filename: example.html
  •   Content-Disposition: INLINE; FILENAME= "an example.html"
    
    filename: an example.html
  •   Content-Disposition: attachment;
                           filename*= UTF-8''%e2%82%ac%20rates
    
    filename: € rates
  •   Content-Disposition: attachment;
                           filename="EURO rates";
                           filename*=utf-8''%e2%82%ac%20rates
    
    filename: € rates here, too (not EURO rates, as filename* takes precedence)
现在,我可以轻松地调整正则表达式以考虑=周围的可变空格,但是让它处理所有其他变化也会变得相当笨拙。 (由于引用和转义,我甚至不确定RegEx是否可以涵盖所有情况。也许可以,因为没有大括号嵌套。)
那么,我是否必须实现一个全面的解析器,还是可以通过对HTTP库进行一些调用来根据RFC 6266确定文件名(可能是requests本身)?由于RFC 6266是HTTP标准的一部分,我可以想象一些专门处理HTTP的库已经涵盖了这一点。(所以我也在软件推荐SE上提出了问题。)
3个回答

18
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'

需要注意的是:这个库不喜欢标题中出现非标准的空格。


“非标准空格”是什么意思?指标准不允许的空格位置吗?还是指不属于7位ASCII码的UNICODE空格? - das-g
1
@das-g 没有调查足够的信息,无法确定。看起来 parse_headers 有一个 relaxed 选项可以帮助解决这个问题。在 这里 查看代码。 - Alyssa Haroldsen

7
在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)

1
值得注意的是,这个项目采用了MIT许可证,而上面我提供的那个废弃的库则采用了LGPL许可证。 - Alyssa Haroldsen

2
如果您不需要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

如果字符串utf-8不在开头,它应该被特殊处理吗?如果标题是"attachment;filename*= UTF-8''%e2%82%ac%20rates UTF-8'' here"或者"attachment;filename*= @UTF-8''%e2%82%ac%20rates @UTF-8'' here",又该怎么处理呢? - Doncho Gunchev

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