管道简介
作用是处理抓取的数据,包括
- 清洗数据
- 检查抓取的数据是否有效
- 去重
- 保存数据
一个项目包含多条管道,爬虫收集到的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文件夹