如何使用正则表达式验证电话号码

1054

我正在尝试编写一个全面的正则表达式来验证电话号码。理想情况下,它将处理国际格式,但必须处理美国格式,包括以下格式:

  • 1-234-567-8901
  • 1-234-567-8901 x1234
  • 1-234-567-8901 ext1234
  • 1 (234) 567-8901
  • 1.234.567.8901
  • 1/234/567/8901
  • 12345678901

我会回答我的当前尝试,但我希望有人能提供更好和/或更优雅的解决方案。


2
此答案已添加到Stack Overflow正则表达式FAQ,位于“常见验证任务”下。 - aliteralmind
不幸的是,访问那个页面并搜索“常见验证任务”没有任何结果… - Lewis Cianci
这是一个正则表达式任务吗? - Guildenstern
在某些情况下,这是一个 XY 问题。你要求一个谓词,如果电话号码有效则返回 True,但你真正想要的只是一个正确的电话号码。对于任何试图清理网页或手机应用程序中最终用户输入的电话号码的人,我建议只需编写4或5行代码,逐个字符从左到右进行处理并且删除所有非数字字符。(303)873-9919 变成了 3038739919。删除所有非数字字符后,您可以在正确的位置插入(点 .),插入(连字符 -)或斜杠。 - Samuel Muldoon
45个回答

548
更好的选择是在输入时剥离所有非数字字符(除了 'x' 和前导 '+' 符号),要注意英国人习惯以非标准形式+44 (0) ...书写号码,当被要求使用国际前缀时(在这种情况下,您应该完全舍弃 (0))。
然后,你最终得到像这样的值:
 12345678901
 12345678901x1234
 345678901x1234
 12344678901
 12345678901
 12345678901
 12345678901
 +4112345678
 +441234567890

在显示时,您可以随意重新格式化。例如:

  1 (234) 567-8901
  1 (234) 567-8901 x1234

42
如果允许数字来自美国以外的地方,格式化代码将是浪费时间。 - Daniel Earwicker
29
这听起来很不错,但它并没有验证输入的是否真的是电话号码。例如,如果用户没有输入必要的10个数字会怎样?这应该与良好的正则表达式验证相结合。 - Hugh Jeffner
145
考虑到问题是关于验证的,这个回答非常糟糕。 - PlexQ
17
@PlexQ 我不同意你的观点。原始问题在于试图处理电话号码验证,因为它试图处理所有可能的格式选项。与其尝试解决所有这些问题,不如将输入内容进行“预剥离”,将所有格式不必要的内容去除,直到只剩下“数字”。这样做可以解决两个问题——测试结果变得容易了,您现在可以确保呈现回来显示的值都可以一致地进行格式化。这个答案中第一个评论“Complicator's Gloves”是一个很好的阅读...有时候解决问题的答案是以不同的方式来考虑问题。 - scunliffe
46
这个回答怎么会得到这么高的投票?它并没有_验证_任何东西。更糟糕的是,所有其他关于电话号码验证的问题都引用了这个回答... - jlars62
显示剩余19条评论

332
.*
如果用户想要给你他们的电话号码,那就相信他们能填写正确。如果他们不想给你电话号码,那么强制他们输入有效的电话号码将会把他们送到竞争对手的网站或让他们输入一个符合你的正则表达式的随机字符串。我甚至可能会去搜索高收费占星热线的电话号码,然后输入那个号码。
我也认为以下任何一种都可以作为网站上有效的输入:
"123 456 7890 until 6pm, then 098 765 4321"  
"123 456 7890 or try my mobile on 098 765 4321"  
"ex-directory - mind your own business"

243
我同意这里的观点,但有时在用户的利益下,当电话号码真正用于重要事情时进行验证是很好的选择。最好的例子就是信用卡授权购买。如果电话号码错误,授权可能会失败。 - Pointy
64
如果用户不想输入他的电话号码,你可以使该字段变为可选的,但如果用户想要输入电话号码,要求他们输入一个有效的电话号码是否过分了? - Joel McBeth
13
验证的另一个作用就是提醒人们添加区号等信息,这些信息可能会被遗漏,但事后却无法猜测。 - Ben McIntyre
37
@Pointy 但是正则表达式验证并不能帮助你。实际上验证电话号码是否正确的唯一方法是实际发送消息到该号码(在移动设备的情况下),并确保用户使用某种验证代码进行确认。这是在号码准确性很重要时所做的事情。其他所有操作都只是为了用户方便,以防止出现一些(但不是全部)输入错误,并没有验证任何内容。 - Alex B
16
好的。我的电话号码是 1' OR 1=1 -- PWNED_。请查看 http://xkcd.com/327/ 和 https://www.owasp.org/index.php/Testing_for_SQL_Injection(OWASP-DV-005) 。 - Aaron Newton
显示剩余8条评论

