Jak dołączyć komponenty ścieżki podczas tworzenia adresu URL w Pythonie


103

Na przykład chcę dołączyć ścieżkę prefiksu do ścieżek zasobów, takich jak /js/foo.js.

Chcę, aby wynikowa ścieżka była względna w stosunku do katalogu głównego serwera. W powyższym przykładzie, jeśli przedrostkiem byłby „media”, chciałbym, aby wynikiem był /media/js/foo.js.

os.path.join robi to naprawdę dobrze, ale sposób łączenia ścieżek zależy od systemu operacyjnego. W tym przypadku wiem, że moim celem jest sieć internetowa, a nie lokalny system plików.

Czy istnieje najlepsza alternatywa podczas pracy ze ścieżkami, o których wiesz, że będą używane w adresach URL? Czy os.path.join będzie działać wystarczająco dobrze? Czy powinienem po prostu skręcić własną?


1
os.path.joinnie będzie działać. Ale zwykłe łączenie za pomocą /znaku powinno działać we wszystkich przypadkach - /to standardowy separator ścieżki w HTTP zgodnie ze specyfikacją.
intgr

Odpowiedzi:


60

Ponieważ z komentarzy opublikowanych przez OP wynika, że nie chce zachowywać „bezwzględnych adresów URL” w złączeniu (co jest jednym z kluczowych zadań urlparse.urljoin;-), radziłbym tego unikać. os.path.joinbyłoby również złe, dokładnie z tego samego powodu.

Więc użyłbym czegoś w rodzaju '/'.join(s.strip('/') for s in pieces)(jeśli interlinia /również musi być zignorowana - jeśli wiodąca część musi być w specjalnym opakowaniu, to oczywiście jest to wykonalne ;-).


1
Dzięki. Nie przeszkadzało mi to tak bardzo wymaganie, aby wiodący znak „/” w drugiej części nie mógł tam być, ale wymaganie końcowego znaku „/” w pierwszej części sprawia, że ​​czuję się tak, jakby w tym przypadku użycia urljoin nic nie robił dla mnie. Chciałbym przynajmniej dołączyć („/ media”, „js / foo.js”) i dołączyć („/ media /”, „js / foo.js”) do pracy. Dzięki za to, co wydaje się być właściwą odpowiedzią: skręć własną.
amjoconn

Miałem nadzieję, że coś wykona „/” rozebranie się i dołączenie za mnie.
statueofmike

Nie, to nie zadziała w oknach, do których os.path.join('http://media.com', 'content')powróci świat http://media.com\content.
SEF

154

Możesz użyć urllib.parse.urljoin:

>>> from urllib.parse import urljoin
>>> urljoin('/media/path/', 'js/foo.js')
'/media/path/js/foo.js'

Ale uwaga :

>>> urljoin('/media/path', 'js/foo.js')
'/media/js/foo.js'
>>> urljoin('/media/path', '/js/foo.js')
'/js/foo.js'

Powodem, dla którego otrzymujesz różne wyniki /js/foo.jsi js/foo.jsjest to, że pierwszy zaczyna się od ukośnika, który oznacza, że ​​zaczyna się już w katalogu głównym witryny.

W Pythonie 2 musisz to zrobić

from urlparse import urljoin

Więc mam pasek z początkowego znaku „/” w /js/foo.js, ale wygląda na to, że tak będzie w przypadku os.path.join. Wymaganie ukośnika po mediach oznacza, że ​​i tak większość pracy muszę wykonać sam.
amjoconn

W szczególności, gdy już mam, że prefiks musi kończyć się na /, a ścieżka docelowa nie może zaczynać się w / równie dobrze mogę po prostu połączyć. W takim przypadku nie jestem pewien, czy urljoin naprawdę pomaga?
amjoconn

3
@MedhatGayed Nie jest dla mnie jasne, czy urljoinkiedykolwiek usuwa się „/”. Jeśli zadzwonię, urlparse.urljoin('/media/', '/js/foo.js')zwracana wartość to „/js/foo.js”. Usunięto wszystkie media, a nie zduplikowany znak „/”. W urlparse.urljoin('/media//', 'js/foo.js')rzeczywistości zwraca „/media//js/foo.js”, więc żadne duplikaty nie zostały usunięte.
amjoconn

8
urljoin zachowuje się dziwnie, jeśli łączysz komponenty, które nie kończą się na / odcina pierwszy komponent do swojej bazy, a następnie łączy pozostałe argumenty. Nie tego bym się spodziewał.
Pete

