Scrapy 教程

作者: fromradio | 来源:发表于2016-06-23 08:35 被阅读987次

    Scrapy 教程

    本文翻译自scrapy的最新官方教程,觉得有帮助的朋友可以小小打赏一下,谢谢。

    首先,用户需要安装Scrapy,可以参见安装指导。在本教程中,我们将爬取网站dmoz,并包含以下这些任务:

    1. 创建一个全新的Scrapy项目
    2. 定义用户想爬取的数据类别
    3. 编写一个爬虫分析一个网页并提取所需数据
    4. 编写流程来存储所提取的数据

    创建一个项目

    首先,我们在目标路径下输入并执行以下代码

    scrapy startproject tutorial
    

    这会创建一个tutorial目录,其中含有以下内容

    tutorial/
        scrapy.cfg              # 配置文件
        
        tutorial/               # 项目的Python模组
            __init__.py
            
            items.py            # items文件,数据格式定义
            
            pipelines.py        # pipelines文件,定义流程处理
            
            settings.py         # settings文件,项目设置
            
            spiders/            # 爬虫路径,用户根据需要定义自己的爬虫
                __init__.py
                ...
    

    定义我们的数据(Item)

    Items是存储的容器,随着爬虫工作而加载,调用格式上和Python字典类似。Scrapy中也可以使用单纯的Python字典,但是Items提供了额外的对于填充未声明字段的保护机制,避免用户输入错误引起的错误。

    我们通过创建一个scrapy.Item类,并定义其属性为scrapy.Field来声明Scapy中的数据类型。这和对应关系映射(ORM)机制类似。

    我们首先对我们希望从dmoz.org获取到的数据进行建模刻画。具体而言,我们希望获取网页的名称,连接以及描述信息,因此我们对这三个属性进行字段定义。因此我们如下编写tutorial目录下的items.py

    import scrapy
    
    class DmozItem(scrapy.Item):
        title = scrapy.Field()
        link = scrapy.Field()
        desc = scrapy.Field()
    

    这种定义初看比较复杂,但是定义这样一个类会让用户可以进一步使用其他Scrapy中便利的部件和助手。

    第一个爬虫

    爬虫(spiders)是用户定义的一系列类,Scrapy根据这些类在某个定义域内抓取用户感兴趣的信息。在爬虫中,用户定义初始的需要下载的连接,怎么进一步扩展爬虫,如何解析当前页面并提取之间定义的Items

    为了创建一个爬虫,用户首先需要定义一个scrapy.Spider的子类,并且定义某些属性:

    • name:是爬虫的识别符,在当前项目中必须是唯一,不能为不同的爬虫设置相同的名称。
    • start_urls:一组起始链接集合,爬虫将从这些链接开始。接下来的链接会从这些起始链接中提取。
    • parse:爬虫的一个方法,会以下载起始链接得到的Response作为参数调用。其中Response作为唯一参数传递进去。这个方法负责解析响应数据并提取爬取的数据以及更多的链接。 更进一步,parse方法负责处理相应并返回数据和接下来的链接。

    所以我们首先如下定义第一个爬虫,存在tutorial/spiders目录下,命名为dmoz_spider.py

    import scrapy
    
    class DmozSpider(scrapy.Spider):
        name = 'dmoz'
        allowed_domains = ["dmoz.org"]
        start_urls = [
            "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
            "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
        ]
        
        def parse(self, response):
            filename = response.url.split("/")[-2] + '.html'
            with open(filename, 'wb') as f:
                f.write(response.body)
    

    抓取

    Scrapy中我们通过如下命令执行爬虫任务

    scrapy crawl dmoz
    

    注意上述命令在项目的最高层目录执行,而dmoz就是上面定义的爬虫唯一标识符。第一次我们会得到类似下面的结果输出:

    2014-01-23 18:13:07-0400 [scrapy] INFO: Scrapy started (bot: tutorial)
    2014-01-23 18:13:07-0400 [scrapy] INFO: Optional features available: ...
    2014-01-23 18:13:07-0400 [scrapy] INFO: Overridden settings: {}
    2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled extensions: ...
    2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled downloader middlewares: ...
    2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled spider middlewares: ...
    2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled item pipelines: ...
    2014-01-23 18:13:07-0400 [scrapy] INFO: Spider opened
    2014-01-23 18:13:08-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None)
    2014-01-23 18:13:09-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
    2014-01-23 18:13:09-0400 [scrapy] INFO: Closing spider (finished)
    

    由于我们之前的parse中将响应的body写成文件并存储在最高层目录。

    对当前爬虫的分析

    Scrapy首先针对每一个在start_urls中的链接创建scrapy.Request对象,并将parse方法设置为爬虫的回调函数。这些请求被Scrapy安排时间规划并先后执行,返回scrapy.http.Response对象然后通过parse方法传输给爬虫对象。

    提取数据

    选择器简介

    这里有不同的方法从网页中提取数据。Scrapy使用基于XPATH或者CSS的机制实现了自己的选择器Scrapy Selectors。这里简单介绍一些XPath表达式以及其含义

    • /html/head/title: 选择<head>元素中的<title>元素
    • /html/head/title/text(): 选择上面<title>元素中的文本
    • //td: 选择所有的<td>元素
    • //div[@class="mine"]: 选择所有包含属性class="min"div元素

    为了解析CSS和XPath表达式,Scrapy提供选择器Selector类以及方便的快捷式避免每次都重复实例化选择器。总体说来,选择器可以被看作是表示文档结构节点的对象。所以,第一个实例化的选择器是和根节点即整个文档关联在一块的。

    Scrapy中的Selector含有四个常用的基本方法

    • xpath(): 返回一个选择器列表,每一个元素代表根据xpath表达式选择的节点。
    • css(): 返回一个选择器列表,每一个元素代表根据css表达式选择的节点。
    • extract: 返回选择数据的unicode字符串
    • re(): 返回作用上正则表达式的unicode字符串列表

    在壳(shell)中尝试选择器

    在项目的根目录执行命令

    scrapy shell "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/"
    

    此时IPython环境看起来如下

    [ ... Scrapy log here ... ]
    
    2014-01-23 17:11:42-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
    [s] Available Scrapy objects:
    [s]   crawler    <scrapy.crawler.Crawler object at 0x3636b50>
    [s]   item       {}
    [s]   request    <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
    [s]   response   <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
    [s]   settings   <scrapy.settings.Settings object at 0x3fadc50>
    [s]   spider     <Spider 'default' at 0x3cebf50>
    [s] Useful shortcuts:
    [s]   shelp()           Shell help (print this help)
    [s]   fetch(req_or_url) Fetch request (or URL) and update local objects
    [s]   view(response)    View response in a browser
    
    In [1]:
    

    在shell环境加载后,我们可以通过局部变量response获取响应数据,所以如果用户在命令行中输入response.body,就会看到响应的主体,类似地,你可以通过输入response.headers访问其头部数据。

    此时,response变量含有选择器selector属性,为Selector类的一个实例,随着这个具体的reponse实例化。因此用户可以通过调用response.selector.xpath()或者response.selector.css()来获取需要的部分,也可以通过快捷表达式response.xpath()response.css()来调用。测试我们刚刚介绍的选择器,有

    In [1]: response.xpath('//title')
    Out[1]: [<Selector xpath='//title' data=u'<title>Open Directory - Computers: Progr'>]
    
    In [2]: response.xpath('//title').extract()
    Out[2]: [u'<title>Open Directory - Computers: Programming: Languages: Python: Books</title>']
    
    In [3]: response.xpath('//title/text()')
    Out[3]: [<Selector xpath='//title/text()' data=u'Open Directory - Computers: Programming:'>]
    
    In [4]: response.xpath('//title/text()').extract()
    Out[4]: [u'Open Directory - Computers: Programming: Languages: Python: Books']
    
    In [5]: response.xpath('//title/text()').re('(\w+):')
    Out[5]: [u'Computers', u'Programming', u'Languages', u'Python']
    

    可以看到,直接选择出来为Scrapy中的选择器对象,需要通过extract()或者re()方法提取。

    提取数据

    有了之前的基础,我们现在从这些页面中尝试提取出真实有用的信息。注意到response.body是网页的源代码,为HTML代码,通常很难对其进行直接分析,用户可以使用一些可视化方法来辅助,比如Firebug。通过观察源代码可以发现,需要的网页信息其实都存储在一个<ul>元素内,所以整体流程是通过选择<li>元素列表来获取需要信息:

    response.xpath('//ul/li')
    

    以及其中对网页的描述

    response.xpath('//ul/li/text()').extract()
    

    网页的标题为

    response.xpath('//ul/li/a/text()').extract()
    

    以及这些网页的链接地址

    response.xpath('//ul/li/a/@href').extract()
    

    正如我们提到的,每一个.xpath()返回一个选择器列表,所以我们可以通过对选择器调用.xpath()方法去进一步获取需要数据,例如我们将之前的几种选择器融合在一块,有

    for sel in response.xpath('//ul/li'):
        title = sel.xpath('a/text()').extract()
        link = sel.xpath('a/@href').extract()
        desc = sel.xpath('text()').extract()
        print title, link, desc
    

    我们将这部分放到之前的parse()中,有

    import scrapy
    
    class DmozSpider(scrapy.Spider):
        name = "dmoz"
        allowed_domains = ["dmoz.org"]
        start_urls = [
            "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
            "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
        ]
    
        def parse(self, response):
            for sel in response.xpath('//ul/li'):
                title = sel.xpath('a/text()').extract()
                link = sel.xpath('a/@href').extract()
                desc = sel.xpath('text()').extract()
                print title, link, desc
    

    此时我们将不再得到简单的HTML文件,而是需要的数据格式

    使用我们的Item

    Item对象是Scrapy定制的Python字典,所以我们可以简单使用标准的字典语法来获取我们想要的值

    >>> item = DmozItem()
    >>> item['title'] = 'Example title'
    >>> item['title']
    'Example title'
    

    和普通字典没有什么区别。为了返回当前我们爬到的数据,最终的爬虫看上去如下:

    import scrapy
    
    from tutorial.items import DmozItem
    
    class DmozSpider(scrapy.Spider):
        name = "dmoz"
        allowed_domains = ["dmoz.org"]
        start_urls = [
            "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
            "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
        ]
    
        def parse(self, response):
            for sel in response.xpath('//ul/li'):
                item = DmozItem()
                item['title'] = sel.xpath('a/text()').extract()
                item['link'] = sel.xpath('a/@href').extract()
                item['desc'] = sel.xpath('text()').extract()
                yield item
    

    使用这个爬虫去爬取dmoz.org可以得到DmozItem对象

    [scrapy] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
         {'desc': [u' - By David Mertz; Addison Wesley. Book in progress, full text, ASCII format. Asks for feedback. [author website, Gnosis Software, Inc.\n],
          'link': [u'http://gnosis.cx/TPiP/'],
          'title': [u'Text Processing in Python']}
    [scrapy] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
         {'desc': [u' - By Sean McGrath; Prentice Hall PTR, 2000, ISBN 0130211192, has CD-ROM. Methods to build XML applications fast, Python tutorial, DOM and SAX, new Pyxie open source XML processing library. [Prentice Hall PTR]\n'],
          'link': [u'http://www.informit.com/store/product.aspx?isbn=0130211192'],
          'title': [u'XML Processing with Python']}
    

    紧接着的链接

    假设除开仅仅爬取BooksResources页面,我们还想获取所有Python路径下的页面。现在用户知道如何从一个页面提取数据,所以现在问题是如何从当前页面抓取用户感兴趣页面的链接,紧接着在这些页面再次提取感兴趣的数据。将爬虫代码做一些小小的修正:

    import scrapy
    
    from tutorial.items import DmozItem
    
    class DmozSpider(scrapy.Spider):
        name = "dmoz"
        allowed_domains = ["dmoz.org"]
        start_urls = [
            "http://www.dmoz.org/Computers/Programming/Languages/Python/",
        ]
    
        def parse(self, response):
            for href in response.css("ul.directory.dir-col > li > a::attr('href')"):
                url = response.urljoin(href.extract())
                yield scrapy.Request(url, callback=self.parse_dir_contents)
    
        def parse_dir_contents(self, response):
            for sel in response.xpath('//ul/li'):
                item = DmozItem()
                item['title'] = sel.xpath('a/text()').extract()
                item['link'] = sel.xpath('a/@href').extract()
                item['desc'] = sel.xpath('text()').extract()
                yield item
    

    现在parse()方法仅仅提取了链接数据,通过response.urljoin方法建立绝对路径并且产生新的请求,并注册回调函数parse_dir_contents()来爬取需要的数据。这里Scrapy的机制是这样的,当产生新的请求时,Scrapy会调度进程发送请求而回调函数会在请求完成后执行。在这样的机制下,用户可以设计很复杂的爬虫机制,根据规则得到下一步的链接,以及根据当前页面规则提取不同的数据。一个常用的模式是先使用回调函数提取数据,寻找下一个链接然后产生新的请求

    def parse_articles_follow_next_page(self, response):
        for article in response.xpath("//article"):
            item = ArticleItem()
    
            ... extract article data here
    
            yield item
    
        next_page = response.css("ul.navigation > li.next-page > a::attr('href')")
        if next_page:
            url = response.urljoin(next_page[0].extract())
            yield scrapy.Request(url, self.parse_articles_follow_next_page)
    

    存储爬取的数据

    最简单的方法是调用下面的命令

    scrapy crawl dmoz -o items.json
    

    这会产生一个包含所有数据的文件items.json,使用JSON序列化。在小项目中,这种方法一般是足够的,如果想设计更复杂的系统,用户可以编写一个Item Pipeline,即在tutorial/pipelines.py中实现自己的存储方法。

    相关文章

      网友评论

        本文标题:Scrapy 教程

        本文链接:https://www.haomeiwen.com/subject/aamndttx.html