牛骨文教育服务平台(让学习变的简单)
博文笔记

Python中文文本信息抽取中常见的正则表达式

创建时间:2017-09-09 投稿人: 浏览次数:1620

我在使用python做一些文本信息抽取的时候,用到了python的正则表达式匹配。所以这里对常见的python正则表达式做一个归纳。找干货直接看粗体字

本文使用的是python2.7.13版本解释器。
要点包括:中文的正则匹配,python的编码格式,re包里的一些函数


1. 座机电话号码

网上很多的文本信息给出的座机电话号码其实给出的很不规范,主要是中文和英文括号符号混用的问题,这涉及到了正则匹配中文的情况,用正则表达式去寻找文本中的有效的电话号码有点麻烦。比如以下这一段文本中给出的电话号码(我想我已经列的很全了)。

该通知于发布日起生效,请申请人于2017年8月30日前将材料递交到管理中心。
联系电话:(0355)3849512, (0355)39482753, 010 38492045,010-39481253, 0242—24891023,010——39281234,69384023,400-820-8820
联系人:陈先生,王小姐

在上面的例子中, 中文括号()和英文括号(),中文间隔符—和英文间隔符-,甚至区号和电话号码中间使用了中文破折号,还有的电话号码没有给出区号,一些客服电话,这些情况在相当多的文本当中都是存在的。

其中,像没有区号的电话 69384023,存在意义并不大,所以我不去抽取,当然如果确实需要抽取,那其实也很简单的,代码如下:

>>>import re
>>>tel = re.findall(r"d{7,8}", text)  # 输出为结果列表

为了解决混杂字符的电话号码抽取问题,我们需要知道python2.7的字符编码,参考python2.7中的字符编码问题。该文中的内容不多解释,举一个例子:

解释器按utf-8格式编码,我在解释器中存入上面的文本,显示得到的结果是:

>>>text
Out[3]: "xe8xafxa5xe9x80x9axe7x9fxa5xe4xbax8exe5x8fx91xe5xb8x83xe6x97xa5xe8xb5xb7xe7x94x9fxe6x95x88xefxbcx8cxe8xafxb7xe7x94xb3xe8xafxb7xe4xbaxbaxe4xbax8e2017xe5xb9xb48xe6x9cx8830xe6x97xa5xe5x89x8dxe5xb0x86xe6x9dx90xe6x96x99xe9x80x92xe4xbaxa4xe5x88xb0xe7xaexa1xe7x90x86xe4xb8xadxe5xbfx83 xe8x81x94xe7xb3xbbxe7x94xb5xe8xafx9dxefxbcx9axefxbcx880355xefxbcx893849512xefxbcx8c (0355)39482753xefxbcx8c 010-39481253xefxbcx8c 0242xe2x80x9424891023xefxbcx8c010xe2x80x94xe2x80x9439281234xefxbcx8c69384023 xe8x81x94400-820-8820xe7xb3xbbxe4xbaxbaxefxbcx9axe9x99x88xe5x85x88xe7x94x9fxefxbcx8cxe7x8ex8bxe5xb0x8fxe5xa7x90"

>>>len(text)
Out[13]: 248

字符串以str类型存入内存,一个汉字使用3个字节表示,x38x25x28表示一个汉字字符。因此该字符串的长度有248。

如果我以unicode格式存入上述文本,也就是在字符串前加u,显示结果为:

>>>text
Out[10]: u"u8be5u901au77e5u4e8eu53d1u5e03u65e5u8d77u751fu6548uff0cu8bf7u7533u8bf7u4ebau4e8e2017u5e748u670830u65e5u524du5c06u6750u6599u9012u4ea4u5230u7ba1u7406u4e2du5fc3 u8054u7cfbu7535u8bdduff1auff080355uff093849512uff0c (0355)39482753uff0c 010-39481253uff0c 0242u201424891023uff0c010u2014u201439281234uff0c69384023 u8054u7cfbu4eba400-820-8820uff1au9648u5148u751fuff0cu738bu5c0fu59d0"

>>>len(text)
Out[11]: 136

这里u9b23表示一个汉字,字符串长度是136,这才是我们想要的字符串的长度,(248-136)/2正是上文中汉字的个数。

需要说明,python在处理字符串的时候,使用print函数打印字符串中的内容,或将抽取到的信息存入数据库中,不论使用哪种编码方式,打印和存入数据库的结果都是中文没有问题。

下面列出电话号码中的几种符号编码,也可以在上面的代码中找得到。

符号 utf-8 unicode
中文 ( xefxbcx88 uff08
中文 ) xefxbcx89 uff09
中文 — xe2x80x94 u2014

根据这个可以进行座机电话号码的正则匹配。这篇文章比较全面的介绍了语法Python正则表达式指南。
下面直接给出现成代码:

>>>tel = re.findall(r"xefxbcx88?0d{2,3}xefxbcx89d{7,8}", text)
>>>print tel

console显示结果为:

(0355)3849512

