首頁 > 軟體

python中如何使用正規表示式提取資料

2023-11-01 10:00:20

正規表示式是一個特殊的字元序列,它能幫助你方便的檢查一個字串是否與某種模式匹配。

re 模組使 Python 語言擁有全部的正規表示式功能。

compile 函數可建立一個模式字串和可選的標誌引陣列成的一個正規表示式物件。該物件擁有一系列方法用於正規表示式匹配和替換。

re 模組也提供了與這些方法功能完全一致的函數,這些函數使用一個模式字串做為它們的第一個引數。

模式描述
^匹配字串的開頭
$匹配字串的末尾。
.匹配任意字元,除了換行符,當re.DOTALL標記被指定時,則可以匹配包括換行符的任意字元。
[...]用來表示一組字元,單獨列出:[amk] 匹配 'a','m'或'k'
[^...]不在[]中的字元:[^abc] 匹配除了a,b,c之外的字元。
re**匹配0次或多次。貪婪方式,re代表正規表示式
re++匹配1次或多次。
re??匹配0次或1次,非貪婪方式,匹配0次指表示式後面為空的也匹配
re{ n}連續匹配 n 個前面表示式。例如, o{2},連續匹配兩次o, 不能匹配 "Bob" 中的 "o",但是能匹配 "food" 中的兩個 o。
re{ n,}匹配 n 個前面表示式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。"o{1,}" 等價於 "o+"。"o{0,}" 則等價於 "o*"。
re{ n, m}表示匹配 連續的 前面的表示式 至少n次,至多 m 次。表示式 油{3,4} 就表示匹配 連續的 油 字 至少3次,至多 4 次
a| b匹配a或b
(re)對正規表示式分組並記住匹配的文字

常用正規表示式範例

字元匹配

範例描述
python匹配 "python".

字元類

範例描述
[Pp]ython匹配 "Python" 或 "python"
rub[ye]匹配 "ruby" 或 "rube"
[aeiou]匹配中括號內的任意一個字母
[0-9]匹配任何數位。類似於 [0123456789]
[a-z]匹配任何小寫字母
[A-Z]匹配任何大寫字母
[a-zA-Z0-9]匹配任何字母及數位
[^aeiou]除了aeiou字母以外的所有字元
[^0-9]匹配除了數位外的字元

特殊字元類

範例描述
.匹配除 "n" 之外的任何單個字元。要匹配包括 'n' 在內的任何字元,請使用象 '[.n]' 的模式。
d匹配一個數位字元。等價於 [0-9]。
D匹配一個非數位字元。等價於 [^0-9]。
s匹配任何空白字元,包括空格、製表符、換頁符等等。等價於 [ fnrtv]。
S匹配任何非空白字元。等價於 [^ fnrtv]。
w匹配包括下劃線的任何單詞字元。等價於'[A-Za-z0-9_]'。
W匹配任何非單詞字元。等價於 '[^A-Za-z0-9_]'。

re.match只匹配字串的開始,如果字串開始不符合正規表示式,則匹配失敗,函數返回None;而re.search匹配整個字串,直到找到第一個匹配。

# re.match()函數只能從起始的位置匹配,否則返回None
import re
 
matchObj = re.match('www', 'www.runoob.com')
print(re.match('www', 'www.runoob.com').span())  # 在起始位置匹配
print(re.match('com', 'www.runoob.com'))  # 不在起始位置匹配
# (0, 3)
# None
 
# 使用group(num) 或 groups() 函數來獲取用來匹配的正在表示式提取的值。
if matchObj:
    print("matchObj.group() : ", matchObj.group())
    # 執行結果是matchObj.group() :  www
    # matchObj.group() 等同於 matchObj.group(0),表示匹配到的完整文字字元
    # print ("matchObj.group(1) : ", matchObj.group(1))
    # print ("matchObj.group(2) : ", matchObj.group(2))
 
# re.search 掃描整個字串並返回第一個成功的匹配。
import re
 
line = "Cats are smarter than dogs";
 
searchObj = re.search(r'(.*) are (.*?) ', line, re.M | re.I)
 
if searchObj:
    print("searchObj.group() : ", searchObj.group())
    print("searchObj.group(1) : ", searchObj.group(1))
    print("searchObj.group(2) : ", searchObj.group(2))
# 執行結果
# searchObj.group() :  Cats are smarter
# searchObj.group(1) :  Cats
# searchObj.group(2) :  smarter
 
# re.sub()用於替換字串中的匹配項
 
import re
 
phone = "2004-959-559 # 這是一個國外電話號碼"
 
