分布式爬虫初探

工欲善其事,必先利其器。首先我们需要的软件工具有:

  • MongoDB(数据存储)
  • Scrapy(爬虫框架)
  • Redis(消息队列,去重)

搭建MongoDB集群

为了使我们的分布式爬虫更加稳定,不至于MongoDB存储服务器宕机了,就让整个系统瘫痪,我们首先构建一个MongoDB集群,主节点将数据实时同步到从节点,主节点可读可写,从节点不能进行写操作。当主节点宕掉,从节点可以立刻通过仲裁来选举新的主节点,保持系统稳定。
下面配置在一台主机三个不同端口上进行演示,同理扩展到若干个不同的主机。

  1. 在主机上创建三个不同的文件夹,作为数据库的存储位置,每个文件夹里新建一个空的文件夹名为data。
  2. 开启三个命令行窗口来启动服务器,分别输入:
    • 主节点: 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
  3. 再开一个命令行窗口,任意登录上面其中一个服务,如登录主节点
    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() #查看是否是主节点
  1. 测试主从数据是否同步
    在主节点插入数据:
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进入实例的时候,查询仍然会报错,就需要重新配置读写分离了。

  1. 测试主节点宕机时是否可以选举出新的主节点。
    把主节点强制关掉,观察是否有从节点被选为新的主节点。

Scrapy遇到的一些问题

我使用Scrapy框架来做分布式爬虫的过程中,遇到了好多问题,记录如下:

  1. 大致流程:
  • 新建项目: 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结合起来。

你可能感兴趣的:(分布式爬虫初探)