【NLP】Day 3: 自然語言處理的內力,一切的基礎!正規表達式 (1) 搜尋

自來修習內功,不論是為了強身治病,還是為了作為上乘武功的根基,必當水火互濟,陰陽相配,練了「足少陰腎經」之後,便當練「足少陽膽經」,少陰少陽融會調合,體力便逐步增強。
金庸《俠客行》

在進行自然語言處理的同時,常常會需要將資料變得乾淨,或是也有些特定的資料格式要從大量語料中抽取出來,比如說像是地址、電話等等,才有辦法取出我們想要的理想資料,而在這過程中常常會用到的就是正規表達式(regular expression),正規表達式也會被簡稱為re。re可以針對符合的字串大致上分成三種功能:搜尋、替換,以及抽取。

正規表達式

讓我們先以手機號碼舉例,假如我們要從大量語料中抽取手機號碼,例如:0912-345-678,該怎麼做呢?首先,編寫regular expression一個很重要的觀念是要一個字一個字看,並輸入對應的正規表達式。回到手機號碼,我們觀察手機號碼0912-345-678,可以發現全部都是用數字組成,數字的正規表達式為\d,我們首先可以將手機號碼以這種方法表示:

1
\d\d\d\d-\d\d\d-\d\d\d

但是這樣並不是一個足夠有效率的寫法,若在如\d後面加上大括號,則可以指定其出現次數。我們可以發現以橫槓分隔,第一組是由四個數字,後面則是由兩組三個數字所組成。逗號的分隔方式代表其次數,{1,}為一次以上,{,2}為兩次以下,{1,3}則為一到三次。

1
\d{4}-\d{3}-\d{3}

但若要取出符合這個樣式的第一組數字的話,則可以透過小括號,將這些數字分組,並在撰寫程式時指定回傳特定組的字串,整串符合的字串為group 0,第一組,四個數字的,也就是group 1,以此類推。寫法如下:

1
(\d{4})-(\d{3})-(\d{3})

那假如今天要找的是電話號碼,像是(01)234-5678時,裡面有小括號,該怎麼辦才能讓程式知道這裡得括號並不是前述分組的意思?這時我們可以利用逃脫符號\告訴編譯器,這裡的括號是指真的括號,任何會跟正規表達式衝突的符號,都可以透過這種方法進行編寫,寫法如下:

1
\(\d{2}\)\d{3}-\d{4}

這邊只有介紹到數字的寫法,欲得知其他寫法,如英文字母等等的,可以參考以下這個 cheatsheet,另外若要確認自己寫的正規表達式是否符合的話,也可以運用這個網站 regex101 來進行比對。

搜尋

前面有提到說,regular expression有進行搜尋的功能,我們可以將正規表達式的字串放入函式中,確認資料中是否有我們想要的字段。這時,可以運用re函式庫中的search()來達到我們的目的。第一個參數放正規表達式,第二個參數放欲檢驗的字串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import re # 記得加入re函式庫
print(re.search("(\d{4})-(\d{3})-(\d{3})", "0987-654-321"))
# output: <re.Match object; span=(0, 12), match='0987-654-321'>

# 輸出的意思是說,若有符合的字串,就會回傳正規表達式的物件
# span則為符合物件的字串在語料中的位置為何,像這邊是整串符合
# 那就是從index = 0 的地方開始到12都是符合的字串。
# match則是符合的字串。

print(re.search("(\d{4})-(\d{3})-(\d{3})", "0987654321"))
# output: None

# 若沒有符合字串,就會回傳None

# 小應用是,也可以把這個當作條件判斷式的條件之一,例如:

if re.search("(\d{4})-(\d{3})-(\d{3})", "0987-654-321"):
print(123)
# output: 1234
# 這邊因為re.search()有符合字串,判斷為True,執行指定動作

if re.search("(\d{4})-(\d{3})-(\d{3})", "0987654321"):
print(456)
# 判斷為False,不進行任何動作

記得前面說的分組概念嗎?在這裡就可以派上用場了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
print(example_re_obj.group(0))

# 你也可以指派一個變數給他,如:
example_re_obj = re.search("(\d{4})-(\d{3})-(\d{3})", "0987-654-321")

# 記得前面說的分組概念嗎?在這裡就可以派上用場了。
print(example_re_obj.group(0))
# output: 0987-654-321
print(example_re_obj.group(1))
# output: 0987
print(example_re_obj.group(2))
# output: 654
print(example_re_obj.group(3))
# output: 321

好的,大概就是這樣,完成了這步驟,接下來就可以往「替換」以及「抽取」的目的地前進了!我們明天見!