7
Niestety urljoinnie służy do łączenia adresów URL. Służy do rozwiązywania względnych adresów URL, jakie można znaleźć w dokumentach HTML itp.
OrangeDog

46

Jak mówisz, os.path.joinłączy ścieżki w oparciu o bieżący system operacyjny. posixpathjest podstawowym modułem używanym w systemach POSIX w przestrzeni nazw os.path:

>>> os.path.join is posixpath.join
True
>>> posixpath.join('/media/', 'js/foo.js')
'/media/js/foo.js'

Możesz więc po prostu zaimportować i używać posixpath.joinzamiast adresów URL, które są dostępne i będą działać na dowolnej platformie .

Edycja: Sugestia @ Pete'a jest dobra, możesz alias importu dla zwiększenia czytelności

from posixpath import join as urljoin

Edycja: myślę, że jest to wyjaśnione lub przynajmniej pomogło mi to zrozumieć, jeśli spojrzysz na źródło os.py(kod tutaj pochodzi z Pythona 2.7.11, a dodatkowo przyciąłem kilka bitów). Istnieją warunkowe importy w os.pytym, które wybierają moduł ścieżki do użycia w przestrzeni nazw os.path. Wszystkie moduły bazowe ( posixpath, ntpath, os2emxpath, riscospath), które mogą być importowane os.py, alias jak path, istnieją i istnieć być stosowany we wszystkich systemach. os.pyto po prostu wybranie jednego z modułów do użycia w przestrzeni nazw os.pathw czasie wykonywania w oparciu o bieżący system operacyjny.

# os.py
import sys, errno

_names = sys.builtin_module_names

if 'posix' in _names:
    # ...
    from posix import *
    # ...
    import posixpath as path
    # ...

elif 'nt' in _names:
    # ...
    from nt import *
    # ...
    import ntpath as path
    # ...

elif 'os2' in _names:
    # ...
    from os2 import *
    # ...
    if sys.version.find('EMX GCC') == -1:
        import ntpath as path
    else:
        import os2emxpath as path
        from _emx_link import link
    # ...

elif 'ce' in _names:
    # ...
    from ce import *
    # ...
    # We can use the standard Windows path.
    import ntpath as path

elif 'riscos' in _names:
    # ...
    from riscos import *
    # ...
    import riscospath as path
    # ...

else:
    raise ImportError, 'no os specific module found'

4
from posixpath import join as urljoinładnie tworzy aliasy do czegoś łatwego do odczytania.
Pete

29

To ładnie działa:

def urljoin(*args):
    """
    Joins given arguments into an url. Trailing but not leading slashes are
    stripped for each argument.
    """

    return "/".join(map(lambda x: str(x).rstrip('/'), args))

9

Funkcja basejoin w pakiecie urllib może być tym, czego szukasz.

basejoin = urljoin(base, url, allow_fragments=True)
    Join a base URL and a possibly relative URL to form an absolute
    interpretation of the latter.

Edycja: nie zauważyłem wcześniej, ale urllib.basejoin wydaje się mapować bezpośrednio do urlparse.urljoin, co czyni go preferowanym.


9

Używając furla pip install furlbędzie to:

 furl.furl('/media/path/').add(path='js/foo.js')

1
Jeśli chcesz, aby wynik był ciągiem, możesz dodać .urlna końcu:furl.furl('/media/path/').add(path='js/foo.js').url
Eyal Levin

furl działa lepiej w dołączaniu URL-a niż urlparse.urljoin w pythonie 2 atleast (y)
Ciasto piekarz

Lepiej to zrobić, furl('/media/path/').add(path=furl('/js/foo.js').path).urlbo furl('/media/path/').add(path='/js/foo.js').urljest/media/path//js/foo.js
bartolo-otrit

5

Wiem, że to trochę więcej, niż prosił OP, jednak miałem elementy do następującego adresu URL i szukałem prostego sposobu, aby do nich dołączyć:

>>> url = 'https://api.foo.com/orders/bartag?spamStatus=awaiting_spam&page=1&pageSize=250'

Rozglądając się:

>>> split = urlparse.urlsplit(url)
>>> split
SplitResult(scheme='https', netloc='api.foo.com', path='/orders/bartag', query='spamStatus=awaiting_spam&page=1&pageSize=250', fragment='')
>>> type(split)
<class 'urlparse.SplitResult'>
>>> dir(split)
['__add__', '__class__', '__contains__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__getstate__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_asdict', '_fields', '_make', '_replace', 'count', 'fragment', 'geturl', 'hostname', 'index', 'netloc', 'password', 'path', 'port', 'query', 'scheme', 'username']
>>> split[0]
'https'
>>> split = (split[:])
>>> type(split)
<type 'tuple'>

