返回介绍

13.1 再看 Spider

发布于 2024-01-26 22:39:51 字数 8427 浏览 0 评论 0 收藏 0

上一章讲解了Spider模块的基本用法,本节继续讲解Spider的其他用法。Spider类定义了如何爬取某个或某些网站,包括了爬取的动作(例如是否跟进链接),以及如何从网页的内容中提取结构化数据item。换句话说,Spider是定义爬取的动作及分析网页结构的地方。

对Spider来说,爬取的循环流程如下:

1)以入口URL初始化Request,并设置回调函数。此Request下载完毕返回Response,并作为参数传给回调函数。spider中初始的Request是通过调用start_requests()方法来获取的,start_requests()读取start_urls中的URL,并以parse为回调函数生成Request。

2)在回调函数内分析Response,返回Item对象、dict、Request或者一个包括三者的可迭代容器。其中返回的Request对象之后会经过Scrapy处理,下载相应的内容,并调用设置的回调函数,可以是parse()或者其他函数。

3)在回调函数内,可以使用选择器(Selectors)或者其他第三方解析器来分析response,并根据分析的数据生成item。

4)最后,由spider返回的item可以经由Item Pipeline被存到数据库或使用Feed exports存入到文件中。

1.scrapy.Spider

上一章创建Spider模块是通过继承scrapy.Spider类来实现,这是一种最简单的spider。每个spider必须继承自scrapy.Spider类(包括Scrapy自带的spider以及自定义的spider)。Spider并没有提供什么特殊的功能,仅仅提供了start_requests()的默认实现,读取并请求spider属性中的start_urls,并根据返回的response调用spider的parse方法。

Spider类常用的属性为:

·name。定义spider名字的字符串,Scrapy使用spider的名字来定位和初始化spider,所以它必须是唯一的。不过可以生成多个相同的spider实例,这没有任何限制。name是spider最重要的属性,而且是必需的。一个常见的做法是以该网站的域名来命名spider。例如,如果spider爬取cnblogs.com,该spider通常会被命名为cnblogs。

·allowed_domains。可选。包含了spider允许爬取的域名列表。当OffsiteMiddleware组件启用时,域名不在列表中的URL不会被跟进。

·start_urls为URL列表。当没有使用start_requests()方法配置Requests时,Spider将从该列表中开始进行爬取,因此第一个被获取到的页面的URL将是该列表之一。后续的URL将会从获取到的数据中提取。

·custom_settings。该设置是一个dict。当启动spider时,该设置将会覆盖项目级的设置。由于设置必须在初始化前被更新,所以该属性必须定义为class属性。

·crawler。该属性在初始化class后,由类方法from_crawler()设置,并且链接了本spider实例对应的Crawler对象。Crawler包含了很多项目中的组件,作为单一的入口点(例如插件、中间件、信号管理器等)。

常用的方法如下:

