處理文字資料好麻煩?你需要的是正規表示式!

林梓鈞 Ryan Lin
6 min readApr 30, 2021

--

這學期利用學校課程系統性地複習了 Python ,其中收穫最多的就是正規表示式(Regular Expression, Regex)。過去工作和分析多是處理數值資料,因此較少使用,但對於處理文字資料、進行 NLP(Natural Language Processing)、Sentiment Analysis 的工作是不可或缺的工具,尤其是當要查找的字串過於複雜、或文本資料髒亂,就會產生許多雖然規則相符但卻不符預期的結果產生,因此 Regex 和 SQL 有些類似的性質有些類似,上手容易,但許多情況是需要透過經驗累積才想到更好的解法,現在就跟著我快速理解、或複習 Regex!

以下範例使用 Python Re 示範。

什麼是正規表示式?

簡單的來說,就是利用一串由各自代表不同意義的符號組合,來表達字串的組成規則,通常用來查找文本中符合此規則的字串。最簡單的例子,就是在 Word 中使用 crtl + F ,查詢「Ryan」,完全符合這個單詞時,Word 就會標記幫助我們查看。

但如果每次都要如此詳盡才能找到一個單詞,就會耗費太多時間,不夠效率,因此有人利用不同符號將查找方式定義成規則,就成了正規表示式。

字元

從最基本的「字元」開始介紹,基本上可以分成三種類型:英文字母、數字、空白,分別對應:\w、\d、\s,一個表示符號只會對應到一個符合條件的字符,如下:

p = re.compile(r'\w\w\w\w\w') 
m = p.findall('hello world')
['hello', 'world']

如果只不想查找英文字母、數字、空白,分別對應:\W、\D、\S,. 則表示任何字元,包含特殊符號。但很奇特的是,竟然沒有符號代表「全部的特殊符號」,通常使用 \S 代表空白以外的字符,包含特殊符號。

倍數

了解字元後,字串長度也很重要,通常會在字元後放上倍數,代表需要查幾個,可以將上面範例修改成如下:

p = re.compile(r'\w{5}') 
m = p.findall('hello world')
['hello', 'world']

倍數有幾種表示方式,如下:

x{}    : 比對文字出現的次數 
x{3} : 剛好 3 個 x
x{1,3} : 1 到 3 個 x
x{1,} : 至少 1 個 x
x{,3} : 最多 3 個 x
x* : 出現 0 次或多次,相當於 {0,}
x+ : 出現 1 次或多次,相當於 {1,}
x? : 出現 0 次或 1 次,相當於 {0,1}

邊界

如果你想只找「Ryan」,但也可能會出現「RyanAir」,因此我們可以透過代表邊界的符號來區分,^ 代表開頭、$ 代表結尾,\b 代表邊界,可同時表示開頭與結尾,這裡指的是符合條件的邊界,如以下範例,因為此字串因為中間有空白格,因此符合 \w 的開頭就為 hello 的 h 和 world 的 w。

p = re.compile(r'^\w') 
m = p.findall('hello world')
['h', 'w']

群組

用()圈住的規則查找到的字串可以作為一個群組(Capturing Group),群組可以看作是一個整體,能夠運用倍數、邊界等其他規則,且匹配到的字串能夠重複查找,不需要撰寫重複的規則,這行為稱作回溯(Backreference)。

回溯有兩種方式,一種是根據群組的順序,使用 \1、\2 以此類推的方式使用,另一種則是可以為群組命名,使用(?P<name>),類似為變數命名,方便後續的讀取。

p = re.compile(r'(\w+)\1\1')
m = p.findall('ababab bilibilibili')
['ab', 'bili']p = re.compile(r'(?P<word>\w+)(?P=word)(?P=word)')
m = p.findall('ababab bilibilibili')
['ab', 'bili']

另一種群組方式,稱為 Non Capturing Group,只需要作為 Group 使用,不需要回溯。

p = re.compile(r'(\w+)\1\1')
m = p.findall('ababab bilibilibili')
['ab', 'bili']p = re.compile(r'(?P<word>\w+)(?P=word)(?P=word)')
m = p.findall('ababab bilibilibili')
['ab', 'bili']

環顧

如果同樣的規則在多單字內都有,這時會希望查找出現或不要出現在特定字串前後的字串,就會需要用到環顧(Lookaround),可以簡單地分成:

  • Positive Lookahead:查找指定字串前的字串,不包含指定字串, 因此範例結果為 jog 以前的文字 jfi。
p = re.compile(r'\w+(?=jog)')
m = p.findall('jfijogjiorejgi')
['jfi']
  • Positive Lookbehind:查找指定字串後的字串,不包含指定字串, 因此範例為結果為 jog 以後的文字 jiorejgi。
p = re.compile(r'(?<=jog)\w+')
m = p.findall('jfijogjiorejgi')
['jiorejgi']
  • Negative Lookahead:相對於 Positive Lookahead,查找不包含指定字串前的字串,範例為查找句末不為數字的單字,因此結果為 tuseday 。
p = re.compile(r'[a-z]+(?!\d)\b')
m = p.findall('monday1 tuseday')
['tuseday']
  • Negative Lookbehind:相對於 Positive Lookbehind,查找不包含指定字串後的字串,範例為查找僅為 6–8 位數的數字串,因此條件為數字串的前後不為數字,因此結果為 12345678。
p = re.compile(r'(?<!\d)\d{6,8}(?!\d)')
m = p.findall('12345678 987654321 0123456789')
['12345678']

以上就是 Regex 的基本規則,互相搭配使用可以變得相當複雜,但學會後,可以在不同語言,例如:R、Python、SQL 中都可以使用這套規則匹配你要查詢的特定字串,相當實用!

💡 我是 Ryan,目前在新加坡讀商業分析碩士,歡迎 follow 我的 Medium,如果有任何問題都歡迎和我聯繫,同時謝謝你閱讀本篇文章,如果喜歡這篇文章,請幫我拍手,你的鼓勵是我很大的激勵!

--

--

林梓鈞 Ryan Lin

Data Analyst @Lenovo | Data Science Career | LinkedIn: tzuchun-ryan-lin