從 URL 字串取得檔名是常見需求(下載、儲存、日誌)。最簡單的是 os.path.basename(url),但 URL 含查詢字串、片段、百分號編碼時會產生雜訊。本文比較多種方法、處理邊界情況並提供健壯的通用函數。
import os
url = 'https://www.example.com/images/logo.gif'
filename = os.path.basename(url)
print(filename) # logo.gif
url = 'https://example.com/file.pdf?download=1#page=2'
print(os.path.basename(url)) # file.pdf?download=1#page=2 ← 不想要
from urllib.parse import urlparse, unquote
import posixpath # URL 路徑永遠用 POSIX 風格
def get_filename_from_url(url):
parsed = urlparse(url)
path = parsed.path
# 取路徑最後一段
filename = posixpath.basename(path)
# 解碼百分號編碼
filename = unquote(filename)
return filename
print(get_filename_from_url('https://example.com/file.pdf?download=1#page=2')) # file.pdf
print(get_filename_from_url('https://example.com/path/%E4%B8%AD%E6%96%87.txt')) # 中文.txt
HTTP 回應可能在 Content-Disposition 提供真實檔名(含編碼處理),優先順序:
Content-Disposition: attachment; filename="real.pdf"Content-Disposition: attachment; filename*=UTF-8''real.pdf (RFC 5987)download.bin
import re, email.utils
def parse_content_disposition(cd_header):
"""解析 Content-Disposition,回傳 (filename, filename_star)"""
if not cd_header:
return None, None
# 簡易解析,生產建議用 email.message 或 requests-toolbelt
value, params = email.utils.parse_header(cd_header)
return params.get('filename'), params.get('filename*')
def get_final_filename(response, url):
# 1. Content-Disposition
cd = response.headers.get('Content-Disposition')
if cd:
fn, fn_star = parse_content_disposition(cd)
if fn_star:
# RFC 5987: encoding'lang'value
encoding, lang, value = fn_star.split("'", 2)
return unquote(value, encoding=encoding)
if fn:
return unquote(fn)
# 2. URL 路徑
return get_filename_from_url(url)
import requests
from pathlib import Path
def download_file(url, dest_dir='.'):
resp = requests.get(url, stream=True, timeout=30)
resp.raise_for_status()
filename = get_final_filename(resp, url)
dest = Path(dest_dir) / filename
dest.parent.mkdir(parents=True, exist_ok=True)
with open(dest, 'wb') as f:
for chunk in resp.iter_content(8192):
f.write(chunk)
return dest
| URL | os.path.basename | urlparse+basename | 備註 |
|---|---|---|---|
https://a/b/c/ | '' (空) | '' | 目錄型 URL,需回退預設名 |
https://a/b/c?query | c?query | c | 查詢字串污染 |
https://a/b/%E4%B8%AD.txt | %E4%B8%AD.txt | 中.txt | 需 unquote |
https://a/b/..%2Fc.txt | ..%2Fc.txt | c.txt | 路徑遍歷攻擊,需 normalize |
urllib.parse.urlparse 解析 URL,不要用字串切分。posixpath(URL 永遠是 POSIX 風格)。urllib.parse.unquote(支援 UTF-8)。Content-Disposition 標頭。os.path.normpath 後確保不以 .. 開頭)。掌握 URL 檔名提取的完整鏈路,能讓下載器、爬蟲、API 用戶端在面對各種奇葩 URL 時依然健壯可靠。