工作中遇到的一个问题
为方便去重,爬虫起始链接及爬虫结果数据都存放在redis中。
代理是爬虫小组共用的,同样存在redis里面。
爬虫过程中,由于深度抓取会产生很多新的链接,这部分链接也需要放在redis起始链接中。
上述总计4种数据,存放于同一个redis库下属3个不同的key中:
爬虫起始链接 and 深度抓取新生链接 ——> tasks_key
代理 ——> proxy_key
爬虫结果数据 ——> item_key
这就导致了,在scrapy项目代码中,我需要在spider.py、pipilines.py、middleware.py这三个不同的模块中使用同一个数据库
最初代码,在三个模块下自定义类的__init__方法中声明redis链接,分别在parse(),process_item(),process_request()中调用各自的redis链接。。。这三份代码除了self.REDIS_KEY不同,其余部分一!模!一!样!
つ﹏⊂虽然代码运行起来是没问题的,可这写法蠢的不行,实在接受不了,改!
然后,google尝试了一下午,可能是我搜索关键词用的太挫~愣是没找到解决方案。。。。。
google告诉我需要在pipeline中定义open_spider方法和close_spider方法用于建立/关闭数据库链接。可是,此时的self是一个pipeline实例,我要怎么在middleware中通过pipeline实例调用数据库链接啊~臣妾不知道哇,也没搜到结果哇。
啥也不说了,自己试!
一、通过spider参数调用
1.爬虫文件
# spider.py
import scrapy,redis
from baiduSpider.items import BaiduItem # 项目名叫baiduSpider,自定义item叫baiduItem
from baiduSpider.settings import TASKS_KEY # settings中定义的三个redis键,spider中使用的叫TASKS_KEY
class MySpider(scrpy.Spider):
name = 'baidu'
allowed_domains = ['www.baidu.com']
def __init__(self, redis_host, redis_port, redis_db):
self.redis_host = redis_host
self.redis_port = redis_port
self.redis_db = redis_db
self.pool = redis.ConnectionPool(host=self.redis_host,
port=self.redis_port,
db=self.redis_db)
self.conn = redis.StrictRedis(connection_pool=self.pool)
@classmethod
def from_crawler(cls, crawler):
return cls( redis_host=crawler.settings.get('PROXY_REDIS_HOST'), redis_port=crawler.settings.get('PROXY_REDIS_PORT'), redis_db=crawler.settings.get('PROXY_REDIS_DB') )
def start_requests(self):
url = self.conn.blpop(TASKS_KEY, 600)
yield scrapy.Request(url, callback=slef.parse)
def parse(self, response):
item = BaiduItem
yield item
nextPages = response.xpath('').extrace()
for nextPage in nextPages:
self.conn.rpush(TASKS_KEY, nextPage)
通过类方法from_crawler从settings.py中获取redis相关参数,在__init__中创建redis连接池self.pool及redis连接self.conn
start_requests方法直接调用self.conn从redis取数据,parse方法也可以直接调用self.conn往redis存数据
2.中间件
# middlewares.py
from zhipin.settings import PROXY_KEY
class RandomProxyMiddleware:
def process_request(self, request, spider):
proxy = spider.conn.blpop(PROXY_KEY, 600)
if proxy!=None:
proxy = proxy[1]
spider.conn.rpush(PROXY_KEY, proxy)
proxy = json.loads(proxy.decode('utf-8'))
request.meta['proxy'] = "https://%s" % proxy
process_request方法有spider参数,这里的spider就是上面定义的MySpider,通过spider.conn可直接调用MySpider下的self.conn
3.项目管道
# pipelines.py
import json,redis
from .settings import ITEM_KEY
class MyItemPipeline:
def process_item(self, item, spider):
data = dict(item)
spider.conn.rpush(ITEM_KEY, json.dumps(data))
return item
同上,还是通过spider参数调用
二、交给pipeline处理
此时,又过了一天,通过spider类声明redis链接,在其他模块中通过spider参数调用redis链接稳定运行了一天。
我又开始回头继续琢磨google出来的pipeline声明redis链接给其他模块使用,终于,琢磨通了.......
我并不是必须在spider中处理那些深度抓取新生的链接,我完全可以再写一个HrefItem
spider将不同的数据处理成不同的item(HrefItem和DataItem),scrapy会将这些item交给定义好的不同的pipeline处理
由于需要多个pipeline,这些pipeline都需要用到redis链接
这需要自定义一个RedisPipeline基类,在open_spider中建立redis链接
然后再定义其他pipeline,继承自RedisPipeline,各自重写process_item方法对不同的item做处理
这样,spider通过不同的pipeline中连接redis对item(HrefItem和DataItem)做相应处理存入不同的key(ITEM_KEY和TASKS_KEY)
ps:此方法还有一个问题没想通,middleware中的redis要怎么处理?