Scrapy——可配置的爬虫

简介
        Scrapy是一个爬虫程序的框架,用来爬取网页内容和结构化内容的提取。所谓框架,就是可以自己定制一个针对特定网站的爬虫,定制的方法仅仅是需要添加一些简单的规则即可。很多大部分爬虫任务的公共部分是内部写好的,不需要开发人员重新写,减少重复开发。

安装
        Scrapy的官方网站: http://scrapy.org/
        Scrapy的技术文档: http://doc.scrapy.org/en/0.20/
        Scrapy的Github项目: https://github.com/scrapy/scrapy

        安装的话,提供两种最简单的方法:
        To installusing pip:
                pip installScrapy                                
        To install using easy_install:
                easy_installScrapy     

例子:Step by Step
1.      创建项目




        安装好Scrapy后,最想做的肯定是找个HelloWorld项目来尝试一下。体验一下Scrapy的强大之处。
        首先我们得先了解Scrapy的项目目录结构,这样才能方便我们去了解Scrapy的工作原理。Scrapy的目录结构如下图所示:

        tutorial/
                scrapy.cfg
                tutorial/
                        __init__.py
                        items.py
                        pipelines.py
                        settings.py
                        spiders/
                                __init__.py
                                ...
        简单来说:
                •   scrapy.cfg: 项目配置文件。
                •   tutorial/: 项目的代码模块目录。
                •   tutorial/items.py: Item文件,主要用来存储结构化数据的结构信息。
                •   tutorial/pipelines.py: Pipline文件,负责持久化的输出格式。
                •   tutorial/settings.py: 爬虫的属性配置。
                •   tutorial/spiders/: 存放主要的爬虫代码的目录。
        这么复杂的目录结构,但是仅仅需要你的一条命令就能搞定,follow me:
                scrapystartproject tutorial                    
        tutorial是你自己设定的项目名称。
2.      定义Item格式化结构

        我们来试试爬去豆瓣图书上面的一些图书资源,所以我们定义我们的结构化数据结构如下:
        from scrapy .item import Item , Field
        class DoubanItem(Item ):
               url= Field ()        #网页网址
                title = Field ()      #书名
                author = Field ()     #作者
                imgurl = Field()      #图片地址
        这个结构类将用来存储我们在豆瓣图书上面爬去的数据。
3.      第一个爬虫Spider

        我们自己写的爬虫都要继承自基础类scrapy.spider.BaseSpider,这个类实现了大量爬虫共有的特性,我们只需要提供网址和对应的元素的定位就能爬取结构化信息。这个类的子类需要继承并重写的方法和属性如下:
        • name: identifies theSpider. It must be unique, that is, you can’t set the same name for differentSpiders.
        • start_urls: is a list ofURLs where the Spider will begin to crawl from. So, the first pages downloaded willbe those listed here. The subsequent URLs will be generated successively fromdata contained in the start URLs.
        • parse() is a method of thespider, which will be called with the downloaded Response object of each start URL.The response is passed to the method as the first and only argument.
        针对我们的豆瓣读书,我们可以简单实现一个爬虫代码如下:
        from scrapy .spider import BaseSpider   
        from scrapy .selector import Selector
        class DoubanSpider(BaseSpider ):
                name = 'douban'
                allowed_domains = ['douban.com' ]
                start_urls = [' http://read.douban.com/ebook/2004774/' ]

        def parse (self , response ):
                sel = Selector (response )
                douban = DoubanItem ()
                douban ['url' ] = response .url
                douban ['title' ] = sel .xpath ("//h1[@class='article-title']/text()" ).extract ()
                douban ['author' ] = sel .xpath ("//p[@class='author']/span[2]/span/text()" ).extract ()
                douban ['author' ] += sel .xpath ("//p[@class='author']/span[2]/a/text()" ).extract ()
                douban ['imgurl' ] = sel .xpath ("//div[@class='cover']/img/@src" ).extract ()
                return douban
        好了,这样我们就可以方便的将这个豆瓣页面上的信息格式化成我们的douban对象了。

4.      XPath定位信息

        不少人看到上面的parse函数里面的参数肯定很迷惑,这些是啥玩意,很神奇就能定位到了我们需要的信息。这里用到了XPath的Html文档的元素定位方式。
        详细的文档介绍在: http://www.w3.org/TR/xpath/
        中文的教程在: http://www.w3school.com.cn/xpath/
        举几个简单的例子:
        1.      By looking at the page HTML source we can seethat the file name is contained inside a <h1> tag:
                <h1>Home[2009][Eng]XviD-ovd </h1>
                AnXPath expression to extract the name could be:
                //h1/text()


        2.      And the description is contained inside a <div> tag with id="description":
                <h2>Description: </h2>
                <div id="description" >
                "HOME" - adocumentary film by Yann Arthus-Bertrand
                <br/>
                <br/>
                ***
                <br/>
                <br/>
                "Weare living in exceptional times. Scientists tell us that we have 10 years tochange the way we live, ...
                AnXPath expression to select the description could be:
                //div[@id=’description’]


        3.      Finally, the file size is contained in thesecond <p>taginside the <div>tagwith id=specifications:
                <div id="specifications" >
                <p>
                <strong>Category: </strong>
                <a href="/cat/4" >Movies </a> > <ahref="/sub/35" >Documentary </a>
                </p>
                <p>
                <strong>Total size: </strong>
                699.79megabyte </p>
                AnXPath expression to select the file size could be:
                //div[@id=’specifications’]/p[2]/text()[2]
