更改URL中的主机名

71

我正在使用Python来修改URL中的主机名,已经尝试使用urlparse模块一段时间了,但没有找到令人满意的解决方案。例如,考虑以下URL:

https://www.google.dk:80/barbaz

我想将"www.google.dk"替换为"www.foo.dk",以便获得以下URL:

https://www.foo.dk:80/barbaz

因此,我想要替换的部分是urlparse.urlsplit所称为主机名的部分。我原本希望urlsplit的结果能让我进行更改,但所得到的ParseResult类型不允许我这样做。如果没有其他解决方法,当然我可以通过将所有部分连接起来并附加+来重构新的URL,但这会使我的代码变得相当丑陋,并且需要很多条件语句来正确地放置“://”和“:”。


我试图避免任何if语句,因为基础URL是否有端口号可能会有所不同。根据您的答复,似乎我无法避免它:-)。谢谢您的帮助。 - Rikke Bendlin Gammelmark
7个回答

119
你可以使用 urllib.parse.urlparse 函数和 ParseResult._replace 方法(适用于 Python 3):
>>> import urllib.parse
>>> parsed = urllib.parse.urlparse("https://www.google.dk:80/barbaz")
>>> replaced = parsed._replace(netloc="www.foo.dk:80")
>>> print(replaced)
ParseResult(scheme='https', netloc='www.foo.dk:80', path='/barbaz', params='', query='', fragment='')

如果您使用的是Python 2,请将urllib.parse替换为urlparseParseResultnamedtuple的子类,而_replace是一个namedtuple方法,它:
返回命名元组的新实例,并用新值替换指定字段。 更新: 如@2rs2ts在评论中所说,netloc属性包括端口号。
好消息是:ParseResult具有hostnameport属性。 坏消息是:hostnameport不是namedtuple的成员,它们是动态属性,您不能执行parsed._replace(hostname="www.foo.dk")。它会抛出异常。
如果您不想在冒号:处拆分,且您的URL始终具有端口号并且没有用户名密码(这样的URL是"https://username:password@www.google.dk:80/barbaz"),则可以执行以下操作:
parsed._replace(netloc="{}:{}".format(parsed.hostname, parsed.port))

1
请注意,主机名称被称为“netloc”,其中包括任何端口号。这个答案展示了这一点,但并没有明确说明。 - 2rs2ts
22
使用私有方法 _replace 感觉不太妥当。 - Flimm
63
_replacenamedtuple 的公共 API 的一部分,它以下划线开头是为了避免与字段名称冲突。 - Nigel Tufnel
2
提醒一下 - netloc 还包括用户名和密码。如果你解析像 'https://user:hunter2@example.com:444/path' 这样的东西,你的 netloc 将会是 'user:hunter2@example.com:444' - Benjamin Manns
1
urlparse不是pip中可导入的库,因此这个代码无法工作,因为“import urlparse”无法正常运行。 - b264
看起来你正在使用Python 3,很好!我已经更新了我的答案以适应Python 3。 - Nigel Tufnel

29

你可以利用Python的urlparse模块中的urlspliturlunsplit

>>> from urlparse import urlsplit, urlunsplit
>>> url = list(urlsplit('https://www.google.dk:80/barbaz'))
>>> url
['https', 'www.google.dk:80', '/barbaz', '', '']
>>> url[1] = 'www.foo.dk:80'
>>> new_url = urlunsplit(url)
>>> new_url
'https://www.foo.dk:80/barbaz'

正如文档所述,传递给urlunsplit()的参数“可以是任何包含五个元素的可迭代对象”,因此上述代码按预期工作。


9

使用 urlparseurlunparse 方法来处理 urlparse 模块:

import urlparse

old_url = 'https://www.google.dk:80/barbaz'
url_lst = list(urlparse.urlparse(old_url))
# Now url_lst is ['https', 'www.google.dk:80', '/barbaz', '', '', '']
url_lst[1] = 'www.foo.dk:80'
# Now url_lst is ['https', 'www.foo.dk:80', '/barbaz', '', '', '']
new_url = urlparse.urlunparse(url_lst)

print(old_url)
print(new_url)

输出:

https://www.google.dk:80/barbaz
https://www.foo.dk:80/barbaz

6
在大多数情况下,简单地替换netloc中的主机名也可以起到作用:
>>> p = urlparse.urlparse('https://www.google.dk:80/barbaz')
>>> p._replace(netloc=p.netloc.replace(p.hostname, 'www.foo.dk')).geturl()
'https://www.foo.dk:80/barbaz'

如果用户名称或密码与主机名匹配,则此方法将无法正常工作。您不能仅限于替换最后一次出现的str.replace,因此我们可以使用split和join:

>>> p = urlparse.urlparse('https://www.google.dk:www.google.dk@www.google.dk:80/barbaz')
>>> new_netloc = 'www.foo.dk'.join(p.netloc.rsplit(p.hostname, 1))
>>> p._replace(netloc=new_netloc).geturl()
'https://www.google.dk:www.google.dk@www.foo.dk:80/barbaz'

replace是私有的,不应该被客户端代码使用。 - gb.
1
比被接受的答案更好,尤其是第二个选项。 - nirvana-msu
5
@gb: 在NamedTuple中,_replace不是私有的,它是API的一部分: https://docs.python.org/2/library/collections.html#collections.namedtuple - kbyrd
是的,_replace 不是私有的。引用v3文档为了防止与字段名称冲突,方法和属性名称以下划线开头。这比在其他答案中使用列表索引要好得多。 - JL Peyret
尽管 _replace 只是其中的一部分,因为它返回一个新元组而不是改变旧元组。所以 newurl = urlunsplit(urlsplit(url)._replace(netloc="<new netloc>")),上述对 p 的 _replace 没有影响。 - JL Peyret

5

我建议您也使用urlspliturlunsplit,就像@linkyndy的答案一样,但对于Python3,应该是这样的:

>>> from urllib.parse import urlsplit, urlunsplit
>>> url = list(urlsplit('https://www.google.dk:80/barbaz'))
>>> url
['https', 'www.google.dk:80', '/barbaz', '', '']
>>> url[1] = 'www.foo.dk:80'
>>> new_url = urlunsplit(url)
>>> new_url
'https://www.foo.dk:80/barbaz'

4

你可以经常使用这个技巧:

>>> p = parse.urlparse("https://dev59.com/s2Ei5IYBdhLWcg3wHJAR")
>>> parse.ParseResult(**dict(p._asdict(), netloc='perrito.com.ar')).geturl()
'https://perrito.com.ar/questions/21628852/changing-hostname-in-a-url'

3

如果只想更改主机而不影响端口(如果有的话),请使用以下方法:

import re, urlparse

p = list(urlparse.urlsplit('https://www.google.dk:80/barbaz'))
p[1] = re.sub('^[^:]*', 'www.foo.dk', p[1])
print urlparse.urlunsplit(p)

打印
https://www.foo.dk:80/barbaz

如果您没有指定任何端口,这也可以正常工作。

如果您更喜欢 Nigel 指出的 _replace 方法,您可以使用以下方式:

p = urlparse.urlsplit('https://www.google.dk:80/barbaz')
p = p._replace(netloc=re.sub('^[^:]*', 'www.foo.dk', p.netloc))
print urlparse.urlunsplit(p)

@Downvoter: 请问您不喜欢什么?没有理由(不明显)的负评并没有帮助。如果可能的话,我想改进我的回答。 - Alfe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接