1.什麼是Scrapy-Redis
- Scrapy-Redis是scrapy框架基於redis的分散式元件,是scrapy的擴充套件;分散式爬蟲將多臺主機組合起來,共同完成一個爬取任務,快速高效地提高爬取效率。
- 原先scrapy的請求是放在記憶體中,從記憶體中獲取。scrapy-redisr將請求統一放在redis裡面,各個主機檢視請求是否爬取過,沒有爬取過,排隊入佇列,主機取出來爬取。爬過了就看下一條請求。
- 各主機的spiders將最後解析的資料通過管道統一寫入到redis中
- 優點:加快專案的執行速度;單個節點的不穩定性不影響整個系統的穩定性;支援端點爬取
- 缺點:需要投入大量的硬體資源,硬體、網路頻寬等
- 在scrapy框架流程的基礎上,把儲存request物件放到了redis的有序集合中,利用該有序集合實現了請求佇列
- 並對request物件生成指紋物件,也儲存到同一redis的集合中,利用request指紋避免傳送重複的請求
2.Scrapy-Redis分散式策略
假設有三臺電腦:Windows 10、Ubuntu 16.04、Windows 10,任意一臺電腦都可以作為 Master端 或 Slaver端,比如:
- Master端(核心伺服器) :使用 Windows 10,搭建一個Redis資料庫,不負責爬取,只負責url指紋判重、Request的分配,以及資料的儲存。
- Slaver端(爬蟲程式執行端) :使用 Ubuntu 16.04、Windows 10,負責執行爬蟲程式,執行過程中提交新的Request給Master。
首先Slaver端從Master端拿任務(Request、url)進行資料抓取,Slaver抓取資料的同時,產生新任務的Request便提交給 Master 處理
Master端只有一個Redis資料庫,負責將未處理的Request去重和任務分配,將處理後的Request加入待爬佇列,並且儲存爬取的資料。
Scrapy-Redis預設使用的就是這種策略,我們實現起來很簡單,因為任務排程等工作Scrapy-Redis都已經幫我們做好了,我們只需要繼承RedisSpider、指定redis_key就行了。
缺點是,Scrapy-Redis排程的任務是Request物件,裡面資訊量比較大(不僅包含url,還有callback函式、headers等資訊),可能導致的結果就是會降低爬蟲速度、而且會佔用Redis大量的儲存空間,所以如果要保證效率,那麼就需要一定硬體水平。
3.Scrapy-Redis的安裝和專案建立
3.1.安裝scrapy-redis
pip install scrapy-redis
3.2.專案建立前置準備
- win 10
- Redis安裝:redis相關配置參照這個https://www.cnblogs.com/gltou/p/16226721.html;如果跟著筆記學到這的,前面連結那個redis版本3.0太老了,需要重新安裝新版本,新版本下載連結:https://pan.baidu.com/s/1UwhJA1QxDDIi2wZFFIZwow?pwd=thak,提取碼:thak;
- Another Redis Desktop Manager:這個軟體是用於檢視redis中儲存的資料,安裝也很簡單,一直下一步即可;下載連結:https://pan.baidu.com/s/18CC2N6XtPn_2NEl7gCgViA?pwd=ju81 提取碼:ju81
Redia安裝簡單講解:安裝包下載下來後,點選下一步一直安裝就行,把安裝路徑記錄好;注意安裝好後需要將redis的安裝目錄新增到環境變數中;每當你修改了配置檔案,需要重啟redis時,要記得將服務重啟下
Another Redis Desktop Manager簡單講解:點選【New Connection】新增redis連線,連線內容如下(地址、埠等),密碼Auth和暱稱Name不是必填。
可以看到redis安裝的環境、當前redis的版本、記憶體、連線數等資訊。後面我們的筆記會講解通過該軟體檢視待抓取的URL以及URL的指紋
3.3.專案建立
建立普通scrapy爬蟲專案,在普通的專案上改造成scrapy-redis專案;普通爬蟲分為四個階段:建立專案、明確目標、建立爬蟲、儲存內容;
scrapy爬蟲專案建立好後,進行改造,具體改造點如下:
- 匯入scrapy-redis中的分散式爬蟲類
- 繼承類
- 註釋start_url & allowed_domains
- 設定redis_key獲取start_urls
- 編輯settings檔案
3.3.1.建立scrapy爬蟲
step-1:建立專案
建立scrapy_redis_demo目錄,在該目錄下輸入命令 scrapy startproject movie_test ,生成scrapy專案?cd到movie_test專案下 cd .\movie_test\ ?輸入命令 scrapy genspider get_movie 54php.cn 生成spiders模板檔案;這個過程不清楚的,轉到https://www.cnblogs.com/gltou/p/16400449.html學習下
step-2:明確目標
在items.py檔案中,明確我們此次需要爬取目標網站哪些資料。
1 # Define here the models for your scraped items 2 # 3 # See documentation in: 4 # https://docs.scrapy.org/en/latest/topics/items.html 5 6 import scrapy 7 8 9 class MovieTestItem(scrapy.Item): 10 # define the fields for your item here like: 11 # name = scrapy.Field() 12 13 #電影的名稱 14 tiele = scrapy.Field() 15 16 #電影的詳細描述 17 desc= scrapy.Field() 18 19 #電影的URL 20 download_url=scrapy.Field()
step-3:建立爬蟲
匯入items.py的類,在get_movie.py檔案中編寫我們的爬蟲檔案,下面為get_movie.py的code程式碼
import scrapy from ..items import MovieTestItem class GetMovieSpider(scrapy.Spider): name = 'get_movie' allowed_domains = ['54php.cn'] start_urls = ['http://movie.54php.cn/movie/'] def parse(self, response): movie_item=response.xpath("//div[2][@class='row']/div") for i in movie_item: #詳情頁url detail_url=i.xpath(".//a[@class='thumbnail']/@href").extract_first() yield scrapy.Request(url=detail_url,callback=self.parse_detail) next_page=response.xpath("//a[@aria-label='Next']/@href").extract_first() if next_page: next_page_url='http://movie.54php.cn{}'.format(next_page) yield scrapy.Request(url=next_page_url,callback=self.parse) def parse_detail(self,response): """解析詳情頁""" movie_info=MovieTestItem() movie_info["title"]=response.xpath("//div[@class='page-header']/h1/text()").extract_first() movie_info["desc"]=response.xpath("//div[@class='panel-body']/p[4]/text()").extract_first() movie_info["download_url"]=response.xpath("//div[@class='panel-body']/p[5]/text()").extract_first() yield movie_info
在settings.py檔案中啟用 USER_AGENT 並將網站的value放進去;將 ROBOTSTXT_OBEY 協議改為False
step-4:儲存內容
此處暫時不寫,先忽略,後面改成scrapy-redis寫入redis時,再編寫相關程式碼
step-5:執行爬蟲,檢視結果
輸入命令 scrapy crawl get_movie 執行爬蟲,檢視結果; finish_reason 為finished執行結束、 item_scraped_count 共爬取1060條資料、 log_count/DEBUG 日誌檔案中DEBUG記錄共2192條等結果資訊。
scrapy專案建立且執行成功,下面將進行scrapy-redis改造
3.3.2.改為scrapy-redis爬蟲
step-1:修改爬蟲檔案
get_movie.py爬蟲檔案引入 scrapy_redis 的 RedisSpider 類,並繼承他;將 allowed_domains 和 start_urls 註釋掉;新增 redis_key ,值value 是一個鍵值對,key就是name值即爬蟲名字,value就是start_urls這個變數名
1 import scrapy 2 from ..items import MovieTestItem 3 from scrapy_redis.spiders import RedisSpider 4 5 class GetMovieSpider(RedisSpider): 6 name = 'get_movie' 7 # allowed_domains = ['54php.cn'] 8 # start_urls = ['http://movie.54php.cn/movie/'] 9 redis_key = "get_movie:start_urls" 10 11 def parse(self, response): 12 movie_item=response.xpath("//div[2][@class='row']/div") 13 for i in movie_item: 14 #詳情頁url 15 detail_url=i.xpath(".//a[@class='thumbnail']/@href").extract_first() 16 yield scrapy.Request(url=detail_url,callback=self.parse_detail) 17 18 next_page=response.xpath("//a[@aria-label='Next']/@href").extract_first() 19 if next_page: 20 next_page_url='http://movie.54php.cn{}'.format(next_page) 21 yield scrapy.Request(url=next_page_url,callback=self.parse) 22 23 def parse_detail(self,response): 24 """解析詳情頁""" 25 movie_info=MovieTestItem() 26 movie_info["title"]=response.xpath("//div[@class='page-header']/h1/text()").extract_first() 27 movie_info["desc"]=response.xpath("//div[@class='panel-body']/p[4]/text()").extract_first() 28 movie_info["download_url"]=response.xpath("//div[@class='panel-body']/p[5]/text()").extract_first() 29 yield movie_info 30 31
step-2:修改redis配置檔案
將redis.windows.conf和redis.windows-service.conf檔案中 bind 設定成 0.0.0.0 ,重啟redis服務
step-3:redis存入種子url
cmd輸入 redis-cli 進入redis命令列,如果有密碼,追加命令 auth 密碼 如圖
將爬蟲get_movie.py檔案中設定的 redis_key (是一個鍵值對,key就是 name 值即爬蟲名字,value就是 start_urls 這個變數名),在redis中lpush一下;redis命令列輸入lpush後會將命令格式帶出來,不用管他,在lpush後面輸入key和value值
lpush的key值就是程式碼中的 redis_key 的值,value是 start_urls 的值,輸入好後,按下Enter鍵
在Another Redis Desktop Manager軟體中檢查是否新增成功,發現新增成功
step-4:配置檔案新增元件及redis引數
settings.py檔案中新增設定去重元件和排程元件,新增redis連線資訊;
1 # 設定去重元件 2 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" 3 # 排程元件 4 SCHEDULER = "scrapy_redis.scheduler.Scheduler" 5 6 #redis 連線資訊 7 REDIS_HOST = "127.0.0.1" 8 REDIS_PORT = 6379 9 REDIS_ENCODING = 'utf-8' 10 #REDIS_PARAMS = {"password":"123456"}
疑問:有人肯定會問,我在settings.py檔案中新增了redis的資訊,scrapy框架是怎麼拿到的呢?
答案:其實在https://www.cnblogs.com/gltou/p/16400449.html這篇的筆記中已經講到了,scrapy框架的scrapy.cfg檔案裡面會告訴框架配置檔案在哪邊,如圖,即在movie_test目錄下的settings檔案中
然後scrapy執行怕從檔案是通過cmdline的execute方法執行的 ,這個方法其中執行的get_project_settings( )方法,就是匯入全域性配置檔案scrapy.cfg,進而匯入專案的settings .py
通過get_project_settings( )這個方法的project.py檔案,我們在from部分看到匯入了Settings,感興趣的可以自己研究下原始碼
step-5:執行爬蟲檔案
pycharm輸入命令 scrapy crawl get_movie 執行爬蟲檔案
常見報錯:
報錯分析: 這個是因為我們安裝scrapy版本問題導致的,安裝Scrapy==2.5.1 版本即可
解決方案:
pip install -U Scrapy==2.5.1 -i https://pypi.tuna.tsinghua.edu.cn/simple/
注意:在pycharm中重新安裝後,啟動爬蟲檔案如果還報錯。在cmd視窗再執行一下pip命令,問題就解決了
執行爬蟲指令碼,爬取過程中,發現redis有兩張表(易於理解的描述), get_movie:dupefilter 代表已經爬取過的URL, get_movie:requests 代表待爬取的url
爬蟲指令碼執行成功,檢視redis,發現value裡面有好多資料,這些資料都是hash值,已經爬取過的url就會以hash的方式儲存到value裡面,這樣就不會重複爬取同一個url。爬取結束後,pycharm中按ctrl+c停止指令碼
step-6:再次爬取
爬蟲指令碼執行結束後,redis裡面種子url沒有了。想要再次執行爬蟲指令碼,需要將種子再下發給redis,即再執行一次lpush命令,就又可以執行爬蟲指令碼了。
再次下發種子
執行指令碼再次爬取
3.3.3.斷點續爬
當爬蟲爬取一半中斷時,設定了斷點續爬後,爬蟲會接著爬取,而不會再次重新爬取資料。如何實現斷點續爬,很簡單,再settings.py檔案中開啟斷點續爬即可
1 #斷點續爬 2 SCHEDULER_PERSIST = True
示例
場景:爬蟲到一半我們CTRL+C停止爬蟲指令碼?再次啟動爬蟲指令碼?爬取結束後,沒有種子url的情況下再次執行爬蟲指令碼
步驟:
step-1:redis放入種子url執行爬蟲,強制停止爬蟲指令碼
LPUSH get_movie:start_urls http://movie.54php.cn/movie/
結果:爬取到一半,我們強制停止爬蟲指令碼,發現redis裡面requests的資料還在,沒有消失
step-2:再次啟動爬蟲檔案
結果:最後指令碼結束統計的爬取數量是第二次爬取的數量,而不是總的數量
step-3:當爬取結束後,我們再次啟動爬蟲檔案
結果:控制檯告訴我們 start_urls 已經抓取過了,你要抓取的url在 get_movie:dupefilter 裡面都有了,不要要再爬取了
------------------------------------------------------------------------------------------------------------------------------------
思考:在不重新給種子url的情況下,我就是想多次爬取怎麼辦?
解決方案:在爬蟲檔案parse方法裡面yield回撥的時候,加上引數 dont_filter=True 即可再次爬取;dont_filter是scrapy過濾重複請求的,預設為False可以過濾dupefilter中已經抓取過去的請求,避免重複抓取,改為True後即為不過濾,可以再次請求爬取,scrapy提供了這個引數就是讓自己去決定這個資料是應該過濾掉還是可以重複抓取
示例
step-1:下發種子url,執行爬蟲,等待爬蟲結束
step-2:爬取結束,按下CTRL+C停止指令碼,按照圖示點選Flush DB清空DB,再次執行爬蟲指令碼;沒有傳入種子URL的情況下,爬蟲可以照常爬取
3.4.實現分散式爬蟲
通過例項專案來講解分散式爬蟲的實現;
內容較多,在新的隨筆裡面:https://www.cnblogs.com/gltou/p/16433539.html