はじめに
Python で Web スクレイピングや REST API を利用するコードを書いていると、「ベースとなる URL(ドメイン)」 と 「相対パス」 をくっつけて、アクセス先の絶対 URL を作りたい場面が頻繁にあります。
base = "https://example.com/"
path = "api/v1/users"
# これらをくっつけたいこの時、単純に文字列の足し算(+ 演算子)で結合していませんか? 文字列結合は一見簡単そうに見えますが、「スラッシュ(/)が重複してしまった」 あるいは 「足りなくて無効な URL になってしまった」 というバグの温床になりがちです。
Python には、この「スラッシュ問題」を賢く解決してくれる標準ライブラリ urllib.parse.urljoin が用意されています。 本記事では、urljoin の基本的な使い方と、開発者が知っておくべき挙動の注意点(スラッシュの罠)について解説します。
urljoinを使った正しい URL 結合方法- 文字列結合(
+)と比較したメリット - 【重要】 ベース URL の末尾スラッシュ有無による挙動の違い
urljoin の基本的な使い方
urljoin は、Python の標準ライブラリである urllib.parse モジュールに含まれています。 使い方は非常にシンプルで、「ベースとなる URL」 と 「くっつけたい URL(相対パス)」 を引数に渡すだけです。
基本構文
from urllib.parse import urljoin
urljoin(base, url)- 第1引数(base): 基底となる URL(例:
https://example.com/) - 第2引数(url): 結合したい相対パスなど(例:
next/page)
実装例
実際に、ブログのトップページ URL とカテゴリーのパスを結合してみましょう。
from urllib.parse import urljoin
# ベースとなる URL
base_url = 'https://friendsnow.hatenablog.com/'
# 結合したい相対パス
relative_url = 'archive/category/プログラミング-Python'
# 結合を実行
full_url = urljoin(base_url, relative_url)
print(full_url)▼ 実行結果
https://friendsnow.hatenablog.com/archive/category/プログラミング-Pythonこのように、ベース URL と相対パスがきれいに結合され、正しい絶対 URL が生成されました。
【重要】スラッシュ(/)の有無による挙動の違い
urljoin を使う上で最も注意が必要なのが、「Base URL の末尾」 と 「結合するパス(相対 URL)の先頭」 にあるスラッシュ(/)の扱いです。
以下の3パターンを理解しておけば、バグを未然に防げます。
パターンA: Base の末尾に「/」がある場合(通常)
Base URL がディレクトリ(フォルダ)として扱われます。 直感的で一番わかりやすい結合です。
base = "http://example.com/api/" # 末尾あり
path = "users"
print(urljoin(base, path))
# 結果: http://example.com/api/users
# → 素直に後ろにくっつきます。パターンB: Base の末尾に「/」がない場合(置換の罠)
これがハマりポイントです。 末尾にスラッシュがない場合、urljoin はベースの最後の部分(ここでは api)を「ファイル名」だと判断します。 そのため、最後の部分を取り除いてから結合します。
base = "http://example.com/api" # 末尾なし(ファイル扱い)
path = "users"
print(urljoin(base, path))
# 結果: http://example.com/users
# → 「api」が消えて、「users」に置き換わりました!
💡 なぜこうなる? ブラウザで http://example.com/api というページを見ている時に、<a href="users"> というリンクをクリックした時の挙動と同じです。「同じ階層にある別のファイル(users)に移動する」という動きになります。
パターンC: 相対URL が「/」で始まる場合(ルート相対パス)
結合するパスの先頭にスラッシュがある場合、Base URL のパス部分は無視され、ドメイン直下(ルート)からのパスになります。
base = "http://example.com/api/v1/"
path = "/users" # 先頭あり(ルート指定)
print(urljoin(base, path))
# 結果: http://example.com/users
# → 「/api/v1/」は無視され、ドメイン直下につきます。挙動まとめ(早見表)
| パターン | Base URL(第1引数) | Relative URL(第2引数) | 結合結果 | 解説 |
|---|---|---|---|---|
| A(通常) | …/folder/ | file | …/folder/file | ディレクトリの中に配置 |
| B(置換) | …/folder | file | …/file | folder が file に置換される |
| C(ルート) | …/folder/ | /file | …/file | ドメイン直下に配置 |



開発時は「Base URL の末尾スラッシュ」を意識しましょう。
文字列結合(+)との比較
ここまで読んだ方の中には、「仕様が複雑だから、自分で <code>+</code> を使って結合したほうが簡単では?」と思った方もいるかもしれません。
しかし、単純な文字列結合(<code>+</code>)で安全な URL を作ろうとすると、コードは意外と複雑になります。
文字列結合(+)で書く場合の苦労
「スラッシュが重ならないように」「足りなければ足すように」という処理を自前で書く必要があります。
base = "http://example.com/api"
path = "users"
# そのまま足すと...
# http://example.com/apiusers (スラッシュ抜け!)
# 自力で制御しようとすると...
if not base.endswith("/") and not path.startswith("/"):
full_url = base + "/" + path
elif base.endswith("/") and path.startswith("/"):
full_url = base[:-1] + path
else:
full_url = base + path


↑ たったこれだけのために、こんな条件分岐を書くのはナンセンスですよね。
urljoin を使う場合
<code>urljoin</code> は、Web の標準規格(RFC 1808など)に基づいて、これらの処理を内部で自動的に行ってくれます。
# これだけでOK!
full_url = urljoin(base, path)


「スラッシュの有無を気にせず、ライブラリに任せる」ことができるのが最大のメリットです。 ただし、先ほどの 「パターンB(末尾にスラッシュがないと置換される)」 という仕様だけは、意識して使いこなしましょう。
まとめ
本記事では、Python で URL を結合する際に推奨される <code>urllib.parse.urljoin</code> について解説しました。
- 基本
-
URL の結合は
+ではなくurljoinを使う。 - 注意
-
Base URL の末尾にスラッシュがないと、最後のパスが上書き(置換)される。
- 対策
-
ディレクトリの下にパスを繋げたい場合は、Base URL の末尾に必ず
/をつけておく。
以上、最後までお読みいただきありがとうございました。



