Cách nối các thành phần của đường dẫn khi bạn đang xây dựng một URL bằng Python


103

Ví dụ: tôi muốn tham gia một đường dẫn tiền tố đến các đường dẫn tài nguyên như /js/foo.js.

Tôi muốn đường dẫn kết quả liên quan đến thư mục gốc của máy chủ. Trong ví dụ trên nếu tiền tố là "media", tôi muốn kết quả là /media/js/foo.js.

os.path.join làm điều này thực sự tốt, nhưng cách nó tham gia các đường dẫn là phụ thuộc vào hệ điều hành. Trong trường hợp này, tôi biết mình đang nhắm mục tiêu web, không phải hệ thống tệp cục bộ.

Có giải pháp thay thế tốt nhất khi bạn đang làm việc với các đường dẫn mà bạn biết sẽ được sử dụng trong URL không? Os.path.join có hoạt động đủ tốt không? Tôi có nên tự lăn bánh không?


1
os.path.joinsẽ không làm việc. Nhưng chỉ cần kết hợp bằng /ký tự sẽ hoạt động trong mọi trường hợp - /là dấu phân cách đường dẫn tiêu chuẩn trong HTTP theo đặc điểm kỹ thuật.
intgr

Câu trả lời:


60

Vì, từ các nhận xét mà OP đã đăng, có vẻ như anh ấy không muốn giữ "URL tuyệt đối" trong tham gia (đó là một trong những công việc quan trọng của urlparse.urljoin;-), tôi khuyên bạn nên tránh điều đó. os.path.joincũng sẽ tệ, vì lý do chính xác.

Vì vậy, tôi sẽ sử dụng một cái gì đó như '/'.join(s.strip('/') for s in pieces)(nếu phần đầu /cũng phải được bỏ qua - nếu phần đầu phải được viết chữ đặc biệt, điều đó tất nhiên cũng khả thi ;-).


1
Cảm ơn. Tôi không bận tâm nhiều đến việc yêu cầu không thể có '/' ở phần thứ hai, nhưng việc yêu cầu '/' ở phần đầu khiến tôi cảm thấy như thể trong trường hợp sử dụng này, urljoin không làm gì cả cho tôi. Tôi muốn ít nhất tham gia ("/ media", "js / foo.js") và tham gia ("/ media /", "js / foo.js") để hoạt động. Cảm ơn vì điều có vẻ là câu trả lời đúng: cuộn của riêng bạn.
amjoconn

Tôi hy vọng điều gì đó sẽ thực hiện việc tước bỏ và tham gia '/' cho tôi.
tạc tượng vào

Không, điều này sẽ không hoạt động trên windows, nơi os.path.join('http://media.com', 'content')wourd trở lại http://media.com\content.
SeF

154

Bạn có thể sử dụng urllib.parse.urljoin:

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

Nhưng hãy cẩn thận :

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

Lý do bạn nhận được các kết quả khác nhau /js/foo.jsjs/foo.jslà vì kết quả đầu tiên bắt đầu bằng dấu gạch chéo có nghĩa là nó đã bắt đầu ở gốc trang web.

Trên Python 2, bạn phải làm

from urlparse import urljoin

Vì vậy, tôi đã loại bỏ "/" ở đầu trên /js/foo.js, nhưng có vẻ như đó cũng sẽ là trường hợp với os.path.join. Yêu cầu dấu gạch chéo sau phương tiện truyền thông có nghĩa là tôi phải tự mình làm hầu hết công việc.
amjoconn

Cụ thể là một khi tôi có tiền tố phải kết thúc bằng / và đường dẫn đích không thể bắt đầu bằng / tôi cũng có thể chỉ nối. Trong trường hợp này, tôi không chắc liệu urljoin có thực sự hữu ích không?
amjoconn

3
@MedhatGayed Tôi không rõ là urljoinbao giờ xóa '/'. Nếu tôi gọi nó với urlparse.urljoin('/media/', '/js/foo.js')giá trị trả về là '/js/foo.js'. Nó đã loại bỏ tất cả các phương tiện, không phải bản sao '/'. Trên thực tế, urlparse.urljoin('/media//', 'js/foo.js')thực tế trả về '/media//js/foo.js', vì vậy không có bản sao bị xóa.
amjoconn

8
urljoin có hành vi kỳ lạ nếu bạn đang tham gia một thành phần không kết thúc bằng / nó tách thành phần đầu tiên thành cơ sở của nó và sau đó nối các args khác. Không phải những gì tôi mong đợi.
Pete

7
Thật không may urljoinlà không phải để nối URL Nó nó cho việc giải quyết các liên kết URL như được tìm thấy trong các tài liệu HTML, vv
OrangeDog

46

