本小节中我们将详细介绍 Scrapy 中的 Pipeline 及其多种用法和使用场景。Pipeline 是 Scrapy 框架的一个重要模块,从前面的 Scrapy 架构图中我们可以看到它位于架构图的最左边,用于连续处理从网页中抓取到的每条记录,就像一个流水线工厂加工食品那样,完成食品最后的封装、保存等操作。此外,我们还会介绍 Scrapy 内置的图片管道,可以自动下载对应地址的图片。最后,我们会基于上述内容完成一个小说网站的爬取案例。
Pipeline 的中文意思是管道,类似于工厂的流水线那样。Scrapy 中的 Pipeline 通常是和 Items 联系在一起的,其实就是对 Items 数据的流水线处理。 一般而言,Pipeline 的典型应用场景如下:
通过前面的 Scrapy 架构图可知,Pipeline 位于 Scrapy 数据处理流程的最后一步,但是它也不是必须,Pipeline 默认处于关闭状态。如果需要的话,我们只需要在 settings.py 中设置 ITEM_PIPELINES 属性值即可。它是一个数组值,我们可以定义多个 Item Pipeline,并且在 ITEM_PIPELINES 中设置相应 Pipeline 的优先级。这样 Scrapy 会依次处理这些 Pipelines,最后达到我们想要的效果。
item 经过 pipelines 处理
注意:上面的 pipeline 顺序和功能都可以任意调整,保证逻辑性即可。比如有一个去重的 pipeline 和保存到数据库的 pipeline,那么去重的 pipeline 一定要在保存数据库之前,这样保存的就是不重复的数据。
编写自己的 Item Pipeline 非常简单,我们只需要编写一个简单的类,实现四个特定名称的方法即可 (部分方法非必须)。我们来简单说明下这三个方法:
完成这样一个 Item Pipeline 后,将该类的路径地址添加到 settings.py 中的 ITEM_PIPELINES 中即可。下图是我们一个简单项目完成的两个 pipelines。
一个简单项目的 pipelines 示例
学习了上面的一些知识,我们来使用一个简单的网站进行实战演练,在该过程中介绍更多的和 Item Pipeline 相关的用法。
假设我们是一名小说爱好者,我想到起点中文网上去找一些好的小说看,我该怎么找呢?起点中文网的月票榜是一个不错的参考方式,如下图所示:
起点中文网月票榜
其实简单看一看就知道月票榜的 url 组成:
目前我们只需要从 01 月份开始到 07 月份的月票榜中,每次都取得第一页的数据,也就是月票榜的前20 名。7 个月份的前 20 名加起来,然后再去重,就得到了曾经的占据月票榜的作品,这中间大概率都是比较好看的书。完成这个简单的需求我们按照如下的步骤进行:
创建初始项目 qidian_yuepiao:
[root@server ~]# pyenv activate scrapy-test(scrapy-test) [root@server ~]# cd scrapy-test(scrapy-test) [root@server scrapy-test]# scrapy startproject qidian_yuepia(scrapy-test) [root@server qidian_yuepiao]# ls __init__.py items.py middlewares.py pipelines.py settings.py spider
接下来我们准备获取小说作品的字段,大概会获取如下几个数据:
根据定义的这些字段,我们可以写出对应的 Items 类,如下:
(scrapy-test) [root@server qidian_yuepiao]# cat items.py # Define here the models for your scraped items## See documentation in:# https://docs.scrapy.org/en/latest/topics/items.htmlimport scrapyclass QidianYuepiaoItem(scrapy.Item): # define the fields for your item here like: name = scrapy.Field() author = scrapy.Field() fiction_type = scrapy.Field() state = scrapy.Field() image_url = scrapy.Field() images = scrapy.Field() brief_introduction = scrapy.Field() book_url = scrapy.Field()代码块12345678910111213141516171819
到了最关键的地方,需要解析网页数据,提取月票榜的作品信息。这个和前面一些,我们只需要完成相应的 xpath 即可。此外,我们会从 01 月份的月票榜开始,每次会新生成一个 url,主要改动的就是月份参数,每次将月份数加一;如果当前月份大于07,则终止。
(scrapy-test) [root@server qidian_yuepiao]# touch spiders/qidian_yuepiao_parse.py代码块1import refrom scrapy import Requestfrom scrapy.spiders import Spiderfrom qidian_yuepiao.items import QidianYuepiaoItemdef num_to_str(num, size=2, padding='0'): """ 0 - > 00 1 -> 01 11 -> 11 :param num: :param size: :param padding: :return: """ str_num = str(num) while len(str_num) < size: str_num = padding + str_num return str_numclass QidianSpider(Spider): name = "qidian_yuepiao_spider" start_urls = [ "https://www.qidian.com/rank/yuepiao?month=01&chn=-1&page=1" ] def parse(self, response): fictions = response.xpath('//div[@id="rank-view-list"]/div/ul/li') for fiction in fictions: name = fiction.xpath('div[@class="book-mid-info"]/h4/a/text()').extract_first() author = fiction.xpath('div[@class="book-mid-info"]/p[@class="author"]/a[1]/text()').extract_first() fiction_type = fiction.xpath('div[@class="book-mid-info"]/p[@class="author"]/a[1]/text()').extract_first() # 注意一定要是列表,不然会报错 image_url = ['http:{}'.format(fiction.xpath('div[@class="book-img-box"]/a/img/@src').extract()[0])] brief_introduction = fiction.xpath('div[@class="book-mid-info"]/p[@class="intro"]/text()').extract_first() state = fiction.xpath('div[@class="book-mid-info"]/p[@class="author"]/a[2]/text()').extract()[0] book_url = fiction.xpath('div[@class="book-mid-info"]/h4/a/@href').extract()[0] item = QidianYuepiaoItem() item['name'] = name item['author'] = author item['fiction_type'] = fiction_type item['brief_introduction'] = brief_introduction.strip() item['image_url'] = image_url item['state'] = state item['book_url'] = book_url yield item # 提取月份数,同时也要提取请求的url url = response.url regex = "https://(.*)\?month=(.*?)&(.*)" pattern = re.compile(regex) m = pattern.match(url) if not m: return [] prefix = m.group(1) month = int(m.group(2)) suffix = m.group(3) # 大于7月份则停止,目前是2020年7月20日 if month > 7: return # 一定要将月份转成01, 02, s03这样的形式,否则不能正确请求到数据 next_month = num_to_str(month + 1) next_url = f"https://{prefix}?month={next_month}&{suffix}" yield Request(next_url)
最后到了我们本节课的重点。首先我想要将数据保存成 json 格式,存储到文本文件中,但是在保存之前,需要对作品去重。因为有些作品会连续好几个月出现在月票榜的前20位置上,会有比较多重复。我们通过作品的 url 地址来唯一确定该小说。因此需要定义两个 Item Pipeline:
import jsonfrom itemadapter import ItemAdapterfrom scrapy.exceptions import DropItemclass QidianYuepiaoPipeline: """ 保存不重复的数据到文本中 """ def open_spider(self, spider): self.file = open("yuepiao_top.json", 'w+') def close_spider(self, spider): self.file.close() def process_item(self, item, spider): data = json.dumps(dict(item), ensure_ascii=False) self.file.write(f"{data}
") return itemclass DuplicatePipeline: """ 去除重复的数据,重复数据直接抛出异常,不会进入下一个流水线处理 """ def __init__(self): self.book_url_set = set() def process_item(self, item, spider): if item['book_url'] in self.book_url_set: raise DropItem('duplicate fiction, drop it') self.book_url_set.add(item['book_url']) return item代码块12345678910111213141516171819202122232425262728293031323334
我来简单介绍下上面实现的两个 pipelines 的代码。首先爬虫抓取的 item 结果经过的是 DuplicatePipeline 这个管道 (我们通过管道的优先级控制),我们在 DuplicatePipeline 中定义了一个全局的集合 (set),在 管道的核心方法process_item() 中,我们先判断传过来的 item 中 book_url 的值是否存在,如果存在则判定重复,然后抛出异常,这样下一个管道 (即 QidianYuepiaoPipeline) 就不会去处理;
在经过的第二个管道 (QidianYuepiaoPipeline) 中,我们主要是将不重复 item 保存到本地文件中,因此我们会在 open_spider() 方法中打开文件句柄,在 close_spider() 方法中关闭文件句柄,而在 process_item() 中将 item 数据保存到指定的文件中。
接着就是将这两个 Pipelines 加到 settings.py 中:
ITEM_PIPELINES = { 'qidian_yuepiao.pipelines.DuplicatePipeline': 200, 'qidian_yuepiao.pipelines.QidianYuepiaoPipeline': 300,}代码块1234
最后,我们来介绍一个 Scrapy 内置的图片管道,其实现的 Pipeline 代码位置为:scrapy/pipelines/images.py,对应的还有一个内置的文件管道。我们不需要编写任何代码,只需要在 settings.py 中指定下载的图片字段即可:
# 下载图片存储位置IMAGES_STORE = '/root/scrapy-test/qidian_yuepiao/qidian_yuepiao/images'# 保存下载图片url地址的字段IMAGES_URLS_FIELD = 'image_url'# 图片保存地址字段IMAGES_RESULT_FIELD = 'images'IMAGES_THUMBS = { 'small': (102, 136), 'big': (150, 200)}# ...ITEM_PIPELINES = { 'scrapy.pipelines.images.ImagesPipeline': 1, 'qidian_yuepiao.pipelines.DuplicatePipeline': 200, 'qidian_yuepiao.pipelines.QidianYuepiaoPipeline': 300,}
由于 ImagesPipeline 继承自 FilesPipeline,我们可以从官网的介绍中知道该图片下载功能的执行流程如下:
完成了以上四步之后,我们的这样一个简单需求就算完成了。还等什么,快来运行看看!
这样爬取数据是不是非常有趣?使用了 Scrapy 框架后,我们的爬取流程会变得比较固定化以及流水线化。但我们不仅仅要学会怎么使用 Scrapy 框架,还要能够基于 Scrapy 框架在特定场景下做些改造,这样才能达到完全驾驭 Scrapy 框架的目的。
本小节中,我们介绍了 Scrapy 中 Pipeline 相关的知识并在起点中文网上进行了简单的演示。在我们的爬虫项目中使用了两个自定义管道,分别用于去除重复小说以及将非重复的小说数据保存到本地文件中;另外我们还启用了 Scrapy 内置的图片下载管道,帮助我们自动处理图片 URL 并下载。
留言与评论(共有 0 条评论) “” |