URL 是七个部件拼起来的,每个缺省都有默认值。 自己拼地址时,路径段必须过一遍编码。
6.2.2 与 6.2.3 讲报文,这一节讲报文里那行路径从哪来。 6.3 进入 FLV 的字节层。
一、七个部件
scheme://host:port/path?query#fragment,一条地址就这七段。
① scheme:http 或 https,决定用哪套默认端口。
② netloc:主机加端口,host:port 的原文。
③ hostname:去掉端口的主机名。
④ port:端口。没写时是 None,不是 0。
⑤ path:从第一个斜杠到问号之前。
⑥ query:问号之后、井号之前,参数串。
⑦ fragment:井号之后,只在浏览器内部用,不会发给服务端。
第 ⑦ 条容易误解: fragment 从来不会被发到服务端。 所以用它传 token 是无效的——服务端根本收不到。
二、三个默认值
① 端口:http 默认 80,https 默认 443。
② 路径:没写就是 /。
③ 协议:浏览器地址栏里没写协议,默认按 http 处理。
代码里要注意 port 的行为:
没写端口时 urlsplit(...).port 返回 None。
所以判断要写成「None 就按 scheme 取默认」,不能当 0 处理。
还有一个坑:urlsplit 不会做归一化。
它不补默认端口、不合并多余斜杠、也不解引用。
想要归一化得自己写,或换用 urljoin 之类的函数。
三、中文与空格必须编码
URL 里只允许一小部分字符直接出现,其余都要转成百分号形式。
实测三个例子:
测试.ts 变成 %E6%B5%8B%E8%AF%95.ts(UTF-8 逐字节转),
a b.ts 变成 a%20b.ts(空格不能直接出现),
a&b=c 变成 a%26b%3Dc(& 与 = 是分隔符,必须转义)。
第三个例子的后果最隐蔽:
不转义的话,服务端会把 a&b=c 拆成「文件名 a」加「参数 b=c」,
取到的文件就不是你想要的那个。
所以凡是自己拼 URL 的代码,路径段都要过一遍 quote。
注意 quote 与 quote_plus 的区别:
前者把空格转成 %20,后者转成 +。
路径里必须用 quote——+ 在路径里就是加号本身,不会被解释成空格。
四、动手 Demo:URL——五个部件,各有默认值
拆一个带端口、查询串、锚点的地址,把七段都打出来。 再实测三种必须编码的字符。
import urllib.parse as up
url = "https://live.example.com:8443/hls/index.m3u8?token=abc&t=123#pos"
sp = up.urlsplit(url)
for k in ("scheme", "netloc", "hostname", "port",
"path", "query", "fragment"):
print(" %-9s %s" % (k, getattr(sp, k)))
print()
sp2 = up.urlsplit("http://a.com/x")
print("没写端口时 port = %r" % (sp2.port,))
print("按 scheme 取默认端口:%d" % (sp2.port or (443 if sp2.scheme == "https" else 80)))
print()
for s in ("测试.ts", "a b.ts", "a&b=c"):
print(" %-8s -> %s" % (s, up.quote(s)))
print(" %-8s -> %s" % ("a b.ts (quote_plus)", up.quote_plus("a b.ts")))
实跑输出:
--------------------------------------------------------------------
URL——五个部件,各有默认值
--------------------------------------------------------------------
**① 拆一个带查询串的地址**
**原地址:**https://live.example.com:8443/hls/index.m3u8?token=abc&t=123#pos
部件 值
--------------------------------------
scheme https
netloc live.example.com:8443
hostname live.example.com
port 8443
path /hls/index.m3u8
query token=abc&t=123
fragment pos
**② 三个默认值**
省略的部分 默认值
-------------------------------------
端口 http 80/https 443
路径 /
协议 http
**`sp.port` 在没写端口时会返回 None,不是 0。**
**所以判断要用「None 就按 scheme 取默认」。**
**③ 中文与空格必须编码**
原始 编码后 为什么
---------------------------------------------------------
测试.ts %E6%B5%8B%E8%AF%95.ts UTF-8 转百分号
a b.ts a%20b.ts 空格变 %20
a&b=c a%26b%3Dc & 与 = 必须转义
**不编码的后果很隐蔽:**
**`a&b=c` 会被服务端拆成参数,文件名就变了。**
**所以凡是自己拼 URL 的代码,路径段都要过一遍 quote。**
port 打印出来是 8443(整数),不是字符串,因为 urlsplit 顺手转了。
sp2.port 是 None,所以不能写 if sp2.port == 0。
quote 给 %20、quote_plus 给 +——路径用前者,表单用后者。
30 秒实验
- 把地址里的端口删掉,看 port——变成 None,这就是「默认值要靠自己补」。
- 用
up.urljoin把相对路径接到基地址上,看结果——能自动处理斜杠,比手工拼字符串可靠得多。 - 把
quote(s, safe='')的参数改成默认,看/的变化——默认safe='/'会保留斜杠,拼单段路径时要显式清空。
常见误区
- 「fragment 会发给服务端」——不会,只在浏览器内部用。
- 「没写端口时 port 是 0」——是 None。
- 「路径里的空格用 + 代替」——那是表单编码,路径要用 %20。
自测三问(点开对答案)
1. URL 有哪七段? —— **scheme、netloc、hostname、port、path、query、fragment。** 2. 没写端口时该返回什么? —— **None**,再按 scheme 取 80 或 443。 3. 路径段为什么必须 quote? —— **`&` 与 `=` 是分隔符**,不转义会被拆成参数,文件名就变了。- ☐ (本节完,下一节 6.3.1 FLV 格式解析)