由于上述座机号码的模式非常多,综合一下,处理如下:
如果采用了utf-8编码的话,匹配模式为(可以直接copy拿去,找出任何奇葩类型的座机电话号码)

>>>tel = re.findall(r"xefxbcx880d{2,3}xefxbcx89d{7,8}|(?0d{2,3}[) -]?d{7,8}|0d{2,3}xe2x80x94d{7,8}|0d{2,3}xe2x80x94xe2x80x94d{7,8}|d{3,4}[ -]?d{3,4}[ -]?d{4}", text)  # 输出结果为列表
>>>print tel
["xefxbcx880355xefxbcx893849512", "(0355)39482753", "010-39481253", "0242xe2x80x9424891023", "010xe2x80x94xe2x80x9439281234"]
>>>for i in xrange(len(tel)):
       print tel[i]
(0355)3849512
(0355)39482753
010-39481253
0242—24891023
010——39281234
400-820-8820

在这里必须要强调的是:
1、如果采用了unicode编码的话,正则匹配会失败,原因在于utf-8编码模式下,每一个ASCII码对应一个字节,而unicode编码下,每一个ascii码对应了两个字节,re包只能按照ascii码字符识别pattern中的内容,将这些字符转换成unicode之后,就识别不了了。
re包可以处理unicode 编码格式的匹配模式,比如 text=u”…” 这样,但是必须是先定义好字符串,然后再用将 该 text 字符串放在函数里,即可匹配unicode 格式的中文文本。
2、re包里提供了compile函数和findall函数,另外还有大量的match函数,search函数,split函数,group函数等等。在python解释器上执行这些函数的时候,调用代码对象而不是一个字符串,性能上会有明显提升。所以,如果是在做大量文本匹配的时候,要避免使用findall,而是用compile函数先将模式转换为代码对象,这样能够提高性能。
3、我在使用python的时候,发现Python2中,如果不定义编码格式,中文会有两种处理方式,一种是三个字节长度表示一个汉字,如上所述,另一种是两个字节长度表示一个汉字。这两种编码方式在python中都是被认可的。但是,在做中文正则匹配时,需要区别对待。


2. 手机号码

从大量文本中利用re抽取手机号码比较简单(我没有具体考虑三大运营商开放号码的细节),正则表达式为:

>>>pattern = re.compile(r"1[345789]d{9}")

但是如果文本里有一个银行账号如下:

请将汇款打入银行账号622204158291273940001,中国工商银行。

那么用上面的式子去匹配该段文本会找出来15829127394,错误的被当成了电话号码。
然后我决定这么匹配:

>>>pattern = re.compile(r"[^d]1[345789]d{9}[^d]")
>>>cell_num = re.findall(pattern, text)  # text即上面的文本
>>>if cell_num:
       for i in xrange(len(cell_num)):
           print cell_num[i][1:len(cell_num[i])-1]

这样就可以剔除银行账号,身份证号里的错误手机号,找到正确的电话号码了


3.电子邮箱

讲真网上很多博客写的那个电子邮箱表达式我看了、试了都觉得写的很是蛋疼,匹配种类少,错误多。有的只匹配163邮箱的,只匹配 .com邮箱,有的人连 w相当于字母数字下滑杠都不知道就出来写个博客瞎糊弄。
http://blog.csdn.net/catkint/article/details/55260281
http://blog.csdn.net/u012965373/article/details/51395599
http://blog.csdn.net/linwh8/article/details/50614363
http://www.bkjia.com/ASPjc/846889.html
http://www.cjjjs.com/paper/bcyy/2016821144646609.aspx
以上都是不可取的。
邮箱种类很多:

dongdong.123@163.com
学校的邮箱xixi_xixi.xixi@buaa.com.cn
企业的邮箱123beibei.123_beibei@qiye.csn.net

值得参考的是这个博客里的代码,比较全面,可以拿来主义
python邮箱匹配的正确姿势

>>>email_pattern = re.compile(r"([w-]+(.[w-]+)*@[w-]+(.[w-]+)+)")

具体用法是:

>>>email = re.findall(email_pattern, text)  # text代表被抽取邮箱信息的文本
>>>outcome = []
>>>if email:
       for i in xrange(email):
           outcome.append(email[i][0])
>>>print outcome  # 得到文本中所有的邮箱并存入列表

4.身份证号码

不多说了,比较单一固定,拿来主义

r = r"^([1-9]d{5}[12]d{3}(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])d{3}[0-9xX])$"
ID_num = re.findall(r, text)

5.银行卡号

也是比较固定的位数,格式有好几种:

中国工商银行:622202 1702357908932
中国农业银行:6222 6247 1935 7438 072
中国民生银行:6223-1024-0589-2038-132
中国工商银行:6222021702357908932

没啥难度,当然,银行卡前几位的数字也和手机号一样,很有规律,但是因为银行卡号位数比较固定,所以就不考虑那么多情况了。拿来主义一下:

r = r"d{4}[- ]?d{4}[- ]?d{4}[- ]?d{4}[- ]?d{3}|d{6}[ -]d{13}"
account_num = re.findall(r, text)
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。