Aggiungi parametri all'URL fornito in Python


125

Supponiamo che mi sia stato fornito un URL.
Potrebbe già avere parametri GET (ad esempio http://example.com/search?q=question) o potrebbe non esserlo (ad esempio http://example.com/).

E ora ho bisogno di aggiungere alcuni parametri come {'lang':'en','tag':'python'}. Nel primo caso avrò http://example.com/search?q=question&lang=en&tag=pythone nel secondo - http://example.com/search?lang=en&tag=python.

Esiste un modo standard per farlo?

Risposte:


180

Ci sono un paio di stranezze con i moduli urllibe urlparse. Ecco un esempio funzionante:

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, il risultato di urlparse(), è di sola lettura e dobbiamo convertirlo in a listprima di poter tentare di modificarne i dati.


13
Probabilmente vuoi usare al urlparse.parse_qsposto di parse_qsl. Quest'ultimo restituisce una lista mentre tu vuoi un dict. Vedi docs.python.org/library/urlparse.html#urlparse.parse_qs .
Florian Brucker

11
@florian: Almeno in python 2.7 devi quindi chiamare urlencodecome urllib.urlencode(query, doseq=True). In caso contrario, i parametri che esistevano nell'URL originale non vengono conservati correttamente (perché vengono restituiti come tuple da @ parse_qs @
rluba

5
L'ho riscritto per funzionare anche in Python 3. Codice qui .
dualità_

12
I risultati di urlparse()e urlsplit()sono in realtà namedtupleistanze. Quindi puoi assegnarli direttamente a una variabile e usarli url_parts = url_parts._replace(query = …)per aggiornarla.
Feuermurmel

2
Attenzione: questa implementazione rimuove i parametri di query ripetuti utilizzati da alcuni servizi RESTful. Con una piccola modifica questo può essere risolto. query = urlparse.parse_qsl (url_parts [4]) query + = params.items () Ma poi se vuoi sostituire i parametri della query in uscita usando dict, ci vuole un po 'di più.
ombre42

51

Perché

Non sono stato soddisfatto di tutte le soluzioni su questa pagina ( dai, dov'è la nostra cosa preferita di copia-incolla? ) Quindi ho scritto la mia in base alle risposte qui. Cerca di essere completo e più pitonico. Ho aggiunto un gestore per i valori dict e bool negli argomenti per essere più amichevoli per il consumatore ( JS ), ma sono ancora opzionali, puoi eliminarli.

Come funziona

Test 1: aggiunta di nuovi argomenti, gestione di array e valori 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'

Test 2: riscrittura degli argomenti esistenti, gestione dei valori 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'

Parlare è economico. Mostrami il codice.

Codice stesso. Ho provato a descriverlo in dettaglio:

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

Tieni presente che potrebbero esserci dei problemi, se ne trovi uno per favore fammelo sapere e miglioreremo questa cosa


Forse aggiungere una prova eccetto con da urllib.parse per includere il supporto per Python 3? Grazie per lo snippet, molto utile!
MattV

Forse aggiungi anche le importazioni?
Christophe Roussy

Annulla la codifica degli URL codificati come http://stackoverflow.com/with%2Fencoded?data=some&data=values&answe%2rs=false. Inoltre, usa tre chevron >>>per aiutare i dottori a raccogliere i tuoi dottori
pelson l'

Perché non passare parsed_get_args = dict(parse_qsl(get_args))aparsed_get_args = parse_qs(get_args)
Matt M.

41

Si desidera utilizzare la codifica URL se le stringhe possono contenere dati arbitrari (ad esempio, è necessario codificare caratteri come e commerciali, barre e così via).

Controlla urllib.urlencode:

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

In python3:

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

5
In python 3, questo è stato spostato in urllib.parse.urlencode
shad0w_wa1k3r

23

Puoi anche utilizzare il modulo 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

Esternalizzalo nella libreria delle richieste testate in battaglia .

Ecco come lo farò:

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 stai usando le richieste lib :

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

1
@chefhose la domanda è ... relativa a cosa? Non sei in una pagina web, non esiste un contesto a cui essere relativo.
Christophe Roussy,

11

Sì: usa urllib .

Dagli esempi nella documentazione:

>>> 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
Puoi fare qualche breve esempio?
z4y4ts

1
f.read () ti mostrerà la pagina HTML. Per vedere l'URL di chiamata, f.geturl ()
ccheneson

5
-1 per l'utilizzo di una richiesta HTTP per l'analisi di un URL (che in realtà è una manipolazione di stringhe di base). Inoltre il problema effettivo non viene considerato, perché è necessario conoscere l'aspetto dell'URL per poter aggiungere correttamente la stringa di query.
colpisci il

O l'autore ha modificato la domanda o questa risposta non è correlata ad essa.
simplylizz

11

Sulla base di questa risposta, una riga per casi semplici (codice 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)

4
So che hai menzionato "casi semplici", ma per chiarire: non funzionerà correttamente se c'è un ?anchor ( #?stuff).
Yann Dìnendal

7

Lo trovo più elegante delle due risposte principali:

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'

Le cose più importanti che non mi piacciono nelle risposte migliori (sono comunque buone):

  • Łukasz: dover ricordare l'indice in cui il file query trova nei componenti dell'URL
  • Sapphire64: il modo molto prolisso di creare l'aggiornamento ParseResult

La cosa negativa della mia risposta è l' dictunione dall'aspetto magico che utilizza lo spacchettamento, ma lo preferisco all'aggiornamento di un dizionario già esistente a causa del mio pregiudizio contro la mutevolezza.


6

Mi è piaciuta la versione di Łukasz, ma poiché le funzioni urllib e urllparse sono un po 'scomode da usare in questo caso, penso che sia più semplice fare qualcosa del genere:

params = urllib.urlencode(params)

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

4
Che ne dici di .query invece di [4]?
Debby Mendez

4

Usa le varie urlparsefunzioni per separare l'URL esistente, urllib.urlencode()sul dizionario combinato, quindiurlparse.urlunparse() per rimetterlo insieme.

Oppure prendi il risultato di urllib.urlencode()e concatenalo all'URL in modo appropriato.


3

Ancora un'altra risposta:

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

Ecco come l'ho implementato.

import urllib

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

Ha funzionato come un fascino. Tuttavia, mi sarebbe piaciuto un modo più pulito per implementarlo.

Un altro modo per implementare quanto sopra è metterlo in un metodo.

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

In 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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.