Scrapy 1.3.2 - 基础教程

Scrapy 教程

我们将要爬取quotes.toscrape.com,这个网站有名人语录的列表。
本教程将会带你做一下步骤:

  1. 创建一个新的Scrapy项目
  2. 编写爬虫爬取网站并提取数据
  3. 使用命令行导出爬取到的数据
  4. 更改爬虫让其跟随链接
  5. 使用爬虫参数

创建项目

在开始爬取之前,你需要创建一个Scrapy项目,进入你存放代码的目录,并且运行它。

scrapy startproject tutorial
这个命令会创建一个带有如下内容的tutorial目录:

tutorial/
    scrapy.cfg            # 部署配置文件

    tutorial/             # 项目的Python模块,将在这里编写代码
        __init__.py

        items.py          # 项目Item定义文件

        pipelines.py      # 项目管道文件

        settings.py       # 项目配置文件

        spiders/          # 放置爬虫(spider)的目录
            __init__.py

第一个Spider

Spider 是用户定义的从网站(或一组网站)上爬取数据的类。它们必须继承自scrapy.spider并且定义初始化请求,跟随链接的规则以及如何从下载的页面中提取出结构化数据。
一下是关于第一个spider的代码,将他保存在tutorial/spiders目录下,并命名为quotes_spidder.py

# -*- coding:utf-8 -*-
import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes'

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/'
        ]
        for url in urls:
            yield scrapy.Request(url= urls, callback=self.parse)

    def parse(self, response):
        page = response.url.split('/')[-2]
        fileName = 'quotes-%s.html' % page
        #打开文件
        with open(fileName, 'wb') as f:
            f.write(response.body)
        self.log('saved file %s ' % fileName)

如上所见,我们的Spider 继承了scrapy.Spider并且定义了一些属性和方法:


  • name: 识别spider,它在项目中必须是唯一的,也就是不可以为不同的spider设置相同的name值。
  • start_requests():必须放回一个可迭代的Requests(你可以直接返回一个Requests的里列表也可以写一个生成器函数),Spider将会从返回的Requests中开始爬取。将从这些初始请求连续生成后续请求。
  • parse():默认用来回调处理请求响应的方法。response 参数是一个TextResponse的实例,它保存着页面的内容我们会有很多方法来处理它。

parse()方法通常用来解析响应,从抓取的数据中提取为dicts,并查找到要跟踪的新的url并创建请求。

怎么运行Spider

在项目的根目录下输入一下命令

scrapy crawl quotes

这个命令将运行我们刚才添加并且**设置name属性为‘quotes’的spider,它将向quotes.toscrape.com发送一些请求,我们可以看到类似以下的输出:

2017-02-26 22:49:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
2017-02-26 22:49:17 [quotes] DEBUG: Saved file quotes-1.html
2017-02-26 22:49:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/2/> (referer: None)
2017-02-26 22:49:17 [quotes] DEBUG: Saved file quotes-2.html
2017-02-26 22:49:17 [scrapy.core.engine] INFO: Closing spider (finished)

现在查看项目的根目录,我们可以看到两个新的文件quotes-1.htmlquotes-2.html被创建,可以参照我们parse方法中的解析。

刚刚发生了什么?

Scrapy根据由Spider的start_requests方法返回的scrapy.Request对象。 在接收到每个响应时,它实例化Response对象并调用与请求相关联的回调方法(在本例中为parse方法),将响应作为参数传递。

start_requests 方法的快捷方式

通常情况下我们可以设置一个包含URL列表的类属性start_urls来替代重写start_requests()方法。然后,此列表将由默认实现的start_requests()用于为我们的Spider创建初始请求。
因此我们可以简化代码如下:

