Добавить параметры к указанному URL-адресу в Python

125

Предположим, мне дали 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.

Есть какой-нибудь стандартный способ сделать это?

z4y4ts
источник

Ответы:

180

Есть несколько причуд с urllibи urlparseмодулями. Вот рабочий пример:

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прежде чем мы сможем попытаться изменить его данные.

Лукаш
источник
13
Вероятно, вы захотите использовать urlparse.parse_qsвместо parse_qsl. Последний возвращает список, а вам нужен dict. См. Docs.python.org/library/urlparse.html#urlparse.parse_qs .
Флориан Брукер
11
@florian: по крайней мере, в python 2.7 вам нужно позвонить urlencodeкак urllib.urlencode(query, doseq=True). В противном случае параметры, которые существовали в исходном URL-адресе, не сохраняются правильно (потому что они возвращаются в виде кортежей из @ parse_qs @
rluba
5
Я переписал это, чтобы работать и в Python 3. Код здесь .
duality_
12
Результаты urlparse()и urlsplit()на самом деле являются namedtupleэкземплярами. Таким образом, вы можете назначить их напрямую переменной и использовать url_parts = url_parts._replace(query = …)для ее обновления.
Feuermurmel
2
Внимание! Эта реализация удаляет повторяющиеся параметры запроса, которые используют некоторые службы RESTful. С небольшими изменениями это можно исправить. query = urlparse.parse_qsl (url_parts [4]) query + = params.items () Но тогда, если вы хотите заменить выходные параметры запроса с помощью dict, потребуется немного больше.
ombre42
52

Зачем

Меня не удовлетворили все решения на этой странице ( давай, а где наша любимая вещь для копирования и вставки? ), Поэтому я написал свои собственные, основанные на ответах здесь. Он пытается быть полным и более питоническим. Я добавил обработчик для значений dict и bool в аргументы, чтобы они были более удобными для потребителя ( JS ), но они все же необязательны, вы можете отказаться от них.

Как это устроено

Тест 1: добавление новых аргументов, обработка массивов и значений типа Bool:

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: перезапись существующих аргументов, обработка значений DICT:

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 = 'http://stackoverflow.com/test?answers=true'
    >> new_params = {'answers': False, 'data': ['some','values']}
    >> add_url_params(url, new_params)
    'http://stackoverflow.com/test?data=some&data=values&answers=false'
    """
    # Unquoting URL first so we don't loose 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 of 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

Имейте в виду, что могут быть некоторые проблемы, если вы их обнаружите, сообщите мне, и мы сделаем это лучше

Sapphire64
источник
Возможно, добавьте попытку, кроме from urllib.parse, чтобы включить поддержку Python 3? Спасибо за фрагмент, очень полезно!
MattV
Может, импорт тоже добавить?
Кристоф Русси
Расшифровывает закодированные URL-адреса, например http://stackoverflow.com/with%2Fencoded?data=some&data=values&answe%2rs=false. Кроме того, используйте три шеврона, >>>чтобы помочь доктестам собрать ваши документы
pelson
Почему бы не перейти parsed_get_args = dict(parse_qsl(get_args))наparsed_get_args = parse_qs(get_args)
Мэтт М.
41

Вы хотите использовать кодировку URL-адреса, если строки могут содержать произвольные данные (например, необходимо будет кодировать такие символы, как амперсанды, косые черты и т. Д.).

Посмотрите urllib.urlencode:

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

В python3:

from urllib import parse
parse.urlencode({'lang':'en','tag':'python'})
Майк Мюллер
источник
5
В python 3 это было перемещено в urllib.parse.urlencode
shad0w_wa1k3r
23

Вы также можете использовать модуль 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
surfeurX
источник
21

Передайте его в библиотеку протестированных запросов .

Вот как я это сделаю:

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)
Варуна
источник
17

Если вы используете запросы lib :

import requests
...
params = {'tag': 'python'}
requests.get(url, params=params)
Кристоф Русси
источник
1
@chefhose вопрос ... относительно чего? Вы не находитесь на веб-странице, вам не нужно относиться к контексту.
Christophe
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
5
-1 для использования HTTP-запроса для анализа URL-адреса (что на самом деле является базовой манипуляцией со строкой). Кроме того, реальная проблема не рассматривается, потому что вам нужно знать, как выглядит URL-адрес, чтобы иметь возможность правильно добавить строку запроса.
тыкает
Либо автор редактировал вопрос, либо этот ответ не имеет к нему отношения.
простоlizz
11

Основываясь на этом ответе, однострочный вариант для простых случаев (код 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
Я знаю, что вы упомянули «простые случаи», но чтобы уточнить: он не будет работать должным образом, если ?в anchor ( #?stuff) есть символ .
Ян Динендал
7

Я считаю это более элегантным, чем два основных ответа:

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'

Самые важные вещи, которые мне не нравятся в верхних ответах (тем не менее, они хорошие):

  • Лукаш: нужно помнить индекс, по которому queryнаходится в компонентах URL
  • Sapphire64: очень подробный способ создания обновленных ParseResult

Что плохо в моем ответе, так это волшебно выглядящее dictслияние с использованием распаковки, но я предпочитаю это обновлению уже существующего словаря из-за моего предубеждения против изменчивости.

butla
источник
6

Мне понравилась версия Лукаша, но поскольку функции urllib и urllparse несколько неудобны в использовании в этом случае, я думаю, что проще сделать что-то вроде этого:

params = urllib.urlencode(params)

if urlparse.urlparse(url)[4]:
    print url + '&' + params
else:
    print url + '?' + params
Факундо Олано
источник
4
Как насчет .query вместо [4]?
Дебби Мендес,
4

Используйте различные urlparseфункции, чтобы разделить существующий URL-адрес urllib.urlencode()в объединенном словаре, а затем urlparse.urlunparse()снова собрать все вместе.

Или просто возьмите результат urllib.urlencode()и соответствующим образом присоедините его к URL-адресу.

Игнасио Васкес-Абрамс
источник
3

Еще один ответ:

def addGetParameters(url, newParams):
    (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
    queryList = urlparse.parse_qsl(query, keep_blank_values=True)
    for key in newParams:
        queryList.append((key, newParams[key]))
    return urlparse.urlunparse((scheme, netloc, path, params, urllib.urlencode(queryList), fragment))
Timmmm
источник
2

Вот как я это реализовал.

import urllib

params = urllib.urlencode({'lang':'en','tag':'python'})
url = ''
if request.GET:
   url = request.url + '&' + params
else:
   url = request.url + '?' + params    

Работал как шарм. Однако мне хотелось бы более чистый способ реализовать это.

Другой способ реализовать вышеизложенное - поместить это в метод.

import urllib

def add_url_param(request, **params):
   new_url = ''
   _params = dict(**params)
   _params = urllib.urlencode(_params)

   if _params:
      if request.GET:
         new_url = request.url + '&' + _params
      else:
         new_url = request.url + '?' + _params
   else:
      new_url = request.url

   return new_ur
Монти
источник
1

В Python 2.5

import cgi
import urllib
import urlparse

def add_url_param(url, **params):
    n=3
    parts = list(urlparse.urlsplit(url))
    d = dict(cgi.parse_qsl(parts[n])) # use cgi.parse_qs for list values
    d.update(params)
    parts[n]=urllib.urlencode(d)
    return urlparse.urlunsplit(parts)

url = "http://stackoverflow.com/search?q=question"
add_url_param(url, lang='en') == "http://stackoverflow.com/search?q=question&lang=en"
Даниэль Патру
источник