Как соединить компоненты пути при создании URL-адреса в Python

104

Например, я хочу присоединить префиксный путь к путям ресурсов, таким как /js/foo.js.

Я хочу, чтобы полученный путь был относительно корня сервера. В приведенном выше примере, если бы префикс был «media», я бы хотел, чтобы результат был /media/js/foo.js.

os.path.join делает это очень хорошо, но способ соединения путей зависит от ОС. В этом случае я знаю, что нацелен на Интернет, а не на локальную файловую систему.

Есть ли лучшая альтернатива, когда вы работаете с путями, которые, как вы знаете, будут использоваться в URL-адресах? Будет ли os.path.join работать достаточно хорошо? Должен ли я просто свернуть свою?

Amjoconn
источник
1
os.path.joinне будет работать. Но простое присоединение по /символу должно работать во всех случаях - /это стандартный разделитель путей в HTTP согласно спецификации.
intgr

Ответы:

61

Поскольку из комментариев, опубликованных OP, кажется, что он не хочет сохранять «абсолютные URL-адреса» в соединении (что является одной из ключевых задач urlparse.urljoin;-), я бы рекомендовал избегать этого. os.path.joinтоже было бы плохо по той же причине.

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

Алекс Мартелли
источник
1
Спасибо. Я не возражал так сильно требовать, чтобы ведущий '/' во второй части не мог быть там, но требование завершающего '/' в первой части заставляло меня чувствовать, что в этом варианте использования urljoin ничего не делает для меня. Я хотел бы хотя бы присоединиться ("/ media", "js / foo.js") и присоединиться ("/ media /", "js / foo.js"), чтобы работать. Спасибо за правильный ответ: катайся самостоятельно.
amjoconn
Я надеялся, что что-то поможет мне в раздевании и присоединении.
статуя Майка
Нет, это не будет работать с окнами, куда os.path.join('http://media.com', 'content')мы вернемся http://media.com\content.
SeF
156

Вы можете использовать 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
47

Как вы говорите, 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-адресов, которые доступны и будут работать на любой платформе .

Изменить: предложение @ Pete - хорошее, вы можете псевдоним импорта для повышения читаемости

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'
GP89
источник
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

Возможно, вы ищете функцию 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, что делает последнее предпочтительным.

mwcz
источник
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

Я знаю, что это немного больше, чем просил OP, однако у меня были фрагменты по следующему 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 Идентификатор фрагмента пустая строка

Jmunsch
источник
5

Rune Kaagaard предоставил отличное и компактное решение, которое сработало для меня, я немного расширил его:

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

Это позволяет объединять все аргументы независимо от конечных и конечных косых черт, сохраняя при этом последнюю косую черту, если она есть.

Futuere
источник
Вы можете сделать эту последнюю строку немного короче и более питонической, используя понимание списка, например:return "/".join([str(x).strip("/") for x in args]) + trailing_slash
Дэн Коутс
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/'
cbare
источник
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'
Гийом Циско
источник