給自己的Python小筆記 - 強大的數據處理工具 - 正則表達式 - Regular Expression - regex詳細教學

為自己Coding
·
·
IPFS
·


Github完整程式碼連結


哈囉,今天想來講一個我在工作上,常常需要用到的工具,我常常需要在一整堆的系統資訊、System Trace等等資料中,萃取出我需要的資料,並自動整理成表格的形式,這時候強大的正則表達式(Regular Expression, regex)就幫了我一個很大的忙,所以今天就來一起好好的學習它吧


攝影師:Danny Meneses,連結:Pexels



正則表達式是什麼?

  • 對於我而言,它是一個非常強大且實用的字串處理方法,它幫助我們從擁有大量字符的文本中,取得我們所需的資訊
  • 正則表達式(Regular Expression 、regex、regexp or Re)透過我們自行定義的字符串規則,幫助我們從文本中找尋對應規則的字符串
  • 過濾出我們所需的資料後,幫助我們將這些資料組成串列,方便我們對文本進行下一步的分析
  • 舉個例子:我們要在文章中的眾多文字資料中,找尋文章提到的人物的身份證資料,我們就會去定義一個規則,第一個字符要是大寫的英文,後面要接續著九個數字,接著它就會根據這個規則找尋匹配的字符串,收集好後,回傳給我們


Python 與 正則表達式?

正則表達式是一種輕量型的程式語言,它並不是Python底下的一個套件而已,像是我也用過Javascript來使用正則表達式處理文本字符串資料,在Python中,我們只要引入re模組就能使用這個強大的字符串處理語言囉

  • 基本的匹配



  • 補充:常見的[...]匹配規則



  • 定義好的字符集



  • 邊界上的匹配



  • 數量上的匹配(通常用在其它匹配符之後)



補充 group():簡單來說,就是幫助我們方便看出匹配出來的字符有哪些

  • 邏輯與分組的概念



  • 特殊用法(不是用於分組)



  • 轉義字符



  • 特殊情況:當需要匹配一些在正則表達式中有特殊意義的字符時,像是\d、\w、\s等,此時就要多加一個""



  • 正則表達式的修飾符號
  • 大部分的正則表達式函數中,都會有一個參數flags,它是用來控制匹配的模式的,裡面有如下表的標誌可以供選擇,如果想一次指定多個標誌,可以使用OR("|"),像是re.I | re.S等組合



正則表達式的貪婪與非貪婪模式

通常在過程中匹配的字符數量,會有兩種情況:貪婪與非貪婪,而Python中的預設模式為貪婪模式

  • 貪婪:,不斷嘗試匹配更多的字符
  • 非貪婪:盡可能的嘗試少匹配字符,它會盡量減少匹配重複的字符
  • 程式碼舉例:
import re
​
## 貪婪模式
print(re.search('go*', 'goooooood').group()) ## 'gooooooo'
​
## 非貪婪模式
print(re.search('go*?', 'goooooood').group()) ## 'g'

執行結果

gooooooo
g

怎麼使用非貪婪模式呢?

我自己的實作心得是在語法後面加上一個"?",如下圖的非貪婪模式介紹




非貪婪模式的常見用法 .*?的用法介紹

  • .*? :盡量匹配較少的字符
  • 大多用在像是.*?a的地方,意思是前面匹配任何的字符,直到字母a出現re.search('.*?e','a_b*c defg').group()



正則表達式的函數介紹與實作


1. match 函數用法

  • re.match會從文本中的起始位置開始進行文字符的匹配,如果不是一開始第一個字符就匹配成功的話,就會直接返回一個none,簡單來說就是欲匹配的文本一開始就要符合我們定義的字符規則,不符合直接回傳none,符合就會回傳字符位置資訊


  • 函數語法格式
