用于修复澳大利亚/新西兰电话号码的Python正则表达式

时间:2010-07-07 04:29:46

标签: python regex phone-number

我有一个Python脚本,我们用它来解析用户输入的电话号码的CSV文件 - 呃,有很多奇怪的格式/错误。我们需要将这些数字解析为它们各自的组件,并修复一些常见的条目错误。

我们的电话号码是悉尼或墨尔本(澳大利亚)或奥克兰(新西兰),以国际格式提供。

我们的标准悉尼号码如下:

+61(2)8328-1972

我们有国际前缀+61,后跟括号中的单个数字区号2,后跟本地组件的两半,用连字符8328-1972分隔

墨尔本数字在区号中只有3而不是2,例如

+61(3)8328-1972

奥克兰的数字相似,但它们有一个7位数的本地成分(3个然后是4个数字),而不是正常的8位数。

+64(9)842-1000

我们还针对一些常见错误进行了匹配。我已将正则表达式分离为自己的类。

class PhoneNumberFormats():
    """Provides compiled regex objects for different phone number formats. We put these in their own class for performance reasons - there's no point recompiling the same pattern for each Employee"""
    standard_format = re.compile(r'^\+(?P<intl_prefix>\d{2})\((?P<area_code>\d)\)(?P<local_first_half>\d{3,4})-(?P<local_second_half>\d{4})')
    extra_zero = re.compile(r'^\+(?P<intl_prefix>\d{2})\(0(?P<area_code>\d)\)(?P<local_first_half>\d{3,4})-(?P<local_second_half>\d{4})')
    missing_hyphen = re.compile(r'^\+(?P<intl_prefix>\d{2})\(0(?P<area_code>\d)\)(?P<local_first_half>\d{3,4})(?P<local_second_half>\d{4})')
    space_instead_of_hyphen = re.compile(r'^\+(?P<intl_prefix>\d{2})\((?P<area_code>\d)\)(?P<local_first_half>\d{3,4}) (?P<local_second_half>\d{4})')

