Ajouter des paramètres à une URL donnée en Python

125

Supposons que l'on me donne une URL.
Il peut déjà avoir des paramètres GET (par exemple http://example.com/search?q=question) ou pas (par exemple http://example.com/).

Et maintenant, je dois y ajouter des paramètres comme {'lang':'en','tag':'python'}. Dans le premier cas, je vais avoir http://example.com/search?q=question&lang=en&tag=pythonet dans le second - http://example.com/search?lang=en&tag=python.

Existe-t-il un moyen standard de le faire?

z4y4ts
la source

Réponses:

180

Il y a quelques bizarreries avec les modules urllibet urlparse. Voici un exemple de travail:

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, le résultat de urlparse(), est en lecture seule et nous devons le convertir en un listavant de pouvoir tenter de modifier ses données.

Łukasz
la source
13
Vous souhaitez probablement utiliser à la urlparse.parse_qsplace de parse_qsl. Ce dernier renvoie une liste alors que vous voulez un dict. Voir docs.python.org/library/urlparse.html#urlparse.parse_qs .
Florian Brucker
11
@florian: Au moins en python 2.7, vous devez alors appeler en urlencodetant que urllib.urlencode(query, doseq=True). Sinon, les paramètres qui existaient dans l'url d'origine ne sont pas conservés correctement (car ils sont renvoyés sous forme de tuples de @ parse_qs @
rluba
5
J'ai réécrit ceci pour fonctionner également dans Python 3. Codez ici .
duality_
12
Les résultats de urlparse()et urlsplit()sont en fait des namedtupleinstances. Ainsi, vous pouvez les affecter directement à une variable et utiliser url_parts = url_parts._replace(query = …)pour la mettre à jour.
Feuermurmel
2
Attention - cette implémentation supprime les paramètres de requête répétés utilisés par certains services RESTful. Avec une petite modification, cela peut être corrigé. query = urlparse.parse_qsl (url_parts [4]) query + = params.items () Mais alors si vous voulez remplacer les paramètres de requête sortants en utilisant dict, cela prend un peu plus.
ombre42
51

Pourquoi

Je n'ai pas été satisfait de toutes les solutions sur cette page ( allez, où est notre chose préférée de copier-coller? ) Alors j'ai écrit la mienne en fonction des réponses ici. Il essaie d'être complet et plus pythonique. J'ai ajouté un gestionnaire pour les valeurs dict et booléen dans les arguments pour être plus convivial côté consommateur ( JS ), mais ils sont encore facultatifs, vous pouvez les supprimer.

Comment ça fonctionne

Test 1: Ajout de nouveaux arguments, gestion des tableaux et des valeurs booléennes:

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'

Test 2: réécriture d'arguments existants, gestion des valeurs 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'

Parler n'est pas cher. Montre-moi le code.

Code lui-même. J'ai essayé de le décrire en détail:

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

Sachez qu'il peut y avoir des problèmes, si vous en trouvez un, faites-le moi savoir et nous améliorerons cette chose

Saphir64
la source
Peut-être ajouter un essai sauf avec de urllib.parse pour inclure le support de Python 3? Merci pour l'extrait, très utile!
MattV
Peut-être ajouter des importations aussi?
Christophe Roussy
Décode les URL encodées telles que http://stackoverflow.com/with%2Fencoded?data=some&data=values&answe%2rs=false. Aussi, utilisez trois chevrons >>>pour aider les docteurs à récupérer vos docteurs
Pelson
Pourquoi ne pas changer parsed_get_args = dict(parse_qsl(get_args))pourparsed_get_args = parse_qs(get_args)
Matt M.
41

Vous souhaitez utiliser le codage URL si les chaînes peuvent contenir des données arbitraires (par exemple, des caractères tels que des esperluettes, des barres obliques, etc. devront être codés).

Découvrez 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
la source
5
Dans python 3, cela a été déplacé vers urllib.parse.urlencode
shad0w_wa1k3r
23

Vous pouvez également utiliser le module 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
la source
21

Externaliser vers la bibliothèque de requêtes testées au combat .

Voici comment je vais le faire:

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
la source
17

Si vous utilisez les requêtes lib :

import requests
...
params = {'tag': 'python'}
requests.get(url, params=params)
Christophe Roussy
la source
1
@chefhose la question est ... relative à quoi? Vous n'êtes pas dans une page Web, il n'y a pas de contexte auquel être relatif.
Christophe Roussy
11

Oui: utilisez urllib .

D'après les exemples de la documentation:

>>> 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
se détendre
la source
1
Pouvez-vous donner un bref exemple?
z4y4ts
1
f.read () vous montrera la page HTML. Pour voir l'url d'appel, f.geturl ()
ccheneson
5
-1 pour utiliser une requête HTTP pour analyser une URL (qui est en fait une manipulation de chaîne de base). De plus, le problème réel n'est pas pris en compte, car vous devez savoir à quoi ressemble l'URL pour pouvoir ajouter correctement la chaîne de requête.
poke
Soit l'auteur a édité la question, soit cette réponse n'y est pas liée.
simplylizz
11

Sur la base de cette réponse, une ligne pour les cas simples (code 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)
Mikhail Gerasimov
la source
4
Je sais que vous avez mentionné des "cas simples", mais pour clarifier: cela ne fonctionnera pas correctement s'il y a un ?dans l'ancre ( #?stuff).
Yann Dìnendal
7

Je trouve cela plus élégant que les deux principales réponses:

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'

Les choses les plus importantes que je n'aime pas dans les premières réponses (elles sont néanmoins bonnes):

  • Łukasz: devoir se souvenir de l'index auquel le query trouve les composants URL
  • Sapphire64: la manière très verbeuse de créer la mise à jour ParseResult

Ce qui est mauvais dans ma réponse, c'est l'aspect magique dict fusion utilise le déballage, mais je préfère cela à la mise à jour d'un dictionnaire déjà existant en raison de mes préjugés contre la mutabilité.

butla
la source
6

J'ai aimé la version Łukasz, mais comme les fonctions urllib et urllparse sont un peu difficiles à utiliser dans ce cas, je pense qu'il est plus simple de faire quelque chose comme ceci:

params = urllib.urlencode(params)

if urlparse.urlparse(url)[4]:
    print url + '&' + params
else:
    print url + '?' + params
Facundo Olano
la source
4
Que diriez-vous de .query au lieu de [4]?
Debby Mendez
4

Utilisez les différentes urlparsefonctions pour déchirer l'URL existante, urllib.urlencode()sur le dictionnaire combiné, puisurlparse.urlunparse() pour tout remettre ensemble.

Ou prenez simplement le résultat de urllib.urlencode()et concaténez-le à l'URL de manière appropriée.

Ignacio Vazquez-Abrams
la source
3

Encore une autre réponse:

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
la source
2

Voici comment je l'ai implémenté.

import urllib

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

A travaillé comme un charme. Cependant, j'aurais aimé une façon plus propre de mettre en œuvre cela.

Une autre façon de mettre en œuvre ce qui précède est de le mettre dans une méthode.

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
la source
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
la source