re.match(pattern, string, flags)
  • 參數介紹
  1. pattern: 匹配的規則,使用正則表達式的語法撰寫
  2. string: 要進行匹配的字符串
  3. flags: 設定一些正則表達式的匹配方式,像是規則是忽略大小寫,或使用UNICODE字符規則來解析字符等,如果沒有特別需求,可以忽略不寫 可以選擇的標誌,可以參考我上面有提到的正則表達式修飾符號


  • 使用group()函數來獲取匹配的字符,而不是返回一個字符的位置
  1. groups(): 將匹配好的字符組合起來,形成一個tuple元數組
  2. group(num=0): 選擇第幾個匹配好的字符


  • 程式碼舉例1
import re
​
text = 'https://matters.news/@CHWang'
text1 = 'Matters.news'
​
​
print(re.match('https', text))
print(re.match('https', text).span())
print(re.match('matters', text))
print(re.match('matters', text1))
print(re.match('matters', text1, flags = re.I))

執行結果

<re.Match object; span=(0, 5), match='https'>
(0, 5)
None
None
<re.Match object; span=(0, 7), match='Matters'>
  • 程式碼舉例2:關於group的用法
import re
​
​
text = 'Jack lives in HsinChu and he is 25 years old, but ...'
​
match_result = re.match(r'(.*) lives in ([a-z]*) and he is (\d+).*', text, re.I)
            
            
print(match_result.group())
print(match_result.group(1))
print(match_result.group(2))
print(match_result.group(3))
​
print(type(match_result.groups()))
print(match_result.groups())

執行結果

Jack lives in HsinChu and he is 25 years old, but ...
Jack
HsinChu
25
<class 'tuple'>
('Jack', 'HsinChu', '25')


2. Search 函數用法

  • re.search會搜尋整個字符串,然後找到匹配的字符並且傳回,如果失敗,沒有匹配到任何字符則傳回none,如果成功,就會傳回一個匹配的對象,就可以使用group()來取得匹配成功的字符
  • 函數語法格式
re.search(pattern, string, flags) 
  • 參數的用法與match是一樣的喔,這邊就不在說明
  • 程式碼範例1
import re
​
text = 'https://medium.com/@chwang12341'
text1 = 'Medium.Com'
​
print(re.search('https://', text))
print(re.search('dium', text))
print(re.search('medium', text).span())
​
print(re.search('co',text1))
print(re.search('co',text1, flags = re.I).span())

執行結果

<re.Match object; span=(0, 8), match='https://'>
<re.Match object; span=(10, 14), match='dium'>
(8, 14)
None
(7, 9)
  • 程式碼範例2:使用groups()與group(num)來取得字符
import re
​
text = 'Jen likes to eat cake and drink coke, but ...'
​
match_result = re.search('(.*) likes to eat (\w+) and drink ([a-z]*)', text, re.I|re.M)
​
print(match_result.group())
print(match_result.group(1))
print(match_result.group(2))
print(match_result.group(3))
​
print(match_result.groups())

執行結果

Jen likes to eat cake and drink coke
Jen
cake
coke
('Jen', 'cake', 'coke')


小筆記:大家有看出match與search的差別嗎?其實差別就在match一定要從起始位置開始匹配成功,而search則不用的喔!!


3. findall 函數用法


  • re.findall會直接找尋所有匹配的字符,裝進串列後返回,如果沒有找到匹配的字符,就會回傳一個空的串列喔
  • 小筆記:re.findall會匹配所有符合規則的字符,而re.search與re.match只會匹配一次而已喔
  • 函數格式
 findall(pattern, string, pos, endpos)
  • 參數說明
  1. pattern: 匹配的規則,使用正則表達式的語法來撰寫
  2. string:欲進行匹配的字符串
  3. pos: 可選擇的參數,不一定要寫,指定開始匹配的位置,預設為0,也就是起始字符的位置
  4. endpos: 可以選擇的參數,不一定要添加,指定結束匹配字符串的位置
  • 程式碼舉例:
import re
​
find_pattern = re.compile(r'[a-z]+', re.I)
​
match_result1 = find_pattern.findall('good 66 day Tom_28 Yep')
match_result2 = find_pattern.findall('good98MMorning66 Jen666 Yeah', 6,20)
​
print(match_result1)
print(match_result2)

