scrapy爬虫架构介绍和初试

jopen 10年前

今天这篇文章主要是介绍一下scrapy的架构,以及如何创建一个scrapy的工程; 什么是scrapy?为什么要用 scrapy?下面主要是对这两个问题的简要回答。


Scrapy 是一套基于Twisted的异步处理框架,是纯python实现的爬虫框架,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容或者各种图片。下图显示了Scrapy的大体架构,其中包含了scheduler、item pipeline、downloader、spider以及engine这几个组件模块,而其中的绿色箭头则说明了整套系统的数据处理流程。

scrapy爬虫架构介绍和初试

下面就来一个个解释每个组件的作用及数据的处理过程。

一、组件说明:

    1、Scrapy Engine(Scrapy引擎)

    Scrapy引擎是用来控制整个系统的数据处理流程,并进行事务处理的触发。更多的详细内容可以看下面的数据处理流程。

    2、Scheduler(调度)

    调度程序从Scrapy引擎接受请求并排序列入队列,并在Scrapy引擎发出请求后返还给他们。

    3、Downloader(下载器)

    下载器的主要职责是抓取网页并将网页内容返还给蜘蛛( Spiders)。

    4、Spiders(蜘蛛)

    蜘蛛是有Scrapy用户自己定义用来解析网页并抓取制定URL返回的内容的类,每个蜘蛛都能处理一个域名或一组域名。换句话说就是用来定义特定网站的抓取和解析规则。

    蜘蛛的整个抓取流程(周期)是这样的:

    (1)首先获取第一个URL的初始请求,当请求返回后调取一个回调函数。第一个请求是通过调用start_requests()方法。该方法默认从start_urls中的Url中生成请求,并执行解析来调用回调函数。

    (2)在回调函数中,你可以解析网页响应并返回项目对象和请求对象或两者的迭代。这些请求也将包含一个回调,然后被Scrapy下载,然后有指定的回调处理。

    (3)在回调函数中,你解析网站的内容,同程使用的是Xpath选择器(但是你也可以使用BeautifuSoup, lxml或其他任何你喜欢的程序),并生成解析的数据项。

    (4)最后,从蜘蛛返回的项目通常会进驻到项目管道。

5、Item Pipeline(项目管道)

    项目管道的主要责任是负责处理有蜘蛛从网页中抽取的项目,他的主要任务是清晰、验证和存储数据。当页面被蜘蛛解析后,将被发送到项目管道,并经过几个特定的次序处理数据。每个项目管道的组件都是有一个简单的方法组成的Python类。他们获取了项目并执行他们的方法,同时他们还需要确定的是是否需要在项目管道中继续执行下一步或是直接丢弃掉不处理。

项目管道通常执行的过程有:

  1. 清洗HTML数据
  2. 验证解析到的数据(检查项目是否包含必要的字段)
  3. 检查是否是重复数据(如果重复就删除)
  4. 将解析到的数据存储到数据库中

6、Downloader middlewares(下载器中间件)

    下载中间件是位于Scrapy引擎和下载器之间的钩子框架,主要是处理Scrapy引擎与下载器之间的请求及响应。它提供了一个自定义的代码的方式来拓展 Scrapy的功能。下载中间器是一个处理请求和响应的钩子框架。他是轻量级的,对Scrapy尽享全局控制的底层的系统。

7、Spider middlewares(蜘蛛中间件)

    蜘蛛中间件是介于Scrapy引擎和蜘蛛之间的钩子框架,主要工作是处理蜘蛛的响应输入和请求输出。它提供一个自定义代码的方式来拓展Scrapy的功能。蛛中间件是一个挂接到Scrapy的蜘蛛处理机制的框架,你可以插入自定义的代码来处理发送给蜘蛛的请求和返回蜘蛛获取的响应内容和项目。

8、Scheduler middlewares(调度中间件)

    调度中间件是介于Scrapy引擎和调度之间的中间件,主要工作是处从Scrapy引擎发送到调度的请求和响应。他提供了一个自定义的代码来拓展Scrapy的功能。

二、数据处理流程

Scrapy的整个数据处理流程由Scrapy引擎进行控制,其主要的运行方式为:

  1. 引擎打开一个域名,时蜘蛛处理这个域名,并让蜘蛛获取第一个爬取的URL。
  2. 引擎从蜘蛛那获取第一个需要爬取的URL,然后作为请求在调度中进行调度。
  3. 引擎从调度那获取接下来进行爬取的页面。
  4. 调度将下一个爬取的URL返回给引擎,引擎将他们通过下载中间件发送到下载器。
  5. 当网页被下载器下载完成以后,响应内容通过下载中间件被发送到引擎。
  6. 引擎收到下载器的响应并将它通过蜘蛛中间件发送到蜘蛛进行处理。
  7. 蜘蛛处理响应并返回爬取到的项目,然后给引擎发送新的请求。
  8. 引擎将抓取到的项目项目管道,并向调度发送请求。
  9. 系统重复第二部后面的操作,直到调度中没有请求,然后断开引擎与域之间的联系。

========================华丽的分割线========================

以上部分是属于网上抄过来的,谁是第一手就无可考究了,对于大家是否能看明白就没有保证了,我本还还算能够明白七成吧。

上面两个分割线中的内容也算得是回答了第一个问题了,那么为什么要用scrapy呢?我有一个习惯,在提出问题的时候都会先再找一个问题,对于这个问题而提出的问题是:我自己写一个不行吗?

