Scrapy管道

管道简介

作用是处理抓取的数据,包括

  • 清洗数据
  • 检查抓取的数据是否有效
  • 去重
  • 保存数据
    一个项目包含多条管道,爬虫收集到的Item会根据指定顺序传递给管道进行处理。
    官方的项目管道的典型用途有
清理HTML数据
验证抓取的数据(检查项目是否包含某些字段)
检查重复项(并删除它们)
将爬取的项目存储在数据库中

编写自定义管道

每个item pipeline组件都是一个python类,必须实现以下方法:
process_item(self, item, spider)¶
对每个项管道组件调用此方法。
item 是一个 item object 见 支持所有项目类型 .
process_item() 必须:返回 item object 返回A Deferred 或提高 DropItem 例外。
丢弃的项目不再由其他管道组件处理。
参数
item (item object) -- 刮掉的东西
spider (Spider object) -- 爬取项目的蜘蛛
此外,它们还可以实现以下方法:
open_spider(self, spider)¶
当spider打开时调用此方法。
参数
spider (Spider object) -- 打开的蜘蛛
close_spider(self, spider)¶
当spider关闭时调用此方法。
参数
spider (Spider object) -- 关闭的蜘蛛
from_crawler(cls, crawler)¶
如果存在,则调用此ClassMethod从 Crawler . 它必须返回管道的新实例。爬虫对象提供对所有零碎核心组件(如设置和信号)的访问;它是管道访问它们并将其功能连接到零碎的一种方式。
参数
crawler (Crawler object) -- 使用此管道的爬虫程序

项目实例

需要知晓的知识点,dump和dumps是实现json编码功能,dump把dict编码成json字符串并保存在文件中,dumps把dict编码成json字符串;JSON格式的键一定要用双引号扩起;而且在保存中文字典时,记得加ensure_ascii=false,因为JSON在处理中文时,默认使用的是ASCII编码。
load和loads用于JSON的解码,区别是load是需要从文件中解码,而loads加载字符串进行解码。

编写items.py
import scrapy
class JobboleArticleItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 文章标题
    title = scrapy.Field()
    # 内容摘要
    summary = scrapy.Field()
    # 发表日期
    publish_date = scrapy.Field()
    # 标签
    tag = scrapy.Field()

编写pipelines.py

import json


class JobboleArticlePipeline(object):
    # 当启动爬虫时,打开items.json文件,准备写入数据
    def open_spider(self, spider):
        self.file = open('items.json','w')

    
    # 当爬虫执行结束时,关闭打开的文件
    def close_spider(self, spider):
        self.file.close()
    # 将抓取到的数据做json序列化存储
    def process_item(self, item, spider):
        line = json.dumps(dict(item), ensure_ascii=False) + "\n" # json的每条数据加上换行符
        self.file.write(line)
        return item #这返回的数据到哪去了呢

编写完管道之后记得在settings.py中开启管道;只需要将管道的路径加到settings.py中

ITEM_PIPELINES = {
   'jobbole_article.pipelines.JobboleArticlePipeline': 300,
}

最后编写爬虫文件

# -*- coding: utf-8 -*-
import scrapy
from jobbole_article.items import JobboleArticleItem


class ArticleSpider(scrapy.Spider):
    name = 'article'
    allowed_domains = ['jobbole.com']
    start_urls = ['http://blog.jobbole.com/all-posts/']

    def parse(self, response):
        all_post = response.css(".post")
        for post in all_post:
            item = JobboleArticleItem()
            item['title'] = post.css('.archive-title::text').extract_first() # 这里返回的数据其实都是字符串格式
            item['summary'] = post.css('.excerpt p::text').extract_first()
            # 根据正则表达式提取发表日期
            item['publish_date'] = post.css('.post-meta p::text').re_first(r'\d{4}/\d{2}/\d{2}')
            # Tag 标签可能有多个,因此不需要获取第一个值,保存列表即可
            item['tag'] = post.xpath(".//a[2]/text()").extract()
            yield item

        # 检查是否有下一页url,如果有下一页则调用parse进行处理
        next_page = response.css('.next::attr(href)').extract_first()
        if next_page:
            yield scrapy.Request(next_page,callback=self.parse)

使用命令运行爬虫,在该项目文件夹下即可

scrapy crawl article

下载文件和图片

通常使用FilePipeline和ImagesPipeline,分别用于下载文件和图片。
这两种管道都包含以下特性

  • 避免重复下载最近下载过的数据
  • 指定存储的位置,可使用本地文件系统或者云端存储
    同时,ImagePipeline还有些额外的的特性
  • 将下载的图片转换成通用的JPG格式和RGB模式
  • 为下载的图片生成缩率图
  • 检查图片的宽/高,确保能够满足最新要求。
    管道会为计划中下载的文件URL保存在一个内部队列,与保存同样文件的Response相关联,从而避免重复下载几个Item共用的图片。

编写item

import scrapy


class DownloadfileItem(scrapy.Item):
    # define the fields for your item here like:
    # 文件名
    file_name = scrapy.Field()
    # 发布时间
    release_date = scrapy.Field()
    # 文件url
    file_urls = scrapy.Field()
    # 文件结果信息
    files = scrapy.Field()

编写settings.py

DownloadfilePipeline其实还是我们编写的类;但他继承了FilesPipeline类