我们有一个用于standard_format数字,其他用于各种常见错误情况,例如:在区号(02而不是2), or missing hyphens in the local component (e.g. 83281972 instead of 8328-1972`等之前加上零。

然后我们从级联的if / elifs中调用这些:

def clean_phone_number(self):
    """Perform some rudimentary checks and corrections, to make sure numbers are in the right format.
    Numbers should be in the form 0XYYYYYYYY, where X is the area code, and Y is the local number."""
    if not self.telephoneNumber:
        self.PHFull = ''
        self.PHFull_message = 'Missing phone number.'
    else:
        if PhoneNumberFormats.standard_format.search(self.telephoneNumber):
            result = PhoneNumberFormats.standard_format.search(self.telephoneNumber)
            self.PHFull = '0' + result.group('area_code') + result.group('local_first_half') + result.group('local_second_half')
            self.PHFull_message = ''
        elif PhoneNumberFormats.extra_zero.search(self.telephoneNumber):
            result = PhoneNumberFormats.extra_zero.search(self.telephoneNumber)
            self.PHFull = '0' + result.group('area_code') + result.group('local_first_half') + result.group('local_second_half')
            self.PHFull_message = 'Extra zero in area code - ask user to remediate.'
        elif PhoneNumberFormats.missing_hyphen.search(self.telephoneNumber):
            result = PhoneNumberFormats.missing_hyphen.search(self.telephoneNumber)
            self.PHFull = '0' + result.group('area_code') + result.group('local_first_half') + result.group('local_second_half')
            self.PHFull_message = 'Missing hyphen in local component - ask user to remediate.'
        elif PhoneNumberFormats.space_instead_of_hyphen.search(self.telephoneNumber):
            result = PhoneNumberFormats.missing_hyphen.search(self.telephoneNumber)
            self.PHFull = '0' + result.group('area_code') + result.group('local_first_half') + result.group('local_second_half')
            self.PHFull_message = 'Space instead of hyphen in local component - ask user to remediate.'
        else:
            self.PHFull = ''
            self.PHFull_message = 'Number didn\'t match recognised format. Original text is: ' + self.telephoneNumber

我的目标是尽可能地使匹配尽可能紧,但至少还要抓住常见错误。

我上面所做的事情有很多问题:

  1. 我正在使用\d{3,4}来匹配本地组件的前半部分。然而,理想情况下,我们只想抓住3位数的前半部分如果,如果它是新西兰数字(即以+64(9)开头)。这样,我们可以标记缺少数字的悉尼/墨尔本数字。我可以在PhoneNumberFormats中将auckland_number分成它自己的正则表达式模式,但是,这意味着它不会捕获与错误情况相结合的新西兰数字(extra_zero,missing_hyphen,space_instead_of_hyphen)。因此,除非我为奥克兰重新创建它们的版本,例如auckland_extra_zero,这似乎是毫无意义的重复,我看不出如何轻松解决这个问题。
  2. 我们不会提取错误组合 - 例如如果他们有一个额外的零,并且缺少连字符,我们将不会选择它。有没有一种简单的方法可以使用正则表达式,而无需显式创建不同错误的排列?
  3. 我想解决上述两个问题,并希望收紧一点,以抓住我错过的任何事情。有没有更明智的方法来做我上面尝试过的事情?

    干杯, 维克多

    补充评论:

    以下只是提供一些背景信息:

    此脚本适用于全球公司,其中一个位于悉尼,一个位于墨尔本,另一个位于奥克兰。

    这些数字来自员工的内部Active Directory列表(即,它不是客户列表,而是我们自己的办公室电话)。

    因此,我们不是在寻找一般的澳大利亚电话号码匹配脚本,相反,我们正在寻找一个普通的解决方案来解析三个特定办公室的数字。一般来说,只有最后4个数字应该有所不同。

    不需要手机。

    该脚本旨在解析Active Directory的CSV转储,并将数字重新格式化为另一个程序的可接受格式(QuickComm)

    这个程序来自外部供应商,需要我在上面的代码中生成的确切格式的数字 - 这就是为什么这些数字像0283433422一样吐出来。

    我编写的脚本不能更改记录,它只适用于它们的CSV转储 - 记录存储在Active Directory中,访问它们以解决它们的唯一方法是通过电子邮件发送给员工并要求他们登录并更改自己的记录。

    所以这个脚本由PA运行,以产生该程序所需的输出。她/他还将获得数字格式不正确的人员列表 - 因此有关要求用户进行修复的消息。从理论上讲,应该只有少数这些。然后,我们通过电子邮件发送/响铃这些员工,要求他们修改他们的记录 - 脚本每月运行一次(数字可能会更改),我们还需要标记新员工,这些员工也设法输入错误的记录。

    @John Macklin:您是否建议我废弃正则表达式,并尝试从字符串中提取特定位置的数字?

    我一直在寻找一种方法来捕捉常见的错误情况,组合(例如空格而不是连字符,加上额外的零),但是这不容易实现吗?

3 个答案:

答案 0 :(得分:5)

不要使用复杂的正则表达式。删除除数字以外的所有内容 - 非数字是容易出错的数字。如果第三个数字为0,则将其删除。 预期61后面是有效的AUS区号([23478],一般性NB 4是移动电话),然后是8位数 或64后跟有效的NZL区号(无论是什么)后跟7位数。还有别的坏事。在好东西中,在适当的位置插入+() - 。

顺便说一下(1)区号2是整个NSW + ACT,不仅仅是悉尼,3是VIC + TAS(2)这些天很多人都没有固定电话,只有手机和人往往保留相同的手机号码比保持相同的固定电话号码或相同的邮政地址更长,因此手机号码非常适合模糊匹配客户记录 - 所以我有点好奇为什么你不这样做包括他们。

以下内容将告诉您有关AustralianNew Zealand电话号码方案的所有信息,以及更多内容。

评论正则表达式

(1)您正在使用带有“^”前缀的搜索方法。使用没有前缀的匹配方法稍微不那么优雅。

(2)您似乎没有检查电话号码字段中的尾随垃圾:

>>> import re
>>> standard_format = re.compile(r'^\+(?P<intl_prefix>\d{2})\((?P<area_code>\d)\
)(?P<local_first_half>\d{3,4})-(?P<local_second_half>\d{4})')
>>> m =standard_format.search("+61(3)1234-567890whoopsie")
>>> m.groups()
('61', '3', '1234', '5678')
>>>

您可能希望(a)以\ Z( $)结束某些正则表达式,以便在有尾随垃圾或(b)引入另一个组时将它们与OK匹配赶上垃圾垃圾。

社会工程评论:您是否已经测试了用户对执行此指令的工作人员的反应:“本地组件中的空格而不是连字符 - 请求用户进行修复”?脚本不能修复它并继续吗?

对代码的一些评论

self.PHFull代码

(a)非常重复(如果必须有正则表达式将它们放在带有相应操作代码和错误消息的列表中并遍历列表)

(b)对于“错误”情况与标准情况相同(那么为什么要求用户“修复”???)

(c)抛弃国家代码并替换为0,即您的标准+61(2)1234-5678被保留为0212345678 aarrgghhh ...即使您的国家存储的地址是不合适的,如果NZer迁移到Aus并且地址得到更新但不是电话号码,请不要说您依赖当前(奥克兰地区以外的新西兰客户???)区域代码不重叠......

完整故事发布后更新

让您和员工保持简单。使用Active Directory的工作人员的说明应该(取决于哪个办公室)“填写+61(2)9876-7后跟您的3位数分机号码”。如果他们在几次尝试后无法做到这一点,那么他们就得到了DCM。

因此,每个办公室使用一个正则表达式,填写常量部分,以便说SYD办公室的数字+61(2)9876-7ddd使用正则表达式r"\+61\(2\)9876-7\d{3,3}\Z"。如果正则表达式匹配,则删除所有非数字,并使用"0" + the_digits[2:]作为下一个应用。如果没有正则表达式匹配,请发送火箭。

答案 1 :(得分:3)

+1 @John Machin的建议。

World Telephone Number Guide对国家编号计划非常有用,尤其是例外情况。

ITU也有很多东西的免费标准。

答案 2 :(得分:0)

电话号码的格式是为了让人们更容易记住它们 - 没有理由我可以看到这样存储它们。为什么不用逗号分隔并通过简单地忽略任何不是数字的东西来解析每个数字?

>>> import string
>>> def parse_number(number):
    n = ''
    for x in number:
        if x in string.digits:
            n += x
    return n

一旦你得到它,你就可以根据itl前缀和区号进行验证。 (如果第3个数字是3,那么应该有7个数字等)

经过验证后,拆分成组件很容易。前两位是前缀,下一位是区号等。您可以在不使用正则表达式的情况下检查所有常见错误。在这种情况下,输出也很容易。