Python:如何处理包含‘..’的URL

10 投票
7 回答
4788 浏览
提问于 2025-04-16 07:50

我需要唯一地识别和存储一些网址。问题是,有时候这些网址里会包含“..”,比如 http://somedomain.com/foo/bar/../../some/url。其实,这个网址如果没记错的话,应该是 http://somedomain.com/some/url

有没有什么Python函数或者巧妙的方法可以处理这些网址呢?

7 个回答

3

urljoin 不好使,因为它只在第二个参数不是绝对路径或者为空时才会处理点段(这听起来有点奇怪)。而且,它对过多的 .. 处理得也不太好,按照RFC 3986的规定,这些 .. 应该被去掉,但 urljoin 并不会这样做。posixpath.normpath 也不能用(更别提 os.path.normpath 了),因为它会把连续的多个斜杠合并成一个(比如 ///// 会变成 /),这在处理网址时是不对的。


下面这个简短的函数可以正确处理任何网址路径字符串。不过它不应该用在相对路径上,因为那样就需要做更多的决定(比如:遇到过多的 .. 是抛出错误?还是去掉开头的 .?还是都保留?)——所以,如果你知道可能会处理相对路径,最好先把网址连接起来再处理。废话不多说:

def resolve_url_path(path):
    segments = path.split('/')
    segments = [segment + '/' for segment in segments[:-1]] + [segments[-1]]
    resolved = []
    for segment in segments:
        if segment in ('../', '..'):
            if resolved[1:]:
                resolved.pop()
        elif segment not in ('./', '.'):
            resolved.append(segment)
    return ''.join(resolved)

这个函数可以正确处理尾部的点段(也就是没有尾部斜杠的情况)连续的斜杠。要处理整个网址,你可以使用下面的包装函数(或者直接把路径处理的函数放进去)。

try:
    # Python 3
    from urllib.parse import urlsplit, urlunsplit
except ImportError:
    # Python 2
    from urlparse import urlsplit, urlunsplit

def resolve_url(url):
    parts = list(urlsplit(url))
    parts[2] = resolve_url_path(parts[2])
    return urlunsplit(parts)

你可以这样调用它:

>>> resolve_url('http://example.com/../thing///wrong/../multiple-slashes-yeah/.')
'http://example.com/thing///multiple-slashes-yeah/'

结果显示,正确处理网址可不是一件简单的事!

4

这些是文件路径。可以看看 os.path.normpath 这个函数。

>>> import os
>>> os.path.normpath('/foo/bar/../../some/url')
'/some/url'

编辑:

如果你是在Windows系统上,你输入的路径会用反斜杠而不是斜杠。在这种情况下,你仍然需要用 os.path.normpath 来去掉路径中的 .. 这样的模式(还有 ///./ 以及其他多余的部分),然后把反斜杠转换成斜杠:

def fix_path_for_URL(path):
    result = os.path.normpath(path)
    if os.sep == '\\':
        result = result.replace('\\', '/')
    return result

编辑 2:

如果你想要规范化网址,应该在去掉方法等之前,使用 urlparse 模块,具体可以参考 这个问题的回答

编辑 3:

看起来 urljoin 并不会规范化它所给出的基本路径:

>>> import urlparse
>>> urlparse.urljoin('http://somedomain.com/foo/bar/../../some/url', '')
'http://somedomain.com/foo/bar/../../some/url'

单独使用 normpath 也不够:

>>> import os
>>> os.path.normpath('http://somedomain.com/foo/bar/../../some/url')
'http:/somedomain.com/some/url'

注意到最开始的双斜杠被去掉了。

所以我们需要让它们一起合作:

def fix_URL(urlstring):
    parts = list(urlparse.urlparse(urlstring))
    parts[2] = os.path.normpath(parts[2].replace('/', os.sep)).replace(os.sep, '/')
    return urlparse.urlunparse(parts)

用法:

>>> fix_URL('http://somedomain.com/foo/bar/../../some/url')
'http://somedomain.com/some/url'
13

这里有一个简单的解决办法,使用 urllib.parse.urljoin

>>> from urllib.parse import urljoin
>>> urljoin('http://www.example.com/foo/bar/../../baz/bux/', '.')
'http://www.example.com/baz/bux/'

不过,如果最后没有斜杠(也就是说最后一部分是一个文件,而不是一个文件夹),那么最后那部分会被去掉。

这个解决方案使用了 urlparse 函数来提取路径,然后用 (posixpath 版本的) os.path 来规范化这些部分。它还解决了一个关于斜杠的神秘问题,然后再把 URL 组合起来。下面的内容可以用 doctest 来测试:

from urllib.parse import urlparse
import posixpath

def resolve_components(url):
    """
    >>> resolve_components('http://www.example.com/foo/bar/../../baz/bux/')
    'http://www.example.com/baz/bux/'
    >>> resolve_components('http://www.example.com/some/path/../file.ext')
    'http://www.example.com/some/file.ext'
    """
    parsed = urlparse(url)
    new_path = posixpath.normpath(parsed.path)
    if parsed.path.endswith('/'):
        # Compensate for issue1707768
        new_path += '/'
    cleaned = parsed._replace(path=new_path)
    return cleaned.geturl()

撰写回答