在Python中构造URL时如何联接路径的组成部分


103

例如,我想将前缀路径连接到资源路径(例如/js/foo.js)。

我希望结果路径相对于服务器的根目录。在上面的示例中,如果前缀为“ media”,则我希望结果为/media/js/foo.js。

os.path.join确实做得很好,但是它如何连接路径取决于OS。在这种情况下,我知道我的目标是网络,而不是本地文件系统。

当您使用已知将在URL中使用的路径时,是否有最佳选择?os.path.join是否可以正常工作?我应该自己滚吗?


1
os.path.join不管用。但是/在所有情况下,简单地通过字符连接就应该起作用- /根据规范,这是HTTP中的标准路径分隔符。
intgr

Answers:


60

从OP发布的评论看来,由于他似乎不想保留联接中的“绝对URL”(这是;-的关键工作之一)urlparse.urljoin,因此我建议避免这种情况。 os.path.join出于完全相同的原因也会很糟糕。

因此,我将使用类似的命令'/'.join(s.strip('/') for s in pieces)(如果/必须也忽略引导符-如果引导件必须是特殊情况,那当然也是可行的;-)。


1
谢谢。我不太介意要求第二部分的前导'/'不能在那里,但是要求第一部分的末尾'/'使我感觉在此用例中urljoin没做任何事情为了我。我希望至少join(“ / media”,“ js / foo.js”)和join(“ / media /”,“ js / foo.js”)可以工作。感谢您提供的正确答案:自己动手做。
amjoconn

我希望可以对我进行'/'剥离和加入。
Statueofmike

不,这不会在Windows上正常工作,因为os.path.join('http://media.com', 'content')Windows会返回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.jsjs/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 2014年

8
如果您要加入一个不以/结尾的组件,则urljoin会产生怪异的行为。不是我所期望的。
皮特2015年

7
不幸的urljoin是不是为了加入URL。它用于解析在HTML文档等中找到的相对
URL。– OrangeDog

46

就像您说的那样,os.path.join基于当前os连接路径。posixpath是在posix系统上命名空间下使用的基础模块os.path

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

因此,您可以仅导入posixpath.joinURL并将其用作URL,URL可用并且可以在任何平台上使用

编辑: @Pete的建议是一个好建议,您可以为导入添加别名以提高可读性

from posixpath import join as urljoin

编辑:如果您查看的源os.py代码,我认为这会变得更清楚,或者至少帮助我理解了(此处的代码来自Python 2.7.11,此外我还做了一些修整)。其中有条件导入,os.py可以选择要在namespace中使用哪个路径模块os.path。所有底层模块(posixpathntpathos2emxpathriscospath),其可以在进口os.py,别名为path,在那里,存在要在所有系统中使用。os.pyos.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很好地将其别名为易于阅读的名称。
皮特2015年

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

urllib程序包中的basejoin函数可能正是您想要的。

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
Eyal Levin

卷起的作品更好地相比,在Python 2 ATLEAST(Y)urlparse.urljoin加盟网址
piekarz Ciasto

这是更好地做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
巴托罗-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片段标识符为空字符串


5

符文·卡加德(Rune Kaagaard)为我提供了一个出色而紧凑的解决方案,我对此进行了一些扩展:

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

这样,无论尾随斜杠和尾部是什么斜杠,所有参数都可以连接在一起,同时保留最后一个斜杠(如果存在)。


您可以通过使用列表理解使最后一行短一些,变得更Pythonic,例如:return "/".join([str(x).strip("/") for x in args]) + trailing_slash
Dan Coates

3

为了稍微改善Alex Martelli的响应,以下内容将不仅清理多余的斜杠,而且保留尾随的(结束)斜杠,这有时可能有用:

>>> 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正则表达式(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'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.