跳到正文
返回博客
·约 2 分钟读完

常用正则表达式速查:手机号、邮箱、网址、日期、中文字符(附测试结果和局限)

手机号、邮箱、网址、日期、中文字符的常用正则,每条都在 Node 里用 8 个以上的通过和不通过样例实测,并用 Python re 交叉对照。同时讲清每条的局限、g i m s u 标志位、贪婪与懒惰、灾难性回溯的实测耗时,以及 JavaScript 与 Python 的差异。

简短回答: 下面五条是常用的写法,都只是"格式检查",不是"真实性检查"。手机号 ^1[3-9]\d{9}$ 只能说明长这样,不能说明号码存在;邮箱和网址的正则会放过一些无效的、拒绝一些有效的;日期正则不知道 2 月只有 28 或 29 天;中文字符 [一-龥] 会漏掉生僻字,推荐用 \p{Script=Han} 加 u 标志位。每条我都在 Node 25.8.0 里用至少 8 个"应通过"和 8 个"应拒绝"的样例跑过,并用 Python 3.9.6 的 re 交叉验证。

想边看边试,可以用免费的正则表达式测试工具。它运行的是 JavaScript 正则,匹配在后台线程里完成,超时会被停止;文本留在浏览器里,不上传。

1. 手机号(中国大陆)

/^1[3-9]\d{9}$/

实测(Node):13800138000、15912345678、18612345678、19912345678、13012345678、14512345678、16612345678、17712345678、17000000000 共 9 个全部通过;12800138000(第二位是 2)、1380013800(10 位)、138001380001(12 位)、+8613800138000、138 0013 8000、1380013800a、全角数字 13800138000、10086 等 10 个全部拒绝。

局限:它只检查"1 开头、第二位 3 到 9、一共 11 位"。号段会随运营商和监管调整,这个范围是我写的常见近似,不是官方号段表,我没有核对过任何官方清单;17000000000 能通过,不代表这是个能用的号码。要确认号码真的属于用户,只能发短信验证码。带 +86 或空格的输入,需要先去掉再校验。

2. 邮箱

/^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/

实测:a@b.com、ana.lopez@example.com、sales+uk@shop.example.co.uk、user_name@mail.example.org、x-y@sub.domain.io、123@qq.com、first.last@gmail.com 通过;name@、@host.com、a@b、a b@example.com、a@b.c、a@@b.com、user@exa mple.com、用户@example.com、a@example.com. 等 10 个拒绝。

局限很明显:a@-b.com、a..b@example.com、.a@example.com 这三个无效或可疑的地址同样通过了,实测返回 true。反过来,它拒绝中文用户名之类的国际化邮箱。真实的邮箱规则(RFC 5321/5322)复杂得多,正则无法完整校验。实际做法是:用宽松的正则挡掉明显的输入错误,再发一封确认邮件。

3. 网址

