一、正則基本知識
正則在python中是以c實作的,在後續的爬蟲等工作都需要使用到該子產品,主要搜尋文本的内容。搜尋非常速度。
http://tool.oschina.net/regex
https://www.regexpal.com/ 兩個個線上的正規表達式,可以用來借鑒
1.1 正則常用的比對規則
下面是列舉出來一些常用的,更多的使用help(re)檢視。
正則預設都是貪婪比對的(盡可能的多,貪吃)。還有一種懶惰比對(取到最少那次就不幹活了)。
?是一個非常有意思的規則。 自己單獨的時候,表示0或1個字元。
和貪婪的組合在一起的時候,就變成了控制貪婪變成懶惰比對了。
. 代表任意字元,但是除了換行符
# 開頭和結尾
^ 代表字元串的開始
$ 代表字元串的結尾 ,如果是多行文本,預設隻比對第一行的結尾。
# 任意字元,貪婪比對
* 比對0或多次,貪婪比對,取到滿足的最多
+ 比對1或多次,貪婪比對,取到滿足的最多
? 比對0或一次,貪婪比對,取到滿足的最多
?還可結合*,+,?做懶惰比對
# ?組合-懶惰比對
*? +? ?? 前面的任意字元,得到最少的那次
# 指定次數 比對
{m} 比對m次。
{m,n} 比對m到n次,和切片的使用方法一緻。也是貪婪比對
{m,} m到無窮
{n,} 0 到n次
a{m} 比對6個a
{m,n}? 更改成懶惰,得到最少的那次比對
# 分組
() 下面會詳細的講解
# 字元集
[] 隻取其中一個
[0-9] 代表 1到9,
[a-z] 代表 a到z
[a-zA-z0-9] 所有的字元和數字
[abc] 比對任意一個
[^9] 除了9以外其他的所有
# 或
| 常常搭配字元集和分組使用,[a|b]:找到a了,就不找b了,反之相同。
1.2 轉義字元 \
python的正則規則是以引号 包圍的,裡面的的一些字元 要代表本身的含義,或 轉成其他意思,就需要這個\。
紅色的為常用的。
\. 預設.是代表任意字元,我們可以讓他轉義為。本身
\A 隻在字元串開頭進行比對。
\b 比對位于開頭或者結尾的空字元串
\B 比對不位于開頭或者結尾的空字元串
\d 比對任意十進制數,相當于 [0-9]
\D 比對任意非數字字元,相當于 [^0-9]
\s 比對任意空白字元,相當于 [ \t\n\r\f\v]
\S 比對任意非空白字元,相當于 [^ \t\n\r\f\v]
\w 比對任意數字和字母,相當于 [a-zA-Z0-9_]
\W 比對任意非數字和字母的字元,相當于 [^a-zA-Z0-9_]
\Z 隻在字元串結尾進行比對
\number 在分組中使用,類似占位符
二 、兩種使用方法
2.1 方法1:compile
利用compile方法,生成一個re對象,好處是可以多次使用
import re
res = re.compile('\d{3}') # 第一步,把正則字元串進行編碼,傳回一個re對象,儲存為一個常量
result = res.search('I am hui 123') # 第二步,把compile傳回的對象,去比對字元串,傳回一個mactch對象
result.group() # 第三步,使用match對象的group方法,傳回實際比對的對象
正則支援鍊式程式設計
2.2 方法2:re.方法
直接使用re的方法,對于單一的使用比較友善,如果每次使用,每次都要定義。
import re
x = re.search('\d{3}', 'I am hui 123') # 直接使用match對象
x.group() # 使用match對象的group方法,傳回實際比對的對象
正則支援鍊式程式設計
三、函數
3.1 compile
compile(pattern, flags=0)
Compile a regular expression pattern into a regular expression object re.compile(pattern, flags=0)
把正規表達式的模式和辨別轉化成正規表達式對象,供 match() 和 search() 這兩個 函數使用。
常用的flags:
re.I 忽略大小寫
re.L 表示特殊字元集 \w, \W, \b, \B, \s, \S 依賴于目前環境
re.M 多行模式
re.S 把‘.’切換成 包含換行符,預設 點 是不包含換行符号的。
re.X 為了增加可讀性,忽略空格和’ # ’後面的注釋
一個正則的注釋的列子:
import re
regex = re.compile(r'(\d{1,3}\.){3}\d{1,3}') # 錯誤寫法
regex = re.compile(r'''
(2[0-4]\d|25[0-5]|[01]?\d\d?\.) # 代表一組ip,包含後面的點
{3} # 表示3組
(2[0-4]\d|25[0-5]|[01]?\d\d?) # 最後一組數字
''', re.X)
print(regex.match('192.168.1.1'))
print(regex.match('999.999.999.999')) # 非正常的ip
3.2 seach
搜尋整個字元串,傳回第一個比對的對象-match對象
search(pattern, string, flags=0)
Scan through string looking for a match to the pattern, returning
a match object, or None if no match was found.
import re
s = """
1334-1234-113
133-1234-2123
135-4567-3456
[email protected]
[email protected]
[email protected]
https://github.com
https://taobcom.com
"""
target = '\d{4}'
target = '\d{3}(-\d{4}){2}'
regex = re.compile(target)
result = re.search(regex, s)
if result:
print(result.group())
else:
print('找不到')
3.2 match
比對字元串的開頭,符合則傳回一個match對象。
注意match 和 seach的不同。兩者都是隻比對一次,找到符合就傳回,後面的就不再比對。如果不符合就傳回None
macth:隻比對字元串的開頭,注意前面的空白字元,特别是/n 換行。
seach:搜尋整個字元串。
import re
reg = re.compile('\d{3}')
s1 = '1234dasd' # 正常内容
s2 = '/n1234dasd' # 前面有換行符
s3 = ' 123' # 前面有空格
print(reg.match(s1))
print(reg.match(s2))
print(reg.match(s3))
結果:
<_sre.SRE_Match object; span=(0, 3), match='123'>
None
None
3.3 findall
比對所有的字元串,到結束為之。傳回一個清單。
import re
s = 'ferfewf234ed3de8ge4r3434rde8ger3r3rde8gede8ger34rf34r'
rege = re.compile('de8ge')
# rege = re.compile('de(8)ge') # 進行分組
result = rege.findall(s)
print(result)
3.4 spilit
使用正則的規則,來進行分割。類似字元串的spilt
import re
s = 'ferfewf234ed3de8ge4r3434rde8ger3r3rde8gede8ger34rf34r'
rege = re.compile('de8ge') # 抛棄了de8ge
rege = re.compile('(de8ge)') # 以de8ge進行分割,但是會傳回de8ge
result = rege.split(s)
print(result)
3.5 sub
字元串中也有字元串的替換,replace,。正則中使用sub來替換字元串的内容,比起字元串的内容,整體更靈活。
第一種使用分組進行替換
import re
s = '字元串格式: 10/01/2008, 12/25/2018'
re_date = re.compile(r'(\d+)/(\d+)/(\d+)') # r是raw的意思,原生字元串
x=re_date.sub(r'\3-\1-\2', s)
print(x)
第二種 進行字元替換
import re
s = """
pytyhon is hard to learn, 堅持下,
沒多少了 ok?
"""
# print(s)
regex = re.compile('\s+')
print(regex.sub('', s))
四、擴充知識
4.1 貪婪比對 與 懶惰比對
?是一個非常有意思的規則。
1. 自己單獨的時候,表示0或1個字元。
2. 和貪婪的組合在一起的時候,就變成了控制貪婪變成懶惰比對了。
*? 重複任意次,但盡可能少重複
+? 重複1次或更多次,但盡可能少重複
?? 重複0次或1次,但盡可能少重複
{n,m}? 重複n到m次,但盡可能少重複
{n,}? 重複n次以上,但盡可能少重複
# 小案列,想比對引号内部的,說話的内容
import re
text = 'Ipone say "yes." PC say "No."'
regex = re.compile('"(.*)"') # 使用貪婪比對,會得到最長的那串
#regex = re.compile('"(.*?)"') # 加上?,變成懶惰比對就正常了
print(regex.findall(text))
4.2 分組
分組是用()來表示的,分組在我個人感覺 有三種作用:
1. 将某些規律看成是一組,然後進行組級别的重複。
2. 分組之後,可以通過後向引用簡化表達式。
3. 和find組合,查找指定的字元
第一個作用
用ip位址(這個是簡單版本,ip的真實看上面)作為執行個體:
\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}
但仔細觀察,我們可以發現一定的規律,可以把.\d{1,3}看成一個整體,也就是把他們看成一組,再把這個組重複3次即可。表達式如下:
\d{1,3}(\.\d{1,3}){3}
這樣一看,就比較簡潔了。
第二個作用:
首先強調一個知識點,後向引用,指派的是比對後的結果,非正規表達式。
<title>.*</title>
可以看出,上邊表達式中有兩個title,完全一樣,其實可以通過分組簡寫。表達式如下:
<(title)>.*</\1>
這個例子實際上就是反向引用的實際應用。對于分組而言,整個表達式永遠算作第0組,在本例中,第0組是<(title)>.*</\1>,然後從左到右,依次為分組編号,是以,(title)是第1組。
用\1這種文法,可以引用某組的文本内容,\1當然就是引用第1組的文本内容了,這樣一來,就可以簡化正規表達式,隻寫一次title,把它放在組裡,然後在後邊引用即可。
以此為啟發,我們可不可以簡化剛剛的IP位址正規表達式呢?原來的表達式為\d{1,3}(.\d{1,3}){3},裡邊的\d{1,3}重複了兩次,如果利用後向引用簡化,表達式如下:
(\d{1,3})(.\1){3}
簡單的解釋下,把\d{1,3}放在一組裡,表示為(\d{1,3}),它是第1組,(.\1)是第2組,在第2組裡通過\1文法,後向引用了第1組的文本内容。
經過實際測試,會發現這樣寫是錯誤的,為什麼呢?
小菜一直在強調,後向引用,引用的僅僅是文本内容,而不是正規表達式!
也就是說,組中的内容一旦比對成功,後向引用,引用的就是比對成功後的内容,引用的是結果,而不是表達式。
是以,(\d{1,3})(.\1){3}這個表達式實際上比對的是四個數都相同的IP位址,比如:123.123.123.123。
第三作用:
和find組合,提取組的内容。 先比對整個正規表達式,根據結果再次提取()裡面的内容
import re
data = '''
○ 4.1日,共有4人面試,手機号分别是13812345678,15112345678,13812345678,15112345678
○ 4.5日,共有6人面試13812345678,15112345678,13812345678,15112345678,13812345678,15112345678
○ 4.7日,共有3人面試13812345678,15112345678,13812345678
○ 4.8日,共有5人面試15112345678,13812345678,15112345678,13812345678,15112345678
4.30日,共有6人面試13812345678,15112345678,13812345678,15112345678,13812345678,15112345678
'''
regex = re.compile('共有(\d+)人') # 先比對整個正規表達式,根據結果再次提取()裡面的内容
sum_people = sum([int(i) for i in regex.findall(data)])
print(sum_people)
分組命名:
對于後向引用采用編号的方式進行替換,容易混淆,采用命名的方式便于識别
import re
# regex = re.compile(r'(\d+)\.(\d+)') # 使用編号替換,但是容易混淆,下面的方式采用命名
# print(regex.sub(r'\1月\2日', memo_text))
regex = re.compile(r'(?P<month>\d+)\.(?P<day>\d+)')
print(regex.sub(r'\g<month>月\g<day>日', memo_text))
五、 常用一些正則
import re
RE_PHONE = re.compile('\d{3}-\d{8}|\d{4}-\d{7}')
def phone(str1: str)-> list:
"輸入一個字元串,從中傳回一個清單"
return RE_PHONE.findall(str1)
def main():
s = """
010-23293293deuju010-23223293
0111-3234123
"""
print(phone(s))
if __name__ == '__main__':
main()
比對電話号碼
比對騰訊QQ号: [1-9][0-9]{4,} 騰訊QQ号從10000開始
隻能輸入漢字: ^[\u4e00-\u9fa5]{1,8}$
隻能輸入由數字和26個英文字母組成的字元串: “^[A-Za-z0-9]+$”
驗證使用者密碼: “^[a-zA-Z]\w{7,17}$”正确格式為:以字母開頭, 長度在8-18之間, 隻能包含字元、數字和下劃線。
import re
# 驗證電話
RE_PHONE = re.compile('\d{3}-\d{8}|\d{4}-\d{7}')
# 驗證漢字
RE_CH = re.compile('^[\u4e00-\u9fa5]{1,8}$')
# 驗證密碼
RE_PWD = re.compile('^[a-zA-Z]\w{7,17}$')
def phone(text: str)-> list:
"輸入一個字元串,從中傳回一個清單"
return RE_PHONE.findall(text)
def verify(regex: '正規表達式', text: str)-> list:
"驗證使用者名和密碼"
if regex.match(text):
return True
else:
return False
def main():
s = """
010-23293293deuju010-23223293
0111-3234123
"""
# print(phone(s))
print(verify(RE_CH, '輝'))
print(verify(RE_PWD, 'dewu231_*'))
if __name__ == '__main__':
main()
用函數來實作
轉載于:https://www.cnblogs.com/louhui/p/8971497.html