在之前的文章中我们介绍了 scrapy 框架并给予 scrapy 框架写了一个爬虫来爬取《糗事百科》的糗事, 本章我们继续说一下 scrapy 框架并对之前的糗百爬虫做一下优化和丰富.
在上一篇文章中, 我们在项目中创建了一个 qiushiSpider.py 的文件, 代码如下:
- import scrapy
- from ..items import QiushiItem
- class QiushiSpider(scrapy.Spider):
- # 爬虫名
- name = "qiubai"
- # 允许爬虫作用的范围, 不能越界
- allowd_domains = ["https://www.qiushibaike.com/"]
- # 爬虫起始 url
- start_urls = ["https://www.qiushibaike.com/text/page/1/"]
- # 我们无需再像之前利用 urllib 库那样去请求地址返回数据, 在 scrapy 框架中直接利用下面的 parse 方法进行数据处理即可.
- def parse(self, response):
- # 通过 scrayy 自带的 xpath 匹配想要的信息
- qiushi_list = response.xpath('//div[contains(@id,"qiushi_tag")]')
- for site in qiushi_list:
- # 实例化从 items.py 导入的 QiushiItem 类
- item = QiushiItem()
- # 根据查询发现匿名用户和非匿名用户的标签不一样
- try:
- # 非匿名用户
- username = site.xpath('./div/a/img/@alt')[0].extract() # 作者
- imgUrl = site.xpath('./div/a/img/@src')[0].extract() # 头像
- except Exception:
- # 匿名用户
- username = site.xpath('./div/span/img/@alt')[0].extract() # 作者
- imgUrl = site.xpath('./div/span/img/@src')[0].extract() # 头像
- content = site.xpath('.//div[@class="content"]/span[1]/text()').extract()
- item['username'] = username
- item['imgUrl'] = "https:" + imgUrl
- item['content'] = content
- yield item
在上面的代码中我们定义了一个 QiubaiSpider 的类, 并且集成了 scrapy.Spider, 我们就对这个类做一下详细分析.
Spider 类定义了如何爬取某个 (或某些) 网站. 包括了爬取的动作 (例如: 是否跟进链接) 以及如何从网页的内容中提取结构化数据 (爬取 item). 换句话说, Spider 就是您定义爬取的动作及分析某个网页(或者是有些网页) 的地方.
class scrapy.Spider 是最基本的类, 所有编写的爬虫必须继承这个类.
主要用到的函数及调用顺序为:
__init__() 初始化爬虫名字和 start_urls 列表
start_requests() 调用 make_requests_from url() 生成 Requests 对象交给 Scrapy 下载并返回 response
parse() 解析 response, 并返回 Item 或 Requests(需指定回调函数).Item 传给 Item pipline 持久化 , 而 Requests 交由 Scrapy 下载, 并由指定的回调函数处理(默认 parse() ), 一直进行循环, 直到处理完所有的数据为止.
- # 所有爬虫的基类, 用户定义的爬虫必须从这个类继承
- class Spider(object_ref):
- #定义 spider 名字的字符串(string).spider 的名字定义了 Scrapy 如何定位(并初始化)spider, 所以其必须是唯一的.
- #name 是 spider 最重要的属性, 而且是必须的.
- #一般做法是以该网站 (domain)(加或不加 后缀 ) 来命名 spider. 例如, 如果 spider 爬取 mywebsite.com , 该 spider 通常会被命名为 mywebsite
- name = None
- #初始化, 提取爬虫名字, start_ruls
- def __init__(self, name=None, **kwargs):
- if name is not None:
- self.name = name
- # 如果爬虫没有名字, 中断后续操作则报错
- elif not getattr(self, 'name', None):
- raise ValueError("%s must have a name" % type(self).__name__)
- # python 对象或类型通过内置成员__dict__来存储成员信息
- self.__dict__.update(kwargs)
- #URL 列表. 当没有指定的 URL 时, spider 将从该列表中开始进行爬取. 因此, 第一个被获取到的页面的 URL 将是该列表之一. 后续的 URL 将会从获取到的数据中提取.
- if not hasattr(self, 'start_urls'):
- self.start_urls = []
- # 打印 Scrapy 执行后的 log 信息
- def log(self, message, level=log.DEBUG, **kw):
- log.msg(message, spider=self, level=level, **kw)
- # 判断对象 object 的属性是否存在, 不存在做断言处理
- def set_crawler(self, crawler):
- assert not hasattr(self, '_crawler'), "Spider already bounded to %s" % crawler
- self._crawler = crawler
- @property
- def crawler(self):
- assert hasattr(self, '_crawler'), "Spider not bounded to any crawler"
- return self._crawler
- @property
- def settings(self):
- return self.crawler.settings
- #该方法将读取 start_urls 内的地址, 并为每一个地址生成一个 Request 对象, 交给 Scrapy 下载并返回 Response
- #该方法仅调用一次
- def start_requests(self):
- for url in self.start_urls:
- yield self.make_requests_from_url(url)
- #start_requests()中调用, 实际生成 Request 的函数.
- #Request 对象默认的回调函数为 parse(), 提交的方式为 get
- def make_requests_from_url(self, url):
- return Request(url, dont_filter=True)
- #默认的 Request 对象回调函数, 处理返回的 response.
- #生成 Item 或者 Request 对象. 用户必须实现这个类
- def parse(self, response):
- raise NotImplementedError
- @classmethod
- def handles_request(cls, request):
- return url_is_from_spider(request.url, cls)
- def __str__(self):
- return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))
- __repr__ = __str__
主要属性和方法
name
定义 spider 名字的字符串.
例如, 如果 spider 爬取 mywebsite.com , 该 spider 通常会被命名为 mywebsite
allowed_domains
包含了 spider 允许爬取的域名 (domain) 的列表, 可选.
start_urls
初始 URL 元祖 / 列表. 当没有制定特定的 URL 时, spider 将从该列表中开始进行爬取.
start_requests(self)
该方法必须返回一个可迭代对象 (iterable). 该对象包含了 spider 用于爬取(默认实现是使用 start_urls 的 url) 的第一个 Request.
当 spider 启动爬取并且未指定 start_urls 时, 该方法被调用.
parse(self, response)
当请求 url 返回网页没有指定回调函数时, 默认的 Request 对象回调函数. 用来处理网页返回的 response, 以及生成 Item 或者 Request 对象.
log(self, message[, level, component])
使用 scrapy.log.msg() 方法记录(log)message.
接下来我们再将之前的的糗百爬虫进行一下丰富. 之前的爬虫, 我们只是爬取了第一页内糗事, 即 start_urls = ["https://www.qiushibaike.com/text/page/1/"] , 其中最末尾的 1 即代表了当前页码, 所以当该页数据爬取完毕后让其自动 +1 即为下一页内容, 然后再接着爬取我们想要的数据.
- import scrapy
- from ..items import QiushiItem
- import re
- class QiushiSpider(scrapy.Spider):
- # 爬虫名
- name = "qiubai"
- # 允许爬虫作用的范围, 不能越界
- allowd_domains = ["https://www.qiushibaike.com/"]
- # 爬虫起始 url
- start_urls = ["https://www.qiushibaike.com/text/page/1/"]
- # 我们无需再像之前利用 urllib 库那样去请求地址返回数据, 在 scrapy 框架中直接利用下面的 parse 方法进行数据处理即可.
- def parse(self, response):
- # 通过 scrayy 自带的 xpath 匹配想要的信息
- qiushi_list = response.xpath('//div[contains(@id,"qiushi_tag")]')
- for site in qiushi_list:
- # 实例化从 items.py 导入的 QiushiItem 类
- item = QiushiItem()
- # 根据查询发现匿名用户和非匿名用户的标签不一样
- try:
- # 非匿名用户
- username = site.xpath('./div/a/img/@alt')[0].extract() # 作者
- imgUrl = site.xpath('./div/a/img/@src')[0].extract() # 头像
- except Exception:
- # 匿名用户
- username = site.xpath('./div/span/img/@alt')[0].extract() # 作者
- imgUrl = site.xpath('./div/span/img/@src')[0].extract() # 头像
- content = site.xpath('.//div[@class="content"]/span[1]/text()').extract()
- item['username'] = username
- item['imgUrl'] = "https:" + imgUrl
- item['content'] = content
- # 通过 re 模块匹配到请求的 url 里的数字
- currentPage = re.search('(\d+)', response.url).group(1)
- page = int(currentPage) + 1
- url = re.sub('\d+', str(page), response.url)
- # 将获取的数据交给 pipeline
- yield item
- # 发送新的 url 请求加入待爬队列, 并调用回调函数 self.parse
- yield scrapy.Request(url, callback=self.parse)
在上面的代码中, 我们在将获取到的 item 数据交给管道文件 pipeline 之后, 获取到此时的请求 url 链接, 根据 re 模块获取到链接里的数字, 即页码数, 然后将页码数 +1, 再次发送新的 url 链接请求即可获取其余页码的数据了.
来源: https://www.cnblogs.com/weijiutao/p/10900634.html