/^https?:\/\/[^\s\/$.?#][^\s]*$/

实测通过:https://obelisk.club、http://localhost:3000/api、https://example.com/docs?page=2#top、http://192.168.0.1/a、https://a.b/c%20d、https://例子.中国/路径 等 9 个;拒绝:example.org、ftp://example.com、https://、https:// example.com、http:///path、//example.com、https://a b.com、https://.com、www.example.com 等 10 个。

局限:http://x、http://a..b 也通过了,它不检查域名是否合法或存在;不带 http(s):// 的 www.example.com 会被拒绝。需要严格解析时,用语言自带的 URL 解析器(如 JavaScript 的 new URL()),不要靠正则。

4. 日期(YYYY-MM-DD)

/^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/

实测通过 9 个:2026-10-20、2000-01-01、1999-12-31、2024-02-29、9999-12-31、0000-01-01,以及三个现实中不存在的 2023-02-29、2023-02-30、2023-04-31。拒绝 11 个:2026-13-01、2026-00-10、2026-10-32、20261020、2026/10/20、2026-1-5、10-20-2026、带尾部空格的 2026-10-20 等。

局限:它只管月份 01 到 12、日 01 到 31,不懂大小月和闰年,所以 2 月 30 日照样通过。要判断日期是否真实存在,先用正则确认格式,再交给日期库去解析。

5. 中文字符

常见写法是 [一-龥],即 U+4E00 到 U+9FA5。实测它漏掉很多汉字:

字符码点[一-龥]\p{Script=Han}(u)
你U+4F60匹配匹配
龥U+9FA5匹配匹配
龦U+9FA6不匹配匹配
㐀(扩展 A)U+3400不匹配匹配
𠀀(扩展 B)U+20000不匹配匹配
豈(兼容汉字,注意是 U+F900 而非普通的“豈”U+8C48)U+F900不匹配匹配
〇U+3007不匹配匹配
,。!(全角标点)U+FF0C 等不匹配不匹配

所以更稳的写法是 /\p{Script=Han}/u。注意:必须带 u 标志位,而且 \p{Script=Han} 包含 〇、々 这类不算"表意文字"的字符;全角标点不属于 Han,要匹配标点需另写,例如 [,。!?、]。Python 的 re 不支持 \p{...},我实测 re.compile(r'\p{Script=Han}') 直接报 bad escape \p,需要用第三方库 regex(我没有安装测试)或自己写码点范围。[一-鿿] 在 Node 和 Python 中结果一致,也会漏掉扩展区。

标志位:g i m s u

在 Node 里的实测结果:

  • g(全部匹配):"Cat cat\nCAT" 用 /cat/ 只得到第一个匹配 cat;/cat/gi 得到 ["Cat","cat","CAT"]。i 是忽略大小写。
  • m(多行):对 "ab\ncd",/^\w/g 只得到 ["a"],加 m 后是 ["a","c"]。
  • s(点号匹配换行):/a.b/.test("a\nb") 是 false,加 s 后是 true。
  • u(Unicode):"😀".match(/./g) 得到两个半截代理字符,加 u 后得到完整的 😀("😀".length 是 2)。\p{...} 也需要它。

贪婪与懒惰

. 默认贪婪,会尽量多吃。对 <b>one</b><b>two</b>:/<b>.<\/b>/ 匹配整个字符串;/<b>.?<\/b>/g 在 后加 ? 变成懒惰,得到 ["<b>one</b>","<b>two</b>"]。Python 的结果相同。用正则解析 HTML 仍然不可靠,这里只是演示语法。

灾难性回溯

嵌套重复加上一个会失败的结尾,会让引擎尝试指数级的路径。/^(a+)+$/ 去匹配 n 个 a 加一个 !:

  • Node:n=26 约 307 毫秒,n=28 约 1242 毫秒(时间每次略有波动,我另一轮跑 n=24 还出现过 558 毫秒,所以只看趋势:每多两个字符大约变成 4 倍)。
  • Python 3.9.6:n=24 约 356 毫秒,同样呈指数增长。
  • 对照 /^a+$/:26 个 a 加 ! 只要 0.018 毫秒,10000 个也只要 0.039 毫秒。

我没有在工具页面里实际触发超时。按页面源码,后台线程超过所选时限(1、3 或 10 秒,默认 1 秒)会被终止,并显示提示。避免的办法:去掉嵌套重复,让多选分支互不重叠,或限制输入长度。

JavaScript 和 Python 的差异

以下都是我实际对照得到的:

  • $ 与结尾换行: Python 的 ^1[3-9]\d{9}$ 对 "13800138000\n" 返回匹配,JavaScript 不匹配。Python 里用 \Z 或 re.fullmatch。
  • \d 的范围: Python 的 \d 默认匹配全角数字,re.search(r'\d','5') 为 True,加 re.A 后为 False;JavaScript 的 \d 永远只匹配 ASCII。这让日期正则在 Python 中通过了 2026-10-20。\w 同理:Python 匹配"中",JavaScript 不匹配。
  • 命名分组: JavaScript 写 (?<y>...),Python 写 (?P<y>...),用 (?<y> 会报 unknown extension ?<y。
  • 后行断言: (?<=USD|EU) 在 JavaScript 可用,Python 报 look-behind requires fixed-width pattern。
  • \p{...}: 见上,Python 标准库 re 不支持。
  • 内联标志: Python 可写 (?i)abc,JavaScript 会报 Invalid group;Node 25 支持 (?i:a)b 这种局部写法,其他引擎和旧版本我没有测。

常见问题

手机号正则 ^1[3-9]\d{9}$ 可以用来验证手机号吗?

只能做格式检查。它确认是 11 位、1 开头、第二位 3 到 9,不能确认号码存在或属于本人,号段也会变化。真正验证要靠短信验证码。

邮箱能用正则完整校验吗?

不能。邮箱规范很复杂,我实测的常用写法会放过 a..b@example.com 这样的可疑地址,也拒绝国际化邮箱。常见做法是用宽松的正则挡明显的错误,再发确认邮件。

怎么用正则匹配中文字符?

用 /\p{Script=Han}/u。常见的 [一-龥] 只覆盖 U+4E00 到 U+9FA5,会漏掉 龦(U+9FA6)、㐀、𠀀 等字。标点不属于 Han,要另外列出。

为什么同一个正则在 Python 和 JavaScript 结果不同?

两者对 $、\d、命名分组、后行断言的规则不同。例如 Python 的 $ 能匹配结尾换行前,\d 默认包含全角数字。换语言之前要在目标语言里重新测一遍,可以先在正则测试工具里验证 JavaScript 的行为;处理网址编码可以用 URL 编码工具,格式化 JSON 用 JSON 格式化工具。

作者

独立开发者,在 Obelisk Club 做本地优先的 Mac 与 iOS 效率应用。 关于作者 →