# 刪除字串中的 Python註釋,$匹配字串的末尾。
# 把匹配到的字串替換為空字串
num = re.sub(r'#.*$', "", phone)
print("電話號碼是: ", num)
# 電話號碼是:  2004-959-559
# 刪除非數位(-)的字串,D 匹配任意非數位
num = re.sub(r'D', "", phone)
print("電話號碼是 : ", num)
# 電話號碼是 :  2004959559
"""
findall在字串中找到正規表示式所匹配的所有子串,並返回一個列表,如果沒有找到匹配的,則返回空列表。
注意: match 和 search 是匹配一次 findall 匹配所有。
"""
import re
 
pattern = re.compile(r'd+')  # 建立個正著表示式物件,查詢數位
result1 = pattern.findall('runoob 123 google 456')
result2 = pattern.findall('run88oob123google456', 0, 10)
result3 = pattern.search('runoob 123 google 456')
# 123 只匹配了一次,匹配首個符合要求的字串
 
print(result1)
print(result2)
print(result3.group())
 
# 執行結果:
# ['123', '456']
# ['88', '12']
# 123

re.compile 函數

compile 函數用於編譯正規表示式,生成一個正規表示式( Pattern )物件,供 match() 和 search() 這兩個函數使用。

語法格式為:

import re
pattern = re.compile(r'd+')  # 用於匹配至少一個數位
m = pattern.match('one12twothree34four')  # 查詢頭部,沒有匹配
print(m)
# None
m = pattern.match('one12twothree34four', 2, 10)  # 從'e'的位置開始匹配,沒有匹配
print(m)
# None
m = pattern.match('one12twothree34four', 3, 10)  # 從'1'的位置開始匹配,正好匹配
print(m)  # 返回一個 Match 物件
# <re.Match object; span=(3, 5), match='12'>
print(m.group(0))  # 可省略 0,獲得整個匹配的子串時,可直接使用 group() 或 group(0);
# '12'
print(m.start(0))  # 可省略 0,獲取分組匹配的子串在整個字串中的起始位置(子串第一個字元的索引),引數預設值為 0;
# 3
print(m.end(0))  # 可省略 0,獲取分組匹配的子串在整個字串中的結束位置(子串最後一個字元的索引+1),引數預設值為 0;
5
print(m.span(0))  # 可省略 0,返回 (start(group), end(group))。
# (3, 5)

執行結果: 

None
None
<re.Match object; span=(3, 5), match='12'>
12
3
5
(3, 5)

括號()-分組 

括號稱之為 正規表示式的 組選擇。

組 就是把 正規表示式 匹配的內容 裡面 其中的某些部分 標記為某個組。

我們可以在 正規表示式中 標記 多個 組

為什麼要有組的概念呢?因為我們往往需要提取已經匹配的 內容裡面的 某些部分的資訊。

前面,我們有個例子,從下面的文字中,選擇每行逗號前面的字串,也 包括逗號本身 。

蘋果,蘋果是綠色的
橙子,橙子是橙色的
香蕉,香蕉是黃色的

就可以這樣寫正規表示式 ^.*, 。

但是,如果我們要求 不要包括逗號 呢?

當然不能直接 這樣寫 ^.*

因為最後的逗號 是 特徵 所在, 如果去掉它,就沒法找 逗號前面的了。

但是把逗號放在正規表示式中,又會包含逗號。

解決問題的方法就是使用 組選擇符 : 括號。

我們這樣寫 ^(.*), ,結果如下

大家可以發現,我們把要從整個表示式中提取的部分放在括號中,這樣 水果 的名字 就被單獨的放在 組 group 中了。

對應的Python程式碼如下

content = '''蘋果,蘋果是綠色的
橙子,橙子是橙色的
香蕉,香蕉是黃色的'''
 
import re
p = re.compile(r'^(.*),', re.MULTILINE)
for one in  p.findall(content):
    print(one)

多個分組時,怎麼取每個分組的值。

比如,我們要從下面的文字中,提取出每個人的 名字 和對應的 手機號

張三,手機號碼15945678901
李四,手機號碼13945677701
王二,手機號碼13845666901

可以使用這樣的正規表示式 ^(.+),.+(d{11})

可以寫出如下的程式碼

content = '''張三,手機號碼15945678901
李四,手機號碼13945677701
王二,手機號碼13845666901'''
 
import re
 
p = re.compile(r'^(.+),.+(d{11})', re.MULTILINE)
print(p.findall(content))
#findall()方法返回的是列表
m = p.search(content)
#列表不能呼叫group,因此需使用search()方法,但search方法只能匹配第一個符合的
print(m.group(1))
print(m.group(2))
for one in p.findall(content):
    print(one)
    print(type(one))
 
#執行結果
# [('張三', '15945678901'), ('李四', '13945677701'), ('王二', '13845666901')]
# 張三
# 15945678901
# ('張三', '15945678901')
# <class 'tuple'>
# ('李四', '13945677701')
# <class 'tuple'>
# ('王二', '13845666901')
# <class 'tuple'>
 