ITEM_PIPELINES = {
   # 'scrapy.pipelines.files.FilesPipeline': 1
   'downloadfile.pipelines.DownloadfilePipeline': 300,
}
# 保存文件设置
FILES_STORE = 'D:\\Scrapy\\downloadfiles'
FILES_URLS_FIELD = 'file_urls'
FILES_RESULT_FIELD = 'files'

编写爬虫脚本文件

import scrapy
from downloadfile.items import DownloadfileItem


class GetfileSpider(scrapy.Spider):
    name = 'getfile'
    allowed_domains = ['szhrss.gov.cn']
    start_urls = ['http://hrss.sz.gov.cn/wsbs/xzzx/rcyj/']

    def parse(self, response):
        files_list = response.css('.conRight_text_ul1 li')
        for file in files_list:
            item = DownloadfileItem()
            item['file_name'] = file.css('a::text').extract_first()
            item['release_date'] = file.css('span::text').extract_first()
            # 由于获取到的url类似"./201501/P020170328745500534334.doc"
            # 所以需要手动调整为完成的url格式
            url = file.css('a::attr(href)').extract_first()
            # file_urls必须是list形式
            item['file_urls'] = [response.url + url[1:]]
            yield item

获取的文件名是根据URL自动生成的SHA1哈希值
这时候我们为了知晓文件名,就需要修改pipelines.py文件,添加自定义管道

from scrapy.pipelines.files import FilesPipeline
from scrapy import Request

class DownloadfilePipeline(FilesPipeline): # 继承FilesPipeline
    # 修改file_path方法,使用提取的文件名保存文件
    def file_path(self, request, response=None, info=None):
        # 获取到Request中的item
        item = request.meta['item']
        # 文件URL路径的最后部分是文件格式
        file_type = request.url.split('.')[-1]
        # 修改使用item中保存的文件名作为下载文件的文件名,文件格式使用提取到的格式
        file_name = u'full/{0}.{1}'.format(item['file_name'],file_type)
        return file_name

    def get_media_requests(self, item, info):
        for file_url in item['file_urls']: # item中的字段url是字典的值file_urls
            # 为request带上meta参数,把item传递过去
            yield Request(file_url,meta={'item':item})

图片管道

编写itmes.py


import scrapy


class DownloadimageItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 小说名称
    title = scrapy.Field()
    # 小说作者
    author = scrapy.Field()
    # 小说类型
    type = scrapy.Field()
    # 图片URL
    image_urls = scrapy.Field()
    # 图片结果信息
    images = scrapy.Field()

编写settings.py

ITEM_PIPELINES = {
    'downloadimage.pipelines.DownloadimagePipeline': 300,
    'scrapy.pipelines.images.ImagesPipeline':1 # 这里添加ImagesPipeline
}
IMAGES_STORE = 'D:\\03'
IMAGES_URLS_FIELD = 'image_urls' # 图片URL对应的Item字段
IMAGES_RESULT_FIELD = 'images' # 图片结果信息对应的Item字段
IMAGES_THUMBS = {
    'small' : (80,80),
    'big' : (300,300)
    }

编写自定义管道pipelines.py

# 并没有实现按下载图片的名字为原本图片名,或许原本图片名就是这个
import json
from scrapy import Request
from scrapy.pipelines.images import ImagesPipeline
class DownloadimagePipeline(ImagesPipeline):
    # 将小说信息保存为json文件
    def open_spider(self,spider): # 这样还就把他保存为jso格式了,只要在pipelines.py中重写下函数
        self.file = open('qidian2021.json','w')

    def close_spider(self,spider):
        self.file.close()
    def file_path(self, request, response=None, info=None):
        # 获取到Request中的item
        item = request.meta['item']
        # 文件URL路径的最后部分是文件格式
        file_type = request.url.split('.')[-1]
        # 修改使用item中保存的文件名作为下载文件的文件名,文件格式使用提取到的格式
        file_name = u'full/{0}.{1}'.format(item['title'],file_type)
        return file_name

    def process_item(self, item, spider): # 这个函数才是真正写入json格式的函数,上面那个open_spider和close_spider都是定义
        # 写入文件
        line = json.dumps(dict(item), ensure_ascii=False) + "\n"
        self.file.write(line)
        return item
    # 这个应该没用,是从上面那个pipelines.py中copy过来的
    def get_media_requests(self, item, info):
        for file_url in item['file_urls']: # item中的字段url是字典的值file_urls
            # 为request带上meta参数,把item传递过去
            yield Request(file_url,meta={'item':item})

编写爬虫脚本

import scrapy
from downloadimage.items import DownloadimageItem


class GetimageSpider(scrapy.Spider):
    name = 'getimage'
    allowed_domains = ['qidian.com']
    start_urls = ['https://www.qidian.com/finish']

    def parse(self, response):
        for novel in response.css(".all-img-list > li"):
            item = DownloadimageItem()
            item['title'] = novel.xpath('.//h4/a/text()').extract_first()
            item['author'] = novel.css('.name::text').extract_first()
            item['type'] = novel.css('em + a::text').extract_first() # em就是分割线|
            item['image_urls'] = ['https:' + novel.xpath('.//img/@src').extract_first()]
            yield item

运行该爬虫之后,会在settings.py设置的路径中生成两个文件夹:full和thumbs.thumbs文件夹中有big及small文件夹

posted @ 2021-12-13 15:49  索匣  阅读(322)  评论(0编辑  收藏  举报