返回介绍

2.3 Spider 开发流程

发布于 2024-02-05 21:13:20 字数 4836 浏览 0 评论 0 收藏 0

有了前面知识的铺垫,现在回到本章的主题“编写Spider”。实现一个Spider子类的过程很像是完成一系列填空题,Scrapy框架提出以下问题让用户在Spider子类中作答:

爬虫从哪个或哪些页面开始爬取?

对于一个已下载的页面,提取其中的哪些数据?

爬取完当前页面后,接下来爬取哪个或哪些页面?

上面问题的答案包含了一个爬虫最重要的逻辑,回答了这些问题,一个爬虫也就开发出来了。

接下来,我们以上一章exmaple项目中的BooksSpider为例,讲解一个Spider的开发流程。为方便阅读,再次给出BooksSpider的代码:

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

class BooksSpider(scrapy.Spider):
 # 每一个爬虫的唯一标识
 name = "books"

 # 定义爬虫爬取的起始点,起始点可以是多个,我们这里是一个
 start_urls = ['http://books.toscrape.com/']

 def parse(self, response):
   # 提取数据
   # 每一本书的信息是在<article class="product_pod">中,我们使用
   # css()方法找到所有这样的article 元素,并依次迭代
   for book in response.css('article.product_pod'):
    # 书名信息在article > h3 > a 元素的title属性里
 # 例如: <a title="A Light in the Attic">A Light in the ...</a>
 name = book.xpath('./h3/a/@title').extract_first()

 # 书价信息在 <p class="price_color">的TEXT中。
 # 例如: <p class="price_color">£51.77</p>
 price = book.css('p.price_color::text').extract_first()
 yield {
  'name': name,
  'price': price,
 }

# 提取链接
# 下一页的url 在ul.pager > li.next > a 里面
# 例如: <li class="next"><a href="catalogue/page-2.html">next</a></li>
next_url = response.css('ul.pager li.next a::attr(href)').extract_first()
if next_url:
 # 如果找到下一页的url,得到绝对路径,构造新的Request 对象
 next_url = response.urljoin(next_url)
 yield scrapy.Request(next_url, callback=self.parse)

实现一个Spider只需要完成下面4个步骤:

步骤 01 继承scrapy.Spider。

步骤 02 为Spider取名。

步骤 03 设定起始爬取点。

步骤 04 实现页面解析函数。

2.3.1 继承scrapy.Spider

Scrapy框架提供了一个Spider基类,我们编写的Spider需要继承它:

import scrapy
class BooksSpider(scrapy.Spider):
 ...

这个Spider基类实现了以下内容:

供Scrapy引擎调用的接口,例如用来创建Spider实例的类方法from_crawler。

供用户使用的实用工具函数,例如可以调用log方法将调试信息输出到日志。

供用户访问的属性,例如可以通过settings属性访问配置文件中的配置。

实际上,在初学Scrapy时,不必关心Spider基类的这些细节,未来有需求时再去查阅文档即可。

2.3.2 为Spider命名

在一个Scrapy项目中可以实现多个Spider,每个Spider需要有一个能够区分彼此的唯一标识,Spider的类属性name便是这个唯一标识。

class BooksSpider(scrapy.Spider):
 name = "books"
 ...

执行scrapy crawl命令时就用到了这个标识,告诉Scrapy使用哪个Spider进行爬取。

2.3.3 设定起始爬取点

Spider必然要从某个或某些页面开始爬取,我们称这些页面为起始爬取点,可以通过类属性start_urls来设定起始爬取点:

class BooksSpider(scrapy.Spider):
 ...
 start_urls = ['http://books.toscrape.com/']
 ...

start_urls通常被实现成一个列表,其中放入所有起始爬取点的url(例子中只有一个起始点)。看到这里,大家可能会想,请求页面下载不是一定要提交Request对象么?而我们仅定义了url列表,是谁暗中构造并提交了相应的Request对象呢?通过阅读Spider基类的源码可以找到答案,相关代码如下:

class Spider(object_ref):
 ...
 def start_requests(self):
  for url in self.start_urls:
    yield self.make_requests_from_url(url)

 def make_requests_from_url(self, url):
  return Request(url, dont_filter=True)

 def parse(self, response):
  raise NotImplementedError
 ...

从代码中可以看出,Spider基类的start_requests方法帮助我们构造并提交了Request对象,对其中的原理做如下解释:

实际上,对于起始爬取点的下载请求是由Scrapy引擎调用Spider对象的start_requests方法提交的,由于BooksSpider类没有实现start_requests方法,因此引擎会调用Spider基类的start_requests方法。

在start_requests方法中,self.start_urls便是我们定义的起始爬取点列表(通过实例访问类属性),对其进行迭代,用迭代出的每个url作为参数调用make_requests_from_url方法。

在make_requests_from_url方法中,我们找到了真正构造Reqeust对象的代码,仅使用url和dont_filter参数构造Request对象。

由于构造Request对象时并没有传递callback参数来指定页面解析函数,因此默认将parse方法作为页面解析函数。此时BooksSpider必须实现parse方法,否则就会调用Spider基类的parse方法,从而抛出NotImplementedError异常(可以看作基类定义了一个抽象接口)。

起始爬取点可能有多个,start_requests方法需要返回一个可迭代对象(列表、生成器等),其中每一个元素是一个Request对象。这里,start_requests方法被实现成一个生成器函数(生成器对象是可迭代的),每次由yield语句返回一个Request对象。

由于起始爬取点的下载请求是由引擎调用Spider对象的start_requests方法产生的,因此我们也可以在BooksSpider中实现start_requests方法(覆盖基类Spider的start_requests方法),直接构造并提交起始爬取点的Request对象。在某些场景下使用这种方式更加灵活,例如有时想为Request添加特定的HTTP请求头部,或想为Request指定特定的页面解析函数。

以下是通过实现start_requests方法定义起始爬取点的示例代码(改写BooksSpider):

class BooksSpider(scrapy.Spider):

 # start_urls = ['http://books.toscrape.com/']

 # 实现start_requests 方法, 替代start_urls类属性
 def start_requests(self):
  yield scrapy.Request('http://books.toscrape.com/',
          callback=self.parse_book,
          headers={'User-Agent': 'Mozilla/5.0'},
          dont_filter=True)
 # 改用parse_book 作为回调函数
def parse_book(response):
 ...

到此,我们介绍完了为爬虫设定起始爬取点的两种方式:

定义start_urls属性。

实现start_requests方法。

2.3.4 实现页面解析函数

页面解析函数也就是构造Request对象时通过callback参数指定的回调函数(或默认的parse方法)。页面解析函数是实现Spider中最核心的部分,它需要完成以下两项工作:

使用选择器提取页面中的数据,将数据封装后(Item或字典)提交给Scrapy引擎。

使用选择器或LinkExtractor提取页面中的链接,用其构造新的Request对象并提交给Scrapy引擎(下载链接页面)。

一个页面中可能包含多项数据以及多个链接,因此页面解析函数被要求返回一个可迭代对象(通常被实现成一个生成器函数),每次迭代返回一项数据(Item或字典)或一个Request对象。

关于如何提取数据、封装数据、提取链接等话题,我们在接下来的章节继续学习。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文