刚开始的时候我也是这样想的,于是就自己开始找python怎么抓网页数据之类的了,后来还弄了一个不堪入目的一段代码,用于从一个根网页中找到所有的链接,然后将这些链接都放到一个列表中,然后弄个循环从这个列表中一个个去抓。下面就是我之前第一次接触python时写的代码(不堪入目,不喜勿喷。。。)

#encoding=utf-8    __author__ = 'dragon'    import urllib2  import os  import pymongo  import time  import hashlib    def myspider(startweb, keyword):      list = [startweb]      curindex = 0      Keyword = keyword        #网络上MongoHQ      #con = pymongo.Connection("paulo.mongohq.com", 10042)      #db = con.mytest      #db.authenticate("dragon", "dragon")      #db.urllist.drop()        #本地数据库      con = pymongo.Connection("localhost", 27017)      db = con.mytest        while curindex < len(list):          url = list[curindex]          print "list count =", len(list), "  curcheck ", curindex          print "try to visit ", url            headers = ('User-Agent', 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36')            try:              opener = urllib2.build_opener()              opener.addheaders = [headers]              openness = opener.open(url, None, 30)              data = openness.read()              opener.close()          except:              print "some error ..."              curindex += 1              continue            print "finish get data..."            os.remove("d:/test.txt")          file = open("d:/test.txt", "a")          print >> file, data          file.close()            myfile      = open("d:/test.txt", "r")          mystring    = myfile.read()          myfile.close()            #找到标题          title       = ""          headstart   = mystring.find("<head>")          headend     = mystring.find("</head>")          if headstart < 0:              headstart   = mystring.find("<HEAD>")              headend     = mystring.find("</HEAD>")            if headstart > 0:              titlestart  = mystring.find("<title>")              titleend    = mystring.find("</title>")              if titlestart < 0:                  titlestart  = mystring.find("<TITLE>")                  titleend    = mystring.find("</TITLE>")                if titleend > titlestart and titlestart < headend and titleend < headend:                  title = mystring[titlestart+len("<title>"):titleend]            dbdata = {"title":"", "url":"", "time":""}            try:              title = title.decode("utf-8").encode("utf-8")          except:              try:                  title = title.decode("gbk").encode("utf-8")              except:                  pass              dbdata["title"] = title          dbdata["url"] = url          dbdata["time"] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))          try:              db.urllist.insert(dbdata)          except:              print "insert error"            if len(mystring) > 0:              while len(mystring) > 0:                  start = mystring.find("href=\"")                  if start <= 0:                      break                    substring = mystring[start+6:]                  end = substring.find("\"")                  weblink = substring[:end]                  if Keyword != "":                      if weblink.find(Keyword) >= 0 and list.count(weblink) <= 0:                          list.append(weblink)                  elif 0 > weblink.find("video.sina.com.cn") \                      and 0 > weblink.find("video.baidu.com") \                      and 0 <= weblink.find("http:") \                      and 0 >= list.count(weblink):                        list.append(weblink)                    mystring = mystring[start+6:]            curindex += 1    if __name__ == '__main__':      myspider("http://www.hao123.com", "hao123")

处理的流程是:

    1. 将一个开始的网页url存放到list中

    2. 不断从list中取出url进行数据获取

    3. 在获取到的网页数据中的链接都存放到list里面

    4. 不断重复2、3步骤

上面这段代码写得怎么样就不值得评论了,但是值得我们思考的是,上面的代码出发点是爬虫,并且还是漫无目的的爬虫,没有了终点,或者大家会想到很多退出循环的方法,但是我们还有很多问题需要考虑:如何提高爬虫的效率?如何最大限度利用网络带宽?如果提高抓取回来数据的处理?最重要一点是:我们都不会想着去做google或百度,而是针对一些特定的需求来实现一个爬虫,那么我们如何简单而又快速的去实现我们的定制功能呢?

上面的几个问题就是我们最终都会遇到的问题,也是scrapy能够很好的处理的问题,它通过几个组件完成不同的部分,将类似下载网页数据的这些通用操作封装起来,减少了我们编写爬虫时的难度,并且各个部件之间通过异步来处理,能够最大限度利用了网络带宽。我们只需要按照它的要求来实现几个模块就可以了。

最后,讲讲使用如何生成一个scrapy工程。

打开cmd,cd到你要创建工程的目录,然后使用以下命令创建工程test:

    scrapy startproject test

如下图所示,我们看到创建了一个test文件夹,里面包含了其他一些文件

scrapy爬虫架构介绍和初试

根据上图可以对照第一幅图,找到一些组件的对应文件,我们可以在spider下面创建一个py文件(例如:spider.py),然后写上以下代码:

from scrapy.spider import BaseSpider    class test(BaseSpider):      name = "test"      allowed_domains = ["hao123.com"]      start_urls = ["http://www.hao123.com"]        def parse(self, response):          print response.url
在cmd中,cd进入刚才创建的test目录,使用以下命令运行这个爬虫,我们可以最后看到一些debug输出

scrapy爬虫架构介绍和初试

scrapy爬虫架构介绍和初试

这篇文章到此为止,写得比较粗糙。可能大家对于spider.py中的一些变量和函数名称,以及该模块什么时候被调用存在疑问,大家可以上网找找资料学习,我将在下一篇文件中说明,并且以获取百度文库中的图书信息作为例子,完成相关的代码并提供源码。

来自:http://my.oschina.net/dragonblog/blog/173545