requests+正則表示式爬取豆瓣讀書top250
簡單的python練手專案,通過rquests庫請求得到豆瓣top250網頁原始碼,並通過正則表示式匹配得到對應資訊-書名,作者資訊,評分以及簡介。
網站的URL為’https://book.douban.com/top250?start=0’,但我們拉到底部發現250本讀書的資訊被分成了10頁,這就需要我們首先對URL的規律進行分析得到所有頁面的URL資訊傳遞給get()方法中請求原始碼。
點選到第2頁發現URL為’https://book.douban.com/top250?start=25’,第三頁為’https://book.douban.com/top250?start=50’,最後一頁為’https://book.douban.com/top250?start=225
def get_allpage():
urllist=[]#用於儲存生成的所有URL
baseurl='https://book.douban.com/top250?start='
for i in range(10):
allurl=baseurl+str(i)
urllist.append(allurl)
return urllist
這樣我們就獲取全部10頁的URL資訊。
接下來利用requests獲取網頁原始碼,程式碼如下:
import requests
def get_page(url):
try:
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36(KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134'}
res=requests.get(url,headers=headers)
if res.status_code==200:
return res.text
else:
return None
except RequestException:
print("請求錯誤")
因為豆瓣網沒有發現什麼反爬蟲措施,所以我們只需要給get()傳遞headers以及URL引數即可。
下面就是利用正則表示式匹配我們所需要的資訊,這裡還是先囉嗦幾句關於正則表示式的使用吧。
正則表示式式用來處理字串的強大工具,可以利用它實現字串的檢索替換以及匹配驗證。但初看正則表示式只會感覺它是一串亂七八糟的字元,當然這是一開始不瞭解它的語法結構的原因,比如’\d’是匹配數字,‘a-z’是匹配小寫字母,下面是一些通用的匹配規則:
\w-匹配數字字母和下劃線
\W-與\w相反
\s-匹配任意空白字元
\S-匹配非空字元
\d-匹配數字
^-匹配一行字串的開頭
$-匹配一行字串的結尾
.-匹配任意非換行符外的字元
*和±匹配0個或多個表示式
?-匹配0個或多個前面的正則表示式定義片段,非貪婪模式
()-匹配括號內的表示式,也表示一個組
python的re庫提供了整個正則表示式的實現,下面的程式碼具體介紹一些例項:
match()方法:
這是re庫中最基礎的一個匹配方法,向它傳入正則表示式以及原字串就可以從頭檢測正則表示式是否匹配字串:
#匹配QQ郵箱地址
strr="this is my email address [email protected]"
restr="^this\s\w{2}\s\w{2}\w{5}\s\w{6}\s(\d{10})@\w{2}.\w{3}"
resu=re.match(restr,strr)
if resu:
print(len(strr))
print(resu.group())#匹配內容
print(resu.group(1))#匹配結果
print(resu.span())#匹配範圍
這裡實現匹配字串中的數字內容,即QQ號,正則表示式開頭的^是字串開頭這裡是this,接下來\s匹配空格,\w{2}匹配2個字母,依次匹配字母及空格,匹配的數字使用(\d{10})表示匹配連續出現的10個數字,接下來匹配.以及com$結尾,match()返回bool值,利用group(1)函式即可輸出想要的帶的數字結果。
如果不想從頭開始搜尋,可以使用search()方法,正則表示式就可以不用以this開頭,只需在匹配的數字之前就可以。
通用匹配:上面我們使用了特定的匹配字母,空格以及數字的正則表示式去匹配,這樣不免有些繁瑣,其實可以使用通用的匹配表示式來進行匹配,分析上面列出的匹配規則可以發現,’.‘可以匹配出換行符外的任意字元,而’‘表示匹配前面的字元無限次,也就是說’.'可以匹配任意字元任意次,所以上面的表示式其實可以修改為:
'^this.*(\d+).*com$
這樣使用匹配後,我們其實會發現一個奇怪的問題,匹配的結果只得到了’6‘這一個數字,這是怎麼回事?因為我們要獲取的是中間的數字,所以兩側的均使用通用匹配符來來實現,但數字之前這樣使用會出現一個問題,在這種情況下,之前的通用匹配符會盡可能多的匹配,而只需要給匹配內容留下一個數字就滿足要求。這裡涉及的是正則語法中的貪婪與非貪婪模式,因為上面我們匹配數字時沒有指定數字數量,所以貪婪模式下通用匹配符就儘可能多的匹配字元,解決的辦法也很簡單,只需要在通用匹配符後加上?即可實現非貪婪模式。
轉義匹配:前一段說到’.'可以匹配任意字元,那麼如果目標字串就包含’.‘怎麼處理呢?這裡只需要使用轉義匹配方法,及在要匹配的’.'字元前加上\即可實現轉義,例項如下:
#轉義匹配
conn="(百度)www.baidu.com"
restr3="\(百度\)www\.baidu\.com"
resu3=re.match(restr3,conn)
print(resu3)
print(resu3.group(1))
答應匹配結果,發現成功匹配了原字串。
findall()方法:
前面的match(),search()方法當匹配到第一個內容後就返回,但是如果想要匹配整個字串內容,就需要使用findall方法了,該方法會搜尋這個字串,然後返回所有目標字串。
接下來我們抓取一段網站原始碼試著去寫出匹配我們我要得到資訊的正則字串。
開啟’https://book.douban.com/top250?start=0’,開啟檢查元素,選擇對應位置選取html程式碼,
我們先選區網頁排名第一的圖示,點選追風箏的人書名,找到對應的html程式碼(
追風箏的人
The Kite Runner
)
分析發現,這個資訊是在標籤div,pl2下面,以結尾,匹配內容為title內的內容“追風箏的人”,利用之前的只是,很很輕鬆就可以寫出對應的正則表示式為:href. ?title="(.?)". ?,這樣就匹配到了書名資訊,再接再勵,分析其他資訊的html,可以寫出整個正則表示式為:'href.?title="(. ?)".?. ?pl.?>(. ?) .?rating_nums. ?>(.?). ?inq.?>(.*?)’,由此,我們就可以解析前面得到的整個網頁的html,得到書名,作者等資訊,程式碼如下:
import re
def jx_html(text):
restr=re.compile('href.*?title="(.*?)".*?</a>.*?pl.*?>(.*?)</p>.*?rating_nums.*?>(.*?)</span>.*?inq.*?>(.*?)</span>',re.S)#compile方法用於將正則表示式編譯成正則物件以複用,re.S用於匹配換行字串
result=re.findall(restr,text)
for ree in result:
yield{'bookname':ree[0],'author':ree[1],'score':ree[2],'expl':ree[3]}#利用迭代器返回獲取資訊的字典物件
最後我們需要將得到的解析資訊儲存為.txt檔案,程式碼如下:
def save_fil(content):
with open ('book250.txt','a',encoding='utf-8')as f:
f.write(json.dumps(content,ensure_ascii=False)+'\n')#利用json庫的dumps()方法實現字典的序列化
最後整合整個程式碼如下:
import requests
import re
import time
import json
def get_one_page(url):
try:
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134'}
res=requests.get(url,headers=headers)
if res.status_code==200:
return res.text
else:
return None
except RequestException:
print("請求錯誤")
def jx_html(text):
restr=re.compile('href.*?title="(.*?)".*?</a>.*?pl.*?>(.*?)</p>.*?rating_nums.*?>(.*?)</span>.*?inq.*?>(.*?)</span>',re.S)
result=re.findall(restr,text)
for ree in result:
yield{'bookname':ree[0],'author':ree[1],'score':ree[2],'expl':ree[3]}
def save_fil(content):
with open ('book250.txt','a',encoding='utf-8')as f:
f.write(json.dumps(content,ensure_ascii=False)+'\n')
def main(offset):
url='https://book.douban.com/top250?start='+str(offset)
html= get_one_page(url)
df=jx_html(html)
for dff in df:
save_fil(dff)
if __name__ == '__main__':
for i in range(0,10):
main(offset=i*25)
time.sleep(1)#呼叫time休眠1S,防止因請求過快出發反爬蟲
最後開啟儲存的檔案,效果如下:
看上去一切都正常,但對比到最後發現,爬蟲只爬取了前8頁的資訊,分析發現最後兩頁的html原始碼獲取沒有問題,解析也沒有問題,希望有大神看到給一點指點。