執行結果

['good', 'day', 'Tom', 'Yep']
['MMorning', 'Jen']


4. sub 函數用法

  • 匹配好字符後,將它替換成我們想要的字符,這個方法相當方便,我們在進行數據處理時,有時候會有一些多餘的不要的空格、符號等等,就可以透過這個方法來一次拿掉


  • 函數語法格式 re.sub(pattern, repl, string, count = 0, flags)

  • 參數解釋
  1. pattern: 匹配的規則,使用正則表達式的語法來撰寫
  2. repl: 欲替換的字符,也可以用函數的形式傳入喔
  3. string: 要進行匹配的字符串
  4. count: 匹配好字符後,替換的最大數量,預設為0,表示要全部替換
  5. flags: 設定一些正則表達式的方式,像是規則是否忽略大小寫、使用UNICODE字符規則來解析字符等,如果沒有特別需求可以忽略不寫 可以選擇的標誌,可以參考我上面有提到的正則表達式修飾符號喔

  • 程式碼舉例1:
import re 
​
text = 'Jack/25/1993 and Jen/23/1995'
​
## 把中間的and與空格拿掉,用&替換
sub_result1 = re.sub('\sand\s', '&', text)
print(sub_result1)
​
## 狀況一: 再把/拿掉
sub_result2 = re.sub('/', '', sub_result1)
print(sub_result2)
​
## 狀況二: 再把/拿掉,但只要拿掉前兩個
sub_result3 = re.sub('/', '', sub_result1, 2)
print(sub_result3)

執行結果

Jack/25/1993&Jen/23/1995
Jack251993&Jen231995
Jack251993&Jen/23/1995


  • 程式碼舉例2:repl使用函數傳入時
import re
​
text = 'Jack66Jen58Ken28,Cathy38'
​
## 將匹配好的數字做平方計算
def square(match_result):
  num = int(match_result.group('number'))
  
  return str(num**2)
​
## 給定我們匹配值一個名稱,用?P<name>
final_result = re.sub('(?P<number>\d+)', square, text)
​
print(final_result)

執行結果

Jack4356Jen3364Ken784,Cathy1444


5. Compile 函數


  • re.compile可以幫助我們編譯正則表達式,並生成一個pattern對象,來供給match、search、findall函數使用,簡單來說,就是我們只要定義好一次正則表達式的規則,就能用這個定義好的pattern規則,來提供match、search、findall函數匹配字符
  • 用了這個方法後,我們就不用每次使用匹配函數時,都要重新寫一次正則表達式語法,但明明匹配的規則與寫法是一樣的
  • 函數語法格式:
re.compile(pattern, flags)
  • 參數介紹
  1. pattern: 匹配的規則,使用正則表達式的語法來撰寫
  2. flags: 設定正則表達式匹配的一些模式
  • 程式碼舉例:
import re
​
text = '68Jack66Jen58Ken28,Cathy38'
​
## 匹配字母,並忽略大小寫
pattern = re.compile(r'([a-z]+)', re.I)
​
## match預設從第一個位置開始匹配
compile_result1 = pattern.match(text)
print(compile_result1) ## None,因為match會從第一個位置開始匹配,如果不通過就會返回none
​
## 從第3個位置開始匹配
compile_result2 = pattern.match(text, 2, 20)
print(compile_result2) 
​
​
print(compile_result2.group(0)) 
print(compile_result2.start(0))
print(compile_result2.end(0))
print(compile_result2.span()) 

執行結果

None
<re.Match object; span=(2, 6), match='Jack'>
Jack
2
6
(2, 6)
  • 補充用法說明
  1. group(): 匹配好後,會回傳一個tuple,會根據匹配成功的字符一組一組返回,但由於match方法只會回傳一組,所以只要寫group()就好,其他的話,諾我們想要回傳第一組就寫group(0),以此類推
  2. start(): 起始位置,傳入要查詢的組別,像是第一組就寫start(0),以此類推
  3. end(): 結束位置,傳入要查詢的組別,像是第一組就寫end(0),以此類推
  4. span(): 傳回(起始位置,結束位置)


