Agregar parámetros a la URL dada en Python

125

Supongamos que me dieron una URL.
Puede que ya tenga parámetros GET (p http://example.com/search?q=question. Ej. ) O puede que no (p http://example.com/. Ej .).

Y ahora necesito agregarle algunos parámetros {'lang':'en','tag':'python'}. En el primer caso voy a tener http://example.com/search?q=question&lang=en&tag=pythony en el segundo - http://example.com/search?lang=en&tag=python.

¿Hay alguna forma estándar de hacer esto?

z4y4ts
fuente

Respuestas:

180

Hay un par de peculiaridades con los módulos urlliby urlparse. Aquí hay un ejemplo de trabajo:

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, el resultado de urlparse(), es de solo lectura y necesitamos convertirlo a a listantes de que podamos intentar modificar sus datos.

Łukasz
fuente
13
Probablemente quieras usar en urlparse.parse_qslugar de parse_qsl. Este último devuelve una lista mientras que desea un dict. Ver docs.python.org/library/urlparse.html#urlparse.parse_qs .
Florian Brucker
11
@florian: Al menos en Python 2.7, debe llamar urlencodecomo urllib.urlencode(query, doseq=True). De lo contrario, los parámetros que existían en la url original no se conservan correctamente (porque se devuelven como tuplas de @ parse_qs @
rluba el
55
He reescrito esto para trabajar en Python 3 también. Codifica aquí .
dualidad_
12
Los resultados de urlparse()y urlsplit()son en realidad namedtupleinstancias. Por lo tanto, puede asignarlos directamente a una variable y usar url_parts = url_parts._replace(query = …)para actualizarla.
Feuermurmel
2
Precaución: esta implementación elimina los parámetros de consulta repetidos que utilizan algunos servicios RESTful. Con una pequeña modificación, esto se puede arreglar. query = urlparse.parse_qsl (url_parts [4]) query + = params.items () Pero luego, si desea reemplazar los parámetros de consulta existentes usando dict, se necesita un poco más.
ombre42
51

Por qué

No estoy satisfecho con todas las soluciones en esta página ( vamos, ¿dónde está nuestro elemento favorito de copiar y pegar? ), Así que escribí el mío basado en las respuestas aquí. Intenta ser completo y más pitónico. He agregado un controlador para los valores dict y bool en los argumentos para que sean más amigables con el lado del consumidor ( JS ), pero aún son opcionales, puede descartarlos.

Cómo funciona

Prueba 1: Agregar nuevos argumentos, manejar matrices y 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'

Prueba 2: reescribiendo argumentos existentes, manejando 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'

Hablar es barato. Muéstrame el código.

Código en sí mismo. He tratado de describirlo en detalle:

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

Tenga en cuenta que puede haber algunos problemas, si encuentra uno, avíseme y lo mejoraremos

Zafiro64
fuente
¿Quizás agregar un intento excepto con de urllib.parse para incluir el soporte de Python 3? Gracias por el fragmento, muy útil!
MattV
¿Quizás agregar importaciones también?
Christophe Roussy
Unencodes URL codificadas como http://stackoverflow.com/with%2Fencoded?data=some&data=values&answe%2rs=false. Además, use tres galones >>>para ayudar a los doctests a recoger sus doctests
pelson
¿Por qué no cambiar parsed_get_args = dict(parse_qsl(get_args))aparsed_get_args = parse_qs(get_args)
Matt M.
41

Desea utilizar la codificación de URL si las cadenas pueden tener datos arbitrarios (por ejemplo, se necesitarán codificar caracteres como símbolos de unión, barras inclinadas, etc.).

Echa un vistazo a urllib.urlencode:

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

En python3:

from urllib import parse
parse.urlencode({'lang':'en','tag':'python'})
Mike Mueller
fuente
55
En python 3, esto se ha movido a urllib.parse.urlencode
shad0w_wa1k3r el
23

También puede usar el 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
surfeurX
fuente
21

Subcontratarlo a la biblioteca de solicitudes de prueba de batalla .

Así es como lo haré:

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)
Varun
fuente
17

Si está utilizando las solicitudes lib :

import requests
...
params = {'tag': 'python'}
requests.get(url, params=params)
Christophe Roussy
fuente
1
@chefhose la pregunta es ... ¿relativa a qué? No estás en una página web, no hay contexto con el que estar relacionado.
Christophe Roussy
11

Sí: usa urllib .

De los ejemplos en la documentación:

>>> 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
relajarse
fuente
1
¿Puedes por favor dar un breve ejemplo?
z4y4ts
1
f.read () le mostrará la página HTML. Para ver la url de llamada, f.geturl ()
ccheneson
55
-1 para usar una solicitud HTTP para analizar una URL (que en realidad es una manipulación básica de cadenas). Además, el problema real no se tiene en cuenta, ya que necesita saber cómo se ve la URL para poder agregar la cadena de consulta correctamente.
Poke
O bien el autor editó la pregunta o esta respuesta no está relacionada con ella.
simplylizz
11

Basado en esta respuesta, una línea 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)

o:

url += ('&', '?')[urlparse(url).query == ''] + urlencode(params)
Mikhail Gerasimov
fuente
44
Sé que mencionó "casos simples", pero para aclarar: no funcionará correctamente si hay un ?ancla ( #?stuff).
Yann Dìnendal
7

Esto me parece más elegante que las dos respuestas principales:

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'

Las cosas más importantes que no me gustan en las respuestas principales (sin embargo, son buenas):

  • Łukasz: tener que recordar el índice en el que query está en los componentes de la URL
  • Sapphire64: la forma muy detallada de crear la actualización ParseResult

Lo malo de mi respuesta es la dictfusión mágicamente usando el desempaquetado, pero prefiero eso a actualizar un diccionario ya existente debido a mi prejuicio contra la mutabilidad.

butla
fuente
6

Me gustó la versión Łukasz, pero dado que las funciones urllib y urllparse son algo incómodas de usar en este caso, creo que es más sencillo hacer algo como esto:

params = urllib.urlencode(params)

if urlparse.urlparse(url)[4]:
    print url + '&' + params
else:
    print url + '?' + params
Facundo Olano
fuente
44
¿Qué tal .query en lugar de [4]?
Debby Mendez
4

Use las diversas urlparsefunciones para separar la URL existente, urllib.urlencode()en el diccionario combinado, y luego urlparse.urlunparse()volver a armar todo.

O simplemente tome el resultado urllib.urlencode()y concatenelo a la URL de manera apropiada.

Ignacio Vazquez-Abrams
fuente
3

Otra respuesta más:

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
fuente
2

Así es como lo implementé.

import urllib

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

Trabajado como un encanto. Sin embargo, me hubiera gustado una forma más limpia de implementar esto.

Otra forma de implementar lo anterior es ponerlo en un 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
Monty
fuente
1

En 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"
Daniel Patru
fuente