基于CrawlSpider全栈数据爬取,分布式爬虫
# 1.基于CrawlSpider全栈数据爬取
- CrawlSpider就是爬虫类Spider的一个子类
# 使用流程
创建scrapy工程:scrapy startproject projectName
创建一个基于CrawlSpider的一个爬虫文件 :scrapy genspider -t crawl spider_name www.xxx.com
构造链接提取器和规则解析器
链接提取器:
- 作用:可以根据指定的规则进行指定连接的提取
- 提取的规则: allow = "正则表达式"
- 会先在全局匹配所有的url,然后根据参数allow的规则匹配需要的链接
规则解析器
- 作用:获取链接提取器提取到的链接,对其进行请求发送,根据指定的规则对请求道的页面源码数据进行数据解析.-
- fllow = True 参数的作用: 将链接提取器继续作用到链接提取器提取到的页码链接所对应的页面中
注意事项:
- 链接提取器和规则解析器是一一对应关系
基于CrawlSpider实现深度数据爬取
- spider文件
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from sunspider.items import SunspiderItem, SunspiderItemSecond class SunSpiderSpider(CrawlSpider): name = 'sun_spider' # allowed_domains = ['www.xxx.com'] start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page='] # 链接提取器 两层数据爬取,写两个链接提取器,链接提取器和规则解析器是一一对应关系 link = LinkExtractor(allow=r'type=4&page=\d+') link_detail = LinkExtractor(allow=r'question/\d+/\d+\.shtml') rules = ( # 实例化Rule(规则解析器)的对象 Rule(link, callback='parse_item', follow=True), Rule(link_detail, callback='parse_item_content', follow=True), ) def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/@title').extract_first() status = tr.xpath('./td[3]/span/text()').extract_first() num = tr.xpath('./td[1]/text()').extract_first() item = SunspiderItem() item['title'] = title item['status'] = status item['num'] = num yield item def parse_detail(self, response): content = response.xpath('/html/body/div[9]/table[2]/tbody/tr[1]//text()').extract() content = ''.join(content) num = response.xpath('/html/body/div[9]/table[1]/tbody/tr/td[2]/span[2]/text()').extract_first() if num: num = num.split(':')[-1] item = SunspiderItemSecond() item['content'] = content item['num'] = num yield item
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43items.py文件
import scrapy # 定义两个类,并且通过某种方式(num)标识两个类之间的对应关系 class SunspiderItem(scrapy.Item): title = scrapy.Field() status = scrapy.Field() num = scrapy.Field() class SunspiderItemSecond(scrapy.Item): content = scrapy.Field() num = scrapy.Field()
1
2
3
4
5
6
7
8
9
10pipelines.py文件
- 存储数据
class SunspiderPipeline(object): def process_item(self, item, spider): # 判断item是哪一个类封装 if item.__class__.__name__ == "SunspiderItemSecond": content = item['content'] num = item['num'] print(content, num) else: title = item['title'] status = item['status'] num = item['num'] print(title, status, num) return item
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.分布式爬虫
什么是分布式爬虫
- 基于多台电脑组件一个分布式机群,然后让每一台电脑执行同一组程序,让后让他们对同一个网站的数据进行分布式爬取
为什么使用分布式爬虫
- 提示爬取数据效率
如何实现分布式爬虫
- 基于scrapy + redis 的形式实现分布式
- 原生的scrapy框架不能实现分布式,原因:
- 调度器无法被分布式机群共享
- 管道无法数据共享
- scrapy框架和scrapy-redis 组件实现的分布式
- scrapy-redis 组件作用:
- 提供可以被共享的调度器和管道
- 原生的scrapy框架不能实现分布式,原因:
- 基于scrapy + redis 的形式实现分布式
环境安装
- redis
- pip install scrapy-redis
编码流程
创建一个工程
创建一个爬虫文件:基于CrawlScrapy
- 修改当前的爬虫文件
- 导包:from scrapy_redis.spiders import RedisCrawlSpider
- 将当前爬虫类的父类修改成RedisCrawlSpider
- 将start_urls替换成redis_key = ‘xxx’ #表示的是可被共享调度器中队列的名称
- 编写爬虫类爬取数据的操作
- 修改当前的爬虫文件
对settings进行配置:
指定管道:
# 开启可以被共享的管道 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 }
1
2
3
4指定调度器
# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis组件自己的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据 SCHEDULER_PERSIST = True
1
2
3
4
5
6指定redis的服务
REDIS_HOST = 'redis服务的ip地址' REDIS_PORT = 6379
1
2
redis的配置文件进行配置:redis.windows.conf
56行:#bind 127.0.0.1
75行:protected-mode no
携带配置文件启动redis服务
- ./redis-server redis.windows.conf
启动redis的客户端
- redis-cli
执行当前的工程:
进入到爬虫文件对应的目录中:scrapy runspider xxx.py
向调度器队列中仍入一个起始的url:
- 队列在哪里呢?答:队列在redis中
- lpush fbsQueue www.xxx.com
- 队列在哪里呢?答:队列在redis中
# 3.增量式爬虫
概念:检测网站数据跟新的情况,爬取更新数据
核心:去重!!!
增量式爬虫
深度爬取类型的网站中需要对详情页的url进行记录和检测
记录:将爬取过的详情页的url进行记录保存
- url存储到redis的set中
- redis的sadd方法存取时,如果数据存在返回值为0,如果不存在返回值为1;
检测:如果对某一个详情页的url发起请求之前先要取记录表中进行查看,该url是否存在,存在的话以为 着这个url已经被爬取过了。
代码示例
spider.py文件
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from redis import Redis from zjs_moviePro.items import ZjsMovieproItem class MovieSpider(CrawlSpider): name = 'movie' conn = Redis(host='127.0.0.1',port=6379) # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.4567tv.tv/index.php/vod/show/id/6.html'] rules = (#/index.php/vod/show/id/6/page/2.html Rule(LinkExtractor(allow=r'id/6/page/\d+\.html'), callback='parse_item', follow=False), ) def parse_item(self, response): li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li') for li in li_list: name = li.xpath('./div/div/h4/a/text()').extract_first() detail_url = 'https://www.4567tv.tv'+li.xpath('./div/div/h4/a/@href').extract_first() ex = self.conn.sadd('movie_detail_urls',detail_url) if ex == 1:#向redis的set中成功插入了detail_url print('有最新数据可爬......') item = ZjsMovieproItem() item['name'] = name yield scrapy.Request(url=detail_url,callback=self.parse_detail,meta={'item':item}) else: print('该数据已经被爬取过了!') def parse_detail(self,response): item = response.meta['item'] desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first() item['desc'] = desc yield item
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34item中创建属性
import scrapy class ZjsMovieproItem(scrapy.Item): # define the fields for your item here like: name = scrapy.Field() desc = scrapy.Field()
1
2
3
4
5管道文件中
class ZjsMovieproPipeline(object): def process_item(self, item, spider): conn = spider.conn conn.lpush('movie_data',item) return item
1
2
3
4
5
非深度爬取类型的网站:
- 名词:数据指纹
- 一组数据的唯一标识
- 名词:数据指纹
上次更新: 2023/04/16, 18:35:33