电话号码正则:美国格式、E.164,以及何时该停手
同一个电话号码,人们能输入出十几种写法。本文介绍如何匹配带短横线、点、空格、括号和可选 +1 的美国号码,如何检查国际 E.164 格式,以及为什么真正的校验需要借助库。

· 阅读约 5 分钟 · 更新于
/^\d{3}[-. ]?\d{3}[-. ]?\d{4}$/
抓住
- 555-123-4567
- 555.123.4567
- 5551234567
放过
- 555-1234
- 55-123-4567
- 555-123-456
找十个人要电话号码,你会收到十种写法:555-123-4567、(555) 123-4567、555.123.4567、+1 555 123 4567、5551234567。它们其实是同一个号码。
电话号码正则既要接受人们真正会输入的写法,又要拦住明显的错误。本文先一步步搭建一个美国号码的正则,再介绍国际格式,最后讲讲正则做不到的部分。每个示例都可以交互,修改正则就能看到各行的变化。
01从数字开始
美国号码由 3 位区号、3 位交换码和 4 位线路号组成。最严格的版本只接受一种格式:
如果输入格式由你的输入掩码控制,这样没问题。如果用户随意输入,它就太严格了。
02允许常见的分隔符
把每个短横线换成 [-. ]?:短横线、点或空格,? 让它变成可选,这样纯数字也能通过。
/^\d{3}[-. ]?\d{3}[-. ]?\d{4}$/应当匹配4/4
- 555-123-4567
- 555.123.4567
- 555 123 4567
- 5551234567
不应匹配3/3
- 555-1234
- 55-123-4567
- 555-123-456
有个副作用:每个分隔符是各自独立选择的,所以 555-123.4567 也能通过。这通常无伤大雅。如果你希望两个分隔符一致,可以捕获第一个,再用反向引用重复它(详见我们的反向引用指南):
03区号外面的括号
对付 (555) 123-4567,最诱人的办法是用 \(? 和 \)? 让每个括号都变成可选。但这样会接受不配对的输入:
用分支就能解决。区号要么在括号里(后面可以跟一个空格),要么不带括号(后面可以跟一个分隔符):
/^(\(\d{3}\) ?|\d{3}[-. ]?)\d{3}[-. ]?\d{4}$/应当匹配4/4
- (555) 123-4567
- (555)123-4567
- 555-123-4567
- 555.123.4567
不应匹配2/2
- (555-123-4567
- 555) 123-4567
04可选的 +1
美国的国家代码是 1,可以写作 +1、1,也可以完全省略。在开头用一个可选分组把它包起来:
/^(\+?1[-. ]?)?(\(\d{3}\) ?|\d{3}[-. ]?)\d{3}[-. ]?\d{4}$/应当匹配5/5
- +1 555 123 4567
- 1-555-123-4567
- +1 (555) 123-4567
- 15551234567
- 555-123-4567
不应匹配2/2
- +44 20 7946 0958
- 555-1234
对于只面向美国的表单,这已经是一个可靠的正则了。
05用区号规则收紧
真实的美国区号和交换码(中间三位)从不以 0 或 1 开头。在这两处把 \d{3} 换成 [2-9]\d{2},就能拒绝那个经典的假号码:
/^[2-9]\d{2}[-. ]?[2-9]\d{2}[-. ]?\d{4}$/应当匹配2/2
- 212-555-0199
- 415.555.2671
不应匹配3/3
- 123-456-7890
- 555-012-3456
- 555-123-4567
值不值得这么做,取决于你的用户。它能拦住一些输入错误,也会让正则更难读。
06国际号码:E.164
在美国以外,格式差异太大,一个正则根本覆盖不了。与其硬试,大多数系统会用国际标准 E.164 来存储号码:一个 +、国家代码,然后是号码本身,不带空格和标点,最多 15 位数字。国家代码从不以 0 开头。
/^\+[1-9]\d{1,14}$/应当匹配3/3
- +15551234567
- +442079460958
- +8613800138000
不应匹配4/4
- 5551234567
- +1 555 123 4567
- +0123456789
- +1234567890123456
通常的做法是让用户随意输入号码,去掉标点,再检查结果。正则非常适合做去标点这一步:
/[\s().-]/在 Python 中是 re.sub(r"[\s().-]", "", number),在 JavaScript 中是 number.replace(/[\s().-]/g, "")。
07正则无法告诉你的事
^\+[1-9]\d{1,14}$ 检查的是形状。它无法告诉你:
- 国家代码是否存在(
+999就不是,而这个正则照样接受), - 号码长度对该国家是否正确(英国号码和德国号码长度不同,有些国家的号码长度还不统一),
- 号码是手机、固话还是尚未分配,
- 有没有人接听。
要做真正的校验,请使用 Google 的电话号码库 libphonenumber。它了解每个国家的编号方案,并且有大多数语言的移植版本:Python 中的 phonenumbers,JavaScript 中的 libphonenumber-js。它还能把号码格式化用于显示,或转换成 E.164 用于存储:
import phonenumbers
n = phonenumbers.parse("(212) 555-0199", "US")
phonenumbers.format_number(n, phonenumbers.PhoneNumberFormat.E164) # '+12125550199'
phonenumbers.is_possible_number(n) # True: the length fits the US plan
和电子邮件地址一样,要证明一个号码能用,唯一的办法就是给它发一个验证码。
08试一试
这里是一个混合了各种格式电话号码的棋盘。你不需要逐一检查每种格式。每个怪物都有连续的十位数字(忽略标点),而无辜者都没有:
抓住完整的美国电话号码,放过较短的。
目标 · 抓住 0/5
- 555-123-4567目标
- (555) 123-4567目标
- 555.123.4567目标
- 5551234567目标
- +1 555 123 4567目标
无辜词 · 误抓 0
- 555-1234无辜
- 12345-6789无辜
- 2026-10-12无辜
- 555-123-456无辜
- 911无辜
输入正则——棋盘会实时更新。
09检查清单
- 只接受美国号码时,
^(\+?1[-. ]?)?(\(\d{3}\) ?|\d{3}[-. ]?)\d{3}[-. ]?\d{4}$能覆盖常见格式。 - 用分支,而不是两个各自可选的括号,这样
(和)才会成对出现。 - 对于国际号码,用
[\s().-]去掉标点,并以 E.164 存储。 - 用
^\+[1-9]\d{1,14}$检查形状,在要紧的场合使用 libphonenumber。 - 如果需要确认号码真实存在,就发一个验证码。
10继续练习
- 学院的“实战校验”课程一次一场狩猎,涵盖邮政编码、日期、时间、电子邮件等内容。
- 指南中的常用正则章节有更多可交互示例。
- 试试今天的狩猎,或者到往期里挑战以前的棋盘。