13.1 再看 Spider
上一章讲解了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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论