Python'da bir URL oluştururken bir yolun bileşenleri nasıl birleştirilir


103

Örneğin, /js/foo.js gibi kaynak yollarına bir önek yolu eklemek istiyorum.

Ortaya çıkan yolun sunucunun köküne göre olmasını istiyorum. Yukarıdaki örnekte önek "media" ise, sonucun /media/js/foo.js olmasını isterdim.

os.path.join bunu gerçekten iyi yapıyor, ancak yolları nasıl birleştirdiği işletim sistemine bağlıdır. Bu durumda yerel dosya sistemini değil web'i hedeflediğimi biliyorum.

URL'lerde kullanılacağını bildiğiniz yollarla çalışırken en iyi alternatif var mı? Os.path.join yeterince iyi çalışacak mı? Kendi başıma mı atmalıyım?


1
os.path.joinçalışmayacak. Ancak /karakterle birleştirme her durumda işe yaramalıdır - /spesifikasyona göre HTTP'deki standart yol ayırıcıdır.
intgr

Yanıtlar:


60

O OP yayınlanan yorumlar, öyle görünüyor ki, bu yana gelmez anahtar işlerden biri olan (birleştirme "Mutlak URL'ler" korumak istiyorsanız urlparse.urljoin;-), bunu kaçınarak öneriyoruz. os.path.joinaynı sebepten dolayı da kötü olur.

Bu yüzden, şöyle bir şey kullanırım '/'.join(s.strip('/') for s in pieces)( /baştaki parça da göz ardı edilmesi gerekiyorsa - baştaki parça özel kasalıysa, bu da elbette uygulanabilir ;-).


1
Teşekkürler. İkinci bölümdeki baştaki '/' karakterinin orada olamayacağını pek umursamadım, ancak ilk bölümde '/' karakterini kullanmak bana bu kullanım durumunda urljoin hiçbir şey yapmıyormuş gibi hissettiriyor benim için. Çalışmak için en azından ("/ media", "js / foo.js") katılmak ve katılmak ("/ media /", "js / foo.js") istiyorum. Doğru cevap gibi görünen şey için teşekkürler: kendi cevabınızı yuvarlayın.
amjoconn

Bir şeyin benim için '/' soyunup katılmasını umuyordum.
Statueofmike

Hayır, bu os.path.join('http://media.com', 'content')wourd'un döndüğü pencerelerde işe yaramayacak http://media.com\content.
SEF

154

Şunları kullanabilirsiniz urllib.parse.urljoin:

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

Ancak dikkat edin :

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

Nedeni farklı sonuçlar elde /js/foo.jsve js/foo.jseski zaten web sitesi kökünde başlar anlamına hangi bir eğik çizgi ile başlar çünkü.

Python 2'de yapmanız gerekenler

from urlparse import urljoin

Bu yüzden /js/foo.js üzerinde baştaki "/" dizisini aldım, ancak os.path.join için de durum böyle olacak gibi görünüyor. Medyadan sonra eğik çizgi istemek, işin çoğunu zaten kendim yapmak zorunda olduğum anlamına geliyor.
amjoconn

Özellikle, önekin / ile bitmesi gerektiğine ve hedef yolun başlayamayacağına sahip olduğumda / sadece bitiştirebilirim. Bu durumda urljoin'in gerçekten yardımcı olup olmadığından emin değilim?
amjoconn

3
@MedhatGayed urljoin"/" i kaldıran bana açık değil . Eğer onu urlparse.urljoin('/media/', '/js/foo.js')döndüren değerle çağırırsam '/js/foo.js' olur. "/" Kopyasını değil, tüm medyayı kaldırdı. Aslında urlparse.urljoin('/media//', 'js/foo.js')aslında '/media//js/foo.js' döndürür, bu nedenle yinelenen öğeler kaldırılmaz.
amjoconn

8
urljoin, bitmeyen bir bileşene katılıyorsanız / ilk bileşeni tabanına ayırıyorsa ve ardından diğer bağımsız değişkenlere katılıyorsa garip davranışlara sahiptir. Beklediğim gibi değil.
Pete

7
Maalesef urljoinURL'lere katılmak için değil. HTML belgelerinde vb. Bulunan göreli URL'leri çözmek içindir.
OrangeDog

46

Sizin de söylediğiniz gibi, os.path.joinmevcut işletim sistemine göre yolları birleştirir. posixpathposix sistemlerinde ad alanı altında kullanılan temel modüldür os.path:

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

Dolayısıyla, posixpath.joinbunun yerine mevcut olan ve herhangi bir platformda çalışacak olan url'leri içe aktarabilir ve kullanabilirsiniz .

Düzenleme: @ Pete'in önerisi iyi bir öneridir, daha fazla okunabilirlik için içe aktarmaya takma ad verebilirsiniz

from posixpath import join as urljoin

Düzenleme: Bence bu daha açık hale getirildi veya en azından kaynağına bakarsanız anlamama yardımcı oldu os.py(buradaki kod Python 2.7.11'den, artı bazı bitleri kestim). os.pyAd alanında hangi yol modülünün kullanılacağını seçen koşullu içe aktarmalar vardır os.path. Tüm yatan modülleri ( posixpath, ntpath, os2emxpath, riscospath) ithal edilebileceğini os.pyolarak ad verilmiş, path, orada ve tüm sistemlerde kullanılmak için vardır. mevcut işletim sistemine bağlı olarak çalışma zamanında os.pyad alanında kullanılacak modüllerden birini os.pathseçmektir.

# 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 urljoinonu okunması kolay bir şeye güzel bir şekilde takma ad.
Pete

29

Bu işi güzel yapıyor:

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

Basejoin işlevi urllib paketinin aradığınız şey olabilir.

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

Düzenleme: Daha önce fark etmedim, ancak urllib.basejoin doğrudan urlparse.urljoin ile eşleşiyor ve ikincisini tercih ediyor.


9

Furl kullanarak pip install furl:

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

1
Sonucun bir dizi olmasını istiyorsanız .url, sonuna ekleyebilirsiniz :furl.furl('/media/path/').add(path='js/foo.js').url
Eyal Levin

furl, python 2 atleast (y) 'de urlparse.urljoin ile karşılaştırıldığında URL birleştirmede daha iyi çalışıyor
Ciasto piekarz

furl('/media/path/').add(path=furl('/js/foo.js').path).urlfurl('/media/path/').add(path='/js/foo.js').url/media/path//js/foo.js
Yapması

5

Bunun OP'nin istediğinden biraz daha fazla olduğunu biliyorum, Ancak aşağıdaki url'nin parçaları bende vardı ve onlara katılmak için basit bir yol arıyordum:

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

Biraz etrafa bakmak:

>>> 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'>

Yani diğer cevaplarda zaten cevaplanmış olan yola katılmaya ek olarak , Aradığımı elde etmek için şunları yaptım:

>>> 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'

Belgelere göre TAM OLARAK 5 parçalı bir demet alır.

Aşağıdaki kayıt formatı ile:

şema 0 URL şeması belirleyici boş dize

netloc 1 Ağ konumu bölümü boş dize

yol 2 Hiyerarşik yol boş dize

sorgu 3 Sorgu bileşeni boş dize

parça 4 Parça tanımlayıcı boş dize


5

Rune Kaagaard benim için işe yarayan harika ve kompakt bir çözüm sağladı, biraz genişlettim:

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

Bu, varsa son eğik çizgiyi korurken, tüm argümanların sondaki ve sondaki eğik çizgilere bakılmaksızın birleştirilmesine izin verir.


Aşağıdaki gibi bir liste anlayışı kullanarak bu son satırı biraz daha kısaltabilir ve daha Pythonic yapabilirsiniz:return "/".join([str(x).strip("/") for x in args]) + trailing_slash
Dan Coates

3

Alex Martelli'nin yanıtını biraz iyileştirmek için, aşağıdakiler yalnızca fazladan eğik çizgileri temizlemekle kalmaz, aynı zamanda sondaki (sondaki) eğik çizgileri de korur, bu da bazen yararlı olabilir:

>>> 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/

Yine de okumak o kadar kolay değil ve sondaki birden fazla eğik çizgiyi temizlemiyor.


3

Yukarıdaki çözümlerin hepsinden hoşlanmayacak şeyler buldum, bu yüzden kendi çözümümü buldum. Bu sürüm, parçaların tek bir eğik çizgiyle birleştirilmesini sağlar ve baştaki ve sondaki eğik çizgileri tek başına bırakır. Hayır pip install, urllib.parse.urljointuhaflık yok .

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

Furl ve regex kullanma (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'
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.