Dodaj parametry do podanego adresu URL w Pythonie


125

Załóżmy, że otrzymałem adres URL.
Może już mieć parametry GET (np. http://example.com/search?q=question) Lub może nie (np http://example.com/.).

A teraz muszę dodać do tego kilka parametrów jak np {'lang':'en','tag':'python'}. W pierwszym przypadku będę miał, http://example.com/search?q=question&lang=en&tag=pythonaw drugim - http://example.com/search?lang=en&tag=python.

Czy jest jakiś standardowy sposób, aby to zrobić?

Odpowiedzi:


180

Istnieje kilka dziwactw związanych z modułami urllibi urlparse. Oto działający przykład:

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, Wynik urlparse(), jest tylko do odczytu i musimy przekształcić go listprzed możemy próbować modyfikować swoje dane.


13
Prawdopodobnie chcesz użyć urlparse.parse_qszamiast parse_qsl. Ten ostatni zwraca listę, podczas gdy ty chcesz dyktować. Zobacz docs.python.org/library/urlparse.html#urlparse.parse_qs .
Florian Brucker

11
@florian: Przynajmniej w Pythonie 2.7 musisz wtedy wywołać urlencodeas urllib.urlencode(query, doseq=True). W przeciwnym razie parametry, które istniały w oryginalnym adresie URL, nie są zachowywane poprawnie (ponieważ są zwracane jako krotki z @ parse_qs @
rluba

5
Przepisałem to, aby działało również w Pythonie 3. Kod tutaj .
duality_

12
Wyniki urlparse()i urlsplit()są w rzeczywistości namedtupleprzykładami. W ten sposób możesz przypisać je bezpośrednio do zmiennej i użyć url_parts = url_parts._replace(query = …)do jej aktualizacji.
Feuermurmel,

2
Uwaga - ta implementacja usuwa powtarzające się parametry zapytań, których używają niektóre usługi RESTful. Dzięki niewielkim modyfikacjom można to naprawić. query = urlparse.parse_qsl (url_parts [4]) query + = params.items () Ale jeśli chcesz zamienić parametry wyjściowego zapytania za pomocą dict, zajmie to trochę więcej czasu.
ombre42

52

Czemu

Nie byłem zadowolony ze wszystkich rozwiązań na tej stronie (no dalej, gdzie jest nasza ulubiona rzecz do kopiowania i wklejania? ), Więc napisałem własne na podstawie odpowiedzi tutaj. Stara się być kompletna i bardziej Pythonic. Dodałem procedurę obsługi dla wartości dict i bool w argumentach, aby były bardziej przyjazne dla konsumenta ( JS ), ale są one jeszcze opcjonalne, możesz je usunąć.

Jak to działa

Test 1: Dodawanie nowych argumentów, obsługa tablic i wartości logicznych:

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: Przepisywanie istniejących argumentów, obsługa wartości 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'

Rozmowa jest tania. Pokaż mi kod.

Sam kod. Starałem się to szczegółowo opisać:

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

Pamiętaj, że mogą wystąpić pewne problemy, jeśli znajdziesz taki, daj mi znać, a my poprawimy to


Być może dodaj próbę, z wyjątkiem with z urllib.parse, aby uwzględnić obsługę Python 3? Dzięki za fragment, bardzo przydatny!
MattV,

Może dodać też import?
Christophe Roussy

Unkoduje zakodowane adresy URL, takie jak http://stackoverflow.com/with%2Fencoded?data=some&data=values&answe%2rs=false. Ponadto użyj trzech szewronów, >>>aby pomóc w testach doctestów
pelson

Dlaczego nie zmienić parsed_get_args = dict(parse_qsl(get_args))naparsed_get_args = parse_qs(get_args)
Matt M.

41

Chcesz użyć kodowania adresu URL, jeśli ciągi mogą zawierać dowolne dane (na przykład znaki takie jak ampersandy, ukośniki itp. Będą musiały zostać zakodowane).

Sprawdź urllib.urlencode:

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

W python3:

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

5
W Pythonie 3 zostało to przeniesione do urllib.parse.urlencode
shad0w_wa1k3r

23

Możesz również skorzystać z modułu 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

Zleć go do biblioteki żądań przetestowanych w bitwie .

Oto jak to zrobię:

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

Jeśli używasz żądań lib :

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

1
@chefhose pytanie brzmi ... w stosunku do czego? Nie jesteś na stronie internetowej, nie ma kontekstu, do którego można by się odnosić.
Christophe Roussy

11

Tak: użyj urllib .

Z przykładów w dokumentacji:

>>> 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
Czy możesz podać krótki przykład?
z4y4ts

1
f.read () pokaże stronę HTML. Aby zobaczyć wywołujący adres URL, f.geturl ()
ccheneson

5
-1 za użycie żądania HTTP do parsowania adresu URL (co jest w rzeczywistości podstawową operacją na ciągach znaków). Ponadto rzeczywisty problem nie jest brany pod uwagę, ponieważ musisz wiedzieć, jak wygląda adres URL, aby móc poprawnie dołączyć ciąg zapytania.
szturchnij

Albo autor zredagował pytanie, albo ta odpowiedź nie jest z nim związana.
simplylizz

11

Opierając się na tej odpowiedzi, jednowierszowy dla prostych przypadków (kod 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)

lub:

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

4
Wiem, że wspomniałeś o „prostych przypadkach”, ale dla wyjaśnienia: nie będzie działać poprawnie, jeśli ?w kotwicy ( #?stuff) znajduje się znak .
Yann Dìnendal

7

Uważam to za bardziej eleganckie niż dwie najpopularniejsze odpowiedzi:

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'

Najważniejsze rzeczy, których nie lubię w górnych odpowiedziach (są jednak dobre):

  • Łukasz: konieczność zapamiętania indeksu, pod którym queryznajduje się element URL
  • Sapphire64: bardzo rozwlekły sposób tworzenia zaktualizowanego pliku ParseResult

Złe w mojej odpowiedzi jest magicznie wyglądające dictscalanie przy użyciu rozpakowywania, ale wolę to od aktualizowania już istniejącego słownika z powodu mojego uprzedzenia wobec zmienności.


6

Podobała mi się wersja Łukasz, ale ponieważ funkcje urllib i urllparse są w tym przypadku nieco niewygodne, myślę, że łatwiej jest zrobić coś takiego:

params = urllib.urlencode(params)

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

4
A co z .query zamiast [4]?
Debby Mendez

4

Użyj różnych urlparsefunkcji, aby rozdzielić istniejący adres URL urllib.urlencode()w połączonym słowniku, a następnie urlparse.urlunparse()złożyć go z powrotem.

Lub po prostu weź wynik urllib.urlencode()i odpowiednio połącz go z adresem URL.


3

Jeszcze inna odpowiedź:

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

Oto jak to wdrożyłem.

import urllib

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

Działał jak urok. Wolałbym jednak bardziej przejrzysty sposób wdrożenia tego.

Innym sposobem implementacji powyższego jest umieszczenie go w metodzie.

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

W Pythonie 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"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.