當有多個分組的時候,我們可以使用 (?P<分組名>...) 這樣的格式,給每個分組命名。

這樣做的好處是,更方便後續的程式碼提取每個分組裡面的內容

比如

import re
p = re.compile(r'^(?P<name>.+),.+(?P<phone>d{11})', re.MULTILINE)
print(p.finditer(content))
# 返回string中所有與pattern相匹配的全部字串,返回形式為迭代器。
for match in p.finditer(content):
    print(match.group('name'))
    print(match.group('phone'))
 
# 執行結果
# <callable_iterator object at 0x00000000027C2518>
# 張三
# 15945678901
# 李四
# 13945677701
# 王二
# 13845666901

總結:正則若匹配成功,match()/search()返回的是Match物件,finditer()返回的是Match物件的迭代器,獲取匹配結果需要呼叫Match物件的group()、groups或group(index)方法。 

  • group():母串中與模式pattern匹配的子串;
  • group(0):結果與group()一樣;
  • groups():所有group組成的一個元組,group(1)是字串中第一個匹配成功的子串分組,group(2)是第二個,依次類推,如果index超了邊界,丟擲IndexError;
  • findall():返回的就是所有匹配的子串陣列,就是子串元組組成的列表,例如上面的例子,母串中的第一行組成一個元組,第二行組成一個元組,這些元組共同構成一個list,就是findall()的返回結果。

方括號-匹配幾個字元之一

方括號表示要匹配 指定的幾個字元之一 。

比如

[abc] 可以匹配 a, b, 或者 c 裡面的任意一個字元。等價於 [a-c] 。

[a-c] 中間的 - 表示一個範圍從a 到 c。

如果你想匹配所有的小寫字母,可以使用 [a-z]

一些 元字元 在 方括號內 失去了魔法, 變得和普通字元一樣了。

比如

[akm.] 匹配 a k m . 裡面任意一個字元

這裡 . 在括號裡面不在表示 匹配任意字元了,而就是表示匹配 . 這個 字元

如果在方括號中使用 ^ , 表示  方括號裡面的字元集合。

比如

content = 'a1b2c3d4e5'
 
import re
p = re.compile(r'[^d]' )
for one in  p.findall(content):
    print(one)

[^d] 表示,選擇非數位的字元

輸出結果為:

a
b
c
d
e

切割字串

字串 物件的 split 方法只適用於 簡單的字串分割。 有時,你需要更加靈活的字串切割。

比如,我們需要從下面字串中提取武將的名字。

names = '關羽; 張飛, 趙雲,馬超, 黃忠  李逵'

我們發現這些名字之間, 有的是分號隔開,有的是逗號隔開,有的是空格隔開, 而且分割符號周圍還有不定數量的空格

這時,可以使用正規表示式裡面的 split 方法:

import re
 
names = '關羽; 張飛, 趙雲,   馬超, 黃忠  李逵'
 
namelist = re.split(r'[;,s]s*', names)
print(namelist)

正規表示式 [;,s]s* 指定了,分割符為 分號、逗號、空格 裡面的任意一種均可,並且 該符號周圍可以有不定數量的空格。

字串替換

匹配模式替換

字串 物件的 replace 方法只適應於 簡單的 替換。 有時,你需要更加靈活的字串替換。

比如,我們需要在下面這段文字中 所有的 連結中 找到所以 /avxxxxxx/ 這種 以 /av 開頭,後面接一串數位, 這種模式的字串。

然後,這些字串全部 替換為 /cn345677/ 。

names = '''
下面是這學期要學習的課程:
<a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>點選這裡,邊看視訊講解,邊學習以下內容</a>
這節講的是牛頓第2運動定律
<a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>點選這裡,邊看視訊講解,邊學習以下內容</a>
這節講的是畢達哥拉斯公式
<a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>點選這裡,邊看視訊講解,邊學習以下內容</a>
這節講的是切割磁力線
'''

被替換的內容不是固定的,所以沒法用 字串的replace方法。

這時,可以使用正規表示式裡面的 sub 方法:

import re
 
names = '''
下面是這學期要學習的課程:
<a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>點選這裡,邊看視訊講解,邊學習以下內容</a>
這節講的是牛頓第2運動定律
<a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>點選這裡,邊看視訊講解,邊學習以下內容</a>
這節講的是畢達哥拉斯公式
<a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>點選這裡,邊看視訊講解,邊學習以下內容</a>
這節講的是切割磁力線
'''
 
newStr = re.sub(r'/avd+?/', '/cn345677/' , names)
print(newStr)

sub 方法就是也是替換 字串, 但是被替換的內容 用 正規表示式來表示 符合特徵的所有字串。

