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

来源:互联网 发布:淘宝网单号查物流信息 编辑:程序博客网 时间:2024/05/22 03:34

我在使用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格式编码,我在解释器中存入上面的文本,显示得到的结果是:

>>>textOut[3]: '\xe8\xaf\xa5\xe9\x80\x9a\xe7\x9f\xa5\xe4\xba\x8e\xe5\x8f\x91\xe5\xb8\x83\xe6\x97\xa5\xe8\xb5\xb7\xe7\x94\x9f\xe6\x95\x88\xef\xbc\x8c\xe8\xaf\xb7\xe7\x94\xb3\xe8\xaf\xb7\xe4\xba\xba\xe4\xba\x8e2017\xe5\xb9\xb48\xe6\x9c\x8830\xe6\x97\xa5\xe5\x89\x8d\xe5\xb0\x86\xe6\x9d\x90\xe6\x96\x99\xe9\x80\x92\xe4\xba\xa4\xe5\x88\xb0\xe7\xae\xa1\xe7\x90\x86\xe4\xb8\xad\xe5\xbf\x83 \xe8\x81\x94\xe7\xb3\xbb\xe7\x94\xb5\xe8\xaf\x9d\xef\xbc\x9a\xef\xbc\x880355\xef\xbc\x893849512\xef\xbc\x8c (0355)39482753\xef\xbc\x8c 010-39481253\xef\xbc\x8c 0242\xe2\x80\x9424891023\xef\xbc\x8c010\xe2\x80\x94\xe2\x80\x9439281234\xef\xbc\x8c69384023 \xe8\x81\x94400-820-8820\xe7\xb3\xbb\xe4\xba\xba\xef\xbc\x9a\xe9\x99\x88\xe5\x85\x88\xe7\x94\x9f\xef\xbc\x8c\xe7\x8e\x8b\xe5\xb0\x8f\xe5\xa7\x90'>>>len(text)Out[13]: 248

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

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

>>>textOut[10]: u'\u8be5\u901a\u77e5\u4e8e\u53d1\u5e03\u65e5\u8d77\u751f\u6548\uff0c\u8bf7\u7533\u8bf7\u4eba\u4e8e2017\u5e748\u670830\u65e5\u524d\u5c06\u6750\u6599\u9012\u4ea4\u5230\u7ba1\u7406\u4e2d\u5fc3 \u8054\u7cfb\u7535\u8bdd\uff1a\uff080355\uff093849512\uff0c (0355)39482753\uff0c 010-39481253\uff0c 0242\u201424891023\uff0c010\u2014\u201439281234\uff0c69384023 \u8054\u7cfb\u4eba400-820-8820\uff1a\u9648\u5148\u751f\uff0c\u738b\u5c0f\u59d0'>>>len(text)Out[11]: 136

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

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

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

符号 utf-8 unicode 中文 ( \xef\xbc\x88 \uff08 中文 ) \xef\xbc\x89 \uff09 中文 — \xe2\x80\x94 \u2014

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

>>>tel = re.findall(r'\xef\xbc\x88?0\d{2,3}\xef\xbc\x89\d{7,8}', text)>>>print tel

console显示结果为:

(0355)3849512

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

>>>tel = re.findall(r'\xef\xbc\x880\d{2,3}\xef\xbc\x89\d{7,8}|\(?0\d{2,3}[) -]?\d{7,8}|0\d{2,3}\xe2\x80\x94\d{7,8}|0\d{2,3}\xe2\x80\x94\xe2\x80\x94\d{7,8}|\d{3,4}[ -]?\d{3,4}[ -]?\d{4}', text)  # 输出结果为列表>>>print tel['\xef\xbc\x880355\xef\xbc\x893849512', '(0355)39482753', '010-39481253', '0242\xe2\x80\x9424891023', '010\xe2\x80\x94\xe2\x80\x9439281234']>>>for i in xrange(len(tel)):       print tel[i](0355)3849512(0355)39482753010-394812530242—24891023010——39281234400-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)
阅读全文
1 0