Giống như bạn nói, os.path.jointham gia các đường dẫn dựa trên hệ điều hành hiện tại. posixpathlà mô-đun cơ bản được sử dụng trên hệ thống posix trong không gian tên os.path:

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

Vì vậy, bạn chỉ có thể nhập và sử dụng posixpath.jointhay thế cho url, url có sẵn và sẽ hoạt động trên mọi nền tảng .

Chỉnh sửa: Đề xuất của @ Pete là một gợi ý tốt, bạn có thể đặt bí danh nhập khẩu để tăng khả năng đọc

from posixpath import join as urljoin

Chỉnh sửa: Tôi nghĩ điều này được làm rõ ràng hơn, hoặc ít nhất là giúp tôi hiểu, nếu bạn xem xét nguồn của os.py(mã ở đây là từ Python 2.7.11, cộng với tôi đã cắt bớt một số bit). Có các phép nhập có điều kiện trong os.pyđó chọn mô-đun đường dẫn sẽ sử dụng trong không gian tên os.path. Tất cả các module cơ bản ( posixpath, ntpath, os2emxpath, riscospath) có thể được nhập khẩu os.py, bí danh như path, đang có và tồn tại để được sử dụng trên mọi hệ thống. os.pychỉ là chọn một trong các mô-đun để sử dụng trong không gian tên os.pathtại thời điểm chạy dựa trên hệ điều hành hiện tại.

# 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 urljoinbí danh nó thành một cái gì đó dễ đọc.
Pete

29

Điều này thực hiện công việc tốt:

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

Hàm basejoin trong gói urllib có thể là thứ bạn đang tìm kiếm.

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

Chỉnh sửa: Tôi không nhận thấy trước đây, nhưng urllib.basejoin dường như ánh xạ trực tiếp đến urlparse.urljoin, khiến cái sau được ưu tiên hơn.


9

Sử dụng furl, pip install furlnó sẽ là:

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

1
Nếu bạn muốn kết quả là một chuỗi bạn có thể thêm .urlvào cuối file:furl.furl('/media/path/').add(path='js/foo.js').url
Eyal Levin

công trình furl tốt hơn trong việc gia nhập URL so với urlparse.urljoin trong python 2 ít nhất (y)
Ciasto piekarz

Nó tốt hơn để làm furl('/media/path/').add(path=furl('/js/foo.js').path).urlfurl('/media/path/').add(path='/js/foo.js').url/media/path//js/foo.js
Bartolo-otrit

5

Tôi biết điều này nhiều hơn một chút so với OP yêu cầu, Tuy nhiên, tôi đã có các phần của url sau và đang tìm kiếm một cách đơn giản để kết hợp chúng:

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

Làm một số quan sát xung quanh:

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

Vì vậy, ngoài việc tham gia đường dẫn đã được trả lời trong các câu trả lời khác, Để có được những gì tôi đang tìm kiếm, tôi đã làm như sau:

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

Theo tài liệu, nó cần CHÍNH XÁC một bộ 5 phần.

Với định dạng tuple sau:

lược đồ 0 công cụ chỉ định lược đồ URL chuỗi trống

netloc 1 Chuỗi trống phần vị trí mạng

đường dẫn 2 Đường dẫn phân cấp chuỗi trống

truy vấn 3 Thành phần truy vấn chuỗi trống

phân mảnh 4 Chuỗi trống định danh phân mảnh


5

Rune Kaagaard đã cung cấp một giải pháp tuyệt vời và nhỏ gọn phù hợp với tôi, tôi đã mở rộng thêm một chút về nó:

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

Điều này cho phép tất cả các đối số được tham gia bất kể dấu gạch chéo ở cuối và dấu gạch chéo cuối cùng trong khi vẫn giữ nguyên dấu gạch chéo cuối cùng nếu có.


Bạn có thể làm cho dòng cuối cùng ngắn hơn một chút và nhiều Pythonic hơn bằng cách sử dụng khả năng hiểu danh sách, như:return "/".join([str(x).strip("/") for x in args]) + trailing_slash
Dan Coates

3

Để cải thiện một chút so với phản ứng của Alex Martelli, phần sau sẽ không chỉ xóa các dấu gạch chéo thừa mà còn giữ lại các dấu gạch chéo (kết thúc), đôi khi có thể hữu ích:

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

Tuy nhiên, nó không dễ đọc và sẽ không xóa nhiều dấu gạch chéo ở cuối.


3

Tôi thấy có những điều không thích ở tất cả các giải pháp trên, vì vậy tôi đã đưa ra giải pháp của riêng mình. Phiên bản này đảm bảo các bộ phận được nối bằng một dấu gạch chéo và để lại các dấu gạch chéo đầu và cuối. Khôngpip install , không có gì urllib.parse.urljoinkỳ lạ.

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

Sử dụng 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'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.