【问题标题】:Python: How to resolve URLs containing '..'Python:如何解析包含“..”的 URL
【发布时间】:2011-05-18 01:32:22
【问题描述】:

我需要唯一标识和存储一些 URL。问题是有时它们会包含“..”,比如http://somedomain.com/foo/bar/../../some/url,如果我没记错的话,基本上就是http://somedomain.com/some/url

是否有 Python 函数或解决此 URL 的棘手方法?

【问题讨论】:

  • 网址。我之所以这样放它们是因为后记我将它们组合到域名,方案等...
  • @jleedev:这对我不起作用。 “..”的数字可能会有所不同,并且位于 URL 中的不同位置。这甚至没有按说明工作: urlparse.urljoin("domain.com/a/b/c/d", "/../..") 在我的机器上大喊 "domain.com/...." ... :|
  • @Nicolae 将其更改为 ../.. 有效;我会改正的。
  • Firefox 至少,而且我希望所有体面的浏览器都不会发送这种风格的请求——它们会自己解释 ../ 。我个人会忽略此类 URL 或发送400 Bad Request

标签: python url


【解决方案1】:

有一个使用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 来规范化组件。补偿a mysterious issue with trailing slashes,然后将 URL 重新连接在一起。以下为doctestable:

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()

【讨论】:

  • 超级花哨!效果很好。感谢一百万!
  • 您也应该离开旧示例,并将其添加为编辑,因为它纯粹是令人敬畏的,也许它会帮助其他一些可怜的人
  • 不幸的是,如果 URL 中的 .. 太多,这似乎不起作用。例如urljoin('http://x.com/../index.html','.') -> 'http://x.com/../' 而不是正确的'http://x.com/index.html'
  • 如果第二个组件是绝对的,urljoin 也不起作用,遗憾的是。例如,urljoin("http://example.com/blah.html", "./././whoa.html") 删除点,而 urljoin("http://example.com/blah.html", "/./././whoa.html") 不会。
  • 哦,另外,posixpath.normpath 将多个斜线(例如 "//////)标准化为一个斜线。根据 RFC 和浏览器实现,这不应该发生。
【解决方案2】:

这些是文件路径。看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:

如果您想规范化 URL,请使用 urlparse 模块(在剥离方法等之前)执行此操作,如 the answer to this question 所示。

编辑 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'

【讨论】:

  • 请注意“在 Windows 上,它会将正斜杠转换为反斜杠。”
  • 这几乎是完美的,但也许我的问题不够具体(我已经编辑以更恰当地反映这一点):那些是 URL,我只是省略了域名和架构。
  • 这会将路径 /foo/bar/../../some/url' 转换为我在 mac 上的 python 3 中的 /some/url ..
【解决方案3】:

urljoin 不起作用,因为它仅在第二个参数不是绝对(!?)或空的情况下解析点段。不仅如此,它不会根据RFC 3986 正确处理过多的..s(它们应该被删除;urljoin 不会这样做)。 posixpath.normpath 也不能使用(更不用说os.path.normpath),因为它将一行中的多个斜杠解析为一个(例如///// 变为/),这是不正确的行为网址。


以下简短函数可正确解析任何 URL 路径字符串。但是,它不应该与相对路径一起使用,因为需要对其行为做出额外的决定(在过多的..s 上引发错误?在开头删除.?将它们都保留?) - 相反,如果您知道可以处理相对路径,请在解析之前加入 URL。废话不多说:

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)

这会正确处理尾随点段(即没有尾随斜杠) 连续斜杠。要解析整个 URL,您可以使用以下包装器(或将路径解析函数内联到其中)。

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/'

事实证明,正确的 URL 解析有很多缺陷!

【讨论】:

  • “它不能正确处理过多的..s”——它在 Python 3.5(但不是 2.7)上对我有用
  • ...也不是 3.4 :(
  • 您能否详细说明为什么 ///// 变为 / 是 URL 的错误行为?我浏览了整个RFC 3986 以查找关键字//,但找不到任何他们建议在路径中使用相邻斜杠的任何内容,例如//
  • @RayLuo 看看Section 3.3: Path。在path-absolute的ABNF描述中,它指定它可以简单地包含一个segment,后来被定义为*pchar。该星号表示它可以包含任意数量的pchars,包括0。因此,根据标准,路径/// 是3 个独立的、不可折叠的0 长度段。
【解决方案4】:

我想评论顶部响应中的resolveComponents 函数。

请注意,如果您的路径是/,代码将添加另一个可能有问题的路径。 因此,我将IF 条件更改为:

if parsed.path.endswith( '/' ) and parsed.path != '/':

【讨论】:

    【解决方案5】:

    根据RFC 3986,这应该是“相对解决”过程的一部分。所以答案可能是urlparse.urljoin(url, '')。但是由于错误urlparse.urljoin 在第二个参数为空 url 时不会删除点段。您可以使用 yurl — 替代 url 操作库。它这样做是正确的:

    >>> import yurl
    >>> print yurl.URL('http://somedomain.com/foo/bar/../../some/url') + yurl.URL()
    http://somedomain.com/some/url
    

    【讨论】:

      【解决方案6】:
      import urlparse
      import posixpath
      
      parsed = list(urlparse.urlparse(url))
      parsed[2] = posixpath.normpath(posixpath.join(parsed[2], rel_path))
      proper_url = urlparse.urlunparse(parsed)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-05-07
        • 2013-04-16
        • 1970-01-01
        • 2017-09-02
        • 2019-09-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多