Więc oprócz ścieżki łączenia, na którą już udzielono odpowiedzi w innych odpowiedziach, Aby uzyskać to, czego szukałem, wykonałem następujące czynności:

>>> split
('https', 'api.foo.com', '/orders/bartag', 'spamStatus=awaiting_spam&page=1&pageSize=250', '')
>>> unsplit = urlparse.urlunsplit(split)
>>> unsplit
'https://api.foo.com/orders/bartag?spamStatus=awaiting_spam&page=1&pageSize=250'

Zgodnie z dokumentacją zajmuje DOKŁADNIE 5-częściową krotkę.

Z następującym formatem krotki:

schemat 0 Specyfikator schematu adresu URL pusty ciąg

netloc 1 Element lokalizacji sieciowej pusty ciąg

ścieżka 2 Ścieżka hierarchiczna pusty ciąg

zapytanie 3 Zapytanie o pusty ciąg komponentu

fragment 4 Identyfikator fragmentu pusty ciąg


5

Rune Kaagaard dostarczył świetne i kompaktowe rozwiązanie, które działało dla mnie, rozszerzyłem je trochę:

def urljoin(*args):
    trailing_slash = '/' if args[-1].endswith('/') else ''
    return "/".join(map(lambda x: str(x).strip('/'), args)) + trailing_slash

Pozwala to na łączenie wszystkich argumentów bez względu na końcowe i końcowe ukośniki, przy jednoczesnym zachowaniu ostatniego ukośnika, jeśli jest obecny.


Możesz sprawić, że ta ostatnia linia będzie trochę krótsza i bardziej Pythonic, używając rozumienia list, na przykład:return "/".join([str(x).strip("/") for x in args]) + trailing_slash
Dan Coates

3

Aby poprawić nieco reakcję Alexa Martelliego, poniższe elementy nie tylko usuwają dodatkowe ukośniki, ale także zachowują końcowe (końcowe) ukośniki, które czasami mogą być przydatne:

>>> items = ["http://www.website.com", "/api", "v2/"]
>>> url = "/".join([(u.strip("/") if index + 1 < len(items) else u.lstrip("/")) for index, u in enumerate(items)])
>>> print(url)
http://www.website.com/api/v2/

Nie jest jednak tak łatwy do odczytania i nie usunie wielu dodatkowych końcowych ukośników.


3

Wszystkie powyższe rozwiązania nie podobały mi się, więc wymyśliłem własne. Ta wersja zapewnia łączenie części za pomocą pojedynczego ukośnika i pozostawia same początkowe i końcowe ukośniki. Nie pip install, nie ma urllib.parse.urljoindziwności.

In [1]: from functools import reduce

In [2]: def join_slash(a, b):
   ...:     return a.rstrip('/') + '/' + b.lstrip('/')
   ...:

In [3]: def urljoin(*args):
   ...:     return reduce(join_slash, args) if args else ''
   ...:

In [4]: parts = ['https://foo-bar.quux.net', '/foo', 'bar', '/bat/', '/quux/']

In [5]: urljoin(*parts)
Out[5]: 'https://foo-bar.quux.net/foo/bar/bat/quux/'

In [6]: urljoin('https://quux.com/', '/path', 'to/file///', '//here/')
Out[6]: 'https://quux.com/path/to/file/here/'

In [7]: urljoin()
Out[7]: ''

In [8]: urljoin('//','beware', 'of/this///')
Out[8]: '/beware/of/this///'

In [9]: urljoin('/leading', 'and/', '/trailing/', 'slash/')
Out[9]: '/leading/and/trailing/slash/'

0

Używanie furl i regex (Python 3)

>>> import re
>>> import furl
>>> p = re.compile(r'(\/)+')
>>> url = furl.furl('/media/path').add(path='/js/foo.js').url
>>> url
'/media/path/js/foo.js'
>>> p.sub(r"\1", url)
'/media/path/js/foo.js'
>>> url = furl.furl('/media/path').add(path='js/foo.js').url
>>> url
'/media/path/js/foo.js'
>>> p.sub(r"\1", url)
'/media/path/js/foo.js'
>>> url = furl.furl('/media/path/').add(path='js/foo.js').url
>>> url
'/media/path/js/foo.js'
>>> p.sub(r"\1", url)
'/media/path/js/foo.js'
>>> url = furl.furl('/media///path///').add(path='//js///foo.js').url
>>> url
'/media///path/////js///foo.js'
>>> p.sub(r"\1", url)
'/media/path/js/foo.js'
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.