跳到主内容
d.devtul.fun
EN
正则 · URL

URL 正则

一个能直接用的 http/https URL 正则,外加"万能 URL 正则"为什么是伪命题的解释。

正则模式

https?://[^\s]+

匹配 http 或 https 协议头,后面跟任意非空白字符。它是用来从文本里抠出 URL 的,不是用来证明一个 URL 格式正确的。

在线测试这个正则

在下面直接改文本 —— 匹配在本机完成,不会上传任何内容。

其它写法

协议头、主机、可选端口
https?://[\w\-]+(\.[\w\-]+)+(:\d+)?(/[^\s]*)?

更紧:要求主机里至少有一个点,允许可选端口和路径。但仍会放过一些非法主机。

完整结构版
https?://(?:[\w\-]+\.)+[a-z]{2,}(?::\d+)?(?:/[^\s]*)?

强制字母顶级域和路径。适合日志,对内网和 IP 字面量主机不友好。

日常用的 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否没协议头,正确拒绝

坑死人的常见错误

错误后果改法
用 .* 贪婪匹配把尾随标点和下一句都吞掉遇到空白或安全字符集就停
忽略 userinfohttps://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 解析器对着已知基址解析一遍,再去确认协议头,而且把这两步分开。把相对和绝对塞进同一条正则,正是脆弱校验器出厂的方式。

常见问题

最好的 URL 正则是哪条?

没有。校验请用语言自带的解析器,比如 JavaScript 的 new URL();正则只用来从文本里抠链接。

为什么不该用正校验 URL?

URI 文法太松,一条正则兜不住,而且编码和部件解析这些事正则干不了,解析器能。

怎么从文本里匹配 URL?

用 https?://[^\s]+ 这类先把候选抠出来,再逐个解析确认格式。

我的 URL 正则需要处理 IPv6 吗?

只有你真服务 IPv6 字面量主机才需要。否则用解析器,它原生支持。

其它正则速查

打开完整的正则测试器