5.      运行爬虫

        只需要在我们项目的根目录下运行下面命令即可:
                scrapy crawldouban -o douban.json -t json       
        爬到的结构化数据将存在JSON格式的douban.json文件中。
6.      如此简单的爬虫。。。更复杂的需求。。。To be continue
递归爬取页面信息——Not only in just one page
 多网页爬取
       前面提到的爬虫太弱了,仅仅只能爬取一个页面的内容,而且一个页面中也就一本书的信息,这还需要爬虫吗?打开页面复制粘贴不就好了。于是需求来了,我们需要爬取多个页面的信息。多,可以是成百上千个吧。

      方法一:
       我们可以用到的最直接的方法可以这样来:
       start_urls = [' http://read.douban.com/ebook/2004774/',
              ' http://read.douban.com/ebook/2004775/',
              ' http://read.douban.com/ebook/2004776/',
              ' http://read.douban.com/ebook/2004777/'... ]
       那么我们就会分别爬取各个start_urls里面的网页的内容,这种只能说在你知道需要爬取的所有页面的地址的时候才能使用。

       方法二:
       如果有这样一个页面,他里面含有所有我们需要信息的一个url的列表,我们需要进去每一个url去爬取信息,这个时候想直接给出start_urls就不可能了。我们需要的是让爬虫自己去收集需要爬取的url。有一个方法是官方文档给出的,利用Rule类指定这类url列表,然后定义这类url的固定的parse方案即可。
       class MininovaSpider (CrawlSpider):
              name = ’mininova’
              allowed_domains = [’mininova.org’]
              start_urls = [’ http://www.mininova.org/today’]
              rules = [Rule(SgmlLinkExtractor(allow=[’/tor/\d+’]), ’parse_torrent’)]
              def parse_torrent(self, response):
                     sel = Selector(response)
                     torrent = TorrentItem()
                     torrent[’url’] = response.url
                     torrent[’name’] = sel.xpath("//h1/text()").extract()
                     torrent[’description’]= sel.xpath("//div[@id=’description’]").extract()
                     torrent[’size’] = sel.xpath("//div[@id=’info-left’]/p[2]/text()[2]").extract()
                     return torrent
       这段代码就是在爬取页面 http://www.mininova.org/today中的所有满足规则/tor/\d+的url。并移交给parse_torrent函数做后续处理。

       方法三:
       方法二也只能是有一个页面包含所有待爬连接url的时候,才能实现爬取,不过这样的页面毕竟是少数。在现在这种大规模的数据时代,数据的呈现总是分块儿的,每次页面也只会显示一部分数据。所以我们的爬虫也需要适应这种方式,需要边爬取页面,变获取之后需要爬取的页面url。这看上去是一个广度搜索的问题,而方法二是一层的广度搜索。
       于是研究一下parse函数,发现它的返回值,不仅仅可以是一个Item,也可以是一个Request。所谓Item就是一个数据项,用来存储格式化数据的;而Request则是一个url,表示待爬取的新地址。如果parse函数返回Item,那么它就会被写入到永久数据文件里面,如果parse返回的是Request,则会进一步去请求Request指向的页面,并在得到Response的时候跳转到对应的Callback函数进行处理。Callback函数可以是parse自己本身,也可以是一个其他和parse有相同签名的函数。
       针对豆瓣图书我写了如下程序,可以从它推荐的书继续扩展爬取,最后能够爬取到豆瓣图书的一个子集(可能是一类,或者相近的图书,这个和种子url有关)。
class DoubanSpider ( BaseSpider ):
    name = 'douban'
    allowed_domains = [ 'douban.com' ]
    start_urls = [ 'http://book.douban.com/' ]
    book_set = set ()        
    def parse ( self , response ):
        print '###Get Links###'
        sel = Selector ( response )
        newurls = sel . xpath ( "//div[@class='cover']/a/@href" ). extract ()
        print newurls
        for url in newurls :
            if url . find ( 'http' )==- 1 :
                url = 'http://read.douban.com' + url ;
            if url . find ( 'http://read.douban.com' )==- 1 :
                continue
           
            num = int ( url . replace ( 'http://read.douban.com/ebook/' , '' )[ 0 : 6 ])
            if num not in self . book_set :
                yield Request ( url , callback = self . parse_content , dont_filter =True)
                yield Request ( url , callback = self . parse , dont_filter =True)
                self . book_set . add ( num )
           
       
    def parse_content ( self , response ):
        print '###Get Content###'
        sel = Selector ( response )
        douban = DoubanItem ()
        douban [ 'url' ] = response . url
        douban [ 'title' ] = sel . xpath ( "//h1[@class='article-title']/text()" ). extract ()
        douban [ 'author' ] = sel . xpath ( "//p[@class='author']/span[2]/span/text()" ). extract ()
        douban [ 'author' ] += sel . xpath ( "//p[@class='author']/span[2]/a/text()" ). extract ()
        douban [ 'imgurl' ] = sel . xpath ( "//div[@class='cover']/img/@src" ). extract ()
        return douban
       这里定义了一个book_set,用于对已经爬过的书去重,防止重复爬取一个页面。在parse函数中真对一个页面进行了信息提取——跳转到parse_content函数,和新目标发现——跳转到自己。这样就能不间断的将豆瓣图书的一个集合自动爬取下来。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值