使用scrapy-redis:
Scrapy_redis在scrapy的基礎(chǔ)上實(shí)現(xiàn)了更多,更強(qiáng)大的功能,具體體現(xiàn)在:reqeust去重,爬蟲持久化,和輕松實(shí)現(xiàn)分布式
安裝scrapy-redis:
pip3 install scrapy-redis
Scrapy-redis提供了下面四種組件(components):(意味著四個(gè)模塊都要做相應(yīng)的修改)
1.Scheduler
2.Duplication Filter
3.Item Pipeline
4.Base Spider
scrapy-redis工作流程

Scheduler:
Scrapy中跟“待爬隊(duì)列”直接相關(guān)的就是調(diào)度器Scheduler,它負(fù)責(zé)對(duì)新的request進(jìn)行入列操作(加入Scrapy queue),取出下一個(gè)要爬取的request(從Scrapy queue中取出)等操作。它把待爬隊(duì)列按照優(yōu)先級(jí)建立了一個(gè)字典結(jié)構(gòu).
比如:
{
優(yōu)先級(jí)0 : 隊(duì)列0
優(yōu)先級(jí)1 : 隊(duì)列1
優(yōu)先級(jí)2 : 隊(duì)列2
}
然后根據(jù)request中的優(yōu)先級(jí),來決定該入哪個(gè)隊(duì)列,出列時(shí)則按優(yōu)先級(jí)較小的優(yōu)先出列。為了管理這個(gè)比較高級(jí)的隊(duì)列字典,Scheduler需要提供一系列的方法。但是原來的Scheduler已經(jīng)無法使用,所以使用Scrapy-redis的scheduler組件。
Duplication Filter:
Scrapy中用集合實(shí)現(xiàn)這個(gè)request去重功能,Scrapy中把已經(jīng)發(fā)送的request指紋放入到一個(gè)集合中,把下一個(gè)request的指紋拿到集合中比對(duì),如果該指紋存在于集合中,說明這個(gè)request發(fā)送過了,如果沒有則繼續(xù)操作。這個(gè)核心的判重功能是這樣實(shí)現(xiàn)的:
def request_seen(self, request):
# 把請(qǐng)求轉(zhuǎn)化為指紋
fp = self.request_fingerprint(request)
# 這就是判重的核心操作 ,self.fingerprints就是指紋集合
if fp in self.fingerprints:
return True #直接返回
self.fingerprints.add(fp) #如果不在,就添加進(jìn)去指紋集合
if self.file:
self.file.write(fp + os.linesep)
在scrapy-redis中去重是由Duplication Filter組件來實(shí)現(xiàn)的,它通過redis的set 不重復(fù)的特性,巧妙的實(shí)現(xiàn)了Duplication Filter去重。scrapy-redis調(diào)度器從引擎接受request,將request的指紋存?redis的set檢查是否重復(fù),并將不重復(fù)的request push寫?redis的 request queue。
Item Pipeline:
引擎將(Spider返回的)爬取到的Item給Item Pipeline,scrapy-redis 的Item Pipeline將爬取到的 Item 存?redis的 items queue。
修改過Item Pipeline可以很方便的根據(jù) key 從 items queue 提取item,從?實(shí)現(xiàn) items processes集群。
Base Spider:
不在使用scrapy原有的Spider類,重寫的RedisSpider繼承了Spider和RedisMixin這兩個(gè)類,RedisMixin是用來從redis讀取url的類。
當(dāng)我們生成一個(gè)Spider繼承RedisSpider時(shí),調(diào)用setup_redis函數(shù),這個(gè)函數(shù)會(huì)去連接redis數(shù)據(jù)庫,然后會(huì)設(shè)置signals(信號(hào)):
一個(gè)是當(dāng)spider空閑時(shí)候的signal,會(huì)調(diào)用spider_idle函數(shù),這個(gè)函數(shù)調(diào)用schedule_next_request函數(shù),保證spider是一直活著的狀態(tài),并且拋出DontCloseSpider異常。
一個(gè)是當(dāng)抓到一個(gè)item時(shí)的signal,會(huì)調(diào)用item_scraped函數(shù),這個(gè)函數(shù)會(huì)調(diào)用schedule_next_request函數(shù),獲取下一個(gè)request。
項(xiàng)目說明
使用scrapy-redis的example來修改 先從github上拿到scrapy-redis的示例,然后將里面的example-project目錄移到指定的地址:
clone github scrapy-redis源碼文件
拿官方的項(xiàng)目范例:
mv scrapy-redis/example-project ~/scrapyredis-project
我們clone到的 scrapy-redis 源碼中有自帶一個(gè)example-project項(xiàng)目,這個(gè)項(xiàng)目包含3個(gè)spider,分別是dmoz, myspider_redis,mycrawler_redis。
一.dmoz (class DmozSpider(CrawlSpider))
注意:這里只用到Redis的去重和保存功能,并沒有實(shí)現(xiàn)分布式
這個(gè)爬蟲繼承的是CrawlSpider,它是用來說明Redis的持續(xù)性,當(dāng)我們第一次運(yùn)行dmoz爬蟲,然后Ctrl + C停掉之后,再運(yùn)行dmoz爬蟲,之前的爬取記錄是保留在Redis里的。
分析起來,其實(shí)這就是一個(gè) scrapy-redis 版 CrawlSpider 類,需要設(shè)置Rule規(guī)則,以及callback不能寫parse()方法。 執(zhí)行方式:
scrapy crawl dmoz
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class DmozSpider(CrawlSpider):
"""Follow categories and extract links."""
name = 'dmoz'
allowed_domains = ['dmoz.org']
start_urls = ['http://www.dmoz.org/']
#定義了一個(gè)url的提取規(guī)則,將滿足條件的交給callback函數(shù)處理
rules = [
Rule(LinkExtractor(
restrict_css=('.top-cat', '.sub-cat', '.cat-item')
), callback='parse_directory', follow=True),
]
def parse_directory(self, response):
for div in response.css('.title-and-desc'):
#這里將獲取到的內(nèi)容交給引擎
yield {
'name': div.css('.site-title::text').extract_first(),
'description': div.css('.site-descr::text').extract_first().strip(),
'link': div.css('a::attr(href)').extract_first(),
}
二.myspider_redis (class MySpider(RedisSpider))
這個(gè)爬蟲繼承了RedisSpider, 它能夠支持分布式的抓取,采用的是basic spider,需要寫parse函數(shù)。 其次就是不再有start_urls了,取而代之的是redis_key,scrapy-redis將key從Redis里pop出來,成為請(qǐng)求的url地址。
from scrapy_redis.spiders import RedisSpider
class MySpider(RedisSpider):
"""Spider that reads urls from redis queue (myspider:start_urls)."""
name = 'myspider_redis'
#手動(dòng)設(shè)置允許爬取的域
allowed_domains = ['設(shè)置允許爬取的域']
# 注意redis-key的格式:
redis_key = 'myspider:start_urls'
# 可選:等效于allowd_domains(),__init__方法按規(guī)定格式寫,使用時(shí)只需要修改super()里的類名參數(shù)即可,一般不用
def __init__(self, *args, **kwargs):
# Dynamically define the allowed domains list.
domain = kwargs.pop('domain', '')
self.allowed_domains = filter(None, domain.split(','))
# 修改這里的類名為當(dāng)前類名
super(MySpider, self).__init__(*args, **kwargs)
def parse(self, response):
return {
'name': response.css('title::text').extract_first(),
'url': response.url,
}
注意: RedisSpider類 不需要寫start_urls:
- scrapy-redis 一般直接寫allowd_domains來指定需要爬取的域,也可以從在構(gòu)造方法init()里動(dòng)態(tài)定義爬蟲爬取域范圍(一般不用)。
- 必須指定redis_key,即啟動(dòng)爬蟲的命令,參考格式:redis_key = 'myspider:start_urls'
- 根據(jù)指定的格式,start_urls將在 Master端的 redis-cli 里 lpush 到 Redis數(shù)據(jù)庫里,RedisSpider 將在數(shù)據(jù)庫里獲取start_urls
執(zhí)行方式:
1.通過runspider方法執(zhí)行爬蟲的py文件(也可以分次執(zhí)行多條),爬蟲(們)將處于等待準(zhǔn)備狀態(tài):
scrapy runspider myspider_redis.py
or
scrapy crawl myspider_redis
2.在Master端的redis-cli輸入push指令,參考格式(指定起始url):
lpush myspider:start_urls http://www.dmoz.org/
3.Slaver端爬蟲獲取到請(qǐng)求,開始爬取。
三.mycrawler_redis (class MyCrawler(RedisCrawlSpider))
這個(gè)RedisCrawlSpider類爬蟲繼承了RedisCrawlSpider,能夠支持分布式的抓取。因?yàn)椴捎玫氖莄rawlSpider,所以需要遵守Rule規(guī)則,以及callback不能寫parse()方法。
同樣也不再有start_urls了,取而代之的是redis_key,scrapy-redis將key從Redis里pop出來,成為請(qǐng)求的url地址。
from scrapy.spiders import Rule
from scrapy.linkextractors import LinkExtractor
from scrapy_redis.spiders import RedisCrawlSpider
class MyCrawler(RedisCrawlSpider):
"""Spider that reads urls from redis queue (myspider:start_urls)."""
name = 'mycrawler_redis'
allowed_domains = ['設(shè)置允許爬取的域']
redis_key = 'mycrawler:start_urls'
rules = (
# follow all links
Rule(LinkExtractor(), callback='parse_page', follow=True),
)
# __init__方法必須按規(guī)定寫,使用時(shí)只需要修改super()里的類名參數(shù)即可(一般不用)
def __init__(self, *args, **kwargs):
# Dynamically define the allowed domains list.
domain = kwargs.pop('domain', '')
self.allowed_domains = filter(None, domain.split(','))
# 修改這里的類名為當(dāng)前類名
super(MyCrawler, self).__init__(*args, **kwargs)
def parse_page(self, response):
return {
'name': response.css('title::text').extract_first(),
'url': response.url,
}
注意: 同樣的,RedisCrawlSpider類不需要寫start_urls:
執(zhí)行方式:
1.scrapy runspider myspider_redis.py
2.lpush myspider:start_urls http://www.dmoz.org/
3.Slaver端爬蟲獲取到請(qǐng)求,開始爬取。
總結(jié):
1 如果只是用到Redis的去重和保存功能,就選第一種;
2 如果要寫分布式,則根據(jù)情況,選擇第二種、第三種;
3 通常情況下,會(huì)選擇用第三種方式編寫深度聚焦爬蟲。