Adicionar parâmetros ao URL fornecido em Python


125

Suponha que recebi um URL.
Ele já pode ter parâmetros GET (por exemplo http://example.com/search?q=question) ou pode não ter (por exemplo http://example.com/).

E agora eu preciso adicionar alguns parâmetros a ele, como {'lang':'en','tag':'python'}. No primeiro caso eu vou ter http://example.com/search?q=question&lang=en&tag=pythone no segundo - http://example.com/search?lang=en&tag=python.

Existe alguma maneira padrão de fazer isso?

Respostas:


180

Existem algumas peculiaridades nos módulos urllibe urlparse. Aqui está um exemplo de trabalho:

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, O resultado de urlparse(), é somente leitura e precisamos convertê-lo para um listantes que possamos tentar modificar seus dados.


13
Você provavelmente deseja usar em urlparse.parse_qsvez de parse_qsl. O último retorna uma lista enquanto você deseja um ditado. Consulte docs.python.org/library/urlparse.html#urlparse.parse_qs .
Florian Brucker

11
@florian: Pelo menos no python 2.7, você precisa chamar urlencodecomo urllib.urlencode(query, doseq=True). Caso contrário, os parâmetros que existiam na URL original não são preservadas corretamente (porque eles são retornados como tuplas de @ parse_qs @
rluba

5
Eu reescrevi isso para funcionar no Python 3 também. Código aqui .
precisa saber é o seguinte

12
Os resultados urlparse()e urlsplit()são realmente namedtupleinstâncias. Assim, você pode atribuí-los diretamente a uma variável e usar url_parts = url_parts._replace(query = …)para atualizá-la.
Feuermurmel

2
Cuidado - essa implementação remove parâmetros de consulta repetidos que alguns serviços RESTful usam. Com uma pequena modificação, isso pode ser corrigido. query = urlparse.parse_qsl (url_parts [4]) query + = params.items () Mas se você deseja substituir os parâmetros de consulta de saída usando o dict, é preciso um pouco mais.
ombre42

51

Por quê

Não fiquei satisfeito com todas as soluções desta página ( vamos lá, onde está a nossa coisa favorita de copiar e colar? ), Então escrevi as minhas com base nas respostas aqui. Ele tenta ser completo e mais pitônico. Eu adicionei um manipulador para valores dict e bool nos argumentos para ser mais amigável ao consumidor ( JS ), mas eles ainda são opcionais, você pode descartá-los.

Como funciona

Teste 1: Adicionando novos argumentos, manipulando matrizes e valores 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'

Teste 2: Reescrevendo argumentos existentes, manipulando valores 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'

Falar é fácil. Mostre-me o código.

Código em si. Eu tentei descrevê-lo em detalhes:

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

Esteja ciente de que pode haver alguns problemas. Se você encontrar um, entre em contato e faremos com que isso melhore.


Talvez adicione uma tentativa, exceto com urllib.parse, para incluir o suporte ao Python 3? Obrigado pelo trecho, muito útil!
29415 MattV

Talvez adicione importações também?
Christophe Roussy

Não codifica URLs codificados como http://stackoverflow.com/with%2Fencoded?data=some&data=values&answe%2rs=false. Além disso, use três divisas >>>para ajudar os doctests a pegá-los
pelson

Por que não mudar parsed_get_args = dict(parse_qsl(get_args))paraparsed_get_args = parse_qs(get_args)
Matt M.

41

Você deseja usar a codificação de URL se as seqüências de caracteres puderem ter dados arbitrários (por exemplo, caracteres como e comercial, barras, etc. precisarão ser codificados).

Confira urllib.urlencode:

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

Em python3:

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

5
No python 3, isso foi movido para urllib.parse.urlencode #
shad0w_wa1k3r

23

Você também pode usar o módulo 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

21

Terceirize-o para a biblioteca de solicitações testadas em batalha .

É assim que eu vou fazer:

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

Se você estiver usando os pedidos lib :

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

1
@chefhose a pergunta é ... em relação a quê? Você não está em uma página da Web, não há contexto a ser relativo.
Christophe Roussy

11

Sim: use urllib .

Dos exemplos na documentação:

>>> 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
Você pode dar um breve exemplo?
Z4y4ts

1
f.read () mostrará a página HTML. Para ver o URL de chamada, f.geturl ()
ccheneson

5
-1 para usar uma solicitação HTTP para analisar uma URL (que é realmente a manipulação básica de cadeias). Além disso, o problema real não é considerado, porque você precisa saber como a URL parece para poder anexar a sequência de consultas corretamente.
Poke

A pergunta editada pelo autor ou esta resposta não está relacionada a ela.
simplylizz

11

Com base nesta resposta, uma linha para casos simples (código 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)

ou:

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

4
Eu sei que você mencionou "casos simples", mas para esclarecer: não funcionará corretamente se houver um ?na âncora ( #?stuff).
Yann Dìnendal 12/12

7

Acho isso mais elegante do que as duas principais respostas:

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'

As coisas mais importantes que não gosto nas respostas principais (elas são boas):

  • Łukasz: ter que lembrar o índice no qual queryestá o componente da URL
  • Sapphire64: a maneira muito detalhada de criar as atualizações ParseResult

O que é ruim na minha resposta é a dictmesclagem de aparência mágica usando a descompactação, mas eu prefiro isso a atualizar um dicionário já existente por causa do meu preconceito contra a mutabilidade.


6

Eu gostei da versão Łukasz, mas como as funções urllib e urllparse são um pouco difíceis de usar nesse caso, acho mais simples fazer algo assim:

params = urllib.urlencode(params)

if urlparse.urlparse(url)[4]:
    print url + '&' + params
else:
    print url + '?' + params

4
Que tal .query em vez de [4]?
Debby Mendez


3

Ainda outra resposta:

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))

2

Aqui está como eu o implementei.

import urllib

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

Funcionou como um encanto. No entanto, eu gostaria de ter uma maneira mais limpa de implementar isso.

Outra maneira de implementar o acima é colocá-lo em um método.

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

No 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"
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.