一。数据存储到mongodb
爬取的数据如果需要存储到mongodb中,要通过item,定义一个存储类。再yield一个类。
数据存储的时候需要进过pipelines,再到setting中配置。
from pymongo import MongoClient class ArticleMongodbPipeline(object): def process_item(self, item, spider): # client = MongoClient('mongodb://localhost:27017/') #连接 client = MongoClient('localhost', 27017) #创建article数据库,如果没有叫创建,如果有,叫使用 db = client['article'] # print(db) #如果有使用articleinfo,如果没有,创建这个表 article_info=db['articleinfo'] article_info.insert(dict(item)) # article_info.save({'article_name':item['article_name'],'aritcle_url':item['article_url']}) # 如果retrun None ,下一个就取不到item了,取到的就是None
# -*- coding: utf-8 -*- import scrapy from myscrapy.items import ArticleItem from scrapy import Request from scrapy.core.scheduler import Scheduler from scrapy.dupefilters import RFPDupeFilter class CnblogsSpider(scrapy.Spider): name = 'cnblogs' #爬虫名,必须唯一 allowed_domains = ['cnblogs.com'] #允许的域名, start_urls = ['https://www.cnblogs.com/'] #起始的url custom_settings={"BB":"xxxxx"} #深度优先,广度优先 #爬虫去重:好多策略 #爬虫起始入口 start_requests # def parse_detail(self,response): # print(len(response.text)) # def parse(self, response): # div_list=response.css('.post_item') # for div in div_list: # url=div.css('.post_item_body a::attr(href)').extract_first() # article_name=div.css('.titlelnk::text').extract_first() # auther_name=div.css('.post_item_foot a::text').extract_first() # commit_count=div.css('.post_item_foot span a::text').extract_first() # article_item=ArticleItem() # #把地址存入数据库,yield item对象 # article_item['article_name']=article_name # article_item['article_url']=url # article_item['auther_name']=auther_name # article_item['commit_count']=commit_count # yield article_item # # next_url=response.css('.pager a:last-child::attr(href)').extract_first() # print('https://www.cnblogs.com'+next_url) # #dont_filter=True 表示不去重,默认去重 # yield Request('https://www.cnblogs.com'+next_url,dont_filter=True,cookies={'name':'xxx'}) def parse(self, response): print(response) # print(response.url) # print(response.status) # print(response.body) # obj=Request('https://www.cnblogs.com' + next_url, dont_filter=True, cookies={'name': 'xxx'}) # obj.meta["aa"]="aa" # yield obj # yield [Request,Request] yield Request('https://www.cnblogs.com')
如果用户需要存储到其他地方,只要再配置文件中继续配置,并将类写成如下规定,
class ArticleFilePipeline(object): # def __init__(self,host,port): def __init__(self): # self.mongo_conn=MongoClient(host,port) pass #如果这个类中有from_crawler,首先会执行from_crawler,aa=ArticleFilePipeline.from_crawler(crawler) #如果没有直接aa=ArticleFilePipeline() @classmethod def from_crawler(cls, crawler): print('有') #mongodb的配置信息在setting中 # host = '从配置文件中拿出来的' # port='从配置文件中拿出来的' #crawler.settings总的配置文件 print('asdfasdfasfdasfd',crawler.settings['AA']) # return cls(host,port) return cls() def open_spider(self, spider): # print('----',spider.custom_settings['AA']) self.f = open('article.txt','a') def close_spider(self, spider): self.f.close() print('close spider')
setting配置:
ITEM_PIPELINES = { 'myscrapy.pipelines.ArticleMongodbPipeline': 300, #数字越小越先执行 'myscrapy.pipelines.ArticleFilePipeline': 100, }
二。去重。
scrapy中的去重方式RFPDupeFilter是将url进行排序,再获取md5,保证长度,最后每有一个请求就和这个集合进行对比。
setting配置:
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' #默认的去重规则帮我们去重,去重规则在内存中
自定义去重
from scrapy.dupefilters import BaseDupeFilter #自己定制去重方案 class MyDupeFilter(BaseDupeFilter): """Request Fingerprint duplicates filter""" def __init__(self, path=None, debug=False): self.aa=set() def request_seen(self, request): if request.url in self.aa: return True def close(self, reason): if self.file: self.file.close() def log(self, request, spider): if self.debug: msg = "Filtered duplicate request: %(request)s (referer: %(referer)s)" args = {'request': request, 'referer': referer_str(request) } self.logger.debug(msg, args, extra={'spider': spider}) elif self.logdupes: msg = ("Filtered duplicate request: %(request)s" " - no more duplicates will be shown" " (see DUPEFILTER_DEBUG to show all duplicates)") self.logger.debug(msg, {'request': request}, extra={'spider': spider}) self.logdupes = False spider.crawler.stats.inc_value('dupefilter/filtered', spider=spider)
再setting中配置。
其他去重方法亦可以通过redis进行,或者布隆过滤器(比特位去重)
三。下载中间件
中间件可以拦截各种请求,需要再setting中配置
class MyscrapyDownloaderMiddleware(object): @classmethod def from_crawler(cls, crawler): s = cls() return s def process_request(self, request, spider): print('来了') print(request.url) #headers可以改 print(request.headers) #从cookie池中取出cookie赋值进去 print(request.cookies) #使用代理 # request.meta['download_timeout'] = 20 # request.meta["proxy"] = 'http://192.168.1.1:7878' # print(request) # print(spider) # 返回值:None,Request对象,Response对象 #None 继续走下一个下载中间件 #Response 直接返回,进入spider中做解析 #Request 回到调度器,重新被调度 #可以使用selenium return HtmlResponse(url="www.baidu.com", status=200, body=b'sdfasdfasfdasdfasf') def process_response(self, request, response, spider): print('走了') print(request) print(spider) print(type(response)) return response def process_exception(self, request, exception, spider): print('代理%s,访问%s出现异常:%s' % (request.meta['proxy'], request.url, exception)) import time time.sleep(5) #删除代理 # delete_proxy(request.meta['proxy'].split("//")[-1]) #重新获取代理,放进去 # request.meta['proxy'] = 'http://' + get_proxy() #return request 会放到调度器重新调度 return request def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)
其中request是任务来时做的动作。respons是任务走是做的动作,
可以再cookies中设置代理(meta)和cookies池(cookies)
再spriders中返回的parse中的request中添加对象,就会在下载中间件中的request中获取。
return None,Response,Resquest
四。爬虫中间件
#自己写爬虫中间件 class MyscrapySpiderMiddleware(object): @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_spider_input(self, response, spider): print('我进爬虫的解析') return None def process_spider_output(self, response, result, spider): print("我往外走") for i in result: yield i def process_spider_exception(self, response, exception, spider): pass def process_start_requests(self, start_requests, spider): print("process_start_requests") for r in start_requests: yield r def spider_opened(self, spider): print("spider_opened") spider.logger.info('Spider opened: %s' % spider.name)
五。信号
信号与django中的信号差不多,大体步骤如下:
1.写一个类:class MyExtension(object):
2.在from_crawler绑定一些信号
3.当scrapy执行到信号执行点的时候,会自动触发这些函数
4.在setting中配置
EXTENSIONS = { 'extentions.MyExtension': 100, }
from scrapy import signals class MyExtension(object): @classmethod def from_crawler(cls, crawler): # val = crawler.settings.getint('MMMM') obj = cls() crawler.signals.connect(obj.spider_opened, signal=signals.spider_opened) crawler.signals.connect(obj.spider_closed, signal=signals.spider_closed) return obj def spider_opened(self, spider): print('=============>open') def spider_closed(self, spider): print('=============>close')
六。哈希
哈希就是一个字典。
当数据进行存储的时候,就为其标记一个记号,放入链表中,当发送哈希冲突的时候,就将该点存储的数据变成一个地址,地址指向另一个列表。
布隆过滤器。
当访问不存在数据频繁的时候,redis就有可能被击穿,被击穿后就回直接访问数据库。
所以,使用过滤器事先判断数据是否有,如果没有直接返回。
redis自带布隆过滤器。
七。分布式爬虫。
分布式爬虫的基本原理就是同一调度统一去重。分布到不同的机器上爬虫。
安装:
pip3 install scrapy-redis
1.设置setting中的调度器:
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
2.设置去重规则:
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
3.配置指定的持久化类:
ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300 }
4.在爬虫类中,继承RedisSpider,把start_url去掉,新增:redis_key = 'cnblogs:start_urls'。
cnblogs和输入到redis中有关。
5.启动项目,redis中发送爬虫指令
-lpush cnblogs:start_urls https://www.cnblogs.com
源码组成:
-connection.py redis的连接
-defaults.py 配置文件
-dupefilter.py 去重规则
-picklecompat.py 序列化相关
-pipelines.py 持久化
-queue.py 队列
-scheduler.py 调度器
-spiders.py 爬虫相关
-utils.py py2和py3兼容的