13.2. urllib.parse — 将 URL 拆分为各组成部分
目标:将 URL 拆分为各组成部分
urllib.parse
模块提供了一系列用于操纵 URLs 地址及其各组成部分的函数,这些函数或者用于拆分或者用于组装。
分析
urlparse()
函数的返回值是一个 ParseResult
对象,该对象与含有六个元素的元组 tuple
类似。
urllib_parse_urlparse.py
from urllib.parse import urlparse
url = 'http://netloc/path;param?query=arg#frag'
parsed = urlparse(url)
print(parsed)
可以通过元组索引的方式获取的 URL 地址的六个部分为:方案 (scheme) ,网址 (network location) ,路径 (path) ,路径段 (path segment) 参数(用分号与路径隔开),查询字符串 (query) 和片段 (fragment) 。
$ python3 urllib_parse_urlparse.py
ParseResult(scheme='http', netloc='netloc', path='/path',
params='param', query='query=arg', fragment='frag')
尽管返回值可以像个元组一样操作,它实际上是基于 tuple
的一个子类 namedtuple
实现的。该子类同时允许使用名称或索引来获取 URL 中各部分属性的值 。对程序员来说,这样除了用起来比较简单以外,还可以用这些属性 API 来获取一些在 tuple
的 API 中没有的值。
urllib_parse_urlparseattrs.py
from urllib.parse import urlparse
url = 'http://user:pwd@NetLoc:80/path;param?query=arg#frag'
parsed = urlparse(url)
print('scheme :', parsed.scheme)
print('netloc :', parsed.netloc)
print('path :', parsed.path)
print('params :', parsed.params)
print('query :', parsed.query)
print('fragment:', parsed.fragment)
print('username:', parsed.username)
print('password:', parsed.password)
print('hostname:', parsed.hostname)
print('port :', parsed.port)
在输入的 URL 中如果有用户名和密码,则可以用属性名 username
和 password
来获取它们,如果没有,那么相应的属性的取值为 None
。 属性 hostname
的值与属性 netloc
的值相同,只不过去掉了端口号,并且都是小写形式。如果还有端口,则属性 port
的值被转换为一个整数,如果没有,属性的值就是 None
。
$ python3 urllib_parse_urlparseattrs.py
scheme : http
netloc : user:pwd@NetLoc:80
path : /path
params : param
query : query=arg
fragment: frag
username: user
password: pwd
hostname: netloc
port : 80
urlsplit()
函数可以作为 urlparse()
的一个替代选择。它的行为略有不用,不同之处在于它不拆分 URL 里的参数。这对于符合 RFC 2396 标准的 URLs 是很有用的,因为这样的 URL 支持在每一个路径段都可以带有参数的格式。
urllib_parse_urlsplit.py
from urllib.parse import urlsplit
url = 'http://user:pwd@NetLoc:80/p1;para/p2;para?query=arg#frag'
parsed = urlsplit(url)
print(parsed)
print('scheme :', parsed.scheme)
print('netloc :', parsed.netloc)
print('path :', parsed.path)
print('query :', parsed.query)
print('fragment:', parsed.fragment)
print('username:', parsed.username)
print('password:', parsed.password)
print('hostname:', parsed.hostname)
print('port :', parsed.port)
由于参数没有被拆分出来,元组 API 仅显示五个元素,而不是六个,因为不再有 params
属性。
$ python3 urllib_parse_urlsplit.py
SplitResult(scheme='http', netloc='user:pwd@NetLoc:80',
path='/p1;para/p2;para', query='query=arg', fragment='frag')
scheme : http
netloc : user:pwd@NetLoc:80
path : /p1;para/p2;para
query : query=arg
fragment: frag
username: user
password: pwd
hostname: netloc
port : 80
如果想要简单地去掉 URL 里的片段 (fragment) 标识,比如仅需要 URL 里的基础网页地址,则可以使用 urldefrag()
。
urllib_parse_urldefrag.py
from urllib.parse import urldefrag
original = 'http://netloc/path;param?query=arg#frag'
print('original:', original)
d = urldefrag(original)
print('url :', d.url)
print('fragment:', d.fragment)
此时,返回值是一个 DefragResult
对象,该对象同样基于 namedtuple
,包含基础 URL 和片段 (fragment) 。
$ python3 urllib_parse_urldefrag.py
original: http://netloc/path;param?query=arg#frag
url : http://netloc/path;param?query=arg
fragment: frag
逆解析
要把拆分后的 URL 的各部分重新组装回去,得到一个完整 URL 字符串的方法不止有一种。解析后的 URL 对象有一个 geturl()
方法。
urllib_parse_geturl.py
from urllib.parse import urlparse
original = 'http://netloc/path;param?query=arg#frag'
print('ORIG :', original)
parsed = urlparse(original)
print('PARSED:', parsed.geturl())
geturl()
只对 urlparse()
或 urlsplit()
返回的对象有效。
$ python3 urllib_parse_geturl.py
ORIG : http://netloc/path;param?query=arg#frag
PARSED: http://netloc/path;param?query=arg#frag
可以用 urlunparse()
将一个常规的字符串元组组装为一个 URL 地址。
urllib_parse_urlunparse.py
from urllib.parse import urlparse, urlunparse
original = 'http://netloc/path;param?query=arg#frag'
print('ORIG :', original)
parsed = urlparse(original)
print('PARSED:', type(parsed), parsed)
t = parsed[:]
print('TUPLE :', type(t), t)
print('NEW :', urlunparse(t))
尽管由 urlparse()
返回的 ParseResult
对象可以被当做元组使用,但在本例中,我们 显式地创建了一个新元组来证明 urlunparse()
也可以用在内建的元组上。
$ python3 urllib_parse_urlunparse.py
ORIG : http://netloc/path;param?query=arg#frag
PARSED: <class 'urllib.parse.ParseResult'>
ParseResult(scheme='http', netloc='netloc', path='/path',
params='param', query='query=arg', fragment='frag')
TUPLE : <class 'tuple'> ('http', 'netloc', '/path', 'param',
'query=arg', 'frag')
NEW : http://netloc/path;param?query=arg#frag
如果作为输入的 URL 包含多余的部分,则它们可能会在重组过程中被丢弃。
urllib_parse_urlunparseextra.py
from urllib.parse import urlparse, urlunparse
original = 'http://netloc/path;?#'
print('ORIG :', original)
parsed = urlparse(original)
print('PARSED:', type(parsed), parsed)
t = parsed[:]
print('TUPLE :', type(t), t)
print('NEW :', urlunparse(t))
在本例的情况中,「参数」,「查询字符串」,和「分段」在原 URL 中都是缺失的。而新组装成的 URL 和原本 URL 也并不是一模一样,但是按照标准来说,它们是等价的。
$ python3 urllib_parse_urlunparseextra.py
ORIG : http://netloc/path;?#
PARSED: <class 'urllib.parse.ParseResult'>
ParseResult(scheme='http', netloc='netloc', path='/path',
params='', query='', fragment='')
TUPLE : <class 'tuple'> ('http', 'netloc', '/path', '', '', '')
NEW : http://netloc/path
拼接
urllib.parse
模块中除了解析 URLs 用的 urlparse()
函数,它还包含 urljoin()
函数,可以用它从相对地址的片段中创建出绝对 URLs 地址 。
urllib_parse_urljoin.py
from urllib.parse import urljoin
print(urljoin('http://www.example.com/path/file.html',
'anotherfile.html'))
print(urljoin('http://www.example.com/path/file.html',
'../anotherfile.html'))
在本例中,在拼接第二个 URL 的时候,表示相对路径的 ("../"
) 被考虑在内。
$ python3 urllib_parse_urljoin.py
http://www.example.com/path/anotherfile.html
http://www.example.com/anotherfile.html
非相对路径则以 os.path.join()
的方式同样处理。
urllib_parse_urljoin_with_path.py
from urllib.parse import urljoin
print(urljoin('http://www.example.com/path/',
'/subpath/file.html'))
print(urljoin('http://www.example.com/path/',
'subpath/file.html'))
如果要被拼接到 URL 地址的路径以斜杠 (/
) 开始,那么就以该路径作为顶层重设 URL 地址。否则,它仅仅被添加到 URL 路径尾部。.
$ python3 urllib_parse_urljoin_with_path.py
http://www.example.com/subpath/file.html
http://www.example.com/path/subpath/file.html
编码查询参数
查询参数必须在编码后才能加入 URL 地址。
urllib_parse_urlencode.py
from urllib.parse import urlencode
query_args = {
'q': 'query string',
'foo': 'bar',
}
encoded_args = urlencode(query_args)
print('Encoded:', encoded_args)
编码过程将替换一些特殊字符,比如空格,以保证传递给服务器的查询字符串的格式是符合标准的。
$ python3 urllib_parse_urlencode.py
Encoded: q=query+string&foo=bar
在查询字符串中,为了让一序列变量值中的每一个以单独的方式出现,可以在调用 urlencode()
的时候将 doseq
设为 True
。
urllib_parse_urlencode_doseq.py
from urllib.parse import urlencode
query_args = {
'foo': ['foo1', 'foo2'],
}
print('Single :', urlencode(query_args))
print('Sequence:', urlencode(query_args, doseq=True))
其结果就是将一系列值多次与同一个查询名称联系起来的查询字符串。
$ python3 urllib_parse_urlencode_doseq.py
Single : foo=%5B%27foo1%27%2C+%27foo2%27%5D
Sequence: foo=foo1&foo=foo2
要解析查询字符串,只要使用 parse_qs()
或 parse_qsl()
即可。
urllib_parse_parse_qs.py
from urllib.parse import parse_qs, parse_qsl
encoded = 'foo=foo1&foo=foo2'
print('parse_qs :', parse_qs(encoded))
print('parse_qsl:', parse_qsl(encoded))
parse_qs()
返回的结果是一个字典,字典的每一项都是一个查询名称与其对应的(一个或多个)值的列表, 而 parse_qsl()
返回一个元组的列表,每个元组是一对查询名称与查询值。
$ python3 urllib_parse_parse_qs.py
parse_qs : {'foo': ['foo1', 'foo2']}
parse_qsl: [('foo', 'foo1'), ('foo', 'foo2')]
URL 中查询参数的某些在服务器端可能会造成解析困难的特殊字符,在传递给 urlencode()
后会被「引用」起来。当然,为了在本地创建某个字符串的安全版本,也可以直接调用函数 quote()
或 quote_plus()
。
urllib_parse_quote.py
from urllib.parse import quote, quote_plus, urlencode
url = 'http://localhost:8080/~hellmann/'
print('urlencode() :', urlencode({'url': url}))
print('quote() :', quote(url))
print('quote_plus():', quote_plus(url))
quote_plus()
实现的引用机制更强一些,它会替换更多的字符。
$ python3 urllib_parse_quote.py
urlencode() : url=http%3A%2F%2Flocalhost%3A8080%2F%7Ehellmann%2F
quote() : http%3A//localhost%3A8080/%7Ehellmann/
quote_plus(): http%3A%2F%2Flocalhost%3A8080%2F%7Ehellmann%2F
想要逆转引用操作以还原字符串,可以对应地使用 unquote()
或 unquote_plus()
。
urllib_parse_unquote.py
from urllib.parse import unquote, unquote_plus
print(unquote('http%3A//localhost%3A8080/%7Ehellmann/'))
print(unquote_plus(
'http%3A%2F%2Flocalhost%3A8080%2F%7Ehellmann%2F'
))
编码后的字符串就被转换为普通 URL 字符串了。
$ python3 urllib_parse_unquote.py
http://localhost:8080/~hellmann/
http://localhost:8080/~hellmann/
参考
- urllib.parse 的标准库文档
urllib.request
-- 获取某个由 URL 指定的资源的内容。- RFC 1738 -- 统一资源定位 (URL) 的语法
- RFC 1808 -- 相对 URLs
- RFC 2396 -- 统一资源标识符 (URI) 的一般语法
- RFC 3986 -- 统一资源标识符 (URI) 的语法
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。