正在 Python 中下载文件(有请求?)

Downloading File in Python (With Requests?)

我想做的是构建一个简单的爬虫来帮助我从 Ultimate-Guitar 下载吉他谱。我可以为乐队提供 URL,它会为所有列为 'Guitar Pro' 标签的标签抓取 links。

一个典型的 link 看起来像这样:

https://tabs.ultimate-guitar.com/a/agalloch/you_were_but_a_ghost_in_my_arms_guitar_pro.htm

我能用这个 link 做的是使用以下代码找到 tab_id:

for tabid in tab.findAll("input", {"type" : "hidden", "name" : "id", "id" : "tab_id"}):
        tabID = tabid.get("value")

我想做的是使用它来构建 link 到实际下载。我 运行 遇到的问题就在这里。我能构建的最好的 link 看起来像这样:

https://tabs.ultimate-guitar.com/tabs/download?id=904610

注意那个URL最后的id就是我之前提到的tab_id

如果在浏览器中输入此 link 将立即导致下载。我 运行 进入我的问题的地方是我找不到任何方法来生成依赖于实际文件名的 link 。此文件名应类似于 [此处为歌曲名称].gp5。其他可接受的文件类型可以是 .gpx、.gp4 和 .gp3。

我想做的是获取实际的文件名,以便我可以正确保存文件(如果下载名为垃圾文件,如 ID,这对我没有帮助,因为这对我来说是无用的文件名,而且我显然需要适当的扩展名)。有什么方法可以获取上面的 link 并正确初始化下载,或者我可能在这方面运气不好?我确信有一种方法可以做我需要的,我只是对这类事情没有足够的经验。我对请求什么的一无所知,所以也许可以提供这个 URL 的东西并在 return?

中下载

注意:如果很难获得实际的文件名和扩展名,我确实有解决方法,但显然我至少需要适当的扩展名。

文件名包含在响应的 headers 中。您可以使用 cgi.parse_header() 从 headers 中解析出这些,并使用它来保存文件:

>>> import requests
>>> r = requests.get('https://tabs.ultimate-guitar.com/tabs/download?id=904610')
>>> r.headers['Content-Disposition']
'attachment; filename="Agalloch - You Were But A Ghost In My Arms (Pro).gp5"'
>>> cgi.parse_header(r.headers['Content-Disposition'])[-1]['filename']
'Agalloch - You Were But A Ghost In My Arms (Pro).gp5'

完成下载的完整函数如下所示:

import cgi
import requests
import shutil

def download_url(url, directory):
    """Download file from url to directory

    URL is expected to have a Content-Disposition header telling us what
    filename to use.

    Returns filename of downloaded file.

    """
    response = requests.get(url, stream=True)
    if response.status != 200:
        raise ValueError('Failed to download')

    params = cgi.parse_header(
        response.headers.get('Content-Disposition', ''))[-1]
    if 'filename' not in params:
        raise ValueError('Could not find a filename')

    filename = os.path.basename(params['filename'])
    abs_path = os.path.join(directory, filename)
    with open(abs_path, 'wb') as target:
        response.raw.decode_content = True
        shutil.copyfileobj(response.raw, target)

    return filename