6. finditer 函數用法

  • re.finditer的用法與re.findall相似,找到所有符合匹配規則的字符後,以迭代器的形式傳回
  • 函數語法格式:
 re.finditer(pattern, string, flags) 
  • 程式碼舉例:
import re
​
match_result = re.finditer(r'[a-z]+', '68Jack66Jen58Ken28,Cathy38', re.I)
​
​
for name in match_result:
#  print(name)
  print(name.group())

執行結果

Jack
Jen
Ken
Cathy



7. split 函數用法

  • re.split將匹配的字符進行切割,並且回傳一組串列
  • 函數語法格式:
re.split(pattern, string, maxsplit, flags)
  • 參數介紹
  1. pattern: 匹配的規則,使用正則表達式的語法撰寫
  2. string: 欲進行匹配的字符串
  3. maxsplit: 分割的次數,如maxsplit=1,代表分割一次,預設為0,表示不限分割次數
  4. flags: 設定一些匹配的模式
  • 程式碼舉例:
#### import re
​
text = 'Jack66Jen58Ken28Cathy'
​
## 用數字來做為分隔依據
print(re.split('\d+', text))
​
## 分隔,並將數字也傳進陣列
print(re.split('(\d+)', text))
​
## 如果匹配的一句剛好在前後的位置,就會傳回空值
text1 = '66Jack66Jen58Ken28Cathy38'
print(re.split('\d+', text1))
​
## 如果找不到匹配會回串全部字串
print(re.split('\s+', text1))

執行結果

['Jack', 'Jen', 'Ken', 'Cathy']
['Jack', '66', 'Jen', '58', 'Ken', '28', 'Cathy']
['', 'Jack', 'Jen', 'Ken', 'Cathy', '']
['66Jack66Jen58Ken28Cathy38']



重要筆記:匹配時將我們需要爬取的數據,用()來包住它的匹配規則,才會被獨立出來放入串列


舉個例子來說,我們想要爬取文本字符串中符合我們指定格式的字符串,但是我們只想要取得|Example_format|前後的數值,並分別放入串列,這時候我們就需要將它們括號起來,像是(\d+)|Example_format|(\d+)這樣

指定的字符串格式:6658|Example_format|666

程式碼舉例:

import re
​
text = '6658|Example_format|2020'
​
print(re.findall(r'(\d+)(?:\WExample_format\W)(\d+)', text))

執行結果

[('6658', '2020')]



這樣的話,之後大家就可以在有很大量字符串的數據文本中,爬取符合我們需求格式的資料,而且我們不只要爬取符合這個格式的資料,還要只收集我們在這個格式中需要的數據值



太好了,會了強大的正則表達式(Regular Expression)後,我們就能在茫茫文字海中爬取各種我們所需的資料囉,真的超級實用!!感謝大家的閱讀,如果覺得我寫得還行XD,在幫我拍拍手鼓勵一下,或有問題也可以在底下留言跟我討論討論喔~~



Reference

https://www.itread01.com/content/1552631899.html

https://www.runoob.com/python/python-reg-expression.html#flags

https://kknews.cc/zh-tw/code/mjnv352.html

https://docs.python.org/zh-tw/3/howto/regex.html

CC BY-NC-ND 2.0 授权

喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!

為自己CodingYO~~ 剛跨入AI人工智慧領域的小小工程師, 熱愛自學, 熱愛分享, 下班後的我想為自己Coding, 積極撰寫教學文, 想將自學的程式知識分享給大家, 不斷追求進步的自己, 希望有一天能回饋社會,幫助需要幫助的人, 如果您有什麼很酷的想法,也覺得我還行,歡迎您找我合作~~ IG: https://www.instagram.com/coding_4_me/
  • 来自作者
  • 相关推荐

[Takeaways]原力效應 — Part1

[行銷5.0] 人工智慧的緣起

[Aptos學習筆記#8]Move進階使用 - Resource介紹一