Python:如何处理包含‘..’的URL
我需要唯一地识别和存储一些网址。问题是,有时候这些网址里会包含“..”,比如 http://somedomain.com/foo/bar/../../some/url
。其实,这个网址如果没记错的话,应该是 http://somedomain.com/some/url
。
有没有什么Python函数或者巧妙的方法可以处理这些网址呢?
7 个回答
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/'
结果显示,正确处理网址可不是一件简单的事!
这些是文件路径。可以看看 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'
这里有一个简单的解决办法,使用 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()