如何确定 Python 中使用 HTTP 下载的内容的文件名?

how to determine the filename of content downloaded with HTTP in Python?

我使用 Python requests 库的 get 函数下载文件。为了存储文件,我想确定文件名的方式与网络浏览器对其 'save' 或 'save as ...' 对话框的方式相同。

简单吧?我可以 HTTP header,可在响应 object:

上访问
import re
d = r.headers['content-disposition']
fname = re.findall("filename=(.+)", d)

但仔细观察这个话题, 并不容易:

根据 RFC 6266 section 4.3, and the grammar in the section 4.1,该值可以是不带引号的标记(例如 the_report.pdf)或也可以包含空格(例如 "the report.pdf")和转义序列的带引号的字符串。此外,

when both "filename" and "filename*" are present in a single header field value, [we] SHOULD pick "filename*" and ignore "filename".

filename* 的值比 filename.

的值 yet a bit more complicated

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

因此,对于 examples listed in the RFC,我想要以下结果:

现在,我可以很容易地调整正则表达式来解释 = 周围的可变空格,但是让它也处理所有其他变体会变得相当笨拙。 (通过引用和转义,我什至不确定 RegEx 是否可以涵盖所有情况。也许他们可以,因为没有涉及 brace-nesting。)

所以 我是否必须实现一个 full-blown 解析器,或者我可以通过几次调用 HTTP 库来根据 RFC 6266 确定文件名(也许 requests 本身)?由于 RFC 6266 是 HTTP 标准的一部分,我可以想象一些专门针对 HTTP 的库已经涵盖了这一点。 (所以我 also asked on Software Recommendations SE。)

rfc6266 library appears to do exactly what you need. It can parse raw headers, requests responses, and urllib2 responses. It's on 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'

不过请注意:这个库 像 header.

中的非标准空格

如果你真的不需要 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