工欲善其事,必先利其器。首先我们需要的软件工具有:
搭建MongoDB集群
为了使我们的分布式爬虫更加稳定,不至于MongoDB存储服务器宕机了,就让整个系统瘫痪,我们首先构建一个MongoDB集群,主节点将数据实时同步到从节点,主节点可读可写,从节点不能进行写操作。当主节点宕掉,从节点可以立刻通过仲裁来选举新的主节点,保持系统稳定。
下面配置在一台主机三个不同端口上进行演示,同理扩展到若干个不同的主机。
- 在主机上创建三个不同的文件夹,作为数据库的存储位置,每个文件夹里新建一个空的文件夹名为data。
- 开启三个命令行窗口来启动服务器,分别输入:
- 主节点:
mongod --dbpath I:\MongoDB\mongodb_master\data --port 1111 --bind_ip=127.0.0.1 --replSet=replcopy1
- 从节点1:
mongod --dbpath I:\MongoDB\mongodb_slave1\data --port 1112 --bind_ip=127.0.0.1 --replSet=replcopy1
- 从节点2:
mongod --dbpath I:\MongoDB\mongodb_slave2\data --port 1113 --bind_ip=127.0.0.1 --replSet=replcopy1
- 主节点:
- 再开一个命令行窗口,任意登录上面其中一个服务,如登录主节点
mongo 127.0.0.1:1111
进行初始化:
> use admin
> config = {
_id:"replcopy1",
members:[{
_id:1,
host:"127.0.0.1:1111"
},{
_id:2,
host:"127.0.0.1:1112"
},{
_id:3,
host:"127.0.0.1:1113"
}]
}
> rs.initiate(config) #初始化
一些其他有用的命令:
rs.conf() #查看配置
rs.status() #查看状态
rs.isMaster() #查看是否是主节点
- 测试主从数据是否同步
在主节点插入数据:
show dbs
show collections
db.country.insert({name:"China"})
show collections
登录从节点:mongo 127.0.0.1:1112
查询数据:db.country.find()
发现并没有查到,而且报错。
这是因为:
对于SECONDARY节点(从节点)默认是不可读的。因为SECONDARY是不允许读和写的,在写多读少的应用中,使用Replica Sets来实现读写分离。通过在连接时指定或者在主库指定slaveOk,由SECONDARY来分担读的压力,PRIMARY(主节点)只承担写操作。一般默认首先进行初始化操作的节点为选择的主节点,其余节点暂时为从节点。
参考:让mongodb的secondary支持读操作
我们在从节点上设置读写分离:
输入rs.slaveOk()
或者db.getMongo().setSlaveOk()
不过,下次再通过mongo进入实例的时候,查询仍然会报错,就需要重新配置读写分离了。
- 测试主节点宕机时是否可以选举出新的主节点。
把主节点强制关掉,观察是否有从节点被选为新的主节点。
Scrapy遇到的一些问题
我使用Scrapy框架来做分布式爬虫的过程中,遇到了好多问题,记录如下:
- 大致流程:
- 新建项目:
scrapy startproject mySpider
- 新建爬虫模块:
scrapy genspider -t crawl zhihu.com zhihu.com
- 定义Item:确定要提取的数据
- 编写爬虫模块:
spider可以继承的类型有scrapy.Spider,CrawlSpider等。scrapy.Spider是相对最简单的。下面就介绍下CrawlSpider的要注意的点:
新的属性rules,包含一个或多个Rule对象的集合,Rule对爬取网站的动作定义了特定规则。举个例子,
rules = ( Rule(LinkExtractor(allow=r'/bk/so2/n(\d+)p(\d+)'), callback='parse_book_list', follow=True), )
LinkExtractor定义了如何从爬取的页面提取链接。
callback是每次从LinkExtractor中获取到链接时调用函数。
follow是指定根据该规则从response提取的链接是否跟进。若callback为None,follow默认为True,否则默认为False。例如:rules = ( Rule(LinkExtractor(allow=r'/bk/so2/n(\d+)p(\d+)'),), )
这句没有callback,作为“跳板”,只下载网页并根据allow中的匹配的链接,继续遍历下一步的页面,因为有些情况我们并不只抓取某个页面,而是需要“顺藤摸瓜”,从几个种子页面依次推进,最终定位到我们需要的页面。 - Pipeline:将Item数据写入MongoDB。
- 反爬虫措施: 伪造随机User-Agent,自动限速,禁用cookie(如果不需登录),IP代理。
- 去重优化:参考爬虫去重方案
Redis
下面说明一下Redis在分布式爬虫中作用。
一个是作为消息队列,来实现不同主机间的通信。
另一个是去重,来实现增量爬取。
可以看一下使用Redis时在Scrapy的settings.py中的配置:
#使用scrapy_redis的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复
SCHEDULER_PERSIST = True
#使用scrapy_redis的去重方式
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
REDIS_HOST = '127.0.0.1' #redis所在主机的ip
REDIS_PORT = 6379
另外,Redis中去重方式是将生成的fingerprint信息经过SHA-1进行摘要后放入Redis的set数据结构中进行去重的,相对来说比较低效。可以将Redis和BloomFilter结合起来。
网友评论