# 常用正则表达式速查:手机号、邮箱、网址、日期、中文字符(附测试结果和局限) > 手机号、邮箱、网址、日期、中文字符的常用正则,每条都在 Node 里用 8 个以上的通过和不通过样例实测,并用 Python re 交叉对照。同时讲清每条的局限、g i m s u 标志位、贪婪与懒惰、灾难性回溯的实测耗时,以及 JavaScript 与 Python 的差异。 - Canonical page: https://obelisk.club/blog/regex-common-patterns-zh - Author: Peter Zhang - Published: 2026-10-20 - Tags: regex, 正则表达式, javascript, 开发工具, 中文 **简短回答:** 下面五条是常用的写法,都只是"格式检查",不是"真实性检查"。手机号 `^1[3-9]\d{9}$` 只能说明长这样,不能说明号码存在;邮箱和网址的正则会放过一些无效的、拒绝一些有效的;日期正则不知道 2 月只有 28 或 29 天;中文字符 `[一-龥]` 会漏掉生僻字,推荐用 `\p{Script=Han}` 加 `u` 标志位。每条我都在 Node 25.8.0 里用至少 8 个"应通过"和 8 个"应拒绝"的样例跑过,并用 Python 3.9.6 的 `re` 交叉验证。 想边看边试,可以用免费的[正则表达式测试工具](https://obelisk.club/tools/regex-tester?lang=zh)。它运行的是 JavaScript 正则,匹配在后台线程里完成,超时会被停止;文本留在浏览器里,不上传。 ## 1. 手机号(中国大陆) ```js /^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. 邮箱 ```js /^[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. 网址 ```js /^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) ```js /^\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{...}` 也需要它。 ## 贪婪与懒惰 `.*` 默认贪婪,会尽量多吃。对 `onetwo`:`/.*<\/b>/` 匹配整个字符串;`/.*?<\/b>/g` 在 `*` 后加 `?` 变成懒惰,得到 `["one","two"]`。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 写 `(?...)`,Python 写 `(?P...)`,用 `(?` 会报 `unknown extension ?