想要玩爬蟲!正則表示式是你的必修課程!這篇足以你玩轉爬蟲了!
python 3.x 爬蟲基礎
python 3.x 爬蟲基礎---http headers詳解
python 3.x 爬蟲基礎---Urllib詳解
python 3.x 爬蟲基礎---Requersts,BeautifulSoup4(bs4)
python 3.x 爬蟲基礎---正則表示式
前言
正則表示式是對字串的一種邏輯公式,用事先定義好的一些特定字元、及這些特定字元的組合,組成一個“規則的字串”,此字串用來表示對字串的一種“過濾”邏輯。正在在很多開發語言中都存在,而非python獨有。對其知識點進行總結後,會寫一個demo。
進群:548377875 即可獲取數十套PDF哦!以及大量的學習資料!
1.正則表示式
python是自1.5開始引進re模組進行處理正則的。我先把正則的匹配規則總結一下,再總結re模組相應的方法。
1.1匹配規則
語法解釋表示式成功匹配物件一般字元匹配自身相對應的字元abcabc.匹配除換行符( )以外的任意字元a.cabc轉義字元,可以改變原字元的意思a.ca.cd匹配數字:0~9dabc1abcw匹配單詞字元,a~z;A~Z;0~9wwwoX2s匹配空格字元( , , ,,)asca cD匹配非數字字元DabcaabcW匹配非單詞字元aWca cS匹配非空格字元SSc1bc[]字符集,對應位置上可以是字符集裡的任意字元a[def]caec[^]對字符集當中的內容進行取反a[^def]ca2c[a-z]指定一個範圍字符集a[A-Z]caBc*允許前一個字元可以出現0次或者無限次a*baaab或b+前一個字元至少出現1次a+baaab或ab?前一個字元只能出現一次或者不出現a?bab或b{m}允許前一個字元只能出現m次a{3}baaab{m,n}允許前一個字元至少出現m次,最多出現n次(如果不寫n,則代表至少出現m次)a{3,5}b和a{3,}aaaab和aaaaaab^匹配字串的開始,多行內容時匹配每一行的開始^abcabc$匹配字串的結尾,多行內容時匹配每一行的結尾abc&abcA匹配字串開始位置,忽略多行模式AabcabcZ匹配字串結束位置,忽略多行模式abcZabc匹配位於單詞開始或結束位置的空字串hello worldhello worldB匹配不位於單詞開始或結束位置的空字串heBllohello|表示左右表示式任意滿足一種即可abc|cbaabc或cba(…)將被括起來的表示式作為一個分組,可以使用索引單獨取出(abc)dabcd(?P<name>…)為該分組起一個名字,可以用索引或名字去除該分組(?P<id>abc)dabcd umber引用索引為number中的內容(abc)dabcdabc(?P=name)引用該name分組中的內容(?P<id>abc)d(?P=id)abcdabc(?:…)分組的不捕獲模式,計算索引時會跳過這個分組(?:a)b(c)dabcdc(?iLmsux)分組中可以設定模式,iLmsux之中的每個字元代表一個模式(?i)abcAbc(?#…)註釋,#後面的內容會被忽略ab(?#註釋)123ab123(?=…)順序肯定環視,表示所在位置右側能夠匹配括號內正則a(?=d)a1最後的結果得到a(?!…)順序否定環視,表示所在位置右側不能匹配括號內正則a(?!w)a c最後的結果得到a(?<=…)逆序肯定環視,表示所在位置左側能夠匹配括號內正則1(?<=w)a1a(?<!…)逆序否定環視,表示所在位置左側不能匹配括號內正則1 (?<!w)a1 a(?(id/name)yes|no)如果前面的索引為id或者名字為name的分組匹配成功則匹配yes區域的表示式,否則匹配no區域的表示式,no可以省略(d)(?(1)d|a)32
上面表格中(?iLmsux)這裡的”i”, “L”, “m”, “s”, “u”, “x”,它們不匹配任何字串,而對應re模組中(re.S|re.S):
I:re.I# 忽略大小寫 L:re.L# 字符集本地化,為了支援多語言版本的字符集使用環境 U :re.U# 使用w,W,,B這些元字元時將按照UNICODE定義的屬性 M:re.M # 多行模式,改變 ^ 和 $ 的行為 S:re.S # '.' 的匹配不受限制,包括換行符 X:re.X # 冗餘模式,可以忽略正則表示式中的空白和#號的註釋
對於一個特殊字元在正則表示式中是不能正常識別的,如果接觸過其他語言我們就這到有一個叫做轉移字元的東西的存在,在特殊字元前加用反斜槓介面。比如 換行\為反斜槓,在這不再累述。下面來介紹一下re這個模組。
1.2.re模組
此模組主要方法如下
re.match()#嘗試從字串的起始位置匹配一個模式(pattern),如果不是起始位置匹配成功的話,match()就返回None re.search()#函式會在字串內查詢模式匹配,只要找到第一個匹配然後返回,如果字串沒有匹配,則返回None。 re.findall()#遍歷匹配,可以獲取字串中所有匹配的字串,返回一個列表。 re.compile()#編譯正則表示式模式,返回一個物件的模式。(可以把那些常用的正則表示式編譯成正則表示式物件,這樣可以提高一點效率。) re.sub()#使用re替換string中每一個匹配的子串後返回替換後的字串。 re.subn()#返回替換次數 re.split()#按照能夠匹配的子串將string分割後返回列表。
1.2.1.re.match()
方法: re.match(pattern, string, flags=0) # pattern:正則表示式(或者正則表示式物件)string:要匹配的字串flags:修飾符
先看一個最簡單的用法
import re content ='Hello 123 4567 wangyanling REDome' print(len(content)) result = re.match('^Hellosdddsd{4}sw{10}.*Dome$', content) print(result) print(result.group()) print(result.span())
結果:
匹配規則就不在累述,以上需要注意的是
(1) .group() 表示的是返回正則匹配的結果
(2) .span() 表示返回正則匹配的範圍
使用:
以上我們已經知道re.matcha()的具體方法,那麼接下我來看一下具體使用,對此我們要理解以下幾種匹配的感念。
1.泛匹配(.*):匹配所有字元
import re content ='Hello 123 4567 wangyanling REDome' result = re.match('^Hello.*Dome$', content) print(result) print(result.group()) print(result.span())
它的結果是和上面的輸出結果完全一樣的。
2.目標匹配(()):將需要的字元匹配出來
import re content ='Hello 123 4567 wangyanling REDome' result = re.match('^Hellosdd(d)sd{4}sw{10}.*Dome$', content) print(result) print(result.group(1)) import re content ='Hello 123 4567 wangyanling REDome' result = re.match('^Hellos(d+)sd{4}sw{10}.*Dome$', content) print(result) print(result.group(1))
結果
以上可以看出:
(1) () 匹配括號內的表示式,也表示一個組
(2) + 匹配1個或多個的表示式
*匹配0個或多個的表示式
(3) .group(1) —輸出第一個帶有()的目標
3.貪婪匹配(.*()):匹配儘可能少的的結果
import re content ='Hello 123 4567 wangyanling REDome' result = re.match('^H.*(d+).*Dome$', content) print(result) print(result.group(1))
結果
4.貪婪匹配(.*?()):匹配儘可能多的結果
import re content ='Hello 123 4567 wangyanling REDome' result = re.match('^H.*?(d+).*?Dome$', content) print(result) print(result.group(1))
結果
以上3,4兩個匹配方式請儘量採用非貪婪匹配
5.其他
換行:
import re content ='''Hello 123 4567 wangyanling REDome''' result = re.match('^H.*?(d+).*?Dome$', content,re.S)#re.S print(result.group(1)) result = re.match('^H.*?(d+).*?Dome$', content) print(result.group(1))
結果:
轉義字元:
import re content = 'price is $5.00' result = re.match('price is $5.00', content) print(result) result = re.match('price is $5.00', content) print(result)
結果:
其中re.I使匹配對大小不敏感,re.S匹配包括換行符在內的所有字元,進行處理轉義字元。匹配規則中有詳細介紹。
1.2.2.re.search()
方法:
re.search(pattern, string, flags=0)#pattern:正則表示式(或者正則表示式物件)string:要匹配的字串flags:修飾符 #re.match()和re.search()用法類似唯一的區別在於re.match()從字串頭開始匹配,若頭匹配不成功,則返回None
對比一下與match()
import re content ='Hello 123 4567 wangyanling REDome' result = re.match('(d+)sd{4}sw{10}.*Dome$', content) print(result)#從開頭開始查詢,不能匹配返回None result = re.search('(d+)sd{4}sw{10}.*Dome$', content) print(result) print(result.group())
結果:
可以看出兩個使用基本一致,search從頭開始匹配,如果匹配不到就返回none.
1.2.3.re.findall()
方法: re.finditer(pattern, string, flags=0) # pattern:正則表示式(或者正則表示式物件)string:要匹配的字串flags:修飾符
與re.search()類似區別在於re.findall()搜尋string,返回一個順序訪問每一個匹配結果(Match物件)的迭代器。找到 RE 匹配的所有子串,並把它們作為一個迭代器返回。
import re html = ''' <div> <li><a href="" singer="魯迅">吶喊</a></li> <li><a href="#" singer="賈平凹">廢都</a></li> <li class="active"><a href="#" singer="路遙">平凡世界</a></li> <span class="rightSpan">謝謝支援</span> </div> ''' regex_4='<a.*?>(.*?)</a>' results=re.findall(regex_4,html,re.S) print(results) for result in results: print(result)
結果:
1.2.4.re.compile()
編譯正則表示式模式,返回一個物件的模式。
方法: re.compile(pattern,flags=0) # pattern:正則表示式(或者正則表示式物件);flags:修飾符
看一個demo
import re content ='Hello 123 4567 wangyanling REDome wangyanling 那小子很帥' rr = re.compile(r'w*wangw*') result =rr.findall(content) print(result)
結果:
我們可以看出compile 我們可以把它理解為封裝了一個公用的正則,類似於方法,然後功用。
1.2.5.其他
re.sub 替換字元
方法: re.sub(pattern, repl, string, count=0, flags=0) # pattern:正則表示式(或者正則表示式物件)repl:替換的字串string:要匹配的字串count:要替換的個數flags:修飾符
re.subn 替換次數
方法: re.subn(pattern, repl, string, count=0, flags=0) # pattern:正則表示式(或者正則表示式物件)repl:替換的字串string:要匹配的字串count:要替換的個數flags:修飾符
re.split()分隔字元
方法
re.split(pattern, string,[maxsplit])#正則表示式(或者正則表示式物件)string:要匹配的字串;maxsplit:用於指定最大分割次數,不指定將全部分割
2.案例:爬取貓眼資訊,寫入txt,csv,下載圖片
2.1.獲取單頁面資訊
def get_one_page(html): pattern= re.compile('<dd>.*?board-index.*?>(d+)</i>.*?data-src="(.*?)".*?name"><a.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime' + '.*?>(.*?)</p>.*?score.*?integer">(.*?)</i>.*?>(.*?)</i>.*?</dd>',re.S)#這裡就用到了我們上述提到的一些知識點,非貪婪匹配,物件匹配,修飾符 items = re.findall(pattern,html) for item in items: yield { 'rank' :item[0], 'img': item[1], 'title':item[2], 'actor':item[3].strip()[3:] if len(item[3])>3 else '', 'time' :item[4].strip()[5:] if len(item[4])>5 else '', 'score':item[5] + item[6] }
對於上面的資訊我們可以看出是存到一個物件中那麼接下來我們應該把它們存到檔案當中去。
2.2.儲存檔案
我寫了兩種方式儲存到txt和csv這些在python都有涉及,不懂得可以去翻看一下。
2.2.1.儲存到txt
def write_txtfile(content): with open("Maoyan.txt",'a',encoding='utf-8') as f: #要引入json,利用json.dumps()方法將字典序列化,存入中文要把ensure_ascii編碼方式關掉 f.write(json.dumps(content,ensure_ascii=False) + " ") f.close()
結果:
以上看到並非按順序排列因為我用的是多執行緒。
2.2.2.儲存到csv
def write_csvRows(content,fieldnames): '''寫入csv檔案內容''' with open("Maoyao.csv",'a',encoding='gb18030',newline='') as f: #將欄位名傳給Dictwriter來初始化一個字典寫入物件 writer = csv.DictWriter(f,fieldnames=fieldnames) #呼叫writeheader方法寫入欄位名 writer.writerows(content) f.close()
結果:
那麼還有一部就是我們要把圖片下載下來。
2.2.3.下載圖片
def download_img(title,url): r=requests.get(url) with open(title+".jpg",'wb') as f: f.write(r.content)
2.3.整體程式碼
這裡面又到了多執行緒在這不在敘述後面會有相關介紹。這個demo僅做一案例,主要是對正則能有個認知。上面寫的知識點有不足的地方望大家多多指教。
#抓取貓眼電影TOP100榜 from multiprocessing import Pool from requests.exceptions import RequestException import requests import json import time import csv import re def get_one_page(url): '''獲取單頁原始碼''' try: headers = { "User-Agent":"Mozilla/5.0(WindowsNT6.3;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/68.0.3440.106Safari/537.36" } res = requests.get(url, headers=headers) # 判斷響應是否成功,若成功列印響應內容,否則返回None if res.status_code == 200: return res.text return None except RequestException: return None def parse_one_page(html): '''解析單頁原始碼''' pattern = re.compile('<dd>.*?board-index.*?>(d+)</i>.*?data-src="(.*?)".*?name"><a.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime' + '.*?>(.*?)</p>.*?score.*?integer">(.*?)</i>.*?>(.*?)</i>.*?</dd>',re.S) items = re.findall(pattern,html) #採用遍歷的方式提取資訊 for item in items: yield { 'rank' :item[0], 'img': item[1], 'title':item[2], 'actor':item[3].strip()[3:] if len(item[3])>3 else '', #判斷是否大於3個字元 'time' :item[4].strip()[5:] if len(item[4])>5 else '', 'score':item[5] + item[6] } def write_txtfile(content): with open("Maoyan.txt",'a',encoding='utf-8') as f: #要引入json,利用json.dumps()方法將字典序列化,存入中文要把ensure_ascii編碼方式關掉 f.write(json.dumps(content,ensure_ascii=False) + " ") f.close() def write_csvRows(content,fieldnames): '''寫入csv檔案內容''' with open("Maoyao.csv",'a',encoding='gb18030',newline='') as f: #將欄位名傳給Dictwriter來初始化一個字典寫入物件 writer = csv.DictWriter(f,fieldnames=fieldnames) #呼叫writeheader方法寫入欄位名 #writer.writeheader() ###這裡寫入欄位的話會造成在抓取多個時重複. writer.writerows(content) f.close() def download_img(title,url): r=requests.get(url) with open(title+".jpg",'wb') as f: f.write(r.content) def main(offset): fieldnames = ["rank","img", "title", "actor", "time", "score"] url = "http://maoyan.com/board/4?offset={0}".format(offset) html = get_one_page(url) rows = [] for item in parse_one_page(html): #download_img(item['rank']+item['title'],item['img']) write_txtfile(item) rows.append(item) write_csvRows(rows,fieldnames) if __name__ == '__main__': pool = Pool() #map方法會把每個元素當做函式的引數,建立一個個程序,在程序池中執行. pool.map(main,[i*10 for i in range(10)])