Як з'єднати компоненти шляху, коли ви створюєте URL-адресу в Python


103

Наприклад, я хочу приєднати префіксний шлях до шляхів ресурсів, таких як /js/foo.js.

Я хочу, щоб отриманий шлях був відносно кореня сервера. У наведеному вище прикладі, якщо префікс був "медіа", я б хотів, щоб результат був /media/js/foo.js.

os.path.join робить це дуже добре, але те, як він приєднується до шляхів, залежить від ОС. У цьому випадку я знаю, що я націлений на Інтернет, а не на локальну файлову систему.

Чи є найкраща альтернатива, коли ви працюєте зі шляхами, які ви знаєте, будуть використовуватися в URL-адресах? Чи буде os.path.join працювати досить добре? Потрібно просто закатати свою?


1
os.path.joinне вийде. Але просто приєднатися до /персонажа має працювати у всіх випадках - /це стандартний роздільник шляхів у HTTP відповідно до специфікації.
intgr

Відповіді:


60

Оскільки, з коментарів, опублікованих ОП, схоже, він не хоче зберігати "абсолютні URL-адреси" в об'єднанні (що є однією з ключових задач urlparse.urljoin;-), я рекомендую цього не уникати. os.path.joinБуло б також погано, саме з тієї ж причини.

Отже, я б використав щось на кшталт '/'.join(s.strip('/') for s in pieces)(якщо ведучого /теж потрібно ігнорувати - якщо головний твір повинен бути спеціально оброблений, це теж можливо, звичайно ;-)


1
Дякую. Я не заперечував проти того, щоб вимагати, щоб ведучий "/" у другій частині не міг бути там, але вимагаючи зворотного "/" у першій частині змушує мене відчувати, ніби в цьому випадку використання urljoin нічого не робив для мене. Я хотів би хоча б приєднатися ("/ media", "js / foo.js") і приєднатися ("/ media /", "js / foo.js") до роботи. Дякуємо за те, що здається правильною відповіддю.
amjoconn

Я сподівався, що щось зробить зачистку та приєднання для мене.
statueofmike

Ні, це не буде працювати на вікнах, куди os.path.join('http://media.com', 'content')повернеться гарбуз http://media.com\content.
SeF

154

Ви можете використовувати urllib.parse.urljoin:

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

Але будьте обережні :

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

Причина ви отримаєте різні результати , /js/foo.jsі js/foo.jsтому , що колишні починається з косими рисами , яка означає , що він вже починає в корені сайту.

На Python 2 ви повинні зробити

from urlparse import urljoin

Тож у мене знімається смуга провідного "/" на /js/foo.js, але, здається, це було б і з os.path.join. Вимагати косою рисою після медіа означає, що я все-таки повинен працювати більшу частину роботи.
amjoconn

Зокрема, як тільки у мене з’явиться, що префікс повинен закінчуватися в / і що цільовий шлях не може починатися в /, я б також міг просто об'єднати. У цьому випадку я не впевнений, чи справді допомагає urljoin?
amjoconn

3
@MedhatGayed Мені не ясно, що urljoinколи-небудь видаляє '/'. Якщо я називаю це urlparse.urljoin('/media/', '/js/foo.js')поверненим значенням, це '/js/foo.js'. У ньому видалено всі носії інформації, а не дублікат '/'. Насправді urlparse.urljoin('/media//', 'js/foo.js')фактично повертає '/media//js/foo.js', тому дублювання не видалено.
amjoconn

8
urljoin має дивну поведінку, якщо ви приєднуєтесь до компонентів, які не закінчуються / він знімає перший компонент до своєї бази, а потім приєднується до інших аргументів. Не те, що я очікував.
Піт

7
На жаль, urljoinце не для приєднання URL-адрес. Це для вирішення відносних URL-адрес, що знаходяться в HTML-документах тощо
OrangeDog

46

Як ви кажете, os.path.joinприєднується до контурів на основі поточного ос. posixpathє базовим модулем, який використовується в системах posix під простором імен os.path:

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

Таким чином, ви можете просто імпортувати та використовувати posixpath.joinнатомість URL-адреси, які доступні та працюватимуть на будь-якій платформі .

Редагувати: @ пропозиція Піта є хорошою, ви можете псевдонімом імпорту для підвищення читабельності

from posixpath import join as urljoin

Редагувати: Я думаю, що це стає зрозумілішим, або, принаймні, допомогло мені зрозуміти, якщо ви подивитеся на джерело os.py(код тут від Python 2.7.11, плюс я обрізав декілька біт). У цьому умовному імпорті os.pyвибирається, який модуль шляху використовувати в просторі імен os.path. Всі основні модулі ( posixpath, ntpath, os2emxpath, riscospath) , які можуть бути імпортовані в os.py, псевдонімами , як path, існують і існують , які будуть використовуватися на всіх системах. os.pyпросто підбирає один з модулів для використання в просторі імен os.pathпід час виконання роботи на основі поточної ОС.

# 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приємно псевдонімує його до чогось легкого для читання.
Піт

29

Це прекрасно виконує роботу:

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

The basejoin в пакеті urllib може бути тим, що ви шукаєте.

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

Редагувати: я раніше не помічав, але urllib.basejoin, здається, відображається безпосередньо на urlparse.urljoin, що робить останній кращим.


9

Використовуючи furl, pip install furlце буде:

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

1
Якщо ви хочете, щоб результат був рядком, ви можете додати .urlв кінці:furl.furl('/media/path/').add(path='js/foo.js').url
Еял Левін

furl краще працює в URL-адресі порівняно з urlparse.urljoin у python 2 atleast (y)
Ciasto piekarz

Краще робити, furl('/media/path/').add(path=furl('/js/foo.js').path).urlбо furl('/media/path/').add(path='/js/foo.js').urlце/media/path//js/foo.js
bartolo-otrit

5

Я знаю, що це трохи більше, ніж просив ОП, однак у мене були фрагменти до наступної URL-адреси, і я шукав простий спосіб приєднатися до них:

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

Дещо озираючись:

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

Тож крім шляху, який приєднався до нього, про який вже відповіли в інших відповідях, щоб отримати те, що я шукав, я зробив наступне:

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

Відповідно до документації вона займає ТОЧНО 5 частин кортеж.

З таким форматом кортежу:

схема 0 специфікатор схеми URL порожній рядок

netloc 1 Частина розташування мережі порожній рядок

шлях 2 Ієрархічний шлях порожній рядок

запит 3 Пустий рядок компонента запиту

фрагмент 4 Ідентифікатор фрагмента порожній рядок


5

Rune Kaagaard забезпечив чудове і компактне рішення, яке працювало на мене, я трохи розширив його:

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

Це дозволяє об'єднувати всі аргументи незалежно від останнього та закінчення косої риси, зберігаючи останню косу рису, якщо вона присутня.


Ви можете зробити цей останній рядок трохи коротшим і пітонічнішим, використовуючи розуміння списку, наприклад:return "/".join([str(x).strip("/") for x in args]) + trailing_slash
Dan Coates

3

Щоб дещо покращити реакцію Алекса Мартеллі, наступне не тільки очистить зайві косої риси, але й збереже останню косу рису, яка може бути корисною:

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

Це не так просто для читання, і не очистить декілька додаткових косих косих ринків.


3

Я знайшов речі, які не сподобались у всіх вищезазначених рішеннях, тому я придумав своє. Ця версія гарантує, що деталі з'єднані з однією косою рискою і залишає провідні та задні косої частини в самоті. Ні pip install, ніякої urllib.parse.urljoinдивності.

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 та 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'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.