1 绪论

在Scrapy模块中有4个现成的spider类,分别是:

  1. Spider
  2. CrawlSpider
  3. XMLFeedSpider
  4. CSVFeedSpider

Spider是最简单的爬虫也是最基础的爬虫类,其他所有的爬虫类包括自定义的爬虫类必须继承它。这一节主要讲Scrapy写爬虫最核心的内容,并从CrawlSpider类展开并开始学习如何构建最简单的爬虫程序。

在我写的第一篇文章中,我说一个爬虫无非是做下面几样事情:

  1. 请求(requests)目标站点的网页(文本);
  2. 利用正则表达式、Beautiful SoupLXML、CSS提取数据
  3. 制定爬取规则(如「下一页」等)、爬取方法(非同步爬取、设置代理等);
  4. 存储数据。

至于如何存储数据我们可以暂时不用关心,因为在Scrapy的命令行中可以使用参数直接将数据存储到文件,可以参见[Command line tool] - 命令行工具。

从我以往的经验中总结,用Scrapy写爬虫再只要理清下面两点就能写成一个爬虫:

  1. 知道要爬哪些页面,爬完这个页面我还要再爬哪些页面,入口在哪;
  2. 如何从这些页面中提取数据

第2点我们在上3次的文章做了细致的介绍了,这里就不多说,在你学习完本节内容后再多多学习提取数据的方法即可:

[Easy XPath] - 开始使用XPath

[RegEx] - 正则表达式

[Selectors in Scrapy] - 数据匹配的方法

所以,本节将带你从实例中理清写爬虫的思路及方法。

2 需求分析

假设现在你对如何用Scrapy写爬虫暂时还不清楚,手头上只有接到的任务,即你要爬取哪个网站的哪些数据。所以第一步要做的就是需求分析,理清爬取顺序,不要管其他。

任务:

爬取 ->网站 热门标签下的所有quotes及其作者

来看下这个页面:

scrapy view http://quotes.toscrape.com/

如图右侧红色方框即为热门标签,我们随机点一个标签进去看看:

http://quotes.toscrape.com/tag/love/

我们定义上图为标签主页,上图中红色方框表示我们需要提取的数据项。在数据的爬取中我们要最大限度地保证数据的完整性,换句话说:获取网站上存在的所有目标数据。除了标签主页的数据,那剩余的数据在哪?入口在哪里?

往下我们发现Next按钮,同时打开调试器看看它的地址:

http://quotes.toscrape.com/tag/love/page/2/

点击第二页,再找不到Next按钮了,也就是love标签下的数据只存在两页面上:

http://quotes.toscrape.com/tag/love/
http://quotes.toscrape.com/tag/love/page/2/

总结两步:

① 从主页开始获取所有右侧热门标签对应的地址:

http://quotes.toscrape.com/tag/love/
http://quotes.toscrape.com/tag/inspirational/
……

② 访问每一个标签主页,在每个标签主页中点击「Next」获取剩余数据,直到找不到Next按钮

最后补充一点:

love标签主页上的数据,分析上来讲就是第一页,那是不是说它等价于:

http://quotes.toscrape.com/tag/love/page/1/

我们发现这个链接在标签主页上有:

因此每个标签下的数据都可以按照如下顺序获取:

http://quotes.toscrape.com/tag/love/page/1/
http://quotes.toscrape.com/tag/love/page/2/
……

这里就涉及到第一重点

下面两个网站指向的是同一页面:

http://quotes.toscrape.com/tag/love/
http://quotes.toscrape.com/tag/love/page/1/

如果我们按照上面的page页数来爬数据,那我们的数据就是重复,这个问题我们需要在下面进一步解决。

3 CrawlSpider类用法详解

先一通气将完它特有的属性和方法,然后再从仅完成上面任务给出爬虫代码、为CrawlSpider类中每个参数用法写例子

① parse_start_url(response)

用于处理start_urls的response,它的用处是:如果需要模拟登录等操作可以重写该方法。

Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)

Rule用于:

  1. 提取指定格式的链接(link_extractor);
  2. 过滤提取的链接(process_links);
  3. 对指定页面指定相应的处理方法(process_request);
  4. 指定页面的处理方法(callback);
  5. 为不同的提取链接的方法指定跟进的规则(follow);
  6. 回调函数传参(cb_kwargs)。