# -*- coding:utf-8 -*-
import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    #设置 start_urls 属性,替代实现 start_requests()
    start_urls = [
         'http://quotes.toscrape.com/page/1/',
         'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('Saved file %s' % filename)

parse()方法将被处理每个请求的那些URL,即使我们没有明确告诉Scrapy这样做。 发生这种情况是因为parse()是Scrapy的默认回调方法,它被没有显式分配回调的请求调用。

提取数据

最好的学习通过Scrapy提取数据就是尝试使用Scrapy Shell 选择器

scrapy shell “http://quotes.toscrape.com/page/1/

运行命令将会看到如下输出

···
[s]   response   <200 http://quotes.toscrape.com/page/1/>
[s]   settings   <scrapy.settings.Settings object at 0x7fa91d888c10>
[s]   spider     <DefaultSpider 'default' at 0x7fa91c8af990>
[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

利用 shell,我们可以利用 CSS 从 返回的response对象中选择节点。

>>> response.css("title")
[<Selector xpath=u'descendant-or-self::title' data=u'<title>Quotes to Scrape</title>'>]

运行response.css(“title”)返回的是一个类似列表的SelectorList,其中包含了HTML元素的选择器对象,允许我们进一步更加精细的选择和提取数据。
要从上边的title中提取文本,可以做如下操作:

>>> response.css("title::text").extract()
[u'Quotes to Scrape']

这里有两点是我们需要注意的:
1. 我们通过添加::text到CSS查询来表明我们希望选择器在<title>元素中选择文本元素,如果我们不添加::text,我们将会得到包含tag的整个title元素。

>>> response.css("title").extract()
[u'<title>Quotes to Scrape</title>']
  1. 我们注意到extract()返回的结果是列表,因为我们正在处理的是一个SelectorList实例。如果我们只想要第一个结果呢,我们使用extract_first()
>>> response.css("title::text").extract_first()
u'Quotes to Scrape'

当然我们还有另外一种写法

>>> response.css("title::text")[0].extract()
u'Quotes to Scrape'

注意
我们是更提倡使用extract_first()的,可以避免发生IndexError,并且在匹配不到任何元素的时候会返回None
这里有一个教训:对于我们的爬虫代码,我们应该尽量设计的对于错误是由弹性的,这样即使一部份页面爬取失败,我们依然可以成功的爬取一些数据。

除了extract()extract_first()外,我们还可以使用re()利用正则表达式来提取:

>>> response.css("title::text").re(r'Quotes.*')
[u'Quotes to Scrape']
>>> response.css("title::text").re(r'Q\w+')
[u'Quotes']
>>> response.css("title::text").re(r'(\w+) to (\w+)')
[u'Quotes', u'Scrape']

在爬取一个比较复杂的页面时,如何找出有效的CSS选择器也是一个很大的挑战,这个时候我们一般都需要利用一些工具,比如浏览器的View视图。

XPath小介绍

除了CSS,Scrapy选择器也支持通过XPath表达式:

>>> response.xpath("//title")
[<Selector xpath='//title' data=u'<title>Quotes to Scrape</title>'>]
>>> response.xpath("//title/text()").extract_first()
u'Quotes to Scrape'

XPath表达式非常强大,是Scrapy选择器的基础,事实上如果你仔细观察shell中输入的文本可以发现CSS最后是转换成XPath的。

虽然不及CSS选择器那么流行,但是XPath提供了更强大的功能,因为除了导航结构之外,它还可以查看内容。 使用XPath,您可以选择以下内容:选择包含文本“下一页”的链接。 这使得XPath非常适合于抓取任务,我们鼓励你学习XPath,即使你已经知道如何构建CSS选择器。

我们这里不再学习更过的关于XPath的内容,更多的内容大家可以到W3School学习,这里有很详细的教程。

提取名言和作者

现在你知道了一点关于选择和提取,让我们通过编写代码从网页中提取名言来完成我们的爬虫。

一部分从 http://quotes.toscrape.com 中返回到的HTML元素如下:

<div class="quote">
    <span class="text">“The world as we have created it is a process of our
    thinking. It cannot be changed without changing our thinking.”</span>
    <span>
        by <small class="author">Albert Einstein</small>
        <a href="/author/Albert-Einstein">(about)</a>
    </span>
    <div class="tags">
        Tags:
        <a class="tag" href="/tag/change/page/1/">change</a>
        <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a>
        <a class="tag" href="/tag/thinking/page/1/">thinking</a>
        <a class="tag" href="/tag/world/page/1/">world</a>
    </div>
</div>

让我们继续使用我们的Scrapy Shell来看看如何提取我们感兴趣的内容:

>>> response.xpath('div[@class="quote"]')

我们将得到一个列表的HTML元素的选择器。

通过上面的查询返回的每个选择器允许我们对它们的子元素执行进一步的查询。 让我们将第一个选择器分配给一个变量,以便我们可以直接对特定的引用运行我们的XPath选择器:

quote = response.xpath('//div[@class="quote"]')[0]

现在我们将从刚刚创建的quote对象中提取名言,作者及标签:

>>> quote.xpath('span[@class="text"]/text()').extract_first()
u'\u201cThe world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.\u201d'
>>> quote.xpath('span/small[@class="author"]/text()').extract_first()
u'Albert Einstein'
>>> quote.xpath('div/a[@class="tag"]/text()').extract()
[u'change', u'deep-thoughts', u'thinking', u'world']

因为标签是有多个的我们用extract()一次性将标签全部取出来,现在我们已经知道如何从一个节点中提取我们想要的内容,下一步我们就利用循环将所有节点的内容都提取出来。

在我们的爬虫提取数据

让我们回到我们的爬虫项目。 到目前为止,它不会提取任何特别的数据,只是将整个HTML页面保存到本地文件。 让我们将上面的提取逻辑集成到我们的爬虫中。

Scrapy Spider 通常会生成许多从页面中提取的数据组成的字典,因此我们使用Python yield 关键字(防止一次生成大量字典占用过多内存)

# -*- coding:utf-8 -*-
import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    #设置 start_urls 属性,替代实现 start_requests()
    start_urls = [
         'http://quotes.toscrape.com/page/1/',
         'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        for quote in response.xpath('//div[@class="quote"]'):
            yield {
                'text': quote.xpath('span[@class="text"]/text()').extract_first(),
                'author': quote.xpath('span/small[@class="author"]/text()').extract_first(),
                'tags': quote.xpath('div/a[@class="tag"]/text()').extract(),
            }

如果我运行该程序,我们将会看到类似以下的输出:

2017-02-27 14:39:29 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'text': u'\u201cThe world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.\u201d', 'tags': [u'change', u'deep-thoughts', u'thinking', u'world'], 'autho
r': u'Albert Einstein'}
2017-02-27 14:39:29 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'text': u'\u201cIt is our choices, Harry, that show what we truly are, far more than our abilities.\u201d', 'tags': [u'abilities', u'choices'], 'author': u'J.K. Rowling'}

存储爬取到的数据

最简单的办法就是利用Feed exports,可以用下面的命令:

scrapy crawl quotes -o quotes.json

这将生成一个quotes.json文件,其中包含所有被抓取的项目,以JSON序列化。

提示
Scrapy 会将爬取的内容附加到给定的文件,而不是覆盖原文件,因此在抓取数据的时候一定要确保生成文件是不存在的。

在小项目(如本教程)中,这应该足够了。 但是,如果要对已爬取的项目执行更复杂的操作,则可以编写项目管道。 在创建项目时,已经在tutorial / pipelines.py中创建了项目管道的占位文件。 如果你只想存储被抓取的项目,你不需要实现任何项目管道。

跟随链接

让我们接着看,现在我们不仅想提取 http://quotes.toscrape.com 的前两页,我们想提取整个网站所有网网页的内容?
现在我们已经知道如何从页面中提取数据,让我们看看如何跟踪他们的链接。
首先是提取我们要关注的网页的链接。 检查我们的页面,我们可以看到有一个链接到下一页的标记。

<ul class="pager">
    <li class="next">
        <a href="/page/2/">Next <span aria-hidden="true">-></span></a>
    </li>
</ul>

我们可以尝试在shell中提取该链接:

>>> response.xpath('//li[@class="next"]/a/@href').extract_first()
u'/page/2/'

现在我们的爬虫被修改为递归地跟随到下一页的链接,从中提取数据。

# -*- coding:utf-8 -*-
import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    #设置 start_urls 属性,替代实现 start_requests()
    start_urls = [
         'http://quotes.toscrape.com/page/1/',
         'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        for quote in response.xpath('//div[@class="quote"]'):
            yield {
                'text': quote.xpath('span[@class="text"]/text()').extract_first(),
                'author': quote.xpath('span/small[@class="author"]/text()').extract_first(),
                'tags': quote.xpath('div/a[@class="tag"]/text()').extract(),
            }
        next_page = response.xpath('//li[@class="next"]/a/@href').extract_first()
        if next_page is not None:
            #添加域名前缀
            next_page = response.urljoin(next_page)
            #递归提取下一页
            yield scrapy.Request(url= next_page, callback= self.parse)

在提取完数据以后,parse()方法寻找到下一页的链接,并通过urljoin()方法构造一个完整的url地址,并产生一个新的请求到下一页, 将其自身注册为回调以处理下一页的数据提取,并保持抓取知道最后的页面。

在这里我们可以看到Scrapy的链接机制:当你在回调方法中产生一个请求时,Scrapy会调度要发送的请求,并注册一个回调方法,以在该请求完成时执行解析。

使用此功能,您可以根据定义的规则构建复杂的抓取工具,并根据访问的网页提取不同类型的数据。

在我们的例子中,我们创建一个循环不断的提取下一页的链接并解析,一直到尾页。

更多的例子

这是另一个爬虫,声明的初始地址很追随链接的规则,这次我们来爬取作者的信息:

# -*- coding:utf-8 -*-
#爬取作者信息

import scrapy

class AuthorSpider(scrapy.Spider):
    name = 'author'
    start_urls = ['http://quotes.toscrape.com/']
    def parse(self, response):
        #作者信息的链接
        for author in response.xpath('//div[@class="quote"]'):
            info_page = author.xpath('span/a/@href').extract_first()
            print info_page
            yield scrapy.Request(url=response.urljoin(info_page), callback= self.author_parse)
        #查询下一页
        next_page = response.xpath('//li[@class="next"]/a/@href').extract_first()
        if next_page is not None:
            yield scrapy.Request(url= response.urljoin(next_page), callback= self.parse)
    #解析作者信息
    def author_parse(self, response):
        def extract_width_xpath(query):
            return response.xpath(query).extract_first()

        yield {
            'name': extract_width_xpath('//h3[@class="author-title"]/text()'),
            'birthday':extract_width_xpath('//span[@class="author-born-date"]/text()'),
            'location': extract_width_xpath('//span[@class="author-born-location"]/text()'),
        }

这个爬虫会从主页面开始,跟随所有指向作者信息页的链接并回调author_parse提取信息,同时还有分页回调的分页链接,正如我们前面做的。

一个需要我们注意的点是:这个爬虫演示中,即使有很多来url指向同一个作者,我们也不需要担心访问同一作者页多次。 默认情况下,Scrapy会过滤掉已访问过的网址的重复请求,从而避免由于编程错误而导致服务器过多的问题。 这可以通过设置DUPEFILTER_CLASS进行配置。

希望现在你对如何使用Scrapy的链接和回调的机制有一个很好的理解。

使用Spider参数

在运行它们时,可以使用-a选项为我们的爬虫提供命令行参数:

scrapy crawl quotes -o quotes-humor.json -a tag=humor

这些参数传递给Spider的init方法,默认情况下成为spider属性。
在此示例中,为tag参数提供的值将通过self.tag提供。 我们可以使用它来使我们的爬虫仅抓取带有特定标记的句子,根据参数构建网址:

# -*- coding:utf-8 -*-
import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes_tag'

    def start_requests(self):
        url = 'http://quotes.toscrape.com/'
        tag = getattr(self, 'tag', None)
        if tag is not None:
            #根据输入的参数构建url
            url = url + 'tag/' + tag
        yield scrapy.Request(url, self.parse)

    def parse(self, response):
        for quote in response.xpath('//div[@class="quote"]'):
            yield {
                'text': quote.xpath('span[@class="text"]/text()').extract_first(),
                'author': quote.xpath('span/small[@class="author"]/text()').extract_first(),
            }
        next_page = response.xpath('//li[@class="next"]/a/@href').extract_first()
        if next_page is not None:
            #添加域名前缀
            next_page = response.urljoin(next_page)
            yield scrapy.Request(url= next_page, callback= self.parse)

如果我们向爬虫输入tag=humor参数,可以看到它只会访问标记中的网址,例如http://quotes.toscrape.com/tag/humor

在之后我们会学习更多关于Spider参数的知识。

到现在为止,我们通过几个例子学习了一些Scrapy的基础知识,可以爬取一些简单的数据,当然这远远不是Scrapy的全部,我们会继续朝下学习的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值