Redis
redis是一种支持分布式的nosql数据库,他的数据是保存在内存中的,同时redis可以定时把内存数据同步到磁盘,即可以将数据持久化。
分布式爬虫架构
- 核心服务器:一台master,用于启动爬虫,爬虫开始的url以及爬虫爬取的数据都保存在此主机中
- 跑爬虫程序的机器:多个slave(叶子节点),用于爬取数据
我们在Master上搭建一个Redis数据库(此数据库只用作url的存储),并对每个需要爬取的网站类型都开辟一个单独的列表字段,通过设置slave上的scrapy-redis获取Url的地址为master地址,这样的结果就是:尽管有多个slave,然而大家获取url的地方只有一个,那就是服务器master上的redis数据库。
并且,由于scrapy-redis自身的队列机制,slave获取的链接不会相互冲突,这样各个slave在完成抓取任务后,再把获取的结果汇总到服务器上
使用场景
- 登录会话机制:存储在redis中,数据不会丢失
- 排行榜/计数器:实时热搜榜,文章阅读量
- 作为消息队列
- 当前在线人数
- 一些常用的数据缓存
- 把前200篇文章缓存或评论缓存:一般用户浏览网站,只会浏览前面一部分的文章或评论,那么可以把前面的200篇文章和对应的评论缓存起来,不用每次请求数据库
- 好友关系:微博的好友关系使用redis实现
- 发布和订阅功能:可以用来做聊天软件
Scrapy=-Redis分布式爬虫组件
Scrapy是一个框架,他本身是不支持分布式的,如果我们想要分布式的爬虫,就需要一个叫做Scrapy-Redos的组件,这个组件利用了Redis可以分布式的功能,集成到Scrapy框架中,使得爬虫可以进行分布式。可以充分利用多个资源(多个ip、多带宽、同步爬取)来提高爬虫的爬行效率。
分布式爬虫的优点
- 可以充分利用多台机器的带宽
- 可以充分利用多台机器的Ip地址
- 多台机器,爬取效率更高
分布式爬虫必须解决的问题
- 分布式爬虫是好几台机器在同时运行,如何保证在不同的机器爬取页面时不会出现重复爬取的问题
- 分布爬虫在不同的机器上运行,在把数据爬完后如何保证保存在同一个地方
分布式爬虫架构
- Redis服务器:不运行爬虫代码,作用是管理爬虫服务器请求的URL并去重,存储爬虫服务器爬下来的数据(存在内存里)
- 爬虫服务器:从Redis获取请求,把爬取下来的数据送给Redis服务器
其他机器访问本机Redis服务器
想要让其他机器访问本机的Redis服务器,要么修改redis.conf的配置文件,将bind[本机ip地址/0.0.0.0],其他机器才能访问
注:bind绑定的是本机网卡的ip地址,而不是想让其他机器连接的ip地址。如果有多块网卡,那么可以绑定多个网卡的ip地址,如果绑定的ip地址是0.0.0.0,那么意味着其他机器可以同通过本机所有的ip地址进行访问。
实际操作
- 在windows打开redis-server
- 在虚拟机(Kali)中连接windows服务器
redis-cli -h windows的IP地址 -p 6379
Redis操作
列表
类似于python的列表,redis以键key-值value方式存储
向key列表左侧加入一个值:
lpush key value #key表示一个列表 lpush websites baidu.com
向key列表右侧加入一个元素:
rpush key value rpush websites google.com
打印key列表的元素:
lrange websites 范围 lrange websites 0 -1 #打印列表所有元素
移除并返回列表最左侧的元素:
lpop key lpop websites
移除并返回列表最右侧的元素:
rpop key rpop websites
移除列表指定位置的元素:
lrem key count value lrem websites 1 baidu.com
注:根据参数count的值,移除列表中与参数value相等的元素,count的值可以是以下几种: 1.count>0:从表头开始向表尾搜索,移除与value相等的元素,移除的数量为count 2. count<0:从表尾开始向表头搜索,移除与value相等的元素,移除的数量为count的绝对值。 3.count=0,移除表中所有与value相等的值
指定返回第几个元素:
lindex key index lindex websites 1
获取列表中的元素个数:
llen key llen websites
集合操作
集合与python的集合相同,集合与列表的不同:
- 集合是无序的,列表是有序的
- 集合的元素是唯一的,列表的元素可以重复
添加元素
sadd set value2 #set表述集合 sadd team zby
查看元素:
smembers set smembers team
查看集合中的元素个数:
scard set scard team
获取多个集合的交集:
sinter set1 set2 sinter team1 team2
获取多个集合的并集:
sunion set1 set2 sunion team1 team2
获取多个集合的差集:
sdiff set1 set2 sdiff team1 team2
实战
在房天下(www.fang.com )分布式爬虫
步骤
收集信息
- 获取所有城市的url链接
https://www.fang.com/SoufunFamily.htm - 获取所有城市的新房的url链接
例如:深圳新房:https://newhouse.sz.fang.com/house/s
注:以上城市的url对北京不适用,北京的链接没有城市前缀
创建爬虫项目
- 将开始爬取的url改为 https://www.fang.com/SoufunFamily.htm
- 找到包含城市的html框架,分析每个城市元素的html架构:
- 找到最上层的html标签:发现整个城市名的框架处于一个div标签中
- 所有城市处于一个table标签中
- 每一行是一个tr标签
- 每行中包含三个td标签,分别对应字母/省份/城市
- 在第三个td标签中包含a标签,每个a标签为一个城市名
sfw.py 获取城市链接,返回城市/链接
import re def parse(self, response): trs = response.xpath("//div[@class='outCont']//tr") #获取最外层div中的每个tr标签 province = None for tr in trs: #遍历每个tr标签 #筛选我们的要的标签,即第三个td标签,根据第三个td属性进行筛选 tds = tr.xpath(".//td[not(@class)]") #没有class标签,筛掉第一个td province_td = tds[0] province_text = province_td.xpath(".//text()").get() #获取第二个td内容 province_text = re.sub(r"\s","",province_text) if province_text: province = province_text #如果有省份就替换省份 if province == "其它": #不爬取国外网站 continue city_td = tds[1] city_links = city_td.xpath(".//a") #提取所有a标签 for city_link in city_links: city = city_link.xpath(".//text()").get() #获取城市名字 city_url = city_link.xpath(".//@href").get() #获取城市url #构建新房的url链接 url_module= city_url.split("//") #将url以//进行分割 first = url_module[0] #分割http second = url_module[1] #分割域名 list1 = second.split(".") location = list1[0] #获取域名中的城市名字 fang = list1[1] #获取域名中fang com = list1[2] #获取域名中com newhouse_url = first + "//"+location+".newhouse."+fang+"."+com+"house/s/" yield scrapy.Request(url=newhouse_url,callback=self.parse_newhouse,meta={"info":(province,city)}) #以该参数的形式方式请求并调用parse_newhouse函数 def parse_newhouse(self,response): province,city= response.meta.get('info') #将上一个省份和城市传递到这个函数来使用 #详细代码见下文 pass
items.py 进行绑定
class NewHouseItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() province = scrapy.Field() city = scrapy.Field() name = scrapy.Field() #小区名字 price = scrapy.Field() #价格 rooms = scrapy.Field() #有几居 area = scrapy.Field() #面积 address = scrapy.Field() #地址 sale = scrapy.Field() #是否在售 origin_url = scrapy.Field() #详情页面的url
sfw.py 处理新房的函数
from fang.items import NewHouseItem def parse_newhouse(self,response): province,city= response.meta.get('info') #将上一个省份和城市传递到这个函数来使用 lis = response.xpath(".//div[contains(@class,'nl_con')]/ul/li") #div下包含nl_con的类下的ul下的li for li in lis: name=li.xpath(".//div[@class='nlcd_name']/a/text()").get().strip() rooms = li.xpath(".//div[contains(@class,'house_type')]/a/text()").getall() #获取div下标签a的所有文本,/text()表示获取子标签下的文本 area = "".join(li.xpath(".//div[contains(@class,'house_type')]/text()").getall()) #获取面积 join转换为字符串,没有join会有换行符 area = re.sub(r"\s|-|/","",area) #去除多余字符 address = "".join(li.xpath(".//div[@class='address']/a/text()").getall()) address =re.sub(r"\s","",address) sale = li.xpath(".//div[@class='fangyuan']/span/text()").get() price = "".join(li.xpath(".//div[@class='nhouse_price']//text()").getall()) #获取同级标签的所有文本需要//text().getall() price = re.sub(r"\s","",price) origin_url = li.xpath(".//div[@class='nlcd_name']/a/@href").get() item = NewHouseItem(name=name,rooms=rooms,area=area,address=address, sale=sale,price=price,origin_url=origin_url, province=province,city=city) yield item next_url = response.xpath("//div[@class='page']/a[@class='next']/@href").get() if next_url: yield scrapy.Request(url=response.urljin(next_url),callback=self.parse_newhouse(), meta={"info":(province,city)}) #请求下一页的url同样调用新房函数处理,并传递省份城市的参数
部署
分布式爬虫架构
- 核心服务器:一台master,用于启动爬虫,爬虫开始的url以及爬虫爬取的数据都保存在此主机中
- 跑爬虫程序的机器:多个slave(叶子节点),用于爬取数据
在master服务器上搭建一个redis数据库,并将要抓取的url存放到redis数据库中,所有的slave爬虫服务器在抓取的时候从redis数据库中去链接,由于scrapy_redis自身的队列机制,slave获取的url不会相互冲突,然后抓取的结果最后都存储到数据库中。master的redis数据库中还会将抓取过的url的指纹存储起来,用来去重。
实现
- 使用三台机器,一台win10,两台kali linux,分别在两台Linux上进行分布式抓取
- win10的ip地址为192.168.1.152,用来作为redis的master端,linux机器作为slave
- master的爬虫运行时会把提取到的Url封装成request放到redis数据库:”dmoz:requests”,并且从该数据库中提取request后下载网页,再把网页的内容放到redis的另一个数据库中”dmoz:items”
- slave从master的redis去除待抓取的request,下载完网页后就把网页的内容送回master的redis
- 重复上面的3和4,直到master的redis中的”dmoz:dupfilter”是用来存储抓取过的url的指纹(使用哈希函数将url运算后的结果),是防止重复抓取的
linux环境搭建
- 安装python环境:kali默认安装python3
- 安装scrapy-redis:
在终端输入以下命令:pip install scrapy-redis
- 克隆以上虚拟机
winodws
服务器
redis默认只支持Linux,如果要在windows里安装需要去github里进行下载:www.github.com/MicrosoftArchive/redis/releases
安装之后打开cmd,进入到redis安装的上一级文件夹下执行以下命令,打开redis服务器,或者直接点击redis-server.exe
redis-server.exe redis.windows.conf
客户端
命令行
直接点击redis-cli.exe,或者重新打开一个cmd进入redis安装的上一级文件夹,连接redis服务器:
redis-cli
分布式爬虫部署
要将一个scrapy项目变成一个scrapy-redis 项目只需修改以下三点:
- 将爬虫的类从scrapy.Spider变成scrapy_redis.spider.RedisSpider或者从scrapy.CrawlSpider变成scrapy_redis.spiders.RdiesCrwalSpider(见下文spider.py)
- 将爬虫中的start_urls删掉,增加一个redis_key=”xxx”,这个redis_key是为了以后在redis中控制爬虫启动的。爬虫的第一个url,就是在redis中通过这个发送出去的。
- 在settings.py文件中增加配置(见下文settings.py)
settings.py
修改robot/user-agent/item_pipelines
#确保request存储在redis中
SCHEDULER = "scrapy_redis.sheduler.Scheduler"
#确保所有爬虫共享相同的去重指纹
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.REPDupeFilter"
#设置redis为item pipelins
ITEM_PIPELINES ={
'scrapy_redis.pipelines.RedisPipeline':300
}
#在redis中保持scrapy-redis用到的队列,不会清理redis中的队列,从而可以实现暂停和恢复的功能
SCHEDULER_PERSIST = True
#设置连接redis信息
REDIS_HOST = 'redis服务器所在主机的ip地址'
REDIS_PORT = 6379
spider.py
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider
class A2345SpiderSpider(RedisCrawlSpider):
name = '2345_spider'
allowed_domains = ['ruanjian.2345.cc']
#start_urls = ['http://ruanjian.2345.cc/list/0_0_2_1.html']
redis_key = 't2345:start_urls'
rules = (
Rule(LinkExtractor(allow=r'/list/.+\.html'), callback='parse_item', follow=False), #跟进全部选择true
)
def parse_item(self, response):
download_url = response.xpath("//div[@class='cont']//ul//a[@class='btn-b']/@href").extract_first()
name = response.xpath("//div[@class='cont']//ul//em/text()").extract_first()
update_time = response.xpath("//div[@class='cont']//ul//span[@class='ml10']/text()").extract_first()
# print(update_times)
yield {
"download_url":download_url,
"name":name,
"update_time":update_time
}
运行爬虫
打开redis服务器
在爬虫服务器上,进入爬虫文件所在的路径,然后输入命令:
scrapy runspider [爬虫名]
出现以下字样则表示连接成功
在redis数据库(通过redis-cli)中推入一个url链接:
lpush test:start_urls 开始爬取的url
通过图形化界面查看redis数据库
数据导入到mongoDB中
等爬虫结束后,如果要把数据存储到mongoDB中,就应该新建一个process_items.py文件(命名任意):
import redis import json import pymongo redis_clinet = redis.Redis(host='localhost',port=6379,db=0) mongo_client = pymongo.MongoClient() collection = mongo_client.t2345.softInfo while True: #实时取出数据,数据取出后redis数据库中对应数据删除 key,data=redis_clinet.blpop(['2345_spider:items']) #爬虫名称 print(key) d = json.loads(data) collection.insert_one(d)
打开mongoDB服务器
查看mongoDB数据库