作者 | Darksun
Python 中的 urllib.parse
模組提供了很多解析和組建 URL 的函式。
解析url
urlparse()
函式可以將 URL 解析成 ParseResult
物件。物件中包含了六個元素,分別為:
from urllib.parse import urlparse
url='http://user:pwd@domain:80/path;params?query=queryarg#fragment'
parsed_result=urlparse(url)
print('parsed_result 包含了',len(parsed_result),'個元素')
print(parsed_result)
結果為:
parsed_result 包含了 6 個元素
ParseResult(scheme='http', netloc='user:pwd@domain:80', path='/path', params='params', query='query=queryarg', fragment='fragment')
ParseResult
繼承於 namedtuple
,因此可以同時透過索引和命名屬性來獲取 URL 中各部分的值。
為了方便起見, ParseResult
還提供了 username
、 password
、 hostname
、 port
對 netloc
進一步進行拆分。
print('scheme :', parsed_result.scheme)
print('netloc :', parsed_result.netloc)
print('path :', parsed_result.path)
print('params :', parsed_result.params)
print('query :', parsed_result.query)
print('fragment:', parsed_result.fragment)
print('username:', parsed_result.username)
print('password:', parsed_result.password)
print('hostname:', parsed_result.hostname)
print('port :', parsed_result.port)
結果為:
scheme : http
netloc : user:pwd@domain:80
path : /path
params : params
query : query=queryarg
fragment: fragment
username: user
password: pwd
hostname: domain
port : 80
除了 urlparse()
之外,還有一個類似的 urlsplit()
函式也能對 URL 進行拆分,所不同的是, urlsplit()
並不會把 路徑引數(params)
從 路徑(path)
中分離出來。
當 URL 中路徑部分包含多個引數時,使用 urlparse()
解析是有問題的:
url='http://user:pwd@domain:80/path1;params1/path2;params2?query=queryarg#fragment'
parsed_result=urlparse(url)
print(parsed_result)
print('parsed.path :', parsed_result.path)
print('parsed.params :', parsed_result.params)
結果為:
ParseResult(scheme='http', netloc='user:pwd@domain:80', path='/path1;params1/path2', params='params2', query='query=queryarg', fragment='fragment')
parsed.path : /path1;params1/path2
parsed.params : params2
這時可以使用 urlsplit()
來解析:
from urllib.parse import urlsplit
split_result=urlsplit(url)
print(split_result)
print('split.path :', split_result.path)
# SplitResult 沒有 params 屬性
結果為:
SplitResult(scheme='http', netloc='user:pwd@domain:80', path='/path1;params1/path2;params2', query='query=queryarg', fragment='fragment')
split.path : /path1;params1/path2;params2
若只是要將 URL 後的 fragment
標識拆分出來,可以使用 urldefrag()
函式:
from urllib.parse import urldefrag
url = 'http://user:pwd@domain:80/path1;params1/path2;params2?query=queryarg#fragment'
d = urldefrag(url)
print(d)
print('url :', d.url)
print('fragment:', d.fragment)
結果為:
DefragResult(url='http://user:pwd@domain:80/path1;params1/path2;params2?query=queryarg', fragment='fragment')
url : http://user:pwd@domain:80/path1;params1/path2;params2?query=queryarg
fragment: fragment
組建URL
ParsedResult
物件和 SplitResult
物件都有一個 geturl()
方法,可以傳回一個完整的 URL 字串。
print(parsed_result.geturl())
print(split_result.geturl())
結果為:
http://user:pwd@domain:80/path1;params1/path2;params2?query=queryarg#fragment
http://user:pwd@domain:80/path1;params1/path2;params2?query=queryarg#fragment
但是 geturl()
只在 ParsedResult
和 SplitResult
物件中有,若想將一個普通的元組組成 URL,則需要使用 urlunparse()
函式:
from urllib.parse import urlunparse
url_compos = ('http', 'user:pwd@domain:80', '/path1;params1/path2', 'params2', 'query=queryarg', 'fragment')
print(urlunparse(url_compos))
結果為:
http://user:pwd@domain:80/path1;params1/path2;params2?query=queryarg#fragment
相對路徑轉換絕對路徑
除此之外,urllib.parse
還提供了一個 urljoin()
函式,來將相對路徑轉換成絕對路徑的 URL。
from urllib.parse import urljoin
print(urljoin('http://www.example.com/path/file.html', 'anotherfile.html'))
print(urljoin('http://www.example.com/path/', 'anotherfile.html'))
print(urljoin('http://www.example.com/path/file.html', '../anotherfile.html'))
print(urljoin('http://www.example.com/path/file.html', '/anotherfile.html'))
結果為:
http://www.example.com/path/anotherfile.html
http://www.example.com/path/anotherfile.html
http://www.example.com/anotherfile.html
http://www.example.com/anotherfile.html
查詢引數的構造和解析
使用 urlencode()
函式可以將一個 dict 轉換成合法的查詢引數:
from urllib.parse import urlencode
query_args = {
'name': 'dark sun',
'country': '中國'
}
query_args = urlencode(query_args)
print(query_args)
結果為:
name=dark+sun&country=%E4%B8%AD%E5%9B%BD
可以看到特殊字元也被正確地轉義了。
相對的,可以使用 parse_qs()
來將查詢引數解析成 dict。
from urllib.parse import parse_qs
print(parse_qs(query_args))
結果為:
{'name': ['dark sun'], 'country': ['中國']}
如果只是希望對特殊字元進行轉義,那麼可以使用 quote
或 quote_plus
函式,其中 quote_plus
比 quote
更激進一些,會把 :
、/
一類的符號也給轉義了。
from urllib.parse import quote, quote_plus, urlencode
url = 'http://localhost:1080/~hello!/'
print('urlencode :', urlencode({'url': url}))
print('quote :', quote(url))
print('quote_plus:', quote_plus(url))
結果為:
urlencode : url=http%3A%2F%2Flocalhost%3A1080%2F%7Ehello%21%2F
quote : http%3A//localhost%3A1080/%7Ehello%21/
quote_plus: http%3A%2F%2Flocalhost%3A1080%2F%7Ehello%21%2F
可以看到 urlencode
中應該是呼叫 quote_plus
來進行轉義的。
逆向操作則使用 unquote
或 unquote_plus
函式:
from urllib.parse import unquote, unquote_plus
encoded_url = 'http%3A%2F%2Flocalhost%3A1080%2F%7Ehello%21%2F'
print(unquote(encoded_url))
print(unquote_plus(encoded_url))
結果為:
http://localhost:1080/~hello!/
http://localhost:1080/~hello!/
你會發現 unquote
函式居然能正確地將 quote_plus
的結果轉換回來。