Pyspider框架(二)

pyspider框架的架构

1.概述

下图显示了pyspider体系结构及其组件的概述,以及系统内部发生的数据流的概要。

组件之间通过消息队列进行连接。每一个组件都包含消息队列,都在它们自己的进程/线程中运行,并且是可以替换的。这意味者,当处理速度缓慢时,这个时候我们可以通过启动多个processor实例来充分利用多核cpu来进行提高效率,或者进行分布式部署来提高效率。

2.组件

(1)Scheduler(调度器)

调度器从processor返回的新任务队列中接收任务。判断是新任务还是需要重新爬取。通过优先级对任务进行分类,并且利用令牌桶算法将任务发送给fetcher, 处理周期任务,丢失的任务和失败的任务,并且稍后重试。

以上都可以通过self.crawl进行设置(我们的第一个脚本就是一个processor)。

注意,在当前的调度器实现中,只允许一个调度器去进行运行。

(2)Fetcher(本质就是一个请求,只是将Scheduler发出的请求进行处理后变成结果,而对于Processor来说它是一个请求)---爬取器

Fetcher的职责是获取web页面然后把结果发送给processor。请求method, headers, cookies, proxy, etag 等,都可以设置。

(3)Processor(如果返回结果就输出,如果返回进行craw()方法就传给Sheduler)---处理器

处理器的职责是运行用户编写的脚本,去解析和提取信息。您的脚本在无限制的环境中运行。尽管我们有各种各样的工具(如PyQuery,还可以用xpath和beautifulsoup)供您提取信息和连接,您可以使用任何您想使用的方法来处理响应。

处理器会捕捉异常和记录日志,发送状态(任务跟踪)和新的任务给调度器,发送结果给Result Worker

(4)Result Worker

Result WorkerPorcess接收结果数据。Pyspider有一个内置的结果处理器将数据保存到resultdb,根据您的需要重写它以处理结果。

(5)WebUI

WebUI是一个面向内容的web前端。它包含:

  • 脚本编辑器,调试器
  • 项目管理器
  • 任务监控程序
  • 结果查看器和导出

也许webui是pyspider最吸引人的地方。使用这个强大的UI,您可以像pyspider一样一步一步地调试脚本。启动停止项目。找到哪个项目出错了,什么请求失败了,然后使用调试器再试一次。

(6)Data flow

pyspider中的数据流如上图所示:

  1. 当您按下WebUI上的Run按钮时,每个脚本都有一个名为on_start的回调。作为项目的入口,on_start产生的新任务将会提交给调度器。
  2. 调度程序使用一个数据URI将这个on_start任务分派为要获取的普通任务。
  3. Fetcher对它发出一个请求和一个响应(对于数据URI,它是一个假的请求和响应,但与其他正常任务没有区别),然后送给处理器。
  4. 处理器调用on_start方法并生成一些要抓取的新URL。处理器向调度程序发送一条消息,说明此任务已完成,并通过消息队列将新任务发送给调度程序(在大多数情况下,这里没有on_start的结果。如果有结果,处理器将它们发送到result_queue)。
  5. 调度程序接收新任务,在数据库中查找,确定任务是新的还是需要重新抓取,如果是,将它们放入任务队列,按顺序分派任务。
  6. 这个过程重复(从步骤3开始),直到WWW死后才停止;)。调度程序将检查定期任务,以抓取最新数据。

3.关于任务

任务是调度的基本单元(就是进行调度的过程就是对任务进行调度)。

(1)基本原理
  • 任务由它的taskid(默认:md5(url), 可以通过重写get_taskid(self, task)方法来修改,在第一个脚本里重写函数方法)来区分。
  • 不同项目之间的任务是隔离的。
  • 一个任务有4个状态:
    • active
    • failed
    • success
    • bad-not used
  • 只有处于active状态的任务会被调度。
  • 任务按优先次序执行。
(2)调度
a、新任务

当一个新的任务(从未见过)出现:

  • 如果设置了exetime但没有到达,它将被放入一个基于时间的队列中等待。
  • 否则将被接受。

当任务已经在队列中:

  • 除非(force_update)强制更新,否则忽略

当一个完成过的任务出现时(重复的任务):

  • 如果age设置,且last_crawl_time + age < now(上一次的抓取时间+age设置的时间小于当前时间) 它将会被接受,否则抛弃。
  • 如果itag设置,且它不等于上一次的值,它会被接受,否则抛弃(进行itag设置使得所有的新任务进行启动一遍)。
b、 重试

当请求错误或脚本错误发生时,任务将在默认情况下重试3次。

第一次重试将在30秒、1小时、6小时、12小时后每次执行,任何更多的重试将推迟24小时。retry_delay是一个指定重试间隔的字典。这个字典中的元素是{retried:seconds}, 如果未指定,则使用特殊键:''空字符串指定的默认推迟时间。

例如默认的retry_delay声明如下:

class MyHandler(BaseHandler):
    retry_delay = {
        0: 30,
        1: 1*60*60,
        2: 6*60*60,
        3: 12*60*60,
        '': 24*60*60
    }

4.关于项目

