日常用的 URL 正则
当有人问"来个 URL 正则"的时候,先得搞清楚他到底要干嘛。是想从一坨文本里把链接抠出来,还是想证明用户手敲的那串是真的合法 URL?这是两件事,正则也不一样。要是抠链接,我一般用这一行:
https?://[^\s]+
它抓住 http:// 或 https:// 这个协议头,然后一路吃到下一个空白为止。这是故意做得笨:它是一张网,不是法官。
为什么不存在万能的 URL 正则
RFC 3986 里的 URI 文法本身就是故意放宽。一个 URL 可以带用户信息的 userinfo、方括号里的 IPv6 字面量、各种稀奇scheme、相对引用,还有能表示任何东西的百分号编码字节。一条正则要是想精确接住浏览器会接受的那一整集字符串、又精确挡掉它会拒绝的,会比规范本身还长,而且边缘照样错。所以别再找"那条唯一的 URL 正则"了。要么为正则挑一个窄活儿,要么用解析器。
JavaScript 里的正解:new URL()
如果你要校验用户敲的 URL,在 JavaScript 里别用正则,用内置解析器:
function isValidUrl(s) {
try { new URL(s); return true; }
catch { return false; }
}
new URL() 跟着平台规则走,畸形输入会直接抛错,顺手把百分号编码处理掉,还白送你解析好的各个部件。这些正则一样都干不稳。正则唯一还占上风的地方,是扫一段自由文本——那种地方解析器会被周围的句子噎住。
把正则拆开看各部分
真要写 URL 正则,得懂每一段。一个结构化的例子:
https?://(?:[\w\-]+\.)+[a-z]{2,}(?::\d+)?(?:/[^\s]*)?
https?://是协议头:http或https加双斜杠。(?:[\w\-]+\.)+[a-z]{2,}是主机:一个或多个点分隔的标签,末尾是两位以上的字母顶级域。(?::\d+)?是可选端口,冒号加数字。(?:/[^\s]*)?是可选路径,以斜杠开头,吃到下一个空白。
这么一拆,你到底在查什么、没查什么就一目了然:协议头、主机形状、端口、路径。至于主机能不能解析,它一个字都没说。
这个正则匹配了什么、漏了什么
| 输入 | 匹配? | 说明 |
|---|---|---|
https://example.com | 是 | 最常见的情况 |
http://sub.example.com:8080/p?q=1 | 是 | 端口和路径都吃进来了 |
https://[2001:db8::1] | 否 | 没处理 IPv6 字面量 |
ftp://example.com | 否 | 协议头只认 http(s) |
example.com | 否 | 没协议头,正确拒绝 |
坑死人的常见错误
| 错误 | 后果 | 改法 |
|---|---|---|
用 .* 贪婪匹配 | 把尾随标点和下一句都吞掉 | 遇到空白或安全字符集就停 |
| 忽略 userinfo | https://user@host 要么漏掉要么错乱 | 明确决定放不放 |
| 忘了 IPv6 字面量 | https://[::1] 合法却被拒 | 加一个方括号主机分支,或用解析器 |
强制 www | 挡掉所有裸域名 URL | 永远别假设有子域 |
给日志用的更严版本
解析你信得过的访问日志时,强制带点的主机加字母顶级域,路径保持可选:
https?://(?:[\w\-]+\.)+[a-z]{2,}(?::\d+)?(?:/[\w\-./?%&=]*)?
它依然看不出主机解不解析、端口开没开。它只描述形状——这正是正则该干的活,也正好是它该停止声称能干的活。
什么时候该换库
如果你要的不只是形状,解析器或库才是老实的选择:
- JavaScript 和 Node:内置的
new URL()和URLSearchParams。 - Python:
urllib.parse.urlparse和urlsplit。 - Go:
net/url包。 - 任何语言:一个维护良好的校验库,都比你从论坛抄来的正则强。
把正则留给它唯一攥在手里的活:从散文里把候选 URL 抠出来,再交给解析器去判。
怎么测、怎么信你的正则
拿上面的表跑一遍你的正则,再加上几个恶心样例:句末带句号的 URL、后面跟逗号的 URL、IPv6 字面量、带 userinfo 的 URL。如果贪婪的 .* 把尾随句号也吃了,换成以空白为界的字符类。而凡是得在采取行动前保证合法的值,就解析它,别只匹配它。
协议相对与相对 URL
你偶尔会撞上 //example.com/path 这种协议相对 URL,或者 /just/a/path 这种相对 URL。它们都匹配不上 http/https 的正则,而这是正确的结果:单看它们本身就不是绝对 URL。一条正则要是"好心"去接受它们,往往顺手把噪声也放进来了。如果你的输入可能是相对的,先拿平台的 URL 解析器对着已知基址解析一遍,再去确认协议头,而且把这两步分开。把相对和绝对塞进同一条正则,正是脆弱校验器出厂的方式。