在Python中,有一个标准库模块
urllib.parse
处理解析URL:
>>> import urllib.parse
>>> urllib.parse.urlparse("https://127.0.0.1:6443")
ParseResult(scheme='https', netloc='127.0.0.1:6443', path='', params='', query='', fragment='')
urllib.parse.ParseResult
上还有返回主机名和端口的属性:
>>> p.hostname
'127.0.0.1'
>>> p.port
6443
并且,由于 ParseResult 是一个命名元组,它有一个
_replace()
方法,该方法返回一个新的 ParseResult 并替换给定的字段:
>>> p._replace(netloc="foobar.tld")
ParseResult(scheme='https', netloc='foobar.tld', path='', params='', query='', fragment='')
但是,它不能取代
hostname
或 port
,因为它们是动态属性而不是元组的字段:
>>> p._replace(hostname="foobar.tld")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.11/collections/__init__.py", line 455, in _replace
raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
ValueError: Got unexpected field names: ['hostname']
简单地将新主机名与现有端口连接起来并将其作为新的 netloc 传递可能很诱人:
>>> p._replace(netloc='{}:{}'.format("foobar.tld", p.port))
ParseResult(scheme='https', netloc='foobar.tld:6443', path='', params='', query='', fragment='')
但是,如果我们考虑一下,这很快就会变得一团糟
https://user:[email protected]
);https://::1
无效,但 https://[::1]
有效);在 Python 中替换 URL 中的主机名的最简洁、正确的方法是什么?
该解决方案必须处理 IPv6(既作为原始 URL 的一部分,又作为替换值)、包含用户名/密码的 URL,以及所有格式正确的 URL。
(有各种各样的现有帖子试图提出相同的问题,但没有一个帖子要求(或提供)符合上述所有标准的解决方案。)
在 netloc 上使用字符串操作似乎是安全的。
import urllib.parse
def host_replace(url, new_host):
parsed = urllib.parse.urlparse(url)
old_hostname = parsed.hostname
left, sep, right = parsed.netloc.rpartition(old_hostname)
return parsed._replace(netloc=left + new_hostname + right).geturl()