在Python中构建URL时如何加入路径的组件

时间:2009-11-24 22:06:08

标签: python url

例如,我想加入资源路径的前缀路径,例如/js/foo.js.。

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

os.path.join做得非常好,但它如何加入路径依赖于操作系统。在这种情况下,我知道我的目标是网络,而不是本地文件系统。

当您使用您知道将在URL中使用的路径时,是否有最佳选择? os.path.join会运行得好吗?我应该自己动手吗?

13 个答案:

答案 0 :(得分:122)

Python2

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

但要注意

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

以及

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

Python3

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

您从/js/foo.jsjs/foo.js获得不同结果的原因是因为前者以斜杠开头,表示它已经从网站根目录开始。

答案 1 :(得分:44)

因为,根据OP发布的评论,似乎想要在联接中保留“绝对URL”(这是urlparse.urljoin的关键作业之一; - ),我建议避免这样做。出于同样的原因,os.path.join也会很糟糕。

所以,我会使用'/'.join(s.strip('/') for s in pieces)之类的东西(如果领先的/也必须被忽略 - 如果领先的作品必须是特殊的,那当然也是可行的;-)。

答案 2 :(得分:40)

就像你说的那样,os.path.join根据当前操作系统加入路径。 posixpath是在命名空间os.path下的posix系统上使用的基础模块:

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

因此,您只需导入并使用posixpath.join代替网址,该网址可用,并可在任何平台上使用。

编辑: @ Pete的建议很好,您可以为导入设置别名以提高可读性

from posixpath import join as urljoin

编辑:如果你查看os.py的来源,我认为这更清楚,或者至少帮助我理解(这里的代码来自Python 2.7.11,加上我已经削减了一些比特)。 os.py中的条件导入选择了在命名空间os.path中使用的路径模块。可以导入posixpath,别名为ntpath的所有基础模块(os2emxpathriscospathos.pypath)都在那里并存在于所有系统上使用。 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'

答案 3 :(得分:23)

这很好地完成了工作:

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

答案 4 :(得分:8)

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,使后者成为首选。

答案 5 :(得分:6)

使用furl,pip install furl它将是:

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

答案 6 :(得分:4)

我知道这比OP要求的要多一些,但是我有以下网址的部分,并且正在寻找一种简单的方式来加入它们:

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

根据documentation,它只需要5个元组。

使用以下元组格式:

  

scheme 0 URL scheme specifier empty string

     

netloc 1网络位置部分空字符串

     

path 2 Hierarchical path empty string

     

查询3查询组件空字符串

     

片段4片段标识符空字符串

答案 7 :(得分:2)

为了稍微改进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/

虽然它并不容易阅读,也不会清理多个额外的尾部斜杠。

答案 8 :(得分:1)

我发现上述所有解决方案都不受欢迎,因此我提出了自己的解决方案。此版本可确保零件以单个斜杠连接,而单独保留前导斜杠和尾随斜杠。没有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/'

答案 9 :(得分:0)

Rune Kaagaard为我提供了一个出色而紧凑的解决方案,我对此进行了扩展:

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

这使得所有参数都可以连接在一起,而不管尾部和结尾的斜杠如何,同时保留最后一个斜杠(如果存在)。

答案 10 :(得分:0)

使用furlregex(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'

答案 11 :(得分:0)

这个怎么样: 它是有点高效且有点简单。 只需要加入url路径的'2'部分:

def UrlJoin(a , b):
    a, b = a.strip(), b.strip()
    a = a if a.endswith('/') else a + '/'
    b = b if not b.startswith('/') else b[1:]
    return a + b

OR:更传统,但如果只加入路径的 2 个 url 部分,则效率不高。

def UrlJoin(*parts):
    return '/'.join([p.strip().strip('/') for p in parts])

测试用例:

>>> UrlJoin('https://example.com/', '/TestURL_1')
'https://example.com/TestURL_1'

>>> UrlJoin('https://example.com', 'TestURL_2')
'https://example.com/TestURL_2'

注意:我可能会在这里分心,但至少这是一个很好的做法,并且可能更具可读性。

答案 12 :(得分:0)

一个班轮:

from functools import reduce
reduce(lambda x,y: '{}/{}'.format(x,y), parts) 

其中部分是例如 ['https://api.somecompany.com/v1', 'weather', 'rain']