写博客只是为了分享一些我踩过的坑,有些坑真的很让人奔溃,所以写
上一篇我们讲了用scrapy的原生管道下载图片,这篇来讲讲用自定义的管道来下载图片(当然也是在继承了原生管道后对方法的重定义)
什么是scrapy的管道(pipelines):
讲了这么多我还没有具体讲一下什么是scrapy里面的管道,按这里我就具体讲一下什么是pipelines,pipelines有什么作用
在一个工程里面,在pipelines.py文件中定义我们的管道,其实一个管道实际上就是一个类,而这个类定义了一些方法(属性),用来处理我们传进类(管道)中的数据,在处理完以后,再返回被处理以后的数据。那么,多个管道合用,当然就是讲一个数据先后传进多个管道中处理,最后输出数据了。
下面我们实现的这个工程就实现了多个管道分别处理一个item
实战项目:
用scrapy爬取豆瓣top250电影的影名,导演等信息还有海报(海报是一个图片,需要下载)
*很明显,这次的重点是图片和文件的内容的双重爬取,也就是我们要处理文本和图片,就需要用到我们的两个管道,图片处理管道MypipeimageslinePipeline和文本处理管道MyitemPipeline*
这篇要结合前面的两篇来讲:
爬取豆瓣文本的scrapy工程
用原生scrapy管道爬取图片
首先我们用spider爬取出我们要的文本和图片的链接,这个我贴上我的代码,不过你最好自己去独立实现,这个不涉及很多,主要只是xpath和正则表达式的应用,具体的代码解释看第一篇文章,我会在这篇里面新增的代码进行注释
# 大部分的代码解释在*爬取豆瓣文本的scrapy工程*,这里进行的是对新增代码的注释
import scrapy
from mypipeline.items import MypipelineItem
from scrapy.selector import Selector
class MypipespiderSpider(scrapy.Spider):
name = 'mypipespider'
allowed_domains = ['douban.com']
start_urls = [
'https://movie.douban.com/top250?start=0&filter='
]
def parse(self, response):
lis = response.xpath('.//div[@class="article"]/ol[@class="grid_view"]/li')
for li in lis:
item = MypipelineItem()
title = li.xpath('.//div[@class="hd"]/a/span/text()').extract_first()
director = li.xpath('.//div[@class="bd"]/p[1]/text()').re('([\u4e00-\u9fa5]?·?[\u4e00-\u9fa5]+?)\s')[0]
time_list = li.xpath('.//div[@class="bd"]/p[1]/text()').re('\d+?')
time = ''.join(time_list)
rate = li.xpath('.//span[@class="rating_num"]/text()').extract_first()
quote = li.xpath('.//p[@class="quote"]/span/text()').extract_first()
detail_link = li.xpath('.//div[@class="hd"]/a/@href').extract_first()
picture_link = li.xpath('.//div[@class="pic"]/a/img/@src').extract()
# 爬取图片的链接,注意:用数组的形式储存链接
if quote:
item['title'] = title
item['director'] = director
item['time'] = time
item['rate'] = rate
item['quote'] = quote
item['my_images_urls'] = picture_link
# 将链接储存到item里面
else:
item['title'] = title
item['director'] = director
item['time'] = time
item['rate'] = rate
item['my_images_urls'] = picture_link
request = scrapy.Request(url=detail_link, meta={"key": item}, callback=self.parse_detail)
yield request
next_page = response.xpath('//span[@class="next"]/a/@href').extract_first()
if next_page is not None:
url = response.urljoin(next_page)
yield scrapy.Request(url, callback=self.parse)
def parse_detail(self, response):
item = response.meta['key']
sele = Selector(response)
short = sele.xpath('.//div[@id="link-report"]/span[1]/text()').extract_first()
item['short'] = short
yield item
用原生scrapy爬取图片这篇讲过当图片链接存在特定的键后,启动爬虫会自动调用scrapy的调度器和下载器对图片链接指向的图片进行下载并存在相应的文件夹
接下来来讲一下pipelines.py文件中的管道(类)
要想定义自己的imagespipeline,那首先要继承原生的imagespipeline
from scrapy.pipelines.images import ImagesPipeline
class MypipeimageslinePipeline(ImagesPipeline):
#继承类
def get_media_requests(self, item, info):
for images_url in item['my_images_urls']:
yield scrapy.Request(images_url)
# 返回图片链接所指向的response
def item_completed(self, results, item, info):
images_path = [x['path'] for ok,x in results if ok]
# results是之前get_media_requests返回的,具体看下面
if not images_path:
raise DropItem("item contains no images")
item['image_path'] = images_path
# 将图片的链接存储在item里面
return item
# return item 必不可少,用来返回item以便其它管道调用item进行处理
上面的results是由get_media_requests方法返回的一个两个元素的元组列表,每个元组里面包含了一个布尔值和一个字典。实例如下:
[(True,
{'checksum':'图片的MD5hash',
'url':'图片的链接',
'path':'图片的储存路径',}
),
...
(False,
Failure())]
上面图片下载成功就是True,失败就是False。
images_path = [x['path'] for ok,x in results if ok]
# 这一句就是用来提取图片的存储路径的
以上我们自定义了imagepipeline,这个管道用来处理图片,接下来我们定义文本处理的管道
class MyitemPipeline(object):
def __init__(self):
self.file = open('movie_and_details.csv','wb')
# 将文本存储到文件中
def process_item(self, item, spider):
self.file.write(bytes(str(item),encoding='utf-8'))
# 这里的bytes是python3可以用的,如果是python2.7等出现问题你可以结合json模块进行数据的序列化,然后存储就可以了。
return item
上面我们定义了两个管道,接下来我们就考虑如何调用这两个管道了。
在settings.py里面我们要更改一些设置。除了上篇讲到的设置外还有
ITEM_PIPELINES = {
'mypipeline.pipelines.MypipeimageslinePipeline':1,
'mypipeline.pipelines.MyitemPipeline': 300
}
数字越小,管道的优先级越高,优先调用。数字控制在0~1000.
其余的设置请参考上一篇文章。
至于items.py文件就是定义item
import scrapy
# 直接导入*from scrapy import Field*会更方便哦
class MypipelineItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
quote = scrapy.Field()
director = scrapy.Field()
time = scrapy.Field()
rate = scrapy.Field()
title = scrapy.Field()
short = scrapy.Field()
my_images_urls = scrapy.Field()
my_images = scrapy.Field()
image_path = scrapy.Field()
那么,这就是,整个文章的内容。有什么不懂的,欢迎询问,还有,开源项目,欢迎留言指导。