1. 程式人生 > >Python中文文字資訊抽取中常見的正則表示式

Python中文文字資訊抽取中常見的正則表示式

我在使用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]: '\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\x
9c\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,顯示結果為:

>>>text
Out[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)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.電子郵箱

[email protected]
學校的郵箱[email protected]
企業的郵箱[email protected]

值得參考的是這個部落格裡的程式碼,比較全面,可以拿來主義
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)