331

原来在北美地区,这个问题有一个规范,叫做NANP

你需要明确你想要什么。合法的分隔符是什么?空格、破折号和句点?不允许使用分隔符吗?可以混合使用分隔符吗(例如:+0.111-222.3333)?如何处理扩展(例如:111-222-3333 x 44444)?特殊号码如911呢?区号是可选还是必须的?

下面是用于匹配7或10位数字及其扩展的正则表达式,分隔符可以是空格、破折号或句点:

^(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$

7
这是没有“extension”部分的正则表达式(我要求用户在另一个字段中输入“ext”):^(?:(?:+?1\s*(?:[.-]\s*)?)?(?:(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})$ - aarona
20
这是一个只匹配10位电话号码的版本(不像843-1212这样的7位号码):/(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})/ - Brian Armstrong
11
10位数字接受()作为区号,并且不允许前导1作为国家代码 (?:(?:(\s*\(?([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\)?\s*(?:[.-]\s*)?)([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4}) - Brooke.
6
@StevenSoroka两年来,我一直将Jeffrey Friedl的书放在桌子旁边,因为正则表达式是我工作中的一个重要部分。真正理解正则表达式需要花费相当长的时间。有时候,这个网站的读者只是在寻找现成的解决方案,而不是自己编写代码,特别是在存在大量特殊情况的领域(比如电话号码表示) 。 - Justin R.
7
@fatcat1111,我知道你的意思,但这里大部分回复都是“我也是”的一次性正则表达式,很可能不适用于你的边角情况。这些表达式最终会出现在我试图使用的所有网站上,因此我无法输入邮政编码、电话号码或电子邮件地址,因为有人使用了粗略的正则表达式(例如:+ 是电子邮件地址中的有效字符)。该页面上最好的回复是指向库而不是草稿纸上的正则表达式。 - Steven Soroka
显示剩余6条评论

201

我建议您也可以看一下谷歌的"libphonenumber"库。虽然它不是正则表达式,但它确实可以实现您想要的功能。

例如,它会识别:

15555555555

是一个可能的号码但不是有效的号码,也支持美国以外的国家。

功能亮点:

  • 解析/格式化/验证全球所有国家/地区的电话号码。
  • getNumberType - 基于号码本身获取号码类型;能够区分固定电话、移动电话、免费电话、高级电话、共享成本、网络电话和个人号码(可行时)。
  • isNumberMatch - 获取两个号码是否可能相同的置信度。
  • getExampleNumber/getExampleNumberByType - 为所有国家/地区提供有效的示例号码,并可指定需要哪种类型的示例电话号码。
  • isPossibleNumber - 仅使用长度信息快速猜测号码是否是可能的电话号码,比完整验证更快。
  • isValidNumber - 使用长度和前缀信息对区域内的电话号码进行完整验证。
  • AsYouTypeFormatter - 用户输入每个数字时即时格式化电话号码。
  • findNumbers - 在文本输入中查找号码。
  • PhoneNumberOfflineGeocoder - 提供与电话号码相关的地理信息。

示例

电话号码验证的最大问题是它非常依赖于文化背景。

  • 美国
    • (408) 974–2042 是一个有效的美国号码
    • (999) 974–2042 不是一个有效的美国号码
  • 澳大利亚
    • 0404 999 999 是一个有效的澳大利亚号码
    • (02) 9999 9999 也是一个有效的澳大利亚号码
    • (09) 9999 9999 不是一个有效的澳大利亚号码

正则表达式可用于检查电话号码的格式,但不能真正检查电话号码的有效性

我建议跳过简单的正则表达式来测试您的电话号码,并使用像Google的libphonenumber(链接到GitHub项目)这样的库。

介绍libphonenumber!

使用您更复杂的示例之一:1-234-567-8901 x1234,您可以从libphonenumber(在线演示链接)中获得以下数据:

Validation Results

Result from isPossibleNumber()  true
Result from isValidNumber()     true

Formatting Results:

E164 format                    +12345678901
Original format                (234) 567-8901 ext. 123
National format                (234) 567-8901 ext. 123
International format           +1 234-567-8901 ext. 123
Out-of-country format from US  1 (234) 567-8901 ext. 123
Out-of-country format from CH  00 1 234-567-8901 ext. 123

通过使用 libphonenumber,不仅可以判断电话号码是否有效(它是有效的),而且您还可以以本地格式获得一致的电话号码格式。

作为额外的奖励,libphonenumber 还有许多数据集用于检查电话号码的有效性,因此检查诸如 +61299999999国际版的(02) 9999 9999)也将以格式正确的方式返回为有效号码:

Validation Results

Result from isPossibleNumber()  true
Result from isValidNumber()     true

Formatting Results

E164 format                    +61299999999
Original format                61 2 9999 9999
National format                (02) 9999 9999
International format           +61 2 9999 9999
Out-of-country format from US  011 61 2 9999 9999
Out-of-country format from CH  00 61 2 9999 9999

libphonenumber还提供了许多附加功能,例如获取被检测的电话号码所在的位置,并从该电话号码获取时区信息:

PhoneNumberOfflineGeocoder Results
Location        Australia

PhoneNumberToTimeZonesMapper Results
Time zone(s)    [Australia/Sydney]

但是无效的澳大利亚电话号码 ((09) 9999 9999) 返回其不是一个有效的电话号码。

Validation Results

Result from isPossibleNumber()  true
Result from isValidNumber()     false

Google的版本包含了Java和Javascript的代码,但也有其他语言的库基于Google i18n电话号码数据集实现:

除非你确定只处理来自某个特定地区的电话号码,并且它们都是同一种格式,否则我强烈建议不要自己编写代码验证和显示电话号码,而是使用libphonenumber库。


请注意,现在还有一个Go端口位于:https://github.com/ttacon/libphonenumber - michaelhanson
在检查是否为可能的电话号码时,您需要指定国家代码吗?我正在使用 PHP 版本,如果我输入一个英国号码像(用真实数字替换 0)07700000000,会出现 Missing or invalid default region. 错误。但是如果我指定国家代码,它就可以通过。 - BugHunterUK
3
当解析一个数字时,你可以指定预期的地区,库会寻找该地区内的非国际化数字。如果你不指定,则该库将拒绝任何不在有效的国际格式内的内容。@BugHunterUK(以及任何看到这个问题并想知道同样答案的人),以上是需要翻译的内容。 - IMSoP
请考虑使用 https://github.com/nyaruka/phonenumbers 作为官方推荐的 Go 包,而不是 libphonenumber。 - DeeZone
我可以确认@BugHunterUK所说的内容。虽然花了些时间,但结果正如预期-本地数值以任何格式接受,而且所有完全指定的国际格式也被接受。 - dimplex
我知道它不是正则表达式,但实际上它确实使用了RegExp,而且很多。值得阅读有关电话号码验证的误解:https://github.com/google/libphonenumber/blob/master/FALSEHOODS.md npm端口的问题在于将其捆绑到前端应用程序中需要> 500kB。考虑到复杂性,我建议使用专业服务,例如https://validatephonenumber.com。 - dsdenes

91

/^(?:(?:\(?(?:00|\+)([1-4]\d\d|[1-9]\d*)\)?)[\-\.\ \\\/]?)?((?:\(?\d{1,}\)?[\-\.\ \\\/]?)+)(?:[\-\.\ \\\/]?(?:#|ext\.?|extension|x)[\-\.\ \\\/]?(\d+))?$/i

这个正则表达式可以匹配:

 - (+351) 282 43 50 50
 - 90191919908
 - 555-8909
 - 001 6867684
 - 001 6867684x1
 - 1 (234) 567-8901
 - 1-234-567-8901 x1234
 - 1-234-567-8901 ext1234
 - 1-234 567.89/01 ext.1234
 - 1(234)5678901x1234
 - (123)8575973
 - (0055)(123)8575973
 - +1 282 282 2828

关于$n,它会保存以下内容:

  1. 国家代码
  2. 电话号码
  3. 分机号码

您可以在https://regex101.com/r/kFzb1s/42上进行测试。


你能否重新表达一下?我不理解这句话的意思:“除非你移除^和$,否则很容易绕过它,或者我可以使用[111] [111] [1111]来绕过它。” 这是否意味着当你去掉^$时,它会验证“[111] [111] [1111]”? - Ismael Miguel
17
这是一个很好的例子说明为什么评论线程不应该进入聊天。我对这次对话的结果非常感兴趣,需要知道这个正则表达式是否足够稳定可靠可以在我的应用程序中使用。不幸的是,聊天记录现在已经消失了。 - Matt Cashatt
2
说实话,我没有去聊天。问了多次解释后,我一无所获。你可以尝试使用各种在线找到的数字以多种格式进行测试。我尝试过的一件事是使用多个电话号码,但如果它们周围有空格,则效果不佳。我必须找到一种解决方案来计算数字的数量并强制执行特定数量。 - Ismael Miguel
2
@heisenberg 谢谢你指出来,我已经修复了。此外,我添加了一个不同的链接和单元测试,以确保它按照预期工作。这个 bug 是一个不应该存在的单个 ? - Ismael Miguel
1
@Gavin 我已经编辑过了,使其匹配 +1。问题在于我假设指示符是 1 到 9,然后是 1 个或多个数字,但美国使用 +1,这破坏了“1个或多个数字”。现在对于这些人来说它可以工作了。 - Ismael Miguel
显示剩余11条评论

66

虽然删除所有空白的答案很简洁,但并没有真正解决问题,问题是要找到一个正则表达式。例如,我的测试脚本会下载网页并使用正则表达式提取所有电话号码。既然您仍需要一个正则表达式,那么最好让它做所有的工作。我想到了这个:

1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?

这是一个用Perl编写的测试脚本。当匹配成功时,$1包含区号,$2和$3包含电话号码,$5包含分机号。我的测试脚本会从互联网下载一个文件,并打印其中的所有电话号码。
#!/usr/bin/perl

my $us_phone_regex =
        '1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?';


my @tests =
(
"1-234-567-8901",
"1-234-567-8901 x1234",
"1-234-567-8901 ext1234",
"1 (234) 567-8901",
"1.234.567.8901",
"1/234/567/8901",
"12345678901",
"not a phone number"
);

foreach my $num (@tests)
{
        if( $num =~ m/$us_phone_regex/ )
        {
                print "match [$1-$2-$3]\n" if not defined $4;
                print "match [$1-$2-$3 $5]\n" if defined $4;
        }
        else
        {
                print "no match [$num]\n";
        }
}

#
# Extract all phone numbers from an arbitrary file.
#
my $external_filename =
        'http://web.textfiles.com/ezines/PHREAKSANDGEEKS/PnG-spring05.txt';
my @external_file = `curl $external_filename`;
foreach my $line (@external_file)
{
        if( $line =~ m/$us_phone_regex/ )
        {
                print "match $1 $2 $3\n";
        }
}

编辑:

你可以将正则表达式中的\W*更改为\s*\W?\s*,以使其更加严格。当我编写它时,并没有考虑到在验证表单用户输入方面使用正则表达式,但这个更改使得可以将正则表达式用于该目的。

'1?\s*\W?\s*([2-9][0-8][0-9])\s*\W?\s*([2-9][0-9]{2})\s*\W?\s*([0-9]{4})(\se?x?t?(\d*))?';

2
顺便提一下,正则表达式也会匹配 (4570457-6789 这种常见的打字错误。匹配组也会被扭曲:http://rubular.com/r/TaTP0mHL5c - SooDesuNe
在多行标志开启的情况下,将 (^|[^\d\n]) 添加到前面可以避免一般问题,确保它不是紧接着数字。 - btown
请注意,这是以北美为中心的——它遗漏了“44 7911 123456”。 - Ben Wheeler

57

我之前在另一个SO问题中回答了这个问题,后来决定将我的答案也作为这个帖子的答案,因为没有人在处理如何要求/不要求项目,只是分发正则表达式:

正则表达式错误,匹配到意外的内容

根据我在该网站上的帖子,我创建了一个快速指南,以帮助任何人制作自己所需的电话号码格式的正则表达式。我必须说明(就像我在另一个网站上做的那样),如果您限制过多,可能无法获得所需的结果,并且接受世界上所有可能的电话号码的“一刀切”解决方案并不存在 - 只有您决定接受哪种格式。风险自负。

快速备忘单

  • 开始表达式:/^
  • 如果您想要需要空格,请使用:[\s]\s
  • 如果您想要需要括号,请使用:[(][)]。使用\(\)很丑,会使事情变得混乱。
  • 如果您想让任何东西都是可选的,请在其后面放置?
  • 如果您想要连字符,只需输入-[-]。但是,如果它不是在一系列其他字符的开头或结尾,您可能需要对其进行转义:\-
  • 如果您想在一个槽中接受不同的选择,请在选项周围加上括号:[-.\s]将需要连字符、句点或空格。最后一个括号后面的问号将使所有这些都变为该插槽的可选项。
  • \d{3}:需要一个三位数:000-999。简写为[0-9][0-9][0-9]
  • [2-9]:该位必须填入2到9之间的数字。
  • (\+|1\s)?:接受一个“加号”或者一个1和一个空格(管道符号“|”表示“或”),并将其设为可选。 "加号"符号必须进行转义。
  • 如果您想要匹配特定的数字,输入它们:[246]需要2、4或6。(?:77|78)[77|78] 需要 77 或 78。
  • $/:结束表达式。

  • 1
    这非常有用,但我怀疑并正在寻找一个{min,max}表达式。你能帮忙吗? - Ataboy Josef
    如果我们正在讨论的是一个单个数字(并且您可以根据此使其匹配),请查看我放置在那里的[2-9]块。这意味着你的最小值是2,最大值是9。请相应地进行调整。 - vapcguy

    34

    我写了最简单的(虽然我不需要其中的点)。

    ^([0-9\(\)\/\+ \-]*)$

    正如下面所提到的,它仅检查字符,而不是其结构/顺序。


    41
    这个验证了很多在技术上是无效的数字,比如“-- +()()())())))”。学会阅读正则表达式,这样你就能理解自己在做什么了。 - Steven Soroka
    4
    @StevenSoroka 从技术上讲,它可能允许很多无效情况,但是考虑到仅仅通过最简单的解决方案帮助用户避免常见错误,这就是正确的方式 :) - happyhardik
    2
    这也匹配空格,空行。 - Wasim A.
    1
    @HappyHardik。确实如此。对于基本验证,让用户输入多个点、破折号、括号或加号,简单而强大。 - dijipiji
    1
    刚刚使用了你提供的正则表达式,发现有许多问题。例如,英国电话号码可能以+44开头,或者电话号码内部可能含有(0)。但是根据你的正则表达式,这些都不是有效的。我建议使用 @Ismael Miguel 的答案,它可以很好地工作,并建议您修改您的答案。 - Stelios Voskos

    23
    请注意,去掉 () 字符对于一种常见的英国电话号码写法无效:+44 (0) 1234 567890 这表示要么拨打国际号码:
    +441234567890
    要么在英国内部拨打号码:01234567890

    请参阅英国号码的国际表示法 - arekolek

    22

    如果您只想验证字段中没有随机垃圾(例如来自表单垃圾邮件发送者),则此正则表达式应该很好地完成:

    ^[0-9+\(\)#\.\s\/ext-]+$
    

    请注意,它没有任何特殊规则来确定数字的数量或有效数字,它只验证是否只包含数字、括号、破折号、加号、空格、井号、星号、句点、逗号或字母e、x、t。
    它应该与国际号码和本地化格式兼容。您预见到有必要为某些地区允许使用方括号、花括号或尖括号吗?(目前它们不包括在内)。
    如果您想保持每个数字的规则(例如,在美国,区域代码和前缀(交换代码)必须在200-999范围内),那么祝你好运。维护一个复杂的规则集可能会被世界上任何国家在未来的任何时候更新,这听起来并不好玩。
    虽然剥离所有/大多数非数字字符可能在服务器端运行良好(特别是如果您计划将这些值传递给拨号器),但您可能不希望在验证过程中破坏用户的输入,特别是如果您希望他们在另一个字段中进行更正。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接