避免使用 parse 作为回调函数(callback)

在PyCharm下按如下目录创建文件:

env:虚拟环境
simple:爬虫文件夹
Quotes_CrawlSpider.py:爬虫
run.py:用于启动爬虫,方便调试

run.py代码如下:

from scrapy import cmdline
cmdline.execute("scrapy runspider Quotes_CrawlSpider.py -o quotes.json".split())

解释:

相当与从命令行启动爬虫文件, -o quotes.json 将爬虫yield出来的item存到json文件。

3.1 完成上面爬虫任务所需的爬虫代码

Quotes_CrawlSpider.py代码如下:

# -*- coding: utf-8 -*-
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class MySpider(CrawlSpider):
name = toscrape.com
allowed_domains = [toscrape.com]
start_urls = [http://quotes.toscrape.com/]

rules = (
Rule(LinkExtractor(allow=(/tag/w+/$,)),
follow=True, # 如果有指定回调函数,默认不跟进
callback=parse_item,
process_links=process_links,),

Rule(LinkExtractor(allow=(/tag/w+/page/d+/,), deny=(/tag/w+/page/1/,)),
callback=parse_item,
follow=True,),
)

@staticmethod
def process_links(links): # 对提取到的链接进行处理
for link in links:
link.url = link.url + page/1/
yield link

@staticmethod
def parse_item(response): # 解析网页数据并返回数据字典
quote_block = response.css(div.quote)
for quote in quote_block:
text = quote.css(span.text::text).extract_first()
author = quote.xpath(span/small/text()).extract_first()
item = dict(text=text, author=author)
yield item

流程图:

代码详解:

提取规则1:(① - ④)

Rule(LinkExtractor(allow=(/tag/w+/$,)), # 从主页提取标签主页的地址,利用正则表达式
follow=True, # request标签主页得到内容,继续在该内容上上应用规则提取链接
callback=parse_item, # request标签主页得到内容,对该内容应用parse_item函数提取数据
process_links=process_links,), # 用process_links方法对提取到的链接做处理,将标签主页变成/page/1/形式

提取规则2:(⑤、⑥)

Rule(LinkExtractor(allow=(/tag/w+/page/d+/,), deny=(/tag/w+/page/1/,)),
# 提取链接格式满足「/tag/英文字母/page/」数字/形式的,并拒绝第一页
callback=parse_item, # 指定parse_item作为页面的处理方法
follow=True,), # 需要在得到的页面继续搜索满足规则的链接

parse_start_url(response)

from scrapy.spiders import CrawlSpider

class QuotesSpider(CrawlSpider):
name = "quotes"
custom_settings = {
LOG_LEVEL: INFO,
}
start_urls = [http://quotes.toscrape.com/tag/love/]

def parse_start_url(self, response):
self.logger.info(parse_start_url %s, response.url)
next_page = response.css(li.next a::attr("href")).extract_first()
if next_page is not None:
yield response.follow(next_page, self.next_parse)

def next_parse(self, response):
self.logger.info(next_pares %s, response.url)

rule的几个参数用法示例:

# -*- coding: utf-8 -*-
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class MySpider(CrawlSpider):
name = toscrape.com
custom_settings = {
LOG_LEVEL: INFO, # 设置日志级别
}
allowed_domains = [toscrape.com]
start_urls = [http://quotes.toscrape.com/]
rules = (
Rule(LinkExtractor(allow=(/tag/w+/$,)),
follow=False, # 为了测试几个参数的用法简单设定
callback=parse_item,
cb_kwargs={tag: love}, # 以key名tag作为变数名传给回调函数parse_item
process_links=process_links, # 对提取的链接做处理
process_request=process_req # 对每个请求做处理),
)

@staticmethod
def process_links(links):
for link in links:
link.url = link.url + page/1/
yield link

def process_req(self, req):
if love in req.url: # 我们测试当链接中包含love是转给parse_love处理response
return req.replace(callback=self.parse_love)
elif humor in req.url:
return req # 如果链接中包含humor则正常用回调函数parse_item处理response

def parse_love(self, response):
self.logger.info(parse_love %s % response.url)

def parse_item(self, response, tag):
self.logger.info(parse_item %s % response.url)
self.logger.info(not %s % tag)

运行结果:

运行流程:

推荐阅读:

相关文章