精华内容
下载资源
问答
  • 分布式爬虫

    2018-03-09 11:24:08
    分布式爬虫问题其实也就是多台机器多个 spider 对 多个 url 的同时处理问题,怎样 schedule 这些 url,怎样汇总 spider 抓取的数据。最简单粗暴的方法就是将 url 进行分片,交给不同机器,最后对不同机器抓取的数据...
  • 分布式爬虫原理之分布式爬虫原理

    万次阅读 多人点赞 2018-05-25 22:08:07
    我们在前面已经实现了Scrapy微博爬虫,虽然爬虫是异步加多线程的,但是我们只能在一台主机上运行,所以爬取效率还是有限的,分布式爬虫则是将多台主机组合起来,共同完成一个爬取任务,这将大大提高爬取的效率。...

    我们在前面已经实现了Scrapy微博爬虫,虽然爬虫是异步加多线程的,但是我们只能在一台主机上运行,所以爬取效率还是有限的,分布式爬虫则是将多台主机组合起来,共同完成一个爬取任务,这将大大提高爬取的效率。

    一、分布式爬虫架构

    在了解分布式爬虫架构之前,首先回顾一下Scrapy的架构,如下图所示。


    Scrapy单机爬虫中有一个本地爬取队列Queue,这个队列是利用deque模块实现的。如果新的Request生成就会放到队列里面,随后Request被Scheduler调度。之后,Request交给Downloader执行爬取,简单的调度架构如下图所示。

    如果两个Scheduler同时从队列里面取Request,每个Scheduler都有其对应的Downloader,那么在带宽足够、正常爬取且不考虑队列存取压力的情况下,爬取效率会有什么变化?没错,爬取效率会翻倍。

    这样,Scheduler可以扩展多个,Downloader也可以扩展多个。而爬取队列Queue必须始终为一个,也就是所谓的共享爬取队列。这样才能保证Scheduer从队列里调度某个Request之后,其他Scheduler不会重复调度此Request,就可以做到多个Schduler同步爬取。这就是分布式爬虫的基本雏形,简单调度架构如下图所示。

    我们需要做的就是在多台主机上同时运行爬虫任务协同爬取,而协同爬取的前提就是共享爬取队列。这样各台主机就不需要各自维护爬取队列,而是从共享爬取队列存取Request。但是各台主机还是有各自的Scheduler和Downloader,所以调度和下载功能分别完成。如果不考虑队列存取性能消耗,爬取效率还是会成倍提高。

    二、维护爬取队列

    那么这个队列用什么来维护?首先需要考虑的就是性能问题。我们自然想到的是基于内存存储的Redis,它支持多种数据结构,例如列表(List)、集合(Set)、有序集合(Sorted Set)等,存取的操作也非常简单。

    Redis支持的这几种数据结构存储各有优点。

    • 列表有lpush()lpop()rpush()rpop()方法,我们可以用它来实现先进先出式爬取队列,也可以实现先进后出栈式爬取队列。

    • 集合的元素是无序的且不重复的,这样我们可以非常方便地实现随机排序且不重复的爬取队列。

    • 有序集合带有分数表示,而Scrapy的Request也有优先级的控制,我们可以用它来实现带优先级调度的队列。

    我们需要根据具体爬虫的需求来灵活选择不同的队列。

    三、如何去重

    Scrapy有自动去重,它的去重使用了Python中的集合。这个集合记录了Scrapy中每个Request的指纹,这个指纹实际上就是Request的散列值。我们可以看看Scrapy的源代码,如下所示:

    import hashlib
    def request_fingerprint(request, include_headers=None):
       if include_headers:
           include_headers = tuple(to_bytes(h.lower())
                                    for h in sorted(include_headers))
       cache = _fingerprint_cache.setdefault(request, {})
       if include_headers not in cache:
           fp = hashlib.sha1()
           fp.update(to_bytes(request.method))
           fp.update(to_bytes(canonicalize_url(request.url)))
           fp.update(request.body or b'')
           if include_headers:
               for hdr in include_headers:
                   if hdr in request.headers:
                       fp.update(hdr)
                       for v in request.headers.getlist(hdr):
                           fp.update(v)
           cache[include_headers] = fp.hexdigest()
       return cache[include_headers]

    request_fingerprint()就是计算Request指纹的方法,其方法内部使用的是hashlib的sha1()方法。计算的字段包括Request的Method、URL、Body、Headers这几部分内容,这里只要有一点不同,那么计算的结果就不同。计算得到的结果是加密后的字符串,也就是指纹。每个Request都有独有的指纹,指纹就是一个字符串,判定字符串是否重复比判定Request对象是否重复容易得多,所以指纹可以作为判定Request是否重复的依据。

    那么我们如何判定重复呢?Scrapy是这样实现的,如下所示:

    def __init__(self):
       self.fingerprints = set()

    def request_seen(self, request):
       fp = self.request_fingerprint(request)
       if fp in self.fingerprints:
           return True
       self.fingerprints.add(fp)

    在去重的类RFPDupeFilter中,有一个request_seen()方法,这个方法有一个参数request,它的作用就是检测该Request对象是否重复。这个方法调用request_fingerprint()获取该Request的指纹,检测这个指纹是否存在于fingerprints变量中,而fingerprints是一个集合,集合的元素都是不重复的。如果指纹存在,那么就返回True,说明该Request是重复的,否则这个指纹加入到集合中。如果下次还有相同的Request传递过来,指纹也是相同的,那么这时指纹就已经存在于集合中,Request对象就会直接判定为重复。这样去重的目的就实现了。

    Scrapy的去重过程就是,利用集合元素的不重复特性来实现Request的去重。

    对于分布式爬虫来说,我们肯定不能再用每个爬虫各自的集合来去重了。因为这样还是每个主机单独维护自己的集合,不能做到共享。多台主机如果生成了相同的Request,只能各自去重,各个主机之间就无法做到去重了。

    那么要实现去重,这个指纹集合也需要是共享的,Redis正好有集合的存储数据结构,我们可以利用Redis的集合作为指纹集合,那么这样去重集合也是利用Redis共享的。每台主机新生成Request之后,把该Request的指纹与集合比对,如果指纹已经存在,说明该Request是重复的,否则将Request的指纹加入到这个集合中即可。利用同样的原理不同的存储结构我们也实现了分布式Reqeust的去重。

    四、防止中断

    在Scrapy中,爬虫运行时的Request队列放在内存中。爬虫运行中断后,这个队列的空间就被释放,此队列就被销毁了。所以一旦爬虫运行中断,爬虫再次运行就相当于全新的爬取过程。

    要做到中断后继续爬取,我们可以将队列中的Request保存起来,下次爬取直接读取保存数据即可获取上次爬取的队列。我们在Scrapy中指定一个爬取队列的存储路径即可,这个路径使用JOB_DIR变量来标识,我们可以用如下命令来实现:

    scrapy crawl spider -s JOB_DIR=crawls/spider

    更加详细的使用方法可以参见官方文档,链接为:https://doc.scrapy.org/en/latest/topics/jobs.html。

    在Scrapy中,我们实际是把爬取队列保存到本地,第二次爬取直接读取并恢复队列即可。那么在分布式架构中我们还用担心这个问题吗?不需要。因为爬取队列本身就是用数据库保存的,如果爬虫中断了,数据库中的Request依然是存在的,下次启动就会接着上次中断的地方继续爬取。

    所以,当Redis的队列为空时,爬虫会重新爬取;当Redis的队列不为空时,爬虫便会接着上次中断之处继续爬取。

    五、架构实现

    我们接下来就需要在程序中实现这个架构了。首先实现一个共享的爬取队列,还要实现去重的功能。另外,重写一个Scheduer的实现,使之可以从共享的爬取队列存取Request。

    幸运的是,已经有人实现了这些逻辑和架构,并发布成叫Scrapy-Redis的Python包。接下来,我们看看Scrapy-Redis的源码实现,以及它的详细工作原理。

    展开全文
  • 通俗来讲,分布式爬虫就是多台机器多个spider对多个url的同时处理操作,分布式的方式可以极大提高程序的抓取效率分布式爬虫一般要配合redis数据库使用,原因有3点:(1)redis数据库可以共享队列(2)重写Scheduler,让其无论...

    什么是分布式爬虫?

    通俗来讲,分布式爬虫就是多台机器多个spider对多个url的同时处理操作,分布式的方式可以极大提高程序的抓取效率

    分布式爬虫一般要配合redis数据库使用,原因有3点:

    (1)redis数据库可以共享队列

    (2)重写Scheduler,让其无论是去重还是任务都去访问共享队列

    (3)为Scheduler定制去重规则

    redis数据库的安装和使用见下文

    分布式爬虫的结构:

    分布式爬虫一般采用主从结构(master-slaver),master端管理redis数据库和分发下载任务,slaver部署爬虫提取网页、解析提取数据和存储数据

    主从结构(master-slaver)的特点:

    (1)如果master出现崩溃,可以马上将其中一个slaver重新部署为master,不会造成数据的丢失

    (2)无论是master或者slaver修改数据时,所有master和slaver都会立马进行数据同步

    (3)slaver可以有多个,master只能有一个

    redis数据库的安装和使用:

    (1)安装:

    70

    下载完成后是一个压缩包,尽量解压到桌面并修改文件夹名为redis,之后使用会方便一点

    注意:一定要把所有文件都放在同一个文件夹下

    (2)使用:

    修改一些数据,用编译器打开下面这个文件,用编译器可以根据行数快速找到要修改的数据

    70

    1.注释掉第56行代码

    70

    2.修改第75行数据,将yes改为no

    70

    3.在123行添加一行代码

    70

    进入终端,输入命令,无论是配置master或slaver时们尽量都要先cd到redis文件夹中,这样可以避免一些不必要的错误

    1.配置master

    cd C:\Users\Administrator\Desktop\redis >>> redis-server redis.windows.conf

    出现以下界面说明配置成功

    注意:配置成功后就不要动了,因为现在这是一个主服务器了

    70

    2.配置slaver,并连接master

    这里可以是另外一台机器,也可以依旧是本机,如果是做项目的话,推荐使用额外一台机器,这里只演示方法

    cd C:\Users\Administrator\Desktop\redis >>> redis-cli -h 255.255.255.255 -p 6379

    注意:-h后面写master的ip,我这里只是举例来完善写法

    -p 后面写端口号,默认为6379,-p 6379 可以省略

    今天就先说这么多吧,之后会继续补充

    展开全文
  • 本章讲的依旧是实战项目,实战内容是打造分布式爬虫,这对初学者来说,是一个不小的挑战,也是一次有意义的尝试。这次打造的分布式爬虫采用比较简单的主从模式,完全手工打造,不使用成熟框架,基本上涵盖了前六章的...
    ")

    25 fout.write("

    26 fout.close()

    27

    28 def output_html(self,path):

    29 '''

    30 将数据写入HTML文件中

    31 :param path: 文件路径

    32 :return:

    33 '''

    34 fout=codecs.open(path,'a',encoding='utf-8')

    35 for data in self.datas:

    36 fout.write("

    37 fout.write("

    %s"%data['url'])

    38 fout.write("

    %s"%data['title'])

    39 fout.write("

    %s"%data['summary'])

    40 fout.write("

    ")

    41 self.datas.remove(data)

    42 fout.close()

    43

    44 def ouput_end(self,path):

    45 '''

    46 输出HTML结束

    47 :param path: 文件存储路径

    48 :return:

    49 '''

    50 fout=codecs.open(path,'a',encoding='utf-8')

    51 fout.write("

    ")

    52 fout.write("

    展开全文
  • 分布式爬虫系统广泛应用于大型爬虫项目中,力求以最高的效率完成任务,这也是分布式爬虫系统的意义所在。分布式系统的核心在于通信,介绍三种分布式爬虫系统的架构思路,都是围绕通信开始,也就是说有多少分布式系统...

    分布式爬虫系统广泛应用于大型爬虫项目中,力求以最高的效率完成任务,这也是分布式爬虫系统的意义所在。

    分布式系统的核心在于通信,介绍三种分布式爬虫系统的架构思路,都是围绕通信开始,也就是说有多少分布式系统的通信方式就有多少分布式爬虫系统的架构思路。

    Redis

    利用redis做分布式系统,最经典的就是scrapy-Redis,这是比较成熟的框架。同时我们也可以利用Redis的队列功能或者订阅发布功能来打造自己的分布式系统。

    db4bfb61a1ee

    image

    Redis作为通信载体的优点是读写迅速,对爬虫的速度影响可忽略不 计,使用比较普遍。

    主程序示例:

    import scrapyfrom scrapy.http import Requestfrom scrapy.selector import HtmlXPathSelectorfrom scrapy.dupefilter import RFPDupeFilterfrom scrapy.core.scheduler import Schedulerimport redisfrom ..items import XiaobaiItemfrom scrapy_redis.spiders import RedisSpiderclass RenjianSpider(RedisSpider): name = 'baidu' allowed_domains = ['baidu.com'] def parse(self, response): news_list = response.xpath('//*[@id="content-list"]/div[@class="item"]') for news in news_list: content = response.xpath('.//div[@class="part1"]/a/text()').extract_first().strip() url = response.xpath('.//div[@class="part1"]/a/@href').extract_first() yield XiaobaiItem(url=url,content=content) yield Request(url='http://dig..com/',callback=self.parse)

    2.RabbitMQ

    RabbitMQ是比较靠谱的消息中间件,得益于它的确认机制,当一条消息消费后如果设置确定模式,那么确认后才会继续消费,如果不确定认,那么这个任务将分配给其他消费者。

    db4bfb61a1ee

    image

    基于这种确认机制,可以在高可靠性和高数据要求情景中,避免数据抓取的遗漏和丢失。

    其设计思路应该是基于mq设计两个接口,一个用于URL的存放,一个用户URL的获取,同时基于Redis的URL去重,通过类似scrapy-redis 的调度使爬虫运行。

    主程序示例:

    import pikaclass RabbitMQBASE: def __new__(cls, *args, **kw): if not hasattr(cls, '_instance'): org = super(RabbitMQBASE, cls) cls._instance = org.__new__(cls) return cls._instance def __init__(self, use='root', pwd='111'): user_pwd = pika.PlainCredentials(use, pwd) self.s_conn = pika.BlockingConnection( pika.ConnectionParameters(host='1.1.1.1', heartbeat_interval=3600, credentials=user_pwd)) def channel(self): return self.s_conn.channel() def close(self): """关闭连接""" self.s_conn.close() @staticmethod def callback(ch, method, properties, body): print(" [消费者] %r" % body)class RabbitMQ(RabbitMQBASE): """ type_:交换机类型fanout、direct、topic exchange:交换机名字 queue_name:队列名字,为空则随机命名 exclusive:队列是否持久化False持久,True不持久 key_list:消费者的交换机、队列绑定的关键词列表 key:生产者路由的关键词 no_ack:是否确认消息True不确定,False确定 """ def __init__(self, use='root', pwd='Kw7pGR4xDD1CsP*U', type_='direct', exchange='test', queue_name=None, exclusive=True, key_list=['test'], key='test', no_ack=True): RabbitMQBASE.__init__(self, use=use, pwd=pwd) self.type_ = type_ self.exchange = exchange self.queue_name = queue_name self.exclusive = exclusive self.key = key self.key_list = key_list self.no_ack = no_ack def rabbit_get(self): """消费者""" channel = self.channel() channel.exchange_declare(exchange=self.exchange, exchange_type=self.type_) if self.queue_name == None: result = channel.queue_declare(exclusive=self.exclusive) self.queue_name = result.method.queue if self.type_ != 'fanout': for key in self.key_list: channel.queue_bind(exchange=self.exchange, # 将交换机、队列、关键字绑定 queue=self.queue_name, routing_key=key) channel.basic_consume(RabbitMQBASE.callback, queue=self.queue_name, no_ack=self.no_ack) channel.start_consuming() def rabbit_put(self, message='hello word'): """生产者""" channel = self.channel() channel.exchange_declare(exchange=self.exchange, exchange_type=self.type_) if self.type_ == 'fanout': self.key = "" channel.basic_publish(exchange=self.exchange, routing_key=self.key, body=message) channel.close()

    3.Celery

    celery典型的分布式任务队列,常用于异步操作中,如tornado、Django的异步任务中,用celery设计分布式爬虫系统,往往结合网络框架,打造一个爬虫任务接口,提供给其他人使用。

    db4bfb61a1ee

    image

    同时celery在定时任务方面有着优势,只需要在配置文件中设置一下,就可以定期执行任务,不必自己写定时操作。

    celery使用消息中间件,而这个消息中间件,可以使用Redis也可以使用RabbitMQ,但他的调度不必担心,已经封装。

    主程序示例:

    # -*- coding:utf-8 -*-from celery import Celeryapp = Celery("tasks")app.config_from_object("celeryconfig") # 指定配置文件@app.taskdef taskA(x, y): return x + y@app.taskdef taskB(x, y, z): return x + y + z@app.taskdef add(x, y): return x + y

    注意:上面的程序片段只是片段,用于示例。

    综上我们应该清楚在不同的任务场景中使用甚至如何设计自己的爬虫系统,分布式爬虫系统的核心是不同主机的通信。

    ID:Python之战

    |作|者|公(zhong)号:python之战

    专注Python,专注于网络爬虫、RPA的学习-践行-总结

    喜欢研究技术瓶颈并分享,欢迎围观,共同学习。

    独学而无友,则孤陋而寡闻!

    展开全文
  • 本篇文章属于进阶知识,可能会用到以前出现在专栏文章中的知识,如果你是第一次关注本专栏,建议你先阅读下其他文章:查询--爬虫(计算机网络)现在搞爬虫的人,可能被问的最多的问题就是“你会不会分布式爬虫?...
  • 14.1 分布式爬虫原理我们在前面已经实现了 Scrapy 微博爬虫,虽然爬虫是异步加多线程的,但是我们只能在一台主机上运行,所以爬取效率还是有限的,分布式爬虫则是将多台主机组合起来,共同完成一个爬取任务,这将...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 36,206
精华内容 14,482
关键字:

分布式爬虫

爬虫 订阅