本篇文章在源碼層面比對feapder、scrapy、scrapy-redis的設計,閱讀本文後,會加深您對scrapy以及feapder的了解,以及為什麼推薦使用feapder
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記憶體容量要求很高
4. 需單獨維護個下發種子任務的腳本
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秒,一億條資料占用記憶體約285MB
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 為啟動入口
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爬蟲文檔:https://boris-code.gitee.io/feapder/#/
feaplat管理平台:https://boris-code.gitee.io/feapder/#/feapder_platform/%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F
- 爬蟲管理系統不僅支援
、feapder
,且支援執行任何腳本,可以把該系統了解成腳本托管的平台 。scrapy
- 支援叢集
- 工作節點根據配置定時啟動,執行完釋放,不常駐
- 一個worker内隻運作一個爬蟲,worker彼此之間隔離,互不影響。
- 支援管理者和普通使用者兩種角色
- 可自定義爬蟲端鏡像