Python知識點之Python爬蟲(干貨)
1.scrapy框架有哪幾個組件/模塊?
Scrapy Engine: 這是引擎,負責Spiders、ItemPipeline、Downloader、Scheduler中間的通訊,信號、數(shù)據(jù)傳遞等等!(像不像人的身體?)
Scheduler(調度器): 它負責接受引擎發(fā)送過來的requests請求,并按照一定的方式進行整理排列,入隊、并等待Scrapy Engine(引擎)來請求時,交給引擎。
Downloader(下載器):負責下載Scrapy Engine(引擎)發(fā)送的所有Requests請求,并將其獲取到的Responses交還給Scrapy Engine(引擎),由引擎交給Spiders來處理,
Spiders:它負責處理所有Responses,從中分析提取數(shù)據(jù),獲取Item字段需要的數(shù)據(jù),并將需要跟進的URL提交給引擎,再次進入Scheduler(調度器),
Item Pipeline:它負責處理Spiders中獲取到的Item,并進行處理,比如去重,持久化存儲(存數(shù)據(jù)庫,寫入文件,總之就是保存數(shù)據(jù)用的)
Downloader Middlewares(下載中間件):你可以當作是一個可以自定義擴展下載功能的組件
2.簡單說一下scrapy工作流程。
數(shù)據(jù)在整個Scrapy的流向:
程序運行的時候,
引擎:Hi!Spider, 你要處理哪一個網(wǎng)站?
Spiders:我要處理23wx.com
引擎:你把第一個需要的處理的URL給我吧。
Spiders:給你第一個URL是XXXXXXX.com
引擎:Hi!調度器,我這有request你幫我排序入隊一下。
調度器:好的,正在處理你等一下。
引擎:Hi!調度器,把你處理好的request給我,
調度器:給你,這是我處理好的request
引擎:Hi!下載器,你按照下載中間件的設置幫我下載一下這個request
下載器:好的!給你,這是下載好的東西。(如果失?。翰缓靡馑?,這個request下載失敗,然后引擎告訴調度器,這個request下載失敗了,你記錄一下,我們待會兒再下載。)
引擎:Hi!Spiders,這是下載好的東西,并且已經(jīng)按照Spider中間件處理過了,你處理一下(注意!這兒responses默認是交給def parse這個函數(shù)處理的)
Spiders:(處理完畢數(shù)據(jù)之后對于需要跟進的URL),Hi!引擎,這是我需要跟進的URL,將它的responses交給函數(shù) def xxxx(self, responses)處理。還有這是我獲取到的Item。
引擎:Hi !Item Pipeline 我這兒有個item你幫我處理一下!調度器!這是我需要的URL你幫我處理下。然后從第四步開始循環(huán),直到獲取到你需要的信息,注意!只有當調度器中不存在任何request了,整個程序才會停止,(也就是說,對于下載失敗的URL,Scrapy會重新下載。)
3.scrapy指紋去重原理和scrappy-redis的去重原理?
scrapy的去重原理流程:利用hash值和集合去重。首先創(chuàng)建fingerprint = set()結合,然后將request對象利用sha1對象進行信息摘要,摘要完成之后, 判斷hash值是否在集合中,如果在,返回true,如果不在,就add到集合。
scrapy-redis 的去重原理基本是一樣的,只不過持久化存儲到redis共享數(shù)據(jù)庫中,當請求數(shù)據(jù)達到10億級以上,這個時候就會非常消耗內(nèi)存,一個sha1 40個字節(jié),就會占40G的內(nèi)存,這個存儲絕大部分的數(shù)據(jù)庫無法承受,這個時候就要使用布隆過濾器。
4.請簡要介紹下scrapy框架。
scrapy 是一個快速(fast)、高層次(high-level)的基于 python 的 web 爬蟲構架,用于抓取web站點并從頁面中提取結構化的數(shù)據(jù)。scrapy 使用了 Twisted異步網(wǎng)絡庫來處理網(wǎng)絡通訊。
5.為什么要使用scrapy框架?scrapy框架有哪些優(yōu)點?
它更容易構建大規(guī)模的抓取項目
它異步處理請求,速度非???/p>
它可以使用自動調節(jié)機制自動調整爬行速度
6.scrapy如何實現(xiàn)分布式抓???
可以借助scrapy_redis類庫來實現(xiàn)。
在分布式爬取時,會有master機器和slave機器,其中,master為核心服務器,slave為具體的爬蟲服務器。
我們在master服務器上搭建一個redis數(shù)據(jù)庫,并將要抓取的url存放到redis數(shù)據(jù)庫中,所有的slave爬蟲服務器在抓取的時候從redis數(shù)據(jù)庫中去鏈接,由于scrapy_redis自身的隊列機制,slave獲取的url不會相互沖突,然后抓取的結果最后都存儲到數(shù)據(jù)庫中。master的redis數(shù)據(jù)庫中還會將抓取過的url的指紋存儲起來,用來去重。相關代碼在dupefilter.py文件中的request_seen()方法中可以找到。
去重問題:
dupefilter.py 里面的源碼:
def request_seen(self, request):
fp = request_fingerprint(request)
added = self.server.sadd(self.key, fp)
return not added
去重是把 request 的 fingerprint 存在 redis 上,來實現(xiàn)的。
7.scrapy和requests的使用情況?
requests 是 polling 方式的,會被網(wǎng)絡阻塞,不適合爬取大量數(shù)據(jù)
scapy 底層是異步框架 twisted ,并發(fā)是最大優(yōu)勢
8.爬蟲使用多線程好?還是多進程好?為什么?
對于IO密集型代碼(文件處理,網(wǎng)絡爬蟲),多線程能夠有效提升效率(單線程下有IO操作會進行IO等待,會造成不必要的時間等待,而開啟多線程后,A線程等待時,會自動切換到線程B,可以不浪費CPU的資源,從而提升程序執(zhí)行效率)。
在實際的采集過程中,既考慮網(wǎng)速和相應的問題,也需要考慮自身機器硬件的情況,來設置多進程或者多線程。
9.了解哪些基于爬蟲相關的模塊?
網(wǎng)絡請求:urllib,requests,aiohttp
數(shù)據(jù)解析:re,xpath,bs4,pyquery
selenium
js逆向:pyexcJs
10.列舉在爬蟲過程中遇到的哪些比較難的反爬機制?
動態(tài)加載的數(shù)據(jù)
動態(tài)變化的請求參數(shù)
js加密
代理
cookie
11.簡述如何抓取動態(tài)加載數(shù)據(jù)?
基于抓包工具進行全局搜索
如果動態(tài)加載的數(shù)據(jù)是密文,則全局搜索是搜索不到
12.移動端數(shù)據(jù)如何抓???
fiddler,appnium,網(wǎng)絡配置
13.如何實現(xiàn)全站數(shù)據(jù)爬取?
基于手動請求發(fā)送+遞歸解析
基于CrwalSpider(LinkExtractor,Rule)
14.如何提升爬取數(shù)據(jù)的效率?
使用框架
線程池,多任務的異步協(xié)程
分布式
15.列舉你接觸的反爬機制?
從功能上來講,爬蟲一般分為數(shù)據(jù)采集,處理,儲存三個部分。這里我們只討論數(shù)據(jù)采集部分。
一般網(wǎng)站從三個方面反爬蟲:用戶請求的Headers,用戶行為,網(wǎng)站目錄和數(shù)據(jù)加載方式。前兩種比較容易遇到,大多數(shù)網(wǎng)站都從這些角度來反爬蟲。第三種一些應用ajax的網(wǎng)站會采用,這樣增大了爬取的難度。
1)通過Headers反爬蟲
從用戶請求的Headers反爬蟲是最常見的反爬蟲策略。很多網(wǎng)站都會對Headers的User-Agent進行檢測,還有一部分網(wǎng)站會對Referer進行檢測(一些資源網(wǎng)站的防盜鏈就是檢測Referer)。如果遇到了這類反爬蟲機制,可以直接在爬蟲中添加Headers,將瀏覽器的User-Agent復制到爬蟲的Headers中;或者將Referer值修改為目標網(wǎng)站域名。對于檢測Headers的反爬蟲,在爬蟲中修改或者添加Headers就能很好的繞過。
2)基于用戶行為反爬蟲
還有一部分網(wǎng)站是通過檢測用戶行為,例如同一IP短時間內(nèi)多次訪問同一頁面,或者同一賬戶短時間內(nèi)多次進行相同操作。大多數(shù)網(wǎng)站都是前一種情況,對于這種情況,使用IP代理就可以解決??梢詫iT寫一個爬蟲,爬取網(wǎng)上公開的代理ip,檢測后全部保存起來。這樣的代理ip爬蟲經(jīng)常會用到,最好自己準備一個。有了大量代理ip后可以每請求幾次更換一個ip,這在requests或者urllib2中很容易做到,這樣就能很容易的繞過第一種反爬蟲。
對于第二種情況,可以在每次請求后隨機間隔幾秒再進行下一次請求。有些有邏輯漏洞的網(wǎng)站,可以通過請求幾次,退出登錄,重新登錄,繼續(xù)請求來繞過同一賬號短時間內(nèi)不能多次進行相同請求的限制。
3)動態(tài)頁面的反爬蟲
上述的幾種情況大多都是出現(xiàn)在靜態(tài)頁面,還有一部分網(wǎng)站,我們需要爬取的數(shù)據(jù)是通過ajax請求得到,或者通過JavaScript生成的。首先用Firebug或者HttpFox對網(wǎng)絡請求進行分析。
如果能夠找到ajax請求,也能分析出具體的參數(shù)和響應的具體含義,我們就能采用上面的方法,直接利用requests或者urllib2模擬ajax請求,對響應的json進行分析得到需要的數(shù)據(jù)。
能夠直接模擬ajax請求獲取數(shù)據(jù)固然是極好的,但是有些網(wǎng)站把ajax請求的所有參數(shù)全部加密了。我們根本沒辦法構造自己所需要的數(shù)據(jù)的請求。我這幾天爬的那個網(wǎng)站就是這樣,除了加密ajax參數(shù),它還把一些基本的功能都封裝了,全部都是在調用自己的接口,而接口參數(shù)都是加密的。遇到這樣的網(wǎng)站,我們就不能用上面的方法了,我用的是selenium+phantomJS框架,調用瀏覽器內(nèi)核,并利用phantomJS執(zhí)行js來模擬人為操作以及觸發(fā)頁面中的js腳本。從填寫表單到點擊按鈕再到滾動頁面,全部都可以模擬,不考慮具體的請求和響應過程,只是完完整整的把人瀏覽頁面獲取數(shù)據(jù)的過程模擬一遍。
用這套框架幾乎能繞過大多數(shù)的反爬蟲,因為它不是在偽裝成瀏覽器來獲取數(shù)據(jù)(上述的通過添加 Headers一定程度上就是為了偽裝成瀏覽器),它本身就是瀏覽器,phantomJS就是一個沒有界面的瀏覽器,只是操控這個瀏覽器的不是人。利用 selenium+phantomJS能干很多事情,例如識別點觸式(12306)或者滑動式的驗證碼,對頁面表單進行暴力破解等等。它在自動化滲透中還 會大展身手,以后還會提到這個。
16.什么是深度優(yōu)先和廣度優(yōu)先(優(yōu)劣)
默認情況下scrapy是深度優(yōu)先。
深度優(yōu)先:占用空間大,但是運行速度快。
廣度優(yōu)先:占用空間少,運行速度慢
17.是否了解谷歌的無頭瀏覽器?
無頭瀏覽器即headless browser,是一種沒有界面的瀏覽器。既然是瀏覽器那么瀏覽器該有的東西它都應該有,只是看不到界面而已。
Python中selenium模塊中的PhantomJS即為無界面瀏覽器(無頭瀏覽器):是基于QtWebkit的無頭瀏覽器。
18.說下Scrapy的優(yōu)缺點。
優(yōu)點:
scrapy 是異步的
采取可讀性更強的xpath代替正則
強大的統(tǒng)計和log系統(tǒng)
同時在不同的url上爬行
支持shell方式,方便獨立調試
寫middleware,方便寫一些統(tǒng)一的過濾器
通過管道的方式存入數(shù)據(jù)庫
缺點:基于python的爬蟲框架,擴展性比較差
基于twisted框架,運行中的exception是不會干掉reactor,并且異步框架出錯后是不會停掉其他任務的,數(shù)據(jù)出錯后難以察覺。
19.需要登錄的網(wǎng)頁,如何解決同時限制ip,cookie,session?
解決限制IP可以使用代理IP地址池、服務器;不適用動態(tài)爬取的情況下可以使用反編譯JS文件獲取相應的文件,或者換用其它平臺(比如手機端)看看是否可以獲取相應的json文件。
20.驗證碼的解決?
1.輸入式驗證碼
解決思路:這種是最簡單的一種,只要識別出里面的內(nèi)容,然后填入到輸入框中即可。這種識別技術叫OCR,這里我們推薦使用Python的第三方庫,tesserocr。對于沒有什么背影影響的驗證碼,直接通過這個庫來識別就可以。但是對于有嘈雜的背景的驗證碼這種,直接識別識別率會很低,遇到這種我們就得需要先處理一下圖片,先對圖片進行灰度化,然后再進行二值化,再去識別,這樣識別率會大大提高。
驗證碼識別大概步驟:
轉化成灰度圖
去背景噪聲
圖片分割
2.滑動式驗證碼
解決思路:對于這種驗證碼就比較復雜一點,但也是有相應的辦法。我們直接想到的就是模擬人去拖動驗證碼的行為,點擊按鈕,然后看到了缺口 的位置,最后把拼圖拖到缺口位置處完成驗證。
第一步:點擊按鈕。然后我們發(fā)現(xiàn),在你沒有點擊按鈕的時候那個缺口和拼圖是沒有出現(xiàn)的,點擊后才出現(xiàn),這為我們找到缺口的位置提供了靈感。
第二步:拖到缺口位置。我們知道拼圖應該拖到缺口處,但是這個距離如果用數(shù)值來表示?通過我們第一步觀察到的現(xiàn)象,我們可以找到缺口的位置。這里我們可以比較兩張圖的像素,設置一個基準值,如果某個位置的差值超過了基準值,那我們就找到了這兩張圖片不一樣的位置,當然我們是從那塊拼圖的右側開始并且從左到右,找到第一個不一樣的位置時就結束,這是的位置應該是缺口的left,所以我們使用selenium拖到這個位置即可。這里還有個疑問就是如何能自動的保存這兩張圖?這里我們可以先找到這個標簽,然后獲取它的location和size,然后 top,bottom,left,right = location['y'] ,location['y']+size['height']+location['x'] + size['width'] ,然后截圖,最后摳圖填入這四個位置就行。具體的使用可以查看selenium文檔,點擊按鈕前摳張圖,點擊后再摳張圖。最后拖動的時候要需要模擬人的行為,先加速然后減速。因為這種驗證碼有行為特征檢測,人是不可能做到一直勻速的,否則它就判定為是機器在拖動,這樣就無法通過驗證了。
3.點擊式的圖文驗證 和 圖標選擇
圖文驗證:通過文字提醒用戶點擊圖中相同字的位置進行驗證。
圖標選擇: 給出一組圖片,按要求點擊其中一張或者多張。借用萬物識別的難度阻擋機器。
這兩種原理相似,只不過是一個是給出文字,點擊圖片中的文字,一個是給出圖片,點出內(nèi)容相同的圖片。
這兩種沒有特別好的方法,只能借助第三方識別接口來識別出相同的內(nèi)容,推薦一個超級鷹,把驗證碼發(fā)過去,會返回相應的點擊坐標。然后再使用selenium模擬點擊即可。具體怎么獲取圖片和上面方法一樣。
21.滑動驗證碼如何破解?
破解核心思路:
1、如何確定滑塊滑動的距離?
滑塊滑動的距離,需要檢測驗證碼圖片的缺口位置
滑動距離 = 終點坐標 - 起點坐標
然后問題轉化為我們需要屏幕截圖,根據(jù)selenium中的position方法并進行一些坐標計算,獲取我們需要的位置
2、坐標我們?nèi)绾潍@???
起點坐標:
每次運行程序,位置固定不變,滑塊左邊界離驗證碼圖片左邊界有6px的距離
終點坐標:
每次運行程序,位置會變,我們需要計算每次缺口的位置
怎么計算終點也就是缺口的位置?
先舉個例子,比如我下面兩個圖片都是120x60的圖片,一個是純色的圖片,一個是有一個藍色線條的圖片(藍色線條位置我事先設定的是60px位置),我現(xiàn)在讓你通過程序確定藍色線條的位置,你怎么確定?
答案:
遍歷所有像素點色值,找出色值不一樣的點的位置來確定藍色線條的位置
這句話該怎么理解?大家點開我下面的圖片,是不是發(fā)現(xiàn)圖片都是由一個一個像素點組成的,120×60的圖片,對應的像素就是橫軸有120個像素點,縱軸有60個像素點,我們需要遍歷兩個圖片的坐標并對比色值,從(0,0)(0,1)......一直到(120,60),開始對比兩個圖片的色值,遇到色值不一樣的,我們return返回該位置即可
22.爬下來的數(shù)據(jù)是怎么存儲?
以json格式存儲到文本文件
這是最簡單,最方便,最使用的存儲方式,json格式保證你在打開文件時,可以直觀的檢查所存儲的數(shù)據(jù),一條數(shù)據(jù)存儲一行,這種方式適用于爬取數(shù)據(jù)量比較小的情況,后續(xù)的讀取分析也是很方便的。
存儲到excel
如果爬取的數(shù)據(jù)很容易被整理成表格的形式,那么存儲到excel是一個比較不錯的選擇,打開excel后,對數(shù)據(jù)的觀察更加方便,excel也可以做一些簡單的操作,寫excel可以使用xlwt這個庫,讀取excel可以使用xlrd,同方法1一樣,存儲到excel里的數(shù)據(jù)不宜過多,此外,如果你是多線程爬取,不可能用多線程去寫excel,這是一個限制。
存儲到sqlite
sqlite無需安裝,是零配置數(shù)據(jù)庫,這一點相比于mysql要輕便太多了,語法方面,只要你會mysql,操作sqlite就沒有問題。當爬蟲數(shù)據(jù)量很大時,需要持久化存儲,而你又懶得安裝mysql時,sqlite絕對是最佳選擇,不多呢,它不支持多進程讀寫,因此不適合多進程爬蟲。
存儲到mysql數(shù)據(jù)庫
mysql可以遠程訪問,而sqlite不可以,這意味著你可以將數(shù)據(jù)存儲到遠程服務器主機上,當數(shù)據(jù)量非常大時,自然要選擇mysql而不是sqlite,但不論是mysql還是sqlite,存儲數(shù)據(jù)前都要先建表,根據(jù)要抓取的數(shù)據(jù)結構和內(nèi)容,定義字段,這是一個需要耐心和精力的事情
存儲到mongodb
我最喜歡no sql 數(shù)據(jù)庫的一個原因就在于不需要像關系型數(shù)據(jù)庫那樣去定義表結構,因為定義表結構很麻煩啊,要確定字段的類型,varchar 類型數(shù)據(jù)還要定義長度,你定義的小了,數(shù)據(jù)太長就會截斷。
mongodb 以文檔方式存儲數(shù)據(jù),你使用pymongo這個庫,可以直接將數(shù)據(jù)以json格式寫入mongodb, 即便是同一個collection,對數(shù)據(jù)的格式也是沒有要求的,實在是太靈活了。
剛剛抓下來的數(shù)據(jù),通常需要二次清洗才能使用,如果你用關系型數(shù)據(jù)庫存儲數(shù)據(jù),第一次就需要定義好表結構,清洗以后,恐怕還需要定義個表結構,將清洗后的數(shù)據(jù)重新存儲,這樣過于繁瑣,使用mongodb,免去了反復定義表結構的過程。
23.cookie過期的處理問題?
這時候就需要cookie自動的更新了。通常怎樣自動更新cookie呢?這里會用到selenium。
步驟1、 采用selenium自動登錄獲取cookie,保存到文件;
步驟2、 讀取cookie,比較cookie的有效期,若過期則再次執(zhí)行步驟1;
步驟3、 在請求其他網(wǎng)頁時,填入cookie,實現(xiàn)登錄狀態(tài)的保持。
24.Selenium和PhantomJS
selenium
Selenium是一個用于Web應用程序測試的工具。Selenium測試直接運行在瀏覽器中,就像真正的用戶在操作一樣。支持的瀏覽器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等主流瀏覽器。這個工具的主要功能包括:測試與瀏覽器的兼容性
——測試你的應用程序看是否能夠很好得工作在不同瀏覽器和操作系統(tǒng)之上。
它的功能有:
框架底層使用JavaScript模擬真實用戶對瀏覽器進行操作。測試腳本執(zhí)行時,瀏覽器自動按照腳本代碼做出點擊,輸入,打開,驗證等操作,就像真實用戶所做的一樣,從終端用戶的角度測試應用程序。
使瀏覽器兼容性測試自動化成為可能,盡管在不同的瀏覽器上依然有細微的差別。
使用簡單,可使用Java,Python等多種語言編寫用例腳本
也就是說,它可以根據(jù)指令,做出像真實的人在訪問瀏覽器一樣的動作,比如打開網(wǎng)頁,截圖等功能。
phantomjs
(新版本的selenium已經(jīng)開始棄用phantomjs, 不過有時候我們可以單獨用它做一些事情)是一個基于Webkit的無界面瀏覽器,可以把網(wǎng)站內(nèi)容加載到內(nèi)存中并執(zhí)行頁面上的各種腳本(比如js)。
25.怎么判斷網(wǎng)站是否更新?
1、304頁面http狀態(tài)碼
當?shù)诙握埱箜撁嬖L問的時候,該頁面如果未更新,則會反饋一個304代碼,而搜索引擎也會利用這個304http狀態(tài)碼來進行判斷頁面是否更新。
首先第一次肯定是要爬取網(wǎng)頁的,假設是A.html,這個網(wǎng)頁存儲在磁盤上,相應地有個修改時間(也即是更新這個文件的時間)。
那么第二次爬取的時候,如果發(fā)現(xiàn)這個網(wǎng)頁本地已經(jīng)有了,例如A.html,這個時候,你只需要向服務器發(fā)送一個If-Modified-Since的請求,把A.html的修改時間帶上去。
如果這段時間內(nèi),A.html更新了,也就是A.html過期了,服務器就會HTTP狀態(tài)碼200,并且把新的文件發(fā)送過來,這時候只要更新A.html即可。
如果這段時間內(nèi),A.html的內(nèi)容沒有變,服務器就會返返回HTTP狀態(tài)碼304(不返回文件內(nèi)容),這個時候就不需要更新文件。
2、Last-Modified文件最后修改時間
這是http頭部信息中的一個屬性,主要是記錄頁面最后一次的修改時間,往往我們會發(fā)現(xiàn),一些權重很高的網(wǎng)站,及時頁面內(nèi)容不更新,但是快照卻還是能夠每日更新,這其中就有Last-Modified的作用。通產(chǎn)情況下,下載網(wǎng)頁我們使用HTTP協(xié)議,向服務器發(fā)送HEAD請求,可以得到頁面的最后修改時間LastModifed,或者標簽ETag。將這兩個變量和上次下載記錄的值的比較就可以知道一個網(wǎng)頁是否跟新。這個策略對于靜態(tài)網(wǎng)頁是有效的。是對于絕大多數(shù)動態(tài)網(wǎng)頁如ASP,JSP來說,LastModifed就是服務器發(fā)送Response的時間,并非網(wǎng)頁的最后跟新時間,而Etag通常為空值。所以對于動態(tài)網(wǎng)頁使用LastModifed和Etag來判斷是不合適的,因此Last-Modified只是蜘蛛判斷頁面是否更新的一個參考值,而不是條件。