在Python中将URL保存为文件名

8

I have a url such as

http://example.com/here/there/index.html

现在我想要将文件及其内容保存在一个目录中。我希望文件名为:
http://example.com/here/there/index.html

但是我遇到了错误,我猜测这个错误是由于url名称中的/导致的。

目前我正在执行以下操作。

        with open('~/' + response.url, 'w') as f:
            f.write(response.body)

有什么想法应该怎样做呢?

1
我希望文件的名称是:...。为什么? - njzk2
这正是我所想的,然后我的回答被踩了 :D 我认为很多问题之所以存在,只是因为一个人的头脑中存在一条合适的绕路。 - wenzul
@njzk2 嗯,原因是我要下载一个文件夹中的多个页面,如果你把名称作为文件名来引用URL,那么会更容易。这样我就不必对每个文件做一些疯狂的哈希映射(或其他操作)。 - nafas
所以你实际上想要的是一个与文件名唯一相关且没有任何额外数据的文件名。@ReutSharabani的答案是一个很好的解决方案。 - njzk2
@njzk2 没错,因为 index.html 不是唯一的。Reut Sharabani 的答案很好,但不幸的是编码器的结果有时会包含 **/**,这会产生相同的问题。 - nafas
如果您只需要从URL到文件名的一种方式,您也可以使用哈希作为文件名。 - wenzul
5个回答

32

您可以使用可逆的base64编码。

>>> import base64
>>> base64.b64encode('http://example.com/here/there/index.html')
'aHR0cDovL2V4YW1wbGUuY29tL2hlcmUvdGhlcmUvaW5kZXguaHRtbA=='
>>> base64.b64decode('aHR0cDovL2V4YW1wbGUuY29tL2hlcmUvdGhlcmUvaW5kZXguaHRtbA==')
'http://example.com/here/there/index.html'

或者也可以使用binascii

>>> binascii.hexlify(b'http://example.com/here/there/index.html')
'687474703a2f2f6578616d706c652e636f6d2f686572652f74686572652f696e6465782e68746d6c'
>>> binascii.unhexlify('687474703a2f2f6578616d706c652e636f6d2f686572652f74686572652f696e6465782e68746d6c')
'http://example.com/here/there/index.html'

我假设这个解决方案将被用于超过1个URL。然而,由于base64编码/解码不是唯一的(https://dev59.com/el0a5IYBdhLWcg3wW3jN),因此你可能会遇到不同的URL互相覆盖的情况! - pir
此外,如果对较长的URL进行编码(https://serverfault.com/questions/9546/filename-length-limits-on-linux),您是否会遇到文件长度问题? - pir
我会认真修复这些问题,或者参考已接受的答案作为更好的解决方案。 - pir
1
我使用这段代码处理大量的 URL,发现其中有些被编码成了过长的字符串。而采纳的答案并没有同样的问题。我认为采纳的答案在编码和解码上并没有独特性的问题,但是这段代码存在这个问题(请看我在另一条评论中发布的链接)。 - pir
1
关于长度:被接受的答案也有同样的缺陷。生成的文件名将比URL更长。 - Reut Sharabani
显示剩余4条评论

9

您有几个问题。其中一个是Unix shell缩写(~)不会像在Unix shell中一样被Python自动解释。

第二个问题是,在Unix中编写包含斜杠的文件路径时会遇到困难。如果您想要稍后检索它们,您需要将它们转换为其他内容。您可以使用简单的方法response.url.replace('/','_')来实现,但这会留下许多其他可能有问题的字符。您可能希望一次性“清理”所有这些字符。例如:

import os
import urllib

def write_response(response, filedir='~'):
    filedir = os.path.expanduser(dir)
    filename = urllib.quote(response.url, '')
    filepath = os.path.join(filedir, filename)
    with open(filepath, "w") as f:
        f.write(response.body)

这段代码使用os.path函数清理文件路径,并使用urllib.quote将URL转换为适用于文件名的格式。对应的还有一个unquote函数来反向处理这个过程。

最后,当你写入文件时,可能需要根据响应及其编码方式进行一些微调。如果要以二进制方式写入,您需要使用"wb"而不仅仅是"w"作为文件模式。或者,如果它是文本,则可能需要先进行某种编码(例如,utf-8)。这取决于您的响应及其编码方式。

编辑:在Python 3中urllib.quote现在是urllib.parse.quote

非常感谢,尽管@Reut Sharabani的回答很好,但这个更好、更健壮。 - nafas

4
这是个不好的主意,因为URL很长并且b64编码可能会超过文件名255字节的限制!可以压缩和b64编码,但效果有限。
from base64 import b64encode 
import zlib
import bz2
from urllib.parse import quote

def url_strategies(url):
    url = url.encode('utf8')
    print(url.decode())
    print(f'normal  : {len(url)}')
    print(f'quoted  : {len(quote(url, ""))}')
    b64url = b64encode(url)
    print(f'b64     : {len(b64url)}')
    url = b64encode(zlib.compress(b64url))
    print(f'b64+zlib: {len(url)}')
    url = b64encode(bz2.compress(b64url))
    print(f'b64+bz2: {len(url)}')

这是我在angel.co上找到的一个普通链接:


URL = 'https://angel.co/job_listings/browse_startups_table?startup_ids%5B%5D=972887&startup_ids%5B%5D=365478&startup_ids%5B%5D=185570&startup_ids%5B%5D=32624&startup_ids%5B%5D=134966&startup_ids%5B%5D=722477&startup_ids%5B%5D=914250&startup_ids%5B%5D=901853&startup_ids%5B%5D=637842&startup_ids%5B%5D=305240&tab=find&page=1'

即使使用b64+zlib,它也无法符合255的限制:

normal  : 316
quoted  : 414
b64     : 424
b64+zlib: 304
b64+bz2 : 396

即使使用最佳的zlib压缩和b64编码策略,您仍会遇到问题。
正确的解决方案是,将url哈希化,并将其作为文件属性附加到文件中。
import os
from hashlib import sha256

def save_file(url, content, char_limit=13):
    # hash url as sha256 13 character long filename
    hash = sha256(url.encode()).hexdigest()[:char_limit]
    filename = f'{hash}.html'
    # 93fb17b5fb81b.html
    with open(filename, 'w') as f:
        f.write(content)
    # set url attribute
    os.setxattr(filename, 'user.url', url.encode())

然后你可以获取URL属性:

print(os.getxattr(filename, 'user.url').decode())
'https://angel.co/job_listings/browse_startups_table?startup_ids%5B%5D=972887&startup_ids%5B%5D=365478&startup_ids%5B%5D=185570&startup_ids%5B%5D=32624&startup_ids%5B%5D=134966&startup_ids%5B%5D=722477&startup_ids%5B%5D=914250&startup_ids%5B%5D=901853&startup_ids%5B%5D=637842&startup_ids%5B%5D=305240&tab=find&page=1'

注意:在Python中使用setxattr和getxattr需要添加user.前缀
有关Python文件属性,请参见此处相关问题:https://dev59.com/4p3ha4cB1Zd3GeqPT2n2#56399698


0

使用urllib.urlretrieve:

    import urllib

    testfile = urllib.URLopener()
    testfile.retrieve("http://example.com/here/there/index.html", "/tmp/index.txt")

我喜欢做的事情是能够参考我创建的文件,是否可以将 / 更改为类似于 / 的东西? - nafas

-1

可能需要查看受限字符

我会为这个任务使用典型的文件夹结构。如果您将其用于大量URL,它会变得混乱不堪。您也会遇到文件系统性能问题或限制。


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