·start_requests()方法。该方法必须返回一个可迭代对象,该对象包含了spider用于爬取的第一个Request。当spider启动爬取并且未制定URL时,该方法被调用。当指定了URL时,make_requests_from_url将被调用来创建Request对象。该方法仅仅会被Scrapy调用一次,因此可以将其实现为生成器。该方法的默认实现是使用start_urls的url生成Request。如果想要修改最初爬取某个网站的Request对象,可以重写该方法。例如在进行深层次爬取时,在启动阶段需要POST登录某个网站,获取用户权限,代码如下:

  class MySpider(scrapy.Spider):
     name = 'myspider'
     def start_requests(self):
       return [scrapy.FormRequest("http://www.example.com/login",
                                formdata={'user': 'john', 'pass': '
                                secret'},callback=self.login)]
     def login(self, response):
  
       pass

make_requests_from_url(url)方法。该方法接受一个URL并返回用于爬取的Request对象。该方法在初始化request时被start_requests()调用,也用于转化URL为Request。默认未被复写(overridden)的情况下,该方法返回的Request对象中,parse作为回调函数

·parse(response)方法。response参数即用于分析的response。当response没有指定回调函数时,该方法是Scrapy处理下载的response的默认方法。parse负责处理response并返回处理的数据以及跟进的URL,该方法及其他的Request回调函数必须返回一个包含Request、dict或Item的可迭代的对象。

·closed(reason)方法。当spider关闭时,该函数被调用。可以用来在spider关闭时释放占用的资源。

Scrapy除了提供了Spider类作为基类进行拓展,还提供了CrawlSpider、XMLFeedSpider、CSVFeedSpider和SitemapSpider等类来实现不同的爬虫任务。下面主要讲解CrawlSpider和XMLFeedSpider的用法,其他用法类似。

2.CrawlSpider

CrawlSpider类常用于爬取一般网站,其定义了一些规则(rule)来提供跟进链接功能,使用非常方便。除了从Spider继承过来的属性外,还提供了一个新的属性rules,该属性是一个包含一个或多个Rule对象的集合,每个Rule对爬取网站的动作定义了特定的规则。如果多个Rule匹配了相同的链接,则根据它们在rules属性中被定义的顺序,第一个会被使用。CrawlSpider也提供了一个可复写的方法parse_start_url(response),当start_urls的请求返回时,该方法被调用。该方法分析最初的响应,并返回一个Item对象或者一个Request对象或者一个可迭代的包含二者的对象。

Rule类的原型为:

  scrapy.contrib.spiders.Rule(link_extractor,callback=None,cb_kwargs=None,follow=None,
process_links=None, process_request=None)

构造参数说明:

·link_extractor是一个LinkExtractor对象,其定义了如何从爬取到的页面提取链接。

·callback是一个callable或string,该spider中与string同名的函数将会被调用。每次从link_extractor中获取到链接时将会调用该函数。该回调函数接受一个response作为第一个参数,并返回Item或Request对象。当编写爬虫规则时,应避免使用parse作为回调函数。由于CrawlSpider使用parse方法来实现其逻辑,如果覆盖了parse方法,CrawlSpider将会运行失败。

·cb_kwargs包含传递给回调函数的参数的字典。

·follow是一个布尔值,指定了根据该规则从response提取的链接是否需要跟进。如果callback为None,follow默认设置为True,否则默认为False。

·process_links是一个callable或string,该spider中与string同名的函数将会被调用。从link_extractor中获取到链接列表时将会调用该函数,主要用来过滤链接。

·process_request是一个callable或string,该spider中与string同名的函数将会被调用。该规则提取到每个Request时都会调用该函数,该函数必须返回一个Request或者None,用来过滤Request。

下面将CnblogsSpider类进行一下改造,继承CrawlSpider来实现同样的功能。代码如下:

  class CnblogsSpider(CrawlSpider):
     name = 'cnblogs'
     allowed_domains = ["cnblogs.com"]# 允许的域名
     start_urls = [
     "http://www.cnblogs.com/qiyeboy/default.htmlpage=1"
     ]
     rules = (
       Rule(LinkExtractor(allow=("/qiyeboy/default.html\page=\d{1,}",)),
                follow=True,
                callback='parse_item'
  ),
          )
  def parse_item(self,response):
     papers = response.xpath(".// *[@class='day']")
  # 从每篇文章中抽取数据
  for paper in papers:
     url = paper.xpath(".// *[@class='postTitle']/a/@href").extract()[0]
     title = paper.xpath(".// *[@class='postTitle']/a/text()").extract()[0]
     time = paper.xpath(".// *[@class='dayTitle']/a/text()").extract()[0]
     content = paper.xpath(".// *[@class='postTitle']/a/text()").extract()[0]
     item = CnblogspiderItem(url=url,title=title,time=time,content=content)
     request = scrapy.Request(url=url,callback=self.parse_body)
     request.meta['item'] = item
  yield request
  
  def parse_body(self,response):
     item = response.meta['item']
     body = response.xpath(".// *[@class='postBody']")
     item['cimage_urls'] = body.xpath('.// img// @src').extract()# 提取图片链接
  yield item

在以上代码中,重点说一下Rule的定义,将其中的Rule改成下面的情况:

  Rule(LinkExtractor(allow=("/qiyeboy/default.html\page=\d{1,}",)),)

这句话中不带callback,说明只是“跳板”,即只下载网页并根据allow中匹配的链接,继续遍历下一步的页面。在很多情况下,我们并不是只抓取某个页面,而需要“顺藤摸瓜”,从几个种子页面,依次递进,最终定位到我们想要的页面。LinkExtractor对象的构造也很重要,用来产生过滤规则。LinkExtractor常用的参数有:

·allow:提取满足正则表达式的链接。

·deny:排除正则表达式匹配的链接,优先级高于allow。

·allow_domains:允许的域名,可以是str或list。

·deny_domains:排除的域名,可以是str或list。

·restrict_xpaths:提取满足XPath选择条件的链接,可以是str或list。

·restrict_css:提取满足CSS选择条件的链接,可以是str或list。

·tags:提取指定标记下的链接,默认从a和area中提取,可以是str或list。

·attrs:提取满足拥有属性的链接,默认为href,类型为list。

·unique:链接是否去重,类型为Boolean。

·process_value:值处理函数,优先级大于allow。

注意  rules属性中即使只有一个Rule实例,后面也要用逗号“,”分隔。

3.XMLFeedSpider

XMLFeedSpider被设计用于通过迭代各个节点来分析XML源。迭代器可以从Iternodes、XML、HTML中选择,但是XML以及HTML迭代器需要先读取所有DOM再分析,性能较低,一般推荐使用Iternodes。不过使用HTML作为迭代器能有效应对不标准的XML。在XMLFeedSpider中,需要定义下列类属性来设置迭代器以及标记名称:

·iterator用于确定使用哪个迭代器的string,默认值为iternodes。可选项有:

iternodes:一个高性能的基于正则表达式的迭代器

html:基于Selector的迭代器。

xml:基于Selector的迭代器。

·itertag为一个包含开始迭代的节点名的string。

·namespaces称为命名空间,一个由(prefix,url)元组(tuple)所组成的list,其定义了在该文档中会被Spider处理的可用的namespace。prefix和url会被自动调用,由register_namespace()方法生成namespace。示例如下:

  class YourSpider(XMLFeedSpider):
     namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')]
       itertag = 'n:url'

除了提供了以上的属性,XMLFeedSpider还提供了下列可以被重写的方法:

·adapt_response(response)。该方法在Spider分析Response前被调用,可以在Response被分析之前使用该函数来修改Response内容。该方法接受一个Response并返回一个Response,返回的Response可以是修改过的,也可以是原来的。

·parse_node(response,selector)。当节点符合提供的itertag时,该方法被调用。接收到的Response以及相应的Selector作为参数传递给该方法,需要返回一个Item对象或者Request对象或者一个包含二者的可迭代对象。

·process_results(response,results)。当Spider返回Item或Request时,该方法被调用。该方法的目的是在结果返回给框架核心之前做最后的处理例如修改Item的内容。一个结果列表Results及对应的Response作为参数传递给该方法,其结果必须返回一个结果列表,可以包含Item或者Request对象。

现在很多博客都有RSS订阅功能,输出的是XML格式。下面使用XMLFeedSpider来解析订阅内容,以http://feed.cnblogs.com/blog/u/269038/rss 为例,如图13-1所示。

图13-1 RSS订阅

代码如下:

  class XMLSpider(XMLFeedSpider):
  
     name = 'xmlspider'
     allowed_domains = ['cnblogs.com']
     start_urls = ['http://feed.cnblogs.com/blog/u/269038/rss']
     iterator = 'html'  # This is actually unnecessary, since it's the default value
     itertag = 'entry'
  
     def adapt_response(self,response):
  return response
  
  def parse_node(self, response, node):
  print node.xpath('id/text()').extract()[0]
  print node.xpath('title/text()').extract()[0]
  print node.xpath('summary/text()').extract()[0]

解析效果如图13-2所示。

图13-2 RSS订阅解析

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

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

发布评论

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