在大多数情况下,项目是为一个网站编写的一个脚本(一个网站一个项目)。

  • 项目是独立的,但是可以用from Projects import other_project将另一个项目作为模块导入。
  • 一个项目有5个状态:TODO,STOP,CHECKING,DEBUGRUNNING
    • TODO - 刚创建,正在编写脚本
    • 如果您希望项目停止(= =),可以将其标记为STOP。
    • CHECKING - 当正在运行的项目被修改时,为了防止未完成的修改,项目状态将被设置为自动检查。
    • DEBUG/RUNNING - 这两种状态对爬虫没有区别。但最好在第一次运行时将其标记为DEBUG,然后在检查后将其更改为RUNNING
  • 爬行速率由rateburst并采用令牌桶算法进行控制。
    • rate - 一秒钟内有多少请求(即一秒钟爬多少次)
    • burst (爆发)- 考虑这种情况,RATE/BURST=0.1/3,这意味着蜘蛛每10秒抓取1个页面。所有任务都已完成,Project将每分钟检查最后更新的项目。假设找到3个新项目,pyspider将“爆发”并爬行3个任务,而不等待3*10秒。但是,第四个任务需要等待10秒。
  • 若要删除项目,请将“组”(group)设置为“删除”(delete),将“状态”设置为“停止”,然后等待24小时。
回调 on_finished

您可以在项目中重写on_finished方法,当task_queue变为0时将触发该方法。

第一种情况:当您启动一个项目来抓取一个包含100个页面的网站时,当100个页面被成功抓取或重试失败时,on_finished回调将被触发。

第二种情况:带有auto_recrawl任务的项目永远不会触发on_finished回调,因为当其中有auto_recrawl任务时,时间队列永远不会变为0。

第三种情况:带有@every修饰方法的项目将在每次新提交的任务完成时触发on_finished回调。

5. 脚本环境

(1) 变量
  • self.project_name

  • self.project (当前项目的详细信息)

  • self.response

  • self.task

官方文档:http://docs.pyspider.org/en/latest/

(2)关于脚本
  • handler的名称并不重要,但您需要至少一个继承basehandler的类。

  • 可以设置第三个参数来获取任务对象:def callback(self,response,task)

  • 默认情况下,非200响应不会提交回调。可以使用@catch_status_code_error来处理非200响应。

(3)关于环境
  • logging,print以及异常会被捕获。

  • 你可以通过from projects import some_project将其他项目当做模块导入。

(4)Web view
  • 以浏览器呈现的方式查看页面(大约)
(5)HTML view
  • 查看当前回调的HTML(索引页、细节页等)
(6) Follows view
  • 查看可从当前回调进行的回调(数量,即回调任务的数量和相应的url)

  • 索引页面跟随视图将显示可执行的详细页面回调。

(7)Messages view
  • 显示self.send_message发送的消息
(8)Enable CSS Selector Helper
  • 启用Web视图的CSS选择器帮助程序。它获取您单击的元素的CSS选择器,然后将其添加到脚本中。

6.处理结果

从WebUI下载和查看数据很方便,但可能不适用于计算机,所以要存储到自己的数据库中。

(1)使用resultdb

虽然resultdb仅用于结果预览,但不适用于大规模存储结果数据

但是,如果您想从resultdb中获取数据,那么有一些使用数据库API的简单案例可以帮助您连接和选择数据。

from pyspider.database import connect_database
resultdb = connect_database("")
for project in resultdb.projects:
    for result in resultdb.select(project):
        assert result['taskid']
        assert result['url']
        assert result['result']

result['result']是由你编写的脚本中的RETURN语句提交的对象。

(2)使用ResultWorker

在生产环境中,你可能希望将pyspider连接到你的系统的处理管道,而不是将结果存储到resultdb中。强烈建议重写ResultWorker。

from pyspider.result import ResultWorker

class MyResultWorker(ResultWorker):
    def on_result(self, task, result):
        assert task['taskid']
        assert task['project']
        assert task['url']
        assert result
        # your processing code goes here

result是你脚本中return语句提交的对象。

您可以将这个脚本(例如,my_result_worker.py)放在启动pyspider的文件夹中。

result_worker子命令添加参数:

pyspider result_worker --result-cls=my_result_worker.MyResultWorker (运行命令的时候就这样运行)

或者,如果你使用配置文件

{
  ...
  "result_worker": {
    "result_cls": "my_result_worker.MyResultWorker"
  }
  ...
}

为了兼容性,将存储在数据库中的结果编码为JSON(因为result_worker存储到数据库的数据都是JSON类型的)。强烈建议您设计自己的数据库,并覆盖上面描述的ResultWorker

(3)关于结果的小技巧
想要在回调中返回多个结果?

resultdb会通过taskid(url)对结果进行去重,后面的结果会覆盖前面的(但是在脚本中每个任务只会返回一个结果,如果有多个结果的话只会返回最后一个数据结果)。

一个解决方案是使用send_message API为每个结果生成一个伪taskid。

def detail_page(self, response):
    for li in response.doc('li').items():
        self.send_message(self.project_name, {
            ...写入要发的结果
         #   "url": response.url,
           #  "title": response.doc('title').text(),
        
        }, url=response.url+"#"+li('a.product-sku').text())

def on_message(self, project, msg):#这个方法是返回结果,当上一个方法每进行一次send_message就会调用这个方法,就会返回相应的结果。
    return msg

你可能感兴趣的:(Pyspider框架(二))