在我的上一篇文章里簡單介紹了一下最簡單的爬蟲架構(gòu):《淺談簡單爬蟲架構(gòu)》
如下圖所示
簡單爬蟲架構(gòu)
框架
mySpider
├─ spiderMain.py #爬蟲調(diào)度端
├─ urlManager.py #URL管理器
├─ htmlDownloader.py #網(wǎng)頁下載器
└─ htmlParser.py #網(wǎng)頁解析器
此篇以爬取廖雪峰的官方網(wǎng)站中的python教程為例
不過廖老師的網(wǎng)站對爬蟲進(jìn)行了過濾,建議舉一反三,嘗試爬取其他網(wǎng)站
現(xiàn)在的網(wǎng)絡(luò)爬蟲越來越多,有很多爬蟲都是初學(xué)者寫的,和搜索引擎的爬蟲不一樣,他們不懂如何控制速度,結(jié)果往往大量消耗服務(wù)器資源,導(dǎo)致帶寬白白浪費(fèi)了。
其實(shí)Nginx可以非常容易地根據(jù)User-Agent過濾請求,我們只需要在需要URL入口位置通過一個簡單的正則表達(dá)式就可以過濾不符合要求的爬蟲請求:
...
location / {
if ($http_user_agent ~* "python|curl|java|wget|httpclient|okhttp") {
return 503;
}
# 正常處理
...
}
...
變量$http_user_agent是一個可以直接在location中引用的Nginx變量。~*表示不區(qū)分大小寫的正則匹配,通過python就可以過濾掉80%的Python爬蟲
爬蟲調(diào)度端
爬蟲調(diào)度端的核心代碼實(shí)現(xiàn):
while urlManager.hasUrl(): #詢問url管理器是否有待爬url
newurl = urlManager.getUrl() #獲取待爬url
html = htmlDownloader.download(newurl,headers) #下載頁面
(title,content),urls = htmlParser.parser(html) #從頁面中解析出內(nèi)容和新的url
#此處可以處理爬下來的文件,比如儲存到本地,我僅打印標(biāo)題以測試
print(title)
urlManager.addUrls(urls) #將新的url加入URL管理器
其中的headers是請求頭,用于模擬瀏覽器的行為
可以使用chrome瀏覽器的開發(fā)者工具找到
網(wǎng)頁右擊 檢查->network
刷新頁面

完整代碼:
import requests
from urlManager import UrlManager #url管理器
from htmlDownloader import HtmlDownloader #頁面下載器
from htmlParser import HtmlParser #網(wǎ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('爬蟲開始...')
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)
#文件處理儲存
urlManager.addUrls(urls)
URL管理器
url管理器可以使用queue或set甚至list來實(shí)現(xiàn),如果需要按順序爬取,可以使用隊(duì)列來實(shí)現(xiàn),即先加入url管理器的url先爬取,但是需要注意的是使用隊(duì)列則需要檢查待加入的url是否已經(jīng)在隊(duì)列中了。
而如果對順序沒有特別的要求,使用set更為簡便,可以直接加入待爬url,因?yàn)橹貜?fù)的元素只會在set中出現(xiàn)一次。
但無論使用queue、set還是list實(shí)現(xiàn),都需要檢查待加的url是否已經(jīng)爬過了,以此避免重復(fù)爬取甚至循環(huán)爬取
完整代碼:
class UrlManager(object):
def __init__(self):
self._newUrls = set([]) #set newUrls中儲存未訪問的url
self._oldUrls = set([]) #set oldUrls中儲存已訪問的url
def addUrl(self,url): #添加url
if isinstance(url,str) and url not in self._oldUrls:
self._newUrls.add(url)
def hasUrl(self): #是否還有未訪問的url
return len(self._newUrls) > 0
def getUrl(self): #該函數(shù)返回url視為已訪問
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)頁下載器
網(wǎng)頁下載器十分簡單,在此使用了request模塊
詳情可以查看request模塊文檔
完整代碼:
import requests
class HtmlDownloader(object):
def download(self,url,headers=""): #url:待爬鏈接 headers:請求頭 return value:html文本
if url is None or not isinstance(url,str):
#獲取失敗錯誤處理
return None
r = requests.get(url,headers=headers)
return r.text
網(wǎng)頁解析器
網(wǎng)頁解析器使用了BeautifulSoup模塊,非常方便快捷
具體參考BeautifulSoup文檔
像我就記不太住,都是隨用隨查的,所以這是最費(fè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)
價值數(shù)據(jù)
做到這里,我們就已經(jīng)可以獲取到我們想要的價值數(shù)據(jù)了。
在這里我僅打印標(biāo)題以測試,實(shí)際上我們可以對數(shù)據(jù)做更多的處理,而這就需要我們繼續(xù)深入學(xué)習(xí)了。共勉!

