在我的上一篇文章里簡(jiǎn)單介紹了一下最簡(jiǎn)單的爬蟲架構(gòu):《淺談簡(jiǎn)單爬蟲架構(gòu)》
如下圖所示
框架
mySpider
├─ spiderMain.py #爬蟲調(diào)度端
├─ urlManager.py #URL管理器
├─ htmlDownloader.py #網(wǎng)頁(yè)下載器
└─ htmlParser.py #網(wǎng)頁(yè)解析器
此篇以爬取廖雪峰的官方網(wǎng)站中的python教程為例
不過(guò)廖老師的網(wǎng)站對(duì)爬蟲進(jìn)行了過(guò)濾垫释,建議舉一反三,嘗試爬取其他網(wǎng)站
現(xiàn)在的網(wǎng)絡(luò)爬蟲越來(lái)越多,有很多爬蟲都是初學(xué)者寫的,和搜索引擎的爬蟲不一樣各淀,他們不懂如何控制速度,結(jié)果往往大量消耗服務(wù)器資源刷袍,導(dǎo)致帶寬白白浪費(fèi)了米碰。
其實(shí)Nginx可以非常容易地根據(jù)User-Agent過(guò)濾請(qǐng)求,我們只需要在需要URL入口位置通過(guò)一個(gè)簡(jiǎn)單的正則表達(dá)式就可以過(guò)濾不符合要求的爬蟲請(qǐng)求:
...
location / {
if ($http_user_agent ~* "python|curl|java|wget|httpclient|okhttp") {
return 503;
}
# 正常處理
...
}
...
變量$http_user_agent是一個(gè)可以直接在location中引用的Nginx變量吻育。~*表示不區(qū)分大小寫的正則匹配念秧,通過(guò)python就可以過(guò)濾掉80%的Python爬蟲
爬蟲調(diào)度端
爬蟲調(diào)度端的核心代碼實(shí)現(xiàn):
while urlManager.hasUrl(): #詢問(wèn)url管理器是否有待爬url
newurl = urlManager.getUrl() #獲取待爬url
html = htmlDownloader.download(newurl,headers) #下載頁(yè)面
(title,content),urls = htmlParser.parser(html) #從頁(yè)面中解析出內(nèi)容和新的url
#此處可以處理爬下來(lái)的文件,比如儲(chǔ)存到本地布疼,我僅打印標(biāo)題以測(cè)試
print(title)
urlManager.addUrls(urls) #將新的url加入U(xiǎn)RL管理器
其中的headers是請(qǐng)求頭摊趾,用于模擬瀏覽器的行為
可以使用chrome瀏覽器的開(kāi)發(fā)者工具找到
網(wǎng)頁(yè)右擊 檢查->network
刷新頁(yè)面
完整代碼:
import requests
from urlManager import UrlManager #url管理器
from htmlDownloader import HtmlDownloader #頁(yè)面下載器
from htmlParser import HtmlParser #網(wǎng)頁(yè)解析器
#請(qǐng)求頭
headers={'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8','Accept-Encoding':'gzip, deflate, br','Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8','Cache-Control':'max-age=0','Connection':'keep-alive','Cookie':'Hm_lvt_2efddd14a5f2b304677462d06fb4f964=1516595211; atsp=1516853801496_1516853801154; Hm_lpvt_2efddd14a5f2b304677462d06fb4f964=1516853818','Host':'www.liaoxuefeng.com','Referer':'https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318447437605e90206e261744c08630a836851f5183000','Upgrade-Insecure-Requests':'1','User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
if __name__ == '__main__':
print('爬蟲開(kāi)始...')
urlManager = UrlManager()
htmlDownloader = HtmlDownloader()
htmlParser = HtmlParser()
urlManager.addUrl('https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000')
while urlManager.hasUrl():
newurl = urlManager.getUrl()
if newurl != "":
html = htmlDownloader.download(newurl,headers)
(title,content),urls = htmlParser.parser(html)
print(title)
#文件處理儲(chǔ)存
urlManager.addUrls(urls)
URL管理器
url管理器可以使用queue或set甚至list來(lái)實(shí)現(xiàn),如果需要按順序爬取游两,可以使用隊(duì)列來(lái)實(shí)現(xiàn)砾层,即先加入url管理器的url先爬取,但是需要注意的是使用隊(duì)列則需要檢查待加入的url是否已經(jīng)在隊(duì)列中了贱案。
而如果對(duì)順序沒(méi)有特別的要求肛炮,使用set更為簡(jiǎn)便,可以直接加入待爬url宝踪,因?yàn)橹貜?fù)的元素只會(huì)在set中出現(xiàn)一次侨糟。
但無(wú)論使用queue、set還是list實(shí)現(xiàn)瘩燥,都需要檢查待加的url是否已經(jīng)爬過(guò)了秕重,以此避免重復(fù)爬取甚至循環(huán)爬取
完整代碼:
class UrlManager(object):
def __init__(self):
self._newUrls = set([]) #set newUrls中儲(chǔ)存未訪問(wèn)的url
self._oldUrls = set([]) #set oldUrls中儲(chǔ)存已訪問(wèn)的url
def addUrl(self,url): #添加url
if isinstance(url,str) and url not in self._oldUrls:
self._newUrls.add(url)
def hasUrl(self): #是否還有未訪問(wèn)的url
return len(self._newUrls) > 0
def getUrl(self): #該函數(shù)返回url視為已訪問(wèn)
if not self._newUrls: #不存在新的url
return ""
url = self._newUrls.pop()
self._oldUrls.add(url)
return url
def addUrls(self,urls):
if isinstance(urls,list):
for url in urls:
if url not in self._oldUrls:
self._newUrls.add(url)
網(wǎng)頁(yè)下載器
網(wǎng)頁(yè)下載器十分簡(jiǎn)單,在此使用了request模塊
詳情可以查看request模塊文檔
完整代碼:
import requests
class HtmlDownloader(object):
def download(self,url,headers=""): #url:待爬鏈接 headers:請(qǐng)求頭 return value:html文本
if url is None or not isinstance(url,str):
#獲取失敗錯(cuò)誤處理
return None
r = requests.get(url,headers=headers)
return r.text
網(wǎng)頁(yè)解析器
網(wǎng)頁(yè)解析器使用了BeautifulSoup模塊厉膀,非常方便快捷
具體參考BeautifulSoup文檔
像我就記不太住溶耘,都是隨用隨查的,所以這是最費(fèi)時(shí)間的一部分服鹅,也很容易出錯(cuò)(逃
這里需要我們自己分析頁(yè)面來(lái)解析
完整代碼:
from bs4 import BeautifulSoup
baseUrl = 'https://www.liaoxuefeng.com'
class HtmlParser(object):
def parser(self,text): #text: html文本 return value: (需要的數(shù)據(jù),list[需要的url])
soup = BeautifulSoup(text,'lxml')
#獲取標(biāo)題
title = soup.h4
#print(soup.select(".x-main-content"))
if len(soup.select(".x-main-content")) >0:
# print("True")
content = soup.select(".x-main-content")[0].get_text()
else:
# print("False")
content=""
urls = []
for a in soup.find_all('a',"x-wiki-index-item"):
urls.append(baseUrl+a.get('href'))
return ((title,content),urls)
價(jià)值數(shù)據(jù)
做到這里凳兵,我們就已經(jīng)可以獲取到我們想要的價(jià)值數(shù)據(jù)了。
在這里我僅打印標(biāo)題以測(cè)試企软,實(shí)際上我們可以對(duì)數(shù)據(jù)做更多的處理庐扫,而這就需要我們繼續(xù)深入學(xué)習(xí)了。共勉仗哨!