在Python中向给定的URL添加参数

179
假设我收到了一个URL地址。这个URL可能已经有GET参数了(例如:http://example.com/search?q=question),或者没有任何参数(例如:http://example.com/)。
现在,我需要像{'lang':'en','tag':'python'}这样添加一些参数。对于第一种情况,我将得到http://example.com/search?q=question&lang=en&tag=python,而对于第二种情况,则是http://example.com/search?lang=en&tag=python。
是否有标准的方法来做到这一点?
15个回答

232

urlliburlparse模块有一些特殊之处。下面是一个可行的例子:

try:
    import urlparse
    from urllib import urlencode
except: # For Python 3
    import urllib.parse as urlparse
    from urllib.parse import urlencode

url = "http://stackoverflow.com/search?q=question"
params = {'lang':'en','tag':'python'}

url_parts = list(urlparse.urlparse(url))
query = dict(urlparse.parse_qsl(url_parts[4]))
query.update(params)

url_parts[4] = urlencode(query)

print(urlparse.urlunparse(url_parts))

ParseResult,是urlparse()的结果,只读,我们需要将其转换为list后才能尝试修改数据。


19
你可能想使用urlparse.parse_qs而不是parse_qsl。后者返回一个列表,而你想要一个字典。请参见http://docs.python.org/library/urlparse.html#urlparse.parse_qs。 - Florian Brucker
12
在Python 2.7中,你需要将urlencode作为urllib.urlencode(query, doseq=True)调用。否则,原始URL中存在的参数将无法正确保留(因为它们会被parse_qs返回为元组)。 - rluba
5
我已经重写了这份代码,使其能够在Python 3中运行。代码在此 - duality_
16
urlparse()urlsplit()的结果实际上是namedtuple实例。因此,您可以直接将它们分配给一个变量,并使用url_parts = url_parts._replace(query = …)对其进行更新。 - Feuermurmel
6
注意:这种实现会删除某些RESTful服务使用的重复查询参数。通过稍作修改可以解决这个问题。查询语句为:query = urlparse.parse_qsl(url_parts[4]),然后添加参数:query += params.items()。但如果您想使用字典替换现有的查询参数,则需要进行一些额外的修改。 - ombre42
显示剩余3条评论

89

将其外包给经过实战考验的Requests库

这是我将要做的:

from requests.models import PreparedRequest
url = 'http://example.com/search?q=question'
params = {'lang':'en','tag':'python'}
req = PreparedRequest()
req.prepare_url(url, params)
print(req.url)

这是来自requests包吗? - perymerdeka
@perymerdeka 是的 - Zhymabek Roman

72

为什么

我一直不满意本页面上的所有解决方案(来吧,我们最喜欢的复制粘贴在哪里?),因此我根据这里的答案编写了自己的代码。它试图是完整和更符合Python习惯。我添加了一个处理程序来处理参数中的dictbool值以更有利于消费者端(JS)友好,但它们是可选的,你可以舍弃它们。

它是如何工作的

测试1: 添加新的参数,处理数组和布尔值:

url = 'http://stackoverflow.com/test'
new_params = {'answers': False, 'data': ['some','values']}

add_url_params(url, new_params) == \
    'http://stackoverflow.com/test?data=some&data=values&answers=false'

测试2:重写现有参数,处理字典值:

url = 'http://stackoverflow.com/test/?question=false'
new_params = {'question': {'__X__':'__Y__'}}

add_url_params(url, new_params) == \
    'http://stackoverflow.com/test/?question=%7B%22__X__%22%3A+%22__Y__%22%7D'

言而无信,编程需谨慎

代码本身。我已尽力详细描述:

from json import dumps

try:
    from urllib import urlencode, unquote
    from urlparse import urlparse, parse_qsl, ParseResult
except ImportError:
    # Python 3 fallback
    from urllib.parse import (
        urlencode, unquote, urlparse, parse_qsl, ParseResult
    )


def add_url_params(url, params):
    """ Add GET params to provided URL being aware of existing.

    :param url: string of target URL
    :param params: dict containing requested params to be added
    :return: string with updated URL
    
    >> url = 'https://stackoverflow.com/test?answers=true'
    >> new_params = {'answers': False, 'data': ['some','values']}
    >> add_url_params(url, new_params)
    'https://stackoverflow.com/test?data=some&data=values&answers=false'
    """
    # Unquoting URL first so we don't lose existing args
    url = unquote(url)
    # Extracting url info
    parsed_url = urlparse(url)
    # Extracting URL arguments from parsed URL
    get_args = parsed_url.query
    # Converting URL arguments to dict
    parsed_get_args = dict(parse_qsl(get_args))
    # Merging URL arguments dict with new params
    parsed_get_args.update(params)

    # Bool and Dict values should be converted to json-friendly values
    # you may throw this part away if you don't like it :)
    parsed_get_args.update(
        {k: dumps(v) for k, v in parsed_get_args.items()
         if isinstance(v, (bool, dict))}
    )

    # Converting URL argument to proper query string
    encoded_get_args = urlencode(parsed_get_args, doseq=True)
    # Creating new parsed result object based on provided with new
    # URL arguments. Same thing happens inside urlparse.
    new_url = ParseResult(
        parsed_url.scheme, parsed_url.netloc, parsed_url.path,
        parsed_url.params, encoded_get_args, parsed_url.fragment
    ).geturl()

    return new_url

请注意可能会出现一些问题,如果您发现了,请告诉我,我们将使这件事变得更好


也许可以添加一个try except,使用urllib.parse来支持Python 3?感谢代码片段,非常有用! - MattV
也许需要添加导入吗? - Christophe Roussy
1
解码编码的URL,例如“http://stackoverflow.com/with%2Fencoded?data=some&data=values&answe%2rs=false”。此外,请使用三个尖括号>>>来帮助doctest捕获您的doctest。 - pelson
为什么不将 parsed_get_args = dict(parse_qsl(get_args)) 改为 parsed_get_args = parse_qs(get_args) - Mattwmaster58

61

如果字符串可能包含任意数据(例如可能会包含字符如“&”、“/”等),则应使用URL编码。

可以查看urllib.urlencode:

>>> import urllib
>>> urllib.urlencode({'lang':'en','tag':'python'})
'lang=en&tag=python'

在Python 3中:

from urllib import parse
parse.urlencode({'lang':'en','tag':'python'})

13
在Python 3中,这已经被移至urllib.parse.urlencode - shad0w_wa1k3r

29

您也可以使用furl模块 https://github.com/gruns/furl

>>> from furl import furl
>>> print furl('http://example.com/search?q=question').add({'lang':'en','tag':'python'}).url
http://example.com/search?q=question&lang=en&tag=python

27

如果你正在使用 requests库

import requests
...
params = {'tag': 'python'}
requests.get(url, params=params)

1
@chefhose 这个问题是相对于什么的?你不在一个网页上,没有相对的上下文。 - Christophe Roussy

17

基于这个答案,用一行Python 3代码实现简单情况:

from urllib.parse import urlparse, urlencode


url = "https://stackoverflow.com/search?q=question"
params = {'lang':'en','tag':'python'}

url += ('&' if urlparse(url).query else '?') + urlencode(params)

或:
url += ('&', '?')[urlparse(url).query == ''] + urlencode(params)

4
我知道你提到了“简单情况”,但为了澄清:如果锚点(#)中包含一个问号(?),它将无法正常工作。 - Yann Dìnendal

15

我认为这个方案比前两个答案更优雅:

from urllib.parse import urlencode, urlparse, parse_qs

def merge_url_query_params(url: str, additional_params: dict) -> str:
    url_components = urlparse(url)
    original_params = parse_qs(url_components.query)
    # Before Python 3.5 you could update original_params with 
    # additional_params, but here all the variables are immutable.
    merged_params = {**original_params, **additional_params}
    updated_query = urlencode(merged_params, doseq=True)
    # _replace() is how you can create a new NamedTuple with a changed field
    return url_components._replace(query=updated_query).geturl()

assert merge_url_query_params(
    'http://example.com/search?q=question',
    {'lang':'en','tag':'python'},
) == 'http://example.com/search?q=question&lang=en&tag=python'

在顶部答案中我最不喜欢的几件事情(尽管它们很好):

  • Łukasz: 必须记住query在URL组件中的索引位置
  • Sapphire64: 创建更新后的ParseResult方式过于冗长

我的回答有一个魔法般的 dict 合并方法,但我更喜欢这种方法来更新已经存在的字典,因为我偏爱不可变性。


我正要写一个答案,然后发现了这个。 (y) - Kireeti K

11

是的:使用urllib

来自文档中的示例代码

>>> import urllib
>>> params = urllib.urlencode({'spam': 1, 'eggs': 2, 'bacon': 0})
>>> f = urllib.urlopen("http://www.musi-cal.com/cgi-bin/query?%s" % params)
>>> print f.geturl() # Prints the final URL with parameters.
>>> print f.read() # Prints the contents

1
您可以举个简单的例子吗? - z4y4ts
1
f.read() 将会显示 HTML 页面。要查看调用的 URL,请使用 f.geturl()。 - ccheneson
9
使用HTTP请求解析URL(实际上是基本的字符串操作)会被扣除1分。此外,真正的问题没有得到考虑,因为你需要知道URL的样子才能正确地追加查询字符串。 - poke
无论是作者编辑了问题还是这个答案与之无关。 - simplylizz
对于Python 3,现在是这样的:urllib.request.urlopenurllib.parse.urlencode - smoquet

10

Python3,我想它可以说是不言自明了。

from urllib.parse import urlparse, urlencode, parse_qsl

url = 'https://www.linkedin.com/jobs/search?keywords=engineer'

parsed = urlparse(url)
current_params = dict(parse_qsl(parsed.query))
new_params = {'location': 'United States'}
merged_params = urlencode({**current_params, **new_params})
parsed = parsed._replace(query=merged_params)

print(parsed.geturl())
# https://www.linkedin.com/jobs/search?keywords=engineer&location=United+States

注意!此方法使用了一个内部函数(在函数名前面用“_”表示):_replace。不建议这样做,因为这些内部函数的行为可能会改变或被删除而没有警告。 - Adrian
2
根据另一个Stack Overflow的评论(诚然,这是几年前的),@GrazingScientist说这不是真的:https://dev59.com/s2Ei5IYBdhLWcg3wHJAR#E66gEYcBWogLw_1b9nrP - yellow-saint
1
@yellow-saint:你说得完全正确,即使在Python 3.9中,“_replace”仍然是一个有效的公共方法。我之前不知道这一点,所以感谢你指出来。尽管如此,以下划线(“_”)开头的方法通常并非如此。 - Adrian
1
只是补充一下,_replace 是公开宣传的 API 的一部分,因为它在文档中有记录:https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse (其中一个方法是_replace()。_replace() 方法将返回一个新的 ParseResult 对象,用新值替换指定字段 - Adam Parkin

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