0

我有一个 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 of8328-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)。这样,我们可以标记缺少数字的悉尼/墨尔本号码。我可以将 auckland_number 分离成它自己的正则表达式模式PhoneNumberFormats,但是,这意味着它不会捕获结合错误情况(extra_zero、missing_hyphen、space_instead_of_hyphen)的新西兰数字。因此,除非我仅为奥克兰重新创建它们的版本,例如 auckland_extra_zero,这似乎毫无意义地重复,否则我看不出如何轻松解决这个问题。
  2. 我们不会拾取错误的组合——例如,如果它们有一个额外的零和一个缺失的连字符,我们将不会拾取它。有没有一种简单的方法可以使用正则表达式来做到这一点,而无需明确创建不同错误的排列?

我想解决以上两个问题,并希望稍微收紧一点,以抓住我错过的任何东西。有没有更聪明的方法来做我上面试图做的事情?

干杯,维克多

补充评论:

以下只是提供一些上下文:

此脚本适用于一家全球性公司,在悉尼设有办事处,在墨尔本设有办事处,在奥克兰设有办事处。

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

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

不需要手机。

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

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

我编写的脚本无法更改记录,它仅适用于它们的 CSV 转储 - 记录存储在 Active Directory 中,访问它们以修复它们的唯一方法是向员工发送电子邮件并询问他们登录并更改自己的记录。

所以这个脚本由一个 PA 运行,以产生这个程序所需的输出。她/他还将获得一份数字格式错误的人员列表 - 因此会收到有关要求用户进行补救的消息。理论上,这些应该只有少数。然后我们给这些员工发电子邮件/打电话,要求他们修复他们的记录 - 脚本每月运行一次(数字可能会改变),我们还需要标记那些设法输入错误记录的新员工。

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

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

4

3 回答 3

5

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

顺便说一下 (1) 区号 2 代表整个 NSW+ACT,而不仅仅是悉尼,3 代表 VIC+TAS (2) 现在很多人没有固定电话,只有手机,而且人们倾向于保留同一个手机号码比他们保持同一个固定电话号码或同一个邮政地址的时间长,所以手机号码非常适合模糊匹配客户记录——所以我有点好奇你为什么不包括它们。

以下内容将告诉您有关澳大利亚新西兰电话号码方案的所有信息,以及更多信息。

评论正则表达式

(1) 您正在使用带有“^”前缀的搜索方法。使用不带前缀的 match 方法有点不优雅。

(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 ( NOT $) 结束您的一些正则表达式,以便在有尾随垃圾时它们不匹配 OK 或 (b) 引入另一个组来捕获尾随垃圾。

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

以及对代码的一些评论

self.PH完整代码

(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:]用于下一个应用程序。如果没有匹配的正则表达式,发送火箭。

于 2010-07-07T04:59:09.393 回答
3

+1 @John Machin 的建议。

世界电话号码指南对于国家编号计划非常有用,尤其是例外情况。

国际电联也为很多东西提供了免费的标准。

于 2010-07-07T06:17:52.060 回答
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 个数字,等等)

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

于 2010-07-07T04:59:45.850 回答