วิธีรวมส่วนประกอบของเส้นทางเมื่อคุณสร้าง URL ใน Python


105

ตัวอย่างเช่นฉันต้องการเข้าร่วมเส้นทางคำนำหน้าไปยังเส้นทางทรัพยากรเช่น /js/foo.js

ฉันต้องการให้พา ธ ผลลัพธ์สัมพันธ์กับรูทของเซิร์ฟเวอร์ ในตัวอย่างด้านบนหากคำนำหน้าคือ "สื่อ" ฉันต้องการให้ผลลัพธ์เป็น /media/js/foo.js

os.path.join ทำได้ดีมาก แต่วิธีการรวมพา ธ นั้นขึ้นอยู่กับระบบปฏิบัติการ ในกรณีนี้ฉันรู้ว่าฉันกำหนดเป้าหมายไปที่เว็บไม่ใช่ระบบไฟล์ในเครื่อง

มีทางเลือกอื่นที่ดีที่สุดเมื่อคุณทำงานกับเส้นทางที่คุณทราบว่าจะถูกใช้ใน URL หรือไม่ os.path.join จะทำงานได้ดีเพียงพอหรือไม่ ฉันควรจะม้วนของตัวเองหรือไม่?


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

ฉันหวังว่าจะมีบางอย่างทำให้ '/' ลอกและเข้าร่วมสำหรับฉัน
statueofmike

Nope นี้ไม่ได้ไปทำงานบน Windows ซึ่งผลตอบแทนos.path.join('http://media.com', 'content') wourd http://media.com\content
SeF

160

คุณสามารถใช้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

48

เช่นเดียวกับที่คุณพูด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.pathnamespace ทุกโมดูลพื้นฐาน ( posixpath, ntpath, os2emxpath, riscospath) ที่อาจจะนำเข้าในos.py, aliased เป็น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ใช้นามแฝงเป็นสิ่งที่อ่านง่าย
Pete

32

สิ่งนี้ทำงานได้ดี:

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 โดยตรงทำให้รายการหลังเป็นที่ต้องการ


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

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 แฟรกเมนต์ระบุสตริงว่างเปล่า


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และ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'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.