一、增量式爬蟲背景:
當我們在瀏覽相關網頁的時候會發現,某些網站定時會在原有網頁數據的基礎上更新一批數據,例如某電影網站會實時更新一批最近熱門的電影。小說網站會根據作者創作的進度實時更新最新的章節數據等等。那么,類似的情景,當我們在爬蟲的過程中遇到時,我們是不是需要定時更新程序以便能爬取到網站中最近更新的數據呢?
二、增量式爬蟲分析與設計
- 概念:通過爬蟲程序監測某網站數據更新的情況,以便可以爬取到該網站更新出的新數據。
- 如何進行增量式的爬取工作,檢測重復數據的三種情況:
- 在發送請求之前判斷這個URL是不是之前爬取過
- 在解析內容后判斷這部分內容是不是之前爬取過
- 寫入存儲介質時判斷內容是不是已經在介質中存在
- 分析:
- 不難發現,其實增量爬取的核心是去重, 至於去重的操作在哪個步驟起作用,只能說各有利弊。在我看來,前兩種思路需要根據實際情況取一個(也可能都用)。第一種思路適合不斷有新頁面出現的網站,比如說小說的新章節,每天的最新新聞等等;第二種思路則適合頁面內容會更新的網站。第三個思路是相當於是最后的一道防線。這樣做可以最大程度上達到去重的目的。
- 去重方法:
- 將爬取過程中產生的url進行存儲,存儲在redis的set中。當下次進行數據爬取時,首先對即將要發起的請求對應的url在存儲的url的set中做判斷,如果存在則不進行請求,否則才進行請求。
- 對爬取到的網頁內容進行唯一標識的制定(數據指紋),然后將該唯一表示存儲至redis的set中。當下次爬取到網頁數據的時候,在進行持久化存儲之前,首先可以先判斷該數據的唯一標識在redis的set中是否存在,在決定是否進行持久化存儲。
三、增量式爬蟲實例(4567電影網)
增量式實際需求定制方式:通過獲取電影詳情頁url地址存放redis數據庫的set集合中,利用set自動去重的特性,存取爬蟲爬取記錄,達到增量式爬取需求
需求:獲取網站電影信息(電影名稱&電影詳情頁信息),url: http://www.4567kan.com/index.php/vod/show/id/5.html
爬蟲文件:movie.py
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from ..items import ZlsproItem from redis import Redis class MovieSpider(CrawlSpider): name = 'movie' # allowed_domains = ['www.xx.com'] # 起始url列表 start_urls = ['http://www.4567kan.com/index.php/vod/show/id/5.html'] # 規則解析器 rules = ( # follow=False 爬取當前HTML頁面的所有連接提取器提取到的url Rule(LinkExtractor(allow=r'vod/show/id/5/page/\d+\.html'), callback='parse_item', follow=False), ) # 創建redis連接 conn = Redis(host="127.0.0.1", port=6379) # 數據解析 def parse_item(self, response): # 電影名稱和詳情頁的url li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li') for li in li_list: # 獲取電影名稱 name = li.xpath('.//div[@class="stui-vodlist__detail"]/h4/a/text()').extract_first() # 獲取電影詳情頁url detail_url = 'http://www.4567kan.com' + li.xpath( './/div[@class="stui-vodlist__detail"]/h4/a/@href').extract_first() # 實例化一個item對象 item = ZlsproItem() item['name'] = name # 通過redis中的集合(set自動去重特性,滿足增量式爬取)存儲電影詳情頁url # 向redis集合中插入數據,存在則插入失敗,返回0。否則成功返回1 exist = self.conn.sadd("movie_detail_urls", detail_url) # 插入數據成功,則當前url是新數據,則手動請求獲取內容信息 if exist: print("正在爬取網站更新數據!!!") yield scrapy.Request(detail_url, callback=self.parse_detail, meta={"item": item}) else: print("網站數據暫無更新數據!!!") # 電影詳情頁信息 def parse_detail(self, response): item = response.meta["item"] movie_desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first() item['movie_desc'] = movie_desc # 提交數據到管道 yield item
管道對象類配置:items.py
import scrapy class ZlsproItem(scrapy.Item): # define the fields for your item here like: name = scrapy.Field() movie_desc = scrapy.Field()
管道類:持久化存儲。pipelines.py
class ZlsproPipeline(object):
# 數據持久化
def process_item(self, item, spider):
# print(item)
# 獲取redis連接
conn = spider.conn
# 獲取的電影信息存放redis數據庫 列表中插入數據lpush
# 這里redis數據列表中存儲字典時,版本不同可能保存,推薦版本redis 2.10.6
conn.lpush("movie_data", item)
return item
爬蟲配置文件:settings.py
BOT_NAME = 'zlsPro' SPIDER_MODULES = ['zlsPro.spiders'] NEWSPIDER_MODULE = 'zlsPro.spiders' # Crawl responsibly by identifying yourself (and your website) on the user-agent USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36' # Obey robots.txt rules ROBOTSTXT_OBEY = False # 開啟管道 ITEM_PIPELINES = { 'zlsPro.pipelines.ZlsproPipeline': 300, }
爬取內容,查看數據庫存儲電影詳情頁url
四、增量式爬蟲實例(糗事百科)
增量式實際需求定制方式:針對於同一url中多內容爬取,通過制定數據指紋,對數據制定的一個唯一標識,例如MD5,sha等摘要算法做標識去重
需求:爬取糗事百科(內容&用戶名), url:
爬蟲文件:qiushi.py
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from ..items import QiushiproItem from redis import Redis class QiushiSpider(CrawlSpider): name = 'qiushi' # allowed_domains = ['www.xx.com'] # 起始url列表 start_urls = ['https://www.qiushibaike.com/text/'] # 規則解析器 rules = ( Rule(LinkExtractor(allow=r'/text/page/\d+/'),callback='parse item',follow=True), Rule(LinkExtractor(allow=r"/text/$'),callback='parse_item',follow=True), ) # 創建redis連接 conn = Redis(host="127.0.0.1", port=6379) # 數據解析 def parse_item(self, response): # li標簽列表 li_list = response.xpath('//div[@id="content-1eft"]/div') for li in li_list: # 實例化一個item對象 item = QiushiproItem() # 用戶名
item['author']=div.xpath('./div[1]/a[2]/h2/text()|./div[1]/span[2]/h2/text()").extract_first() # 內容信息 item['content']=div.xpath(.//div[@class="content"]/span/text()").extract_first() #將解析到的數據值生成一個唯一的標識進行redis存儲
source=item['author']+item['content] sourte_id=hashlib.sha256(source.encode()).hexdigest()
#將解析內容的唯一表示存儲到redis的data_id中
ex=self.conn.sadd('data_id',source_id) if ex==1:
print(該條數據沒有爬取過,可以爬取..……) yield item
else:
print(“該條數據已經爬取過了,不需要再次爬取了!!!)