一般來說當我們爬取網頁的整個原始碼後,是需要對網頁進行解析的。
正常的解析方法有三種
①:正則匹配解析
②:BeatuifulSoup解析
③:lxml解析
正則匹配解析:
在之前的學習中,我們學習過爬蟲的基本用法,比如/s,/d,/w,*,+,?等用法,但是在對爬取到的網頁進行解析的時候,僅僅會這些基礎的用法,是不夠用的,因此我們需要了解Python中正則匹配的經典函式。
re.match:
runoob解釋:re.match嘗試從字串的起始位置匹配一個模式,如果不是起始位置匹配成功的話,match就會返回none,如果匹配成功,re.match就會返回一個匹配的物件,否則也為null.檢視匹配返回的物件要使用group函式的方法。
語法:
re.match(pattern,string,flags=0)
解釋:pattern是匹配的正規表示式,string是所要匹配的字元,而flags是標誌位,用於匹配區分是否大小寫,多行匹配等。具體用法如下列表格:
修飾符 | 描述 |
---|---|
re.I | 使得匹配對大小寫不敏感 |
re.M | 多行匹配,影響^和$ |
re.S | 使得.匹配包括換行在內的所有字元(正常情況下.不匹配換行符,這個行為很危險,容易被黑客利用繞過) |
re.U | 根據Unicode字符集解析字元,這個標誌影響\w,\W,\b,\B |
程式碼案例:
#!/usr/bin/python #coding:utf-8 import re line="Anyone who dreams there gets" match=re.match(r'(.*) dreams (.*?).*',line,re.M|re.I) #match1=re.match(r'(.*) Dreams (.*?)',line,re.M)#大小寫敏感,匹配會失敗 match2=re.match(r'(.*) Dreams (.*?).*',line,re.M|re.I)#非貪婪模式 match3=re.match(r'(.*) Dreams (.*).*',line, re.M|re.I)#貪婪模式 print(match.group()) #print(match1.group()) print(match2.group()) print("match 0:",match.group(0))#Anyone who dreams there gets print("match 1:",match.group(1))#Anyone who print("match 2:",match.group(2))#' ' print("match2 0:",match2.group(0))#Anyone who dreams there gets print("match2 1:",match2.group(1))#Anyone who print("match2 2:",match2.group(2))#' ' print("match3 0:",match3.group(0))#Anyone who dreams there gets print("match3 1:",match3.group(1))#Anyone who print("match3 2:",match3.group(2))#there gets
可以看到(.*?)非貪婪模式會匹配空格,並且放入group(2),預設group()==group(0)位匹配的整句話,而groups()函式會把匹配的結果放到一個列表裡。對匹配返回的結果比如match,我們可以使用start(),end(),span(),來檢視開始,結束的位置。
正則匹配字串r'(.*)'的r代表raw string,代表匹配純粹的字串,使用它就不會對反引號裡的反斜槓進行特殊處理,比如換行符\n不會進行轉義。因此我們可以瞭解下正則匹配中的轉義:
在不加r的前提下'\\\\'會被轉義,這是因為字串經過了兩次轉移,一次是字串轉義,轉義成為了'\\',然後進行了正則轉義。
如果加了r就會變成'\\',因為以純粹的字串進行匹配取消了字串轉義。這就是正則匹配中的轉義流程。
re.search
re.search與re.match不同之處在於re.search會掃描整個字串,並且返回第一個結果,此外re.match並無太大差別,同樣可以使用m.span(),m.start(),m.end()來檢視起始位置等等
re.findall
在之前的一篇博文:https://www.cnblogs.com/Tianwenfeigong/p/14397771.html
使用到過re.findall函式,用以對bilibili中上傳Python教程的Up主進行爬取,從那篇博文中,我們可以看出,re.findall返回的是列表,,對他的資料提取一般先用set()轉換為集合的方式會更佳。
在那篇博文中,我還不懂對href進行包含但不選擇,因此正則匹配的內容有限,導致正則匹配適用的環境並不多,因此重新爬取新的內容,並且引入一個新的小技巧;
目標:爬取第一個bilibili搜尋欄所有的python視訊的標題
程式碼:
#!/usr/bin/python #coding=utf-8 import requests import re link="https://search.bilibili.com/all?keyword=python&from_source=nav_search&spm_id_from=333.851.b_696e7465726e6174696f6e616c486561646572.9" headers={"User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0'} r=requests.get(link,headers=headers) text=r.text title_list=re.findall('<a title=(.*?) href=.*?>',text) for i in title_list: print(i)
重點內容:title_list=re.findall('<a title=(.*?) href=.*?>',text)
第二個.?表示匹配滿足條件的結果,第一個(.?)表示對滿足條件的結果進行提取
通過這個方式我們就可以提取標題了
BeautifulSoup解析
BeautifulSoup可以從HTML或者XML中提取資料,類似一個工具箱,使用BeautifulSoup時,可以使用一些解析器使得速度快一點呢,目前比較好的的Lxml HTML解析器,使用方法如下:
soup=BeautifulSoup(r.text,"lxml")
BeautifulSoup的本質是複雜的樹形結構,對他的物件提取一般有三種方法:
①:遍歷文件樹,遍歷文件樹類似於Linux的檔案目錄,既然是樹形結構一定是從上到下的所以第一個節點一定是header,具體方法如下:
#如要獲得以<h1>開頭的標籤,則可以使用 soup.header.h1 #會返回類似於<h1 id="name">CuLin</h1>的內容 #如果要獲得div的所有子節點,則可以使用 soup.header.div.contents #如果要獲得div的所有孩子節點,則可以使用 soup.header.div.children #如果要獲得div的子子孫孫節點,則可以使用 soup.header.div.descendants #如果要獲得節點a的父節點,則可以使用 soup.header.div.a.parents
②:搜尋文件樹:
遍歷文件樹的侷限性很大,一般不是很推薦使用,更多的還是搜尋文件樹。使用方法很便捷,精髓在find和find_all函式,比如我們要爬取部落格文章的標題:
#!/usr/bin/python #coding:utf-8 import requestss from bs4 import BeautifulSoup link="http://www.santostang.com/" r=requests.get(link) soup=BeautifulSoup(r.text,"lxml") #列印第一篇標題 first_title=soup.find("h1",class_="post-title").a.text.strip() #列印所有文章的 all_title=soup.find_all("h1",class_="post-title") for i in range(len(all_titile)): print("No ",i," is ",all_title[i].a.text.strip())
③: CSS選擇器:
Css可以作為遍歷文件樹鼩提取資料,也可以按照搜尋文件樹的方法去搜尋,但是我個人覺得css選擇器不如selenium好用,並且使用上很不方便,所以不再贅述
此外lxml解析器重點是xpath,這在selenium也有很好的體現,而xpath的選擇我們可以在網頁檢查中直接檢查xpath更為方便。
專案實戰
爬取貝殼網站南京房源資訊,總房價,平均房價,地點
#coding:utf-8 import requests import re from bs4 import BeautifulSoup link="https://nj.ke.com/ershoufang/pg" headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0","Host": "nj.ke.com"} for i in range(1,20): link_=link+str(i)+'/' r=requests.get(link_,headers=headers) soup=BeautifulSoup(r.text,"html.parser") houst_list=soup.find_all('li',class_='clear') for house in houst_list: name=house.find('div',class_='title').a.text.strip()+'\n' houst_info=house.find('div',class_='houseInfo').text.strip() houst_info=re.sub('\s+','',houst_info)+'\n' total_money=str(house.find('div',class_='totalPrice').span.text.strip())+'萬' aver_money=str(house.find('div',class_='unitPrice').span.text.strip()) adderess=house.find('div',class_='positionInfo').a.text.strip() text_="標題:"+name+"房屋資訊:"+houst_info+"總價:"+total_money+' '+aver_money+adderess with open ('haha.txt',"a+") as f: f.write(text_)
結果返回一個haha.txt的文字,內容如下: