规范化/规范化 URL?

Posted

技术标签:

【中文标题】规范化/规范化 URL?【英文标题】:Canonicalize / normalize a URL? 【发布时间】:2012-05-22 00:39:59 【问题描述】:

我正在寻找一个库函数来规范化 Python 中的 URL,即删除路径中的“./”或“../”部分,或添加默认端口或转义特殊字符等。结果应该是指向同一网页的两个 URL 唯一的字符串。例如http://google.comhttp://google.com:80/a/../ 应该返回相同的结果。

我更喜欢 Python 3,并且已经浏览过 urllib 模块。它提供了拆分 URL 的功能,但没有将它们规范化。 Java 有 URI.normalize() 函数做类似的事情(虽然它不认为默认端口 80 等于没有给定端口),但是有没有类似的东西是 python?

【问题讨论】:

附带说明,http://google.com/ 等资源与 http://google.com:80/a/../ 不同。也就是说,如果/a 不存在,那么第二条路径将失败。通过“规范化”它,您会丢失这种特殊情况,并在您从无效的 URI 开始时得到一个有效的 URI... 这是不正确的,无论如何在浏览器中都不正确。即使a不存在,你也可以写google.com:80/a/..,它会转到google.com:80。这是因为浏览器在发送到服务器之前会进行初始解析。在服务器端,不同的服务器有不同的行为方式。 【参考方案1】:

这个怎么样:

In [1]: from urllib.parse import urljoin

In [2]: urljoin('http://example.com/a/b/c/../', '.')
Out[2]: 'http://example.com/a/b/'

受this question 的回答启发。它不会对端口进行规范化,但创建一个可以做到这一点的函数应该很简单。

【讨论】:

我没有urllib.parse,但我有urlparse urllib.parse 是 Python 3 的位置 - 最初的问题是关于 Py 3。 这在任何不以'/'结尾的东西上都非常失败【参考方案2】:

这是我使用的,到目前为止它已经奏效了。你可以从 pip 获取 urlnorm。

请注意,我对查询参数进行了排序。我发现这是必不可少的。

from urlparse import urlsplit, urlunsplit, parse_qsl
from urllib import urlencode
import urlnorm

def canonizeurl(url):
    split = urlsplit(urlnorm.norm(url))
    path = split[2].split(' ')[0]

    while path.startswith('/..'):
        path = path[3:]

    while path.endswith('%20'):
        path = path[:-3]

    qs = urlencode(sorted(parse_qsl(split.query)))
    return urlunsplit((split.scheme, split.netloc, path, qs, ''))

【讨论】:

不错,删除无效的父目录 您需要将split[2].split(' ')[0] 替换为urllib.parse.quote(split[2]) - 在某些情况下,URL 中有空格是完全正常的,实际上是必需的。此外,urlnorm 仅适用于 py2k 另外,在一些不寻常的情况下,您将丢弃实际上可能是必需的 URL 组件的片段。是的,blah.com/#watblah.com/ 完全不同的网页数量非零。它通常使用 javascript 完成,并且是一个巨大的 PITA,但它存在。 @FakeName 写道:“在 URL 中有空格是完全正常的,实际上是必需的”。不,那是绝对不正确的。 URL 中不允许有空格。阅读规范:tools.ietf.org/html/rfc2396 一些浏览器会误导性地显示空格,但实际上它们是百分比编码的。【参考方案3】:

旧(已弃用)答案

[不再维护]urltools 模块规范了多个斜杠,... 组件,而不会弄乱 http:// 中的双斜杠。

一旦你这样做了pip install urltools (这不再起作用,因为作者重命名了repo)用法如下:

print urltools.normalize('http://example.com:80/a////b/../c')
>>> 'http://example.com/a/c'

虽然该模块不再可通过 pip 安装,但它是 a single file,因此您可以重复使用它的一部分。

Python3 的更新答案

对于 Python3,请考虑使用 urllib.urlparse 模块中的 urljoin

from urllib.parse import urljoin

urljoin('https://***.com/questions/10584861/', '../dinsdale')
# Out[17]: 'https://***.com/questions/dinsdale'

【讨论】:

urltools 似乎已经离开了这个星球。没有 github,没有 pypi,没有任何地方的缓存副本。如果有人知道发生了什么,请告诉我。 @GaneshKathiresan:看起来作者决定重命名 repo;更新【参考方案4】:

现在有一个专门解决这个问题的库url-normalize

它不仅仅按照文档规范化路径:

URI归一化函数:

    注意 IDN 域。 始终以小写字符提供 URI 方案。 始终以小写字符提供主机(如果有)。 仅在必要时执行百分比编码。 在进行百分比编码时,始终使用大写的 A 到 F 字符。 防止点段出现在非相对 URI 路径中。 对于定义默认权限的方案,如果需要默认权限,请使用空权限。 对于将空路径定义为等效于“/”路径的方案,请使用“/”。 对于定义端口的方案,如果需要默认端口,请使用空端口 URI 的所有部分都必须是来自 Unicode 字符串的 utf-8 编码 NFC

这是一个例子:

from url_normalize import url_normalize

url = 'http://google.com:80/a/../'
print(url_normalize(url))

这给出了:

http://google.com/

【讨论】:

不错!唯一不能正常工作的是当不提供协议时,它返回 HTTPS 而不是 HTTP。 example.com 变成“https:////example.com” @GilCohen ,如今,https 是推荐的做法,因此这种行为可能是故意的。例如,请参阅 W3C statement 和 US government policy。 理论上你可能是正确的,但实际上当协议没有说明时,浏览器会先尝试HTTP。【参考方案5】:

按照good start,我编写了一个适合大多数网络常见案例的方法。

def urlnorm(base, link=''):
  '''Normalizes an URL or a link relative to a base url. URLs that point to the same resource will return the same string.'''
  new = urlparse(urljoin(base, url).lower())
  return urlunsplit((
    new.scheme,
    (new.port == None) and (new.hostname + ":80") or new.netloc,
    new.path,
    new.query,
    ''))

【讨论】:

那么 https 呢?【参考方案6】:

我在上面使用了@Antony 的答案并使用了url-normalize 库,但它有一个当前未修复的错误:当在没有方案的情况下发送 URL 时,不小心将其设置为 HTTPS。我编写了一个函数,通过将其设置为 HTTP 来包装和修复它:

from url_normalize import url_normalize
from urllib.parse import urlparse


def parse_url(url):
    return_val = url_normalize(url)
    wrong_default_prefix = "https://"
    new_default_prefix = "http://"
    # If the URL came with no scheme and the normalize function mistakenly 
    # set it to the HTTPS protocol, then fix it and set it to HTTP
    if urlparse(url).scheme.strip() == '' and return_val.startswith(wrong_default_prefix):
        return_val = new_default_prefix + return_val[len(wrong_default_prefix):]
    return return_val

【讨论】:

该库的作者是否说这是一个错误?对我来说它看起来是故意的,因为为了安全起见,https 应该比 http 更受欢迎。 嗯,不应该,因为这不是浏览器的行为方式。

以上是关于规范化/规范化 URL?的主要内容,如果未能解决你的问题,请参考以下文章

如何规范化 Java 中的 URL?

ExpressJS 路由器规范化/规范 url

标准 URL 规范化 - Java

URL规范化器

使用 javascript (Node.js) 规范化 URL

Python 中用于清理和规范化 URL 的函数