隨着業務需求的變化,大規模爬蟲遇到各類問題。python爬蟲具備先天優點,社區資源比較齊全,各類框架也完美支持。爬蟲性能也獲得極大提高。本次分享從基礎知識入手,涉及python 的兩大爬蟲框架pyspider、scrapy,並基於scrapy、scrapy-redis 作了分佈式爬蟲的介紹(直接粘貼的ppt截圖)會涉及 redis、mongodb等相關知識。css
1.1 爬蟲是什麼?html
網絡爬蟲(又被稱爲網頁蜘蛛,網絡機器人),是一種按照必定的規則,自動的抓取萬維網信息的程序或者腳本。python
1.2 爲何是Python?mysql
簡單易學,編寫完後能夠直接執行,無須編譯,代碼重用性高,跨平臺性。git
2.1 Robots協議程序員
Robots 協議也被稱做爬蟲協議、機器人協議,它的全名叫作網絡爬蟲排除標準(Robots Exclusion Protocol),用來告訴爬蟲和搜索引擎哪些頁面能夠抓取,哪些不能夠抓取。它一般是一個叫作 robots.txt 的文本文件,放在網站的根目錄下。github
當搜索爬蟲訪問一個站點時,它首先會檢查下這個站點根目錄下是否存在 robots.txt 文件,若是存在,搜索爬蟲會根據其中定義的爬取範圍來爬取。若是沒有找到這個文件,那麼搜索爬蟲便會訪問全部可直接訪問的頁面。web
2.2 URL 的含義redis
概念:sql
URL(協議(服務方式) + IP地址(包括端口號) + 具體地址),即統一資源定位符,也就是咱們說的網址,統一資源定位符是對能夠從互聯網上獲得的資源的位置和訪問方法的一種簡潔的表示,是互聯網上標準資源的地址。互聯網上的每一個文件都有一個惟一的URL,它包含的信息指出文件的位置以及瀏覽器應該怎麼處理它。爬蟲爬取數據時必需要有一個目標的URL才能夠獲取數據,
2.3 瀏覽網頁的過程
在用戶瀏覽網頁的過程當中,咱們可能會看到許多好看的圖片,好比 http://image.baidu.com/ ,咱們會看到幾張的圖片以及百度搜索框,這個過程其實就是用戶輸入網址以後,通過 DNS 服務器,找到服務器主機,向服務器發出一個請求,服務器通過解析以後,發送給用戶的瀏覽器 HTML、JS、CSS 等文件,瀏覽器解析出來,用戶即可以看到形形色色的圖片了,其實就是一次http請求的過程
2.4 代理基本原理
基本原理
在本機和服務器之間搭建了一個橋,此時本機不是直接向 Web 服務器發起請求,而是向代理服務器發出請求,這個過程 Web 服務器識別出的真實的 IP 就再也不是咱們本機的 IP 了,就成功實現了 IP 假裝,這就是代理的基本原理
代理的做用
一、突破自身 IP 訪問限制,訪問一些平時不能訪問的站點
二、訪問一些單位或團體內部資源
三、隱藏真實 IP
爬蟲代理
在爬取過程當中可能遇到同一個 IP 訪問過於頻繁的問題,網站就會讓咱們輸入驗證碼或登陸或者直接封鎖 IP,這樣會給爬取帶來極大的不便。讓服務器誤覺得是代理服務器的在請求本身。這樣在爬取過程當中經過不斷更換代理,就不會被封鎖,能夠達到很好的爬取效果
代理分類
FTP 代理服務器、主要用於訪問 FTP 服務器
HTTP 代理服務器,主要用於訪問網頁
SSL/TLS 代理,主要用於訪問加密網站
常見代理設置
免費代理
付費代理
3.1 經常使用爬蟲庫
請求庫:requests、selenium(自動化測試工具)+ChromeDrive(chrome 驅動器)、PhantomJS(無界面瀏覽器)
解析庫: LXML(html、xml、Xpath方式)、BeautifulSoup(html、xml)、PyQuery(支持css選擇器)、Tesserocr(光學字符識別,驗證碼)
數據庫: mongo、mysql、redis, postgteSQL
存儲庫: pymysql、pymongo、redispy、RedisDump(Redis 數據導入導出的工具)
web庫: Flask(輕量級的 Web 服務程序)、Django
其它工具: Charles(網絡抓包工具)
3.2 一個入門栗子
3.3 複雜一點的栗子
反爬蟲:
反爬蟲,服務器會識別 headers 中的 referer 是否是它本身,若是不是,有的服務器不會響應,因此咱們還能夠在 headers 中加入 referer等信息
反「反爬蟲」
一、徹底模擬瀏覽器的工做
二、構造cookie信息
三、設置header信息
四、Proxy 代理設置
其它策略
Timeout設置
3.4 動態渲染頁面抓取
Splash 是一個 JavaScript 渲染服務,是一個帶有 HTTP API 的輕量級瀏覽器,同時它對接了 Python 中的 Twisted 和 QT 庫,利用它咱們一樣能夠實現動態渲染頁面的抓取。
異步方式處理多個網頁渲染過程
獲取渲染後的頁面的源代碼或截圖
經過關閉圖片渲染或者使用 Adblock 規則來加快頁面渲染速度
可執行特定的 JavaScript 腳本
可經過 Lua 腳原本控制頁面渲染過程
獲取渲染的詳細過程並經過 HAR(HTTP Archive)格式呈現
3.5 爬蟲完整流程
4.1 PySpider簡介
一個國人編寫的強大的網絡爬蟲系統並帶有強大的WebUI。採用Python語言編寫,分佈式架構,支持多種數據庫後端,強大的WebUI支持腳本編輯器,任務監視器,項目管理器以及結果查看器
4.2 PySpider特性
一、python 腳本控制,能夠用任何你喜歡的html解析包(內置 pyquery)
二、WEB 界面編寫調試腳本,起停腳本,監控執行狀態,查看活動歷史,獲取結果產出
三、數據存儲支持MySQL, MongoDB, Redis, SQLite, Elasticsearch; PostgreSQL 及 SQLAlchemy
四、隊列服務支持RabbitMQ, Beanstalk, Redis 和 Kombu
五、支持抓取 JavaScript 的頁面
六、組件可替換,支持單機/分佈式部署,支持 Docker 部署
七、強大的調度控制,支持超時重爬及優先級設置
八、支持python2&3
4.3 Scrapy簡介
Scrapy是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。 能夠應用在包括數據挖掘,信息處理或存儲歷史數據等一系列的程序中。
4.4 Scrapy運行流程
一、調度器(Scheduler)從待下載連接中取出一個連接(URL)
二、調度器啓動採集模塊Spiders模塊
三、採集模塊把URL傳給下載器(Downloader),下載器把資源下載下來
四、提取目標數據,抽取出目標對象(Item),則交給實體管道(item pipeline)進行進一步的處理;好比存入數據庫、文本
五、如果解析出的是連接(URL),則把URL插入到待爬取隊列當中
5.1 Scrapy基本使用
建立項目:scrapy startproject tutorial
建立spider:scrapy genspider quotes quotes.toscrapy.com
運行項目:scrapy crawl dmoz
交互調試:scrapy shell quotes.toscrape.com
保存數據(多種格式):scrapy crawl quotes -o quoqes.json
5.2 scrapy全局指令
startproject:建立項目
genspider:建立爬蟲
settings:獲取Scrapy的設定
runspider:在未建立項目的狀況下,運行一個編寫在Python文件中的spider
shell:以給定的URL(若是給出)或者空(沒有給出URL)啓動Scrapy shell
fetch:使用Scrapy下載器(downloader)下載給定的URL,並將獲取到的內容送到標準輸出
view:在瀏覽器中打開給定的URL,並以Scrapy spider獲取到的形式展示
Version:輸出Scrapy版本
5.3 scrapy項目指令
crawl:使用spider進行爬取
check:檢查項目是否有錯
list: 列出當前項目中全部可用的spider,每行輸出一個spider
edit:僅僅是提供一個快捷方式。開發者能夠自由選擇其餘工具或者IDE來編寫調試spiderparse
parse:獲取給定的URL並使用相應的spider分析處理
bench:運行benchmark測試
5.4 scrapy選擇器
BeautifulSoup 是在程序員間很是流行的網頁分析庫,它基於HTML代碼的結構來構造一個Python對象, 對不良標記的處理也很是合理,但它有一個缺點:慢。
lxml 是一個基於 ElementTree (不是Python標準庫的一部分)的python化的XML解析庫(也能夠解析HTML)。
Scrapy提取數據有本身的一套機制。它們被稱做選擇器(seletors),由於他們經過特定的 XPath 或者 CSS 表達式來「選擇」 HTML文件中的某個部分。
XPath 是一門用來在XML文件中選擇節點的語言,也能夠用在HTML上。
CSS 是一門將HTML文檔樣式化的語言。選擇器由它定義,並與特定的HTML元素的樣式相關連。
Scrapy選擇器構建於 lxml 庫之上,這意味着它們在速度和解析準確性上很是類似。
5.5 spiders
Spider類定義瞭如何爬取某個(或某些)網站。包括了爬取的動做(例如:是否跟進連接)以及如何從網頁的內容中提取結構化數據(爬取item)。 換句話說,Spider就是您定義爬取的動做及分析某個網頁(或者是有些網頁)的地方。
以初始的URL初始化Request,並設置回調函數。 當該request下載完畢並返回時,將生成response,並做爲參數傳給該回調函數。
spider中初始的request是經過調用 startrequests() 來獲取的。 startrequests() 讀取 start_urls 中的URL, 並以 parse 爲回調函數生成 Request 。
在回調函數內分析返回的(網頁)內容,返回 Item 對象、dict、 Request 或者一個包括三者的可迭代容器。 返回的Request對象以後會通過Scrapy處理,下載相應的內容,並調用設置的callback函數(函數可相同)。
在回調函數內,您可使用 選擇器(Selectors) (您也可使用BeautifulSoup, lxml 或者您想用的任何解析器) 來分析網頁內容,並根據分析的數據生成item。
最後,由spider返回的item將被存到數據庫(由某些 Item Pipeline 處理)或使用 Feed exports 存入到文件中。
屬性
name: 定義spider名字的字符串(string)
allowed_domains: 包含了 spider 容許爬取的域名(domain)列表(list)
start_urls: URL列表。當沒有制定特定的URL時,spider將從該列表中開始進行爬取
custom_settings: 該設置是一個dict.當啓動spider時,該設置將會覆蓋項目級的設置. 因爲設置必須在初始化(instantiation)前被更新,因此該屬性 必須定義爲class屬性
crawler: 該屬性在初始化class後,由類方法 from_crawler() 設置, 而且連接了本spider實例對應的 Crawler 對象
settings: crawler 的配置管理器,擴展(extensions)和中間件(middlewares)使用它用來訪問 Scrapy 的配置
logger: self.logger.info('日誌:%s', response.status)
方法
from_crawler: 若是存在,則調用此類方法以從 Crawler 建立 pipeline 實例。它必須返回一個新的pipeline實例。 Crawler 對象提供對全部 Scrapy 核心組件(如settings 和 signals)的訪問; 它是 pipeline 訪問它們並將其功能掛鉤到Scrapy中的一種方法
start_requests: 該方法必須返回一個可迭代對象(iterable)。該對象包含了spider用於爬取的第一個Request
make_requests_from_url: 該方法接受一個URL並返回用於爬取的 Request 對象
parse: 當response沒有指定回調函數時,該方法是Scrapy處理下載的response的默認方法
log: 使用 scrapy.log.msg() 方法記錄(log)message
closed: 當spider關閉時,該函數被調用
5.6 Item Pipeline
當 Item 在 Spider 中被收集以後,它將會被傳遞到Item Pipeline,一些組件會按照必定的順序執行對Item的處理。每一個item pipeline組件(有時稱之爲「Item Pipeline」)是實現了簡單方法的Python類。他們接收到Item並經過它執行一些行爲,同時也決定此Item是否繼續經過pipeline,或是被丟棄而再也不進行處理。
如下是item pipeline的一些典型應用:
一、清理HTML數據
二、驗證爬取的數據(檢查item包含某些字段)
三、查重(並丟棄)
四、將爬取結果保存到數據庫中
方法
process_item(self, item, spider): 每一個item pipeline組件都須要調用該方法,這個方法必須返回一個具備數據的dict,或是 Item (或任何繼承類)對象, 或是拋出 DropItem 異常,被丟棄的item將不會被以後的pipeline組件所處理。
open_spider: 當spider被開啓時,這個方法被調用。
close_spider: 當spider被關閉時,這個方法被調用
from_crawler: 獲取setting 配置信息
例子:數據持久化到mongo
5.7 Downloader Middleware
下載器中間件是介於 Scrapy 的 request/response 處理的鉤子框架。 是用於全局修改Scrapy request 和 response 的一個輕量、底層的系統。
激活
要激活下載器中間件組件,將其加入到DOWNLOADER_MIDDLEWARES 設置中。 該設置是一個字典(dict),鍵爲中間件類的路徑,值爲其中間件的順序(order)。
方法
一、process_request(request, spider): 當每一個request經過下載中間件時,該方法被調用
二、processresponse(request, response, spider): processrequest() 必須返回如下之一: 返回一個 Response 對象、 返回一個 Request 對象或raise一個 IgnoreRequest 異常。若是其返回一個 Response (能夠與傳入的response相同,也能夠是全新的對象), 該response會被在鏈中的其餘中間件的 process_response() 方法處理。
三、processexception(request, exception, spider): 當下載處理器(download handler)或 processrequest() (下載中間件)拋出異常(包括 IgnoreRequest 異常)時, Scrapy調用 process_exception()
例子:添加代理
6.1 爬蟲思路
6.2 實際項目分析
起始頁
項目結構
7.1 單主機爬蟲架構
本機維護一個爬蟲隊列,scheduler 進行調度
Q:多臺主機協做的關鍵是什麼?
A:共享爬蟲隊列
單主機爬蟲架構
7.2 分佈式爬蟲架構
分佈式爬蟲架構
7.3 問題
Q1:隊列怎麼選?
A1: Redis隊列
Redis 非關係型數據庫,key-value形式存儲,結構靈活
是內存中的數據結構存儲系統,處理速度快,性能好
提供隊列、集合等多種存儲結構,方便隊列維護
Q2:怎麼去重?
A2:Redis集合
Redis 提供集合數據結構,在redis集合中存儲每一個request的指紋
在向redis集合中存儲request指紋的時候,先驗證指紋是否存在?
[若是存在]:則不添加request到隊列
[不存在]:則將request加入隊列,並將指紋加入集合
Q3:怎樣防止中斷?
A3:啓動判斷
在每臺從機 scrapy 啓動時 都會首先判斷當前 redis reqeust隊列是否爲空
[不爲空]:從隊列中取得下一個 request ,進行執行爬蟲
[空]:從新開始爬取,第一臺從機執行爬取向隊列中添加request
Q4:怎樣實現該架構?
A4:Scrapy-Redis
scrapy是Python的一個很是好用的爬蟲框架,功能很是強大,可是當咱們要爬取的頁面很是多的時候,單個主機的處理能力就不能知足咱們的需求了(不管是處理速度仍是網絡請求的併發數),這時候分佈式爬蟲的優點就顯現出來,人多力量大。而scrapy-Redis就是結合了分佈式數據庫redis,重寫了scrapy一些比較關鍵的代碼(scrapy的調度器、隊列等組件),將scrapy變成一個能夠在多個主機上同時運行的分佈式爬蟲。
github地址:https://github.com/rmax/scrapy-redis
7.4 源碼解讀
閱讀源碼前:須要瞭解 scrapy 的運行原理,不然並沒什麼用。
scrapy-redis 工程的主體仍是 redis 和 scrapy 兩個庫,將兩個庫的核心功能結合,實現分佈式。
1 connection.py
負責根據setting中配置實例化redis鏈接。被dupefilter和scheduler調用,總之涉及到redis存取的都要使用到這個模塊
2 dupefilter.py
經過繼承 BaseDupeFilter 重寫他的方法,實現了基於redis的 request 判重。scrapy-redis使用 redis的一個 set 中插入 fingerprint(不一樣spider的key不一樣)
spider名字+DupeFilter的key就是爲了在不一樣主機上的不一樣爬蟲實例,只要屬於同一種spider,就會訪問到同一個set,而這個set就是他們的url判重池 。
DupeFilter 判重會在 scheduler 類中用到,每個request在進入調度以前都要進行判重,若是重複就不須要參加調度,直接捨棄就行了
3 picklecompat.py
loads 和 dumps 兩個函數,其實就是實現了一個 serializer,由於 redis 數據庫不能存儲複雜對象(value部分只能是字符串,字符串列表,字符串集合和hash,key部分只能是字符串),因此存儲前須要先序列化成文本才行
這個 serializer 主要用於 scheduler 存 reuqest 對象。
爲何不用json格式?(item pipeline 的串行化默認用的就是 json)
4 pipelines.py
pipeline 文件實現了一個 item pipieline 類,和 scrapy 的 item pipeline 是同一個對象,從settings中獲取咱們配置的REDISITEMSKEY做爲key,把item串行化以後存入redis數據庫對應的value中(這個value是list,咱們的每一個item是這個list中的一個結點),這個pipeline把提取出的item存起來,主要是爲了方便咱們延後處理數據。
5 queue.py
這裏實現了三種方式的 Queue
SpiderQueue(隊列):先進先出
SpiderStack(棧):先進後出
SpiderPriorityQueue(優先級隊列)
這些容器類都會做爲scheduler調度request的容器,scheduler在每一個主機上都會實例化一個,而且和spider一一對應,因此分佈式運行時會有一個spider的多個實例和一個scheduler的多個實例存在於不一樣的主機上,可是,由於scheduler都是用相同的容器,而這些容器都鏈接同一個redis服務器,又都使用spider名加queue來做爲key讀寫數據,因此不一樣主機上的不一樣爬蟲實例公用一個request調度池,實現了分佈式爬蟲之間的統一調度。
6 scheduler.py
重寫了scheduler類,代替scrapy.core.scheduler 的原有調度器,對原有調度器的邏輯沒有很大的改變,主要是使用了redis做爲數據存儲的媒介,以達到各個爬蟲之間的統一調度。scheduler負責調度各個spider的request請求,scheduler初始化時,經過settings文件讀取queue和dupefilters的類型,配置queue和dupefilters使用的key。每當一個request要被調度時,enqueuerequest被調用,scheduler使用dupefilters來判斷這個url是否重複,若是不重複,就添加到queue的容器中(能夠在settings中配置)。當調度完成時,nextrequest被調用,scheduler就經過queue容器的接口,取出一個request,把他發送給相應的spider,讓spider進行爬取工做。
7 spider.py
設計的這個spider從redis中讀取要爬的url,而後執行爬取,若爬取過程當中返回更多的url,那麼繼續進行直至全部的request完成。以後繼續從redis中讀取url,循環這個過程。
分析:在這個spider中經過connect signals.spideridle信號實現對crawler狀態的監視。當idle時,返回新的makerequestsfromurl(url)給引擎,進而交給調度器調度。