我們有時需要判斷一段文本是否符合特定的“模式”(Pattern)豪筝,這稱為文本模式匹配——例如手機號的模式可以描述為“1再加上任意10個數(shù)字”絮蒿,你可以寫一個實現(xiàn)此功能的函數(shù):如果字符串長度為11碗硬,首個字符為1吝秕,其他字符均為數(shù)字,就返回真值,否則返回假值课蔬;而如果需要從一大段文本中找出所有的手機號囱稽,你就得從第一個字符開始循環(huán)截取長度為11的字符串進行判斷——這顯然十分笨拙。
更為靈活高效的做法是使用“正則表達式”(Regular Expression)二跋,這是一種專門描述文本模式規(guī)則的字符串战惊,例如“1\d{10}”就表示“1再加上任意10個數(shù)字”。以下Python代碼使用標準庫提供的re模塊實現(xiàn)正則表達式匹配扎即,從來自“選號網(wǎng)”的一段文本中找出所有的手機號——這顯然優(yōu)雅多了:
In [1]: s = "1881001118835400188101888992360018801392999236001881010005523600"
In [2]: import re
In [3]: re.findall(r"1\d{10}", s)
Out[3]: ['18810011188', '18810188899', '18801392999', '18810100055']
findall()函數(shù)的兩個參數(shù)分別指定正則表達式和目標文本吞获,因為正則表達式里經(jīng)常包含反斜杠,所以推薦使用加r前綴的原始字符串來表示——手機號的首個字符一定是1谚鄙,正則表達式里直接用1來匹配各拷;之后的“\d”表示匹配任一數(shù)字,以下是常用的正則轉義碼:
代碼 | 轉義說明 |
---|---|
\d | 數(shù)字類字符闷营,默認也包括全角數(shù)字 |
\D | 非數(shù)字類字符 |
\w | 單詞類字符烤黍,默認也包括漢字等 |
\W | 非單詞類字符 |
\s | 空白類字符,即空格/制表/換行等 |
\S | 非空白類字符 |
\b | 單詞邊界粮坞,用于精確匹配單詞 |
\B | 非單詞邊界 |
“\d”后用花括號指定匹配10次——這類特殊功能符號如下所示(單純作為文本來匹配時就要加反斜杠):
符號 | 功能說明 | 正則示例 | 目標示例 |
---|---|---|---|
. | 匹配任意字符蚊荣,換行符\n除外 | a.c | abc acc |
\ | 轉義 | a\.c | a.c |
* | 匹配前一字符0至任意次 | abc* | ab abccc |
+ | 匹配前一字符1至任意次 | abc+ | abc abccc |
? | 匹配前一字符0至1次 | abc? | ab abc |
{m,n} | 匹配前一字符m至n次,省略n則無上限 | ab{1,2}c | abc abbc |
^ | 匹配字符串開頭 | ^abc | abc |
$ | 匹配字符串末尾 | abc$ | abc |
| | 或 | abc|def | abc def |
[] | 指定字符集如[abc] | a[bc]e | abe ace |
() | 分組 | (ab){2}a(12|34)c | ababa34c |
指定字符集的方括號之內還有更多寫法莫杈,例如[a-z]表示從a到z即任意小寫字母互例,[^abc]表示除abc外的任意字符。
re模塊的常用函數(shù)如下:
- compile() 編譯正則表達式筝闹,返回模式對象——如果某個正則表達式要在程序中多次使用媳叨,就應先編譯并使用模式對象的相應方法來匹配文本以提高運行效率(以下函數(shù)去掉正則參數(shù)就是模式對象的方法)
- findall() 在字符串內查找所有匹配文本,返回字符串列表
- split() 用匹配文本拆分字符串关顷,返回字符串列表
- sub() 將字符串內匹配文本替換為指定文本糊秆,返回替換后的字符串
- subn() 將字符串內匹配文本替換為指定文本,返回替換的次數(shù)
- match() 從字符串開頭匹配文本议双,返回匹配對象
- search() 在字符串內查找匹配文本痘番,返回匹配對象
- finditer() 在字符串內查找所有匹配文本,返回匹配對象迭代器
注意match()平痰、search()和finditer()返回的是匹配對象汞舱,匹配對象有下列方法:
- group() 返回匹配的字符串,如定義了多個分組可以指定分組號
- start() 返回匹配開始位置
- end() 返回匹配結束位置
- span() 返回匹配開始和結束位置(元組類型)
- groups() 返回匹配的所有分組字符串(元組類型)
以下代碼將文本拆分為單詞(對于中文則是句子)宗雇,注意findall()返回列表而finditer()返回生成迭代器——每次迭代返回一個單詞昂芜,這樣更省內存。
In [4]: po = re.compile(r"\w+")
In [5]: po.findall("Life is short, you need Python.")
Out[5]: ['Life', 'is', 'short', 'you', 'need', 'Python']
In [6]: mo = po.finditer("道可道赔蒲,非常道泌神;名可名良漱,非常名。")
In [7]: for i in mo:
...: print(i.group(), end=" ")
...:
道可道 非常道 名可名 非常名
添加圓括號可以在正則表達式中創(chuàng)建分組欢际,假如你在匹配手機號的同時還想分別提取其中的“號段”和“地區(qū)碼”母市,就可以使用分組功能:
In [8]: po = re.compile(r"(1\d{2})(\d{4})(\d{4})")
In [9]: mo = po.search("手機號碼:13366669999")
In [10]: mo.group(0) # 參數(shù)為0與無參數(shù)都返回整個匹配
Out[10]: '13366669999'
In [11]: mo.group(1) # 參數(shù)為1返回第一個分組,以下依次類推
Out[11]: '133'
In [12]: mo.group(2)
Out[12]: '6666'
In [13]: mo.group(3)
Out[13]: '9999'
In [14]: mo.groups() # 此方法返回所有分組
Out[14]: ('133', '6666', '9999')
替換類方法如果需要將匹配文本的一部分放入替換文本中损趋,也是通過添加分組窒篱,在替換文本中用反斜杠加組號表示即可:
In [15]: mo = re.compile(r"特工(\w)\w")
In [15]: mo.sub(r"特工\1某", "特工趙大告訴特工錢二:特工孫三將與特工李四接頭。")
Out[15]: '特工趙某告訴特工錢某:特工孫某將與特工李某接頭舶沿。'
正則表達式默認采用最長匹配(也叫“貪婪”匹配),只要規(guī)則允許就匹配盡可能多的字符配并;有時我們需要采用最短匹配括荡,那就在多次匹配符號(*、+溉旋、})后再加一個?號:
In [16]: s = "子曰:“君子坦蕩蕩”畸冲。子曰:“見賢思齊焉”。"
In [17]: re.findall(r"“(.*)”", s)
Out[17]: ['君子坦蕩蕩”观腊。子曰:“見賢思齊焉']
In [18]: re.findall(r"“(.*?)”", s)
Out[18]: ['君子坦蕩蕩', '見賢思齊焉']
以下網(wǎng)絡爬蟲程序使用正則表達式找出網(wǎng)頁中的圖片鏈接并批量下載:
"""webcrawler.py 百度圖片搜索并批量下載
"""
from urllib.request import urlopen, urlretrieve
from urllib.parse import quote
import re
url = "https://image.baidu.com/search/flip?tn=baiduimage&word="
keyword = "高清動漫"
path = "D:/Test/img/"
def main():
try:
html = urlopen(url + quote(keyword)).read().decode()
links = re.findall(r'"objURL":"(.+?)"', html)
for i in links:
urlretrieve(i, path + i.split("/")[-1]) # 原文件名保存
except Exception as e:
print(repr(e))
if __name__ == "__main__":
main()
學會正則表達式能讓你省下許多寶貴的時間邑闲。
——編程原來是這樣……
編程小提示:在線正則表達式工具
下面是一些在線工具,可以方便地測試正則表達式: