Python urljoin 不删除多余的点

Python urljoin not removing superflous dots

我正在使用 urljoin 获取页面的绝对 URL 链接。在大多数情况下,它在解析相对链接等方面做得很好,但我注意到由于某种原因,它在某些情况下不会删除多余的点。例如:

>>> urljoin("http://x.com","http://x.com/../../X",False)
'http://x.com/../../X'
>>> urljoin("http://x.com","http://x.com/./../X",False)
'http://x.com/./../X'

如果我将这样的 URL 提供给网络浏览器,它会很好地纠正它,但如果我尝试使用 Python 的 urlopen() 它会抛出异常(urllib2.HTTPError:HTTP 错误 400:错误请求)。

这是预期的行为吗?是否有其他一些 Python 函数可以正确删除我应该使用的这些点,或者这是一个错误?

我认为你应该使用绝对 base 和相对 url
如果你这样称呼它,它会删除点:

urljoin("http://x.com/a/b/page.html","../../index.html",False)
# result: 'http://x.com/index.html'

urljoin("http://x.com/a/b/page.html","./index.html",False)
# result: 'http://x.com/a/b/index.html'

我找到了一种在 this answer 中规范化 url 的方法。示例:

urljoin('http://www.example.com/foo/bar/../../baz/bux/', '.')
# result: 'http://www.example.com/baz/bux/'

我觉得无效的url处理(太多..)只能处理"manually",像这样:

def remove_extra_dots(url):
    parsed = list(urlparse(url))
    dirs = []
    for name in parsed[2].split("/"):
        if name == "..":
            if len(dirs) > 1:
                dirs.pop()
        else:
            dirs.append(name)
    parsed[2] = "/".join(dirs)
    return urlunparse(parsed)

这将从 url 中删除所有 ..,甚至是无效的。示例:

"http://x.com/a/b/c/../../X"  #->  http://x.com/a/X
"http://x.com/a/b/../../X"    #->  http://x.com/X
"http://x.com/../../X"        #->  http://x.com/X

这是一个小错误!根据 RFC 3986,那些多余的 .. 片段应该被删除。没有 Python 我能找到的标准库函数确实能正确完成工作。


遗憾的是,现有答案在几个方面略有不正确。 URL 分辨率比人们想象的要复杂一些。

除了那里提到的urljoin的缺点(加入绝对路径时不解析URLs,不处理过多的..s) , 加入 URL 和 . 将删除最后一段。例如,urljoin('http://example.com/dir/./wrong/../file.txt', '.') 将导致 'http://example.com/dir/',删除文件,因此您必须将其重新添加。另外,urljoin('http://example.com/dir/..', '.') 导致 'http://example.com/dir/',这很简单你的情况不对。

不仅如此,提供的remove_extra_dots函数居然还有bug。如果 URL 以尾随点段结尾(上一段中的最后一个错误使它不可能出现,但如果以某种方式修复),则尾随斜线 不会 添加。考虑 remove_extra_dots('http://example.com/..')。这应该会导致 'http://example.com/',但实际上会导致 'http://example.com'(请注意缺少的斜线)。这是一个很小的差异,但是很多网站都会在接收到缺失的斜杠时进行重定向,因此您可能会得到意想不到的结果。


以下函数完全解析 URLs - 即 .s 和 ..s - 遵循 RFC 3986。无需依赖 urljoin,太!

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))
    segments = parts[2].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)
    parts[2] = ''.join(resolved)
    return urlunsplit(parts)

您可以在完成 URL 后调用它(在您的情况下,加入后),如下所示。

>>> resolve_url("http://example.com/dir/../../thing/.")
'http://example.com/thing/'

有关works/doesn无效内容的更多信息,请参阅a similar answer I wrote on the subject