比如,這裡就是第一個引數 /avd+?/ 這個正規表示式,表示以 /av 開頭,後面是一串數位,再以 / 結尾的 這種特徵的字串 ,是需要被替換的。

第二個引數,這裡 是 '/cn345677/' 這個字串,表示用什麼來替換。

第三個引數是 源字串。

指定替換函數

剛才的例子中,我們用來替換的是一個固定的字串 /cn345677/

如果,我們要求,替換後的內容 的是原來的數位+6, 比如 /av66771949/ 替換為 /av66771955/ 。

怎麼辦?

這種更加複雜的替換,我們可以把 sub的第2個引數 指定為一個函數 ,該函數的返回值,就是用來替換的字串。

如下

import re
 
names = '''
下面是這學期要學習的課程:
<a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>點選這裡,邊看視訊講解,邊學習以下內容</a>
這節講的是牛頓第2運動定律
<a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>點選這裡,邊看視訊講解,邊學習以下內容</a>
這節講的是畢達哥拉斯公式
<a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>點選這裡,邊看視訊講解,邊學習以下內容</a>
這節講的是切割磁力線
'''
 
# 替換函數,引數是 Match物件
def subFunc(match):
    # Match物件 的 group(0) 返回的是整個匹配上的字串
    src = match.group(0)
    
    # Match物件 的 group(1) 返回的是第一個group分組的內容
    number = int(match.group(1)) + 6
    dest = f'/av{number}/'
 
    print(f'{src} 替換為 {dest}')
 
    # 返回值就是最終替換的字串
    return dest
 
newStr = re.sub(r'/av(d+?)/', subFunc , names)
print(newStr)
# 正規表示式提取
import re
 
content = '''
Python3 高階開發工程師 上海互教教育科技有限公司上海-浦東新區2萬/月02-18滿員
測試開發工程師(C++/python) 上海墨鵾數碼科技有限公司上海-浦東新區2.5萬/每月02-18未滿員
Python3 開發工程師 上海德拓資訊科技股份有限公司上海-徐彙區1.3萬/每月02-18剩餘11人
測試開發工程師(Python) 赫裡普(上海)資訊科技有限公司上海-浦東新區1.1萬/每月02-18剩餘5人
Python高階開發工程師 上海行動教育科技股份有限公司上海-閔行區2.8萬/月02-18剩餘255人
python開發工程師 上海優似騰軟體開發有限公司上海-浦東新區2.5萬/每月02-18滿員
'''
 
for one in  re.findall(r'([d.]+)萬/每{0,1}月', content):
    print(one)
 
 
#非正規表示式提取
content = '''
Python3 高階開發工程師 上海互教教育科技有限公司上海-浦東新區2萬/月02-18滿員
測試開發工程師(C++/python) 上海墨鵾數碼科技有限公司上海-浦東新區2.5萬/每月02-18未滿員
Python3 開發工程師 上海德拓資訊科技股份有限公司上海-徐彙區1.3萬/每月02-18剩餘11人
測試開發工程師(Python) 赫裡普(上海)資訊科技有限公司上海-浦東新區1.1萬/每月02-18剩餘5人
Python高階開發工程師 上海行動教育科技股份有限公司上海-閔行區2.8萬/月02-18剩餘255人
python開發工程師 上海優似騰軟體開發有限公司上海-浦東新區2.5萬/每月02-18滿員
'''
 
# 將文字內容按行分割,放入列表,按rn,r(回車),n(換行)分割
lines = content.splitlines()
 
# print(lines)
for line in lines:
    # 查詢'萬/月' 在 字串中什麼地方
    # find() 方法檢測字串中是否包含子字串 str ,如果包含的話,返回子字串開始的索引,
    # 不包含的話返回-1
    pos2 = line.find('萬/月')
    # print(pos2)
    if pos2 < 0:
        # 查詢'萬/每月' 在 字串中什麼地方
        pos2 = line.find('萬/每月')
        # 都找不到,滿足條件,觸發continue,不執行後面的程式碼,跳到迴圈開頭進入下一輪迴圈
        if pos2 < 0:
            continue
    # 執行到這裡,說明可以找到薪資關鍵字
    # 接下來分析 薪資 數位的起始位置
    # 方法是 找到 pos2 前面薪資數位開始的位置
    idx = pos2 - 1
 
    # 只要是數位或者小數點,就繼續往前面找
    # isdigit()方法檢測字串是否只由數位組成,如果字串只包含數位則返回 True 否則返回 False
    while line[idx].isdigit() or line[idx] == '.':
        idx -= 1
 
    # 現在 idx 指向 薪資數位前面的那個字,
    # 所以薪資開始的 索引 就是 idx+1
    pos1 = idx + 1
    print(line[pos1:pos2])

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com