一、scrapy 分析
1. 解析函數或數據入庫出錯,不會重試,會造成一定的數據丟失
scrapy
自帶的重試中間件只支持請求重試,解析函數內異常或者數據入庫異常不會重試,但爬蟲在請求數據時,往往會有一些意想不到的頁面返回來,若我們解析異常了,這條任務豈不是丟了。
當然有些大佬可以通過一些自定義中間件的方式或者加異常捕獲的方式來解決,我們這里只討論自帶的。
2. 運行方式,需借助命令行,不方便調試
若想直接運行,需編寫如下文件,麻煩
from scrapy import cmdline name = "spider_name" cmd = "scrapy crawl {0}".format(name) cmdline.execute(cmd.split()
為什么必須通過命令行方式呢?因為 scrapy
是通過這種方式來加載項目中的 settings.py
文件的
3. 入庫 pipeline,不能批量入庫
class TestScrapyPipeline(object): def process_item(self, item, spider): return item
pipelines
里的 item
是一條條傳過來的,沒法直接批量入庫,但數據量大的時候,我們往往是需要批量入庫的,以節省數據庫的性能開銷,加快入庫速度
二、scrapy-redis 分析
scrapy-redis
任務隊列使用 redis
做的,初始任務存在 [spider_name]:start_urls
里,爬蟲產生的子鏈接存在 [spider_name]:requests
下,那么我們先看下 redis
里的任務
1. redis 中的任務可讀性不好
我們看下子鏈任務,可以看到存儲的是序列化后的,這種可讀性不好
2. 取任務時直接彈出,會造成任務丟失
我們分析下 scrapy-redis
幾種任務隊列,取任務時都是直接把任務彈出來,如果任務剛彈出來爬蟲就意外退出,那剛彈出的這條任務就會丟失。
FifoQueue(先進先出隊列) 使用 list 集合
PriorityQueue(優先級隊列),使用 zset 集合
LifoQueue(先進后出隊列),使用 list 集合
scrapy-redis
默認使用 PriorityQueue
隊列,即優先級隊列
3. 去重耗內存
使用 redis
的 set 集合對 request
指紋進行去重,這種面對海量數據去重對 redis 內存容量要求很高
需單獨維護個下發種子任務的腳本
三、feapder 分析
feapder
內置 AirSpider 、 Spider
、 BatchSpider
三種爬蟲,AirSpider
對標 Scrapy
,Spider
對標 scrapy-redis,BatchSpider
則是應于周期性采集的需求,如每周采集一次商品的銷量等場景
上述問題解決方案:
(1)解析函數或數據入庫出錯,不會重試,會造成一定的數據丟失
feapder
對請求、解析、入庫進行了全面的異常捕獲,任何位置出現異常會自動重試請求,若有不想重試的請求也可指定
(2)運行方式,需借助命令行,不方便調試
feapder
支持直接運行,跟普通的 python
腳本沒區別,可以借助 pycharm
調試。
除了斷點調試,feapder
還支持將爬蟲轉為 Debug
爬蟲,Debug
爬蟲模式下,可指定請求與解析函數,生產的任務與數據不會污染正常環境
(3)入庫 pipeline,不能批量入庫
feapder
生產的數據會暫存內存的隊列里,積攢一定量級或每 0.5 秒批量傳給 pipeline,方便批量入庫
def save_items(self, table, items: List[Dict]) -> bool: pass
這里有人會有疑問:
數據放到內存里了,會不會造成擁堵?
答:不會,這里限制了最高能積攢 5000 條的上限,若到達上限后,爬蟲線程會強制將數據入庫,然后再生產數據
若爬蟲意外退出,數據會不會丟?
答:不會,任務會在數據入庫后再刪除,若意外退出了,產生這些數據的任務會重做
入庫失敗了怎么辦?
答:入庫失敗,任務會重試,數據會重新入庫,若失敗次數到達配置的上限會報警
(4) redis 中的任務可讀性不好
feapder
對請求里常用的字段沒有序列化,只有那些 json 不支持的對象才進行序列化
(5) 取任務時直接彈出,會造成任務丟失
feapder 在獲取任務時,沒直接彈出,任務采用 redis
的 zset
集合存儲,每次只取小于當前時間搓分數的任務,同時將取到的任務分數修改為當前時間搓 +10 分鐘,防止其他爬蟲取到重復的任務。若爬蟲意外退出,這些取到的任務其實還在任務隊列里,并沒有丟失
(6)去重耗內存
feapder 支持三種去重方式:
- 內存去重:采用可擴展的
bloomfilter
結構,基于內存,去重一萬條數據約 0.5 秒,一億條數據占用內存約 285MB - 臨時去重:采用
redis
的zset
集合存儲數據的 md5 值,去重可指定時效性。去重一萬條數據約 0.26 秒,一億條數據占用內存約 1.43G - 永久去重:采用可擴展的
bloomfilter
結構,基于 redis,去重一萬條數據約 0.5 秒,一億條數據占用內存約 285 MB
(7)分布式爬蟲需單獨維護個下發種子任務的腳本
feapder
沒種子任務和子鏈接的分別, yield feapder.Request
都會把請求下發到任務隊列,我們可以在 start_requests
編寫下發種子任務的邏輯
這里又有人會有疑問了
我爬蟲啟動多份時, start_requests 不會重復調用,重復下發種子任務么?
答:不會,分布式爬蟲在調用 start_requests 時,會加進程鎖,保證只能有一個爬蟲調用這個函數。并且若任務隊列中有任務時,爬蟲會走斷點續爬的邏輯,不會執行 start_requests
那支持手動下發任務么?
答:支持,按照 feapder 的任務格式,往 redis 里扔任務就好,爬蟲支持常駐等待任務
四、三種爬蟲簡介
1. AirSpider
使用 PriorityQueue
作為內存任務隊列,不支持分布式,示例代碼
import feapder class AirSpiderDemo(feapder.AirSpider): def start_requests(self): yield feapder.Request("https://www.baidu.com") def parse(self, request, response): print(response) if __name__ == "__main__": AirSpiderDemo().start()
2. Spider
分布式爬蟲,支持啟多份,爬蟲意外終止,重啟后會斷點續爬
import feapder class SpiderDemo(feapder.Spider): # 自定義數據庫,若項目中有setting.py文件,此自定義可刪除 __custom_setting__ = dict( REDISDB_IP_PORTS="localhost:6379", REDISDB_USER_PASS="", REDISDB_DB=0 ) def start_requests(self): yield feapder.Request("https://www.baidu.com") def parse(self, request, response): print(response) if __name__ == "__main__": SpiderDemo(redis_key="xxx:xxx").start()
3. BatchSpider
批次爬蟲,擁有分布式爬蟲所有特性,支持分布式
import feapder class BatchSpiderDemo(feapder.BatchSpider): # 自定義數據庫,若項目中有setting.py文件,此自定義可刪除 __custom_setting__ = dict( REDISDB_IP_PORTS="localhost:6379", REDISDB_USER_PASS="", REDISDB_DB=0, MYSQL_IP="localhost", MYSQL_PORT=3306, MYSQL_DB="feapder", MYSQL_USER_NAME="feapder", MYSQL_USER_PASS="feapder123", ) def start_requests(self, task): yield feapder.Request("https://www.baidu.com") def parse(self, request, response): print(response) if __name__ == "__main__": spider = BatchSpiderDemo( redis_key="xxx:xxxx", # redis中存放任務等信息的根key task_table="", # mysql中的任務表 task_keys=["id", "xxx"], # 需要獲取任務表里的字段名,可添加多個 task_state="state", # mysql中任務狀態字段 batch_record_table="xxx_batch_record", # mysql中的批次記錄表 batch_name="xxx", # 批次名字 batch_interval=7, # 批次周期 天為單位 若為小時 可寫 1 / 24 ) # spider.start_monitor_task() # 下發及監控任務 spider.start() # 采集
任務調度過程:
- 從
mysql
中批量取出一批種子任務 - 下發到爬蟲
- 爬蟲獲取到種子任務后,調度到
start_requests
,拼接實際的請求,下發到redis
- 爬蟲從
redis
中獲取到任務,調用解析函數解析數據 - 子鏈接入
redis
,數據入庫 - 種子任務完成,更新種子任務狀態
- 若
redis
中任務量過少,則繼續從mysql
中批量取出一批未做的種子任務下發到爬蟲
封裝了批次(周期)采集的邏輯,如我們指定 7 天一個批次,那么如果爬蟲 3 天就將任務做完,爬蟲重啟也不會重復采集,而是等到第 7 天之后啟動的時候才會采集下一批次。
同時批次爬蟲會預估采集速度,若按照當前速度在指定的時間內采集不完,會發出報警
五、feapder 項目結構
上述的三種爬蟲例子修改配置后可以直接運行,但對于大型項目,可能會有就好多爬蟲組成。feapder 支持創建項目,項目結構如下:
main.py 為啟動入口
1. feapder 部署
feapder
有對應的管理平臺 feaplat
,當然這個管理平臺也支持部署其他腳本
在任務列表里配置啟動命令,調度周期以及爬蟲數等。 爬蟲數 這個對于分布式爬蟲是非常爽的,可一鍵啟動幾十上百份爬蟲,再也不需要一個個部署了
-w1791:
任務啟動后,可看到實例及實時日志
-w1785:
爬蟲監控面板可實時看到爬蟲運行情況,監控數據保留半年,滾動刪除
六、采集效率測試
請求百度 1 萬次,線程都開到 300,測試耗時
scrapy:
class BaiduSpider(scrapy.Spider): name = "baidu" allowed_domains = ["baidu.com"] start_urls = ["https://baidu.com/"] * 10000 def parse(self, response): print(response)
結果:
{"downloader/request_bytes": 4668123,
"downloader/request_count": 20002,
"downloader/request_method_count/GET": 20002,
"downloader/response_bytes": 17766922,
"downloader/response_count": 20002,
"downloader/response_status_count/200": 10000,
"downloader/response_status_count/302": 10002,
"finish_reason": "finished",
"finish_time": datetime.datetime(2021, 9, 13, 12, 22, 26, 638611),
"log_count/DEBUG": 20003,
"log_count/INFO": 9,
"memusage/max": 74240000,
"memusage/startup": 58974208,
"response_received_count": 10000,
"scheduler/dequeued": 20002,
"scheduler/dequeued/memory": 20002,
"scheduler/enqueued": 20002,
"scheduler/enqueued/memory": 20002,
"start_time": datetime.datetime(2021, 9, 13, 12, 19, 58, 489472)}
耗時:148.149139 秒
feapder:
import feapder import time class AirSpiderDemo(feapder.AirSpider): def start_requests(self): for i in range(10000): yield feapder.Request("https://www.baidu.com") def parse(self, request, response): print(response) def start_callback(self): self.start_time = time.time() def end_callback(self): print("耗時:{}".format(time.time() - self.start_time)) if __name__ == "__main__": AirSpiderDemo(thread_count=300).start()
結果:耗時:136.10122799873352
總結:
本文主要分析了 scrapy
及 scrapy-redis
的痛點以及 feapder
是如何解決的,當然 scrapy
也有優點,比如社區活躍、中間件靈活等。但在保證數據及任務不丟的場景,報警監控等場景 feapder
完勝 scrapy
。并且 feapder
是基于實際業務,做過大大小小 100 多個項目,耗時 5 年打磨出來的,因此可滿足絕大多數爬蟲需求
效率方面,請求百度 1 萬次,同為 300 線程的情況下,feapder 耗時 136 秒,scrapy 耗時 148 秒,算上網絡的波動,其實效率差不多。
到此這篇關于爬蟲框架 Feapder
和 Scrapy
的對比分析的文章就介紹到這了,更多相關爬蟲框架 Feapder
和 Scrapy
的對比內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!