百度新闻爬虫项目的SpiderBjhbaiduSpider爬虫仅是爬取了文章作者、发布时间、标题和文章内容。在一些情况下,还需要爬取文章中的图片,把图片下载到本地。
1、 图片管道类
Scrapy专门提供了一个用于下载网络图片的管道类ImagesPipeline,它可以将图片下载到本地,并将图片格式转换为JPG格式,也可以生成缩略图,并能避免重复下载已有的图片。
图像管道类需要Pillow 4.0.0或更高版本,Pillow是Python的图形处理库,图像管道类使用该库将下载的图片生成缩略图和转换为JPEG格式。
ImagesPipeline类继承FilesPipeline类,下表给出了ImagesPipeline类及父类的主要方法:
注释(1)
方法声明:
file_path( self, request, response=None, info=None, *, item=None)
FilesPipeline类提供的方法。该方法返回request请求文件的下载路径。该方法仅被调用一次,使用该方法可以设置文件的下载路径。
重写该方法,可以设置文件的下载路径。若不重写该方法,Scrapy会把文件下载到默认路径。
参数request是请求下载文件的URL。其它参数都是空值。
案例代码:
import os from urllib.parse import urlparse from scrapy.pipelines.images import ImagesPipeline class MyImagesPipeline(ImagesPipeline): def file_path(self, request, response=None, info=None, *, item=None): return 'files/' + os.path.basename(urlparse(request.url).path)
案例代码自定义了MyImagesPipeline管道类,继承于ImagesPipeline类。它重写了file_path()方法,返回request.url的下载路径。
注释(2)
方法声明:
get_media_requests(item, info)
FilesPipeline类提供的方法。该方法返回需要下载文件的request,重写该方法,可以确认哪些文件需要下载,并生成request请求。下载文件的url来自于item数据类。
参数item是Item数据类。若需要下载网页中的文档、图片、音视频等文件。需要在定义Item数据类时,添加file_urls字段,该字段存储了需要下载文件的URL。下载不同格式的文件,存储URL的字段名称也不相同,具体命名如下表所示:
Item数据类案例代码:
import scrapy # 定义结构化数据Article类 # Article类继承scrapy.Item类 class Article(scrapy.Item): # 定义title数据项,存储文章标题 title = scrapy.Field() # 定义author数据项,存储文章作者 author = scrapy.Field() # 定义content数据项,存储文章内容 content = scrapy.Field() # 定义date数据项,存储文章发布时间 date = scrapy.Field() # 定义image_urls数据项,存储图片文件下载路径 image_urls = scrapy.Field() get_media_requests案例代码: from itemadapter import ItemAdapter def get_media_requests(self, item, info): adapter = ItemAdapter(item) # 遍历image_urls,返回reauest对象 for url in adapter[' image_urls ']: yield scrapy.Request(url)
注释(3)
方法声明:
item_completed( results, item, info)
FilesPipeline类提供的方法。当文件下载完成后,该方法被调用。该方法主要用于文件下载后的处理。若需要存储下载文件的路径,可以在Item类添加一个存储下载路径的字段(如:image_paths),在方法内获取下载文件的路径,并存储到该字段内。
参数results是一个二元组,二元组的结构如下:
(success, file_info_or_error)
success是一个布尔值,如果文件下载成功,则为True;若下载失败,则为False。
file_info_or_error是一个字典对象,包含了下面的键值对:
url – 下载文件的URL,这是从get_media_requests()方法返回请求的url。
path – 下载文件的存储路径。
checksum – 下载文件内容的MD5哈希。
status – 下载文件的状态。
参数results样例:
(True,
{'checksum': '2b00042f7481c7b056c4b410d28f33cf',
'path': 'full/0a79c461a4062ac383dc4fade7bc09f1384a3910.jpg',
'url': 'http://www.example.com/files/product1.pdf',
'status': 'downloaded'}
)参数item是传入的Item实例对象,可以将下载文件路径存储到该实例对象内。
2、 使用图片管道类
爬取百家号文章时,同时下载文章内引用的图片。具体开发过程遵循下面的步骤:
(1)在article_item.py添加image_urls、image_paths数据项,代码如下:
import scrapy # 定义结构化数据Article类 # Article类继承scrapy.Item类 class Article(scrapy.Item): # 定义title数据项,存储文章标题 title = scrapy.Field() # 定义author数据项,存储文章作者 author = scrapy.Field() # 定义content数据项,存储文章内容 content = scrapy.Field() # 定义date数据项,存储文章发布时间 date = scrapy.Field() # 定义image_urls数据项,存储图片文件URL路径 image_urls = scrapy.Field() # 定义image_paths数据项,存储图片文件下载路径 image_paths = scrapy.Field()
数据项image_urls存储百家号文章内所有的图片URL,爬虫处理函数提取文章内容所有img标签的src属性值,并赋值给该数据项。数据项image_paths存储已下载图片文件的存储路径(包括图片文件名称)。
(2)在项目包目录下,建立pipelinesbjhimg.py文件,创建BjhImagesPipeline管道类,用于处理下载的图片,代码如下:
# 导入os模块
import os
# 导入scrapy
import scrapy
# 导入适配器
from itemadapter import ItemAdapter
# 导入urlparse模块
from urllib.parse import urlparse
# 导入ImagesPipeline模块
from scrapy.pipelines.images import ImagesPipeline
# 导入Scrapy异常处理模块
from scrapy.exceptions import DropItem
class BjhImagesPipeline(ImagesPipeline):
# 图片文件重命名(包含后缀名),若不重写该函数,图片名默认为图片内容的哈希值
def file_path(self, request, response=None, info=None, *, item=None):
# 图片文件名称取URL中的图片名称
return os.path.basename(urlparse(request.url).path)
# 设置需要下载的图片URL,并返回图片request
def get_media_requests(self, item, info):
# 遍历image_urls数据项,为每个图片URL生成request请求
for image_url in item['image_urls']:
yield scrapy.Request(image_url)
# 存储图片下载路径
def item_completed(self, results, item, info):
# result是一个包含元组的列表
# 元组包含两个值,第一个代表状态True/False,
# 第二个值是一个dict对象,若元素中状态为True则取dict中的path值
image_paths = [x['path'] for ok, x in results if ok]
# 若image_paths为空,说明下载失败
# 抛出DropItem异常
if not image_paths:
raise DropItem("图片文件下载失败")
# 获取Item的适配器
adapter = ItemAdapter(item)
# 存储image_paths
adapter['image_paths'] = image_paths
return item代码使用的DropItem是Scrapy提供的异常类,用于在Pipeline管道类抛出异常,并停止处理Item。
get_media_requests()方法从数据项image_url提取所有的图片URL,并生成Request请求。
file_path()方法返回要存储的图片文件名称,若不重写该方法,图片文件名称默认为图片内容的哈希值。
item_completed()方法处理下载完成的图片,利用列表解析表达式从返回results结构中的字典数据提取key为path的值,path存储了图片的下载路径(包括图片文件名称),若列表解析表达式生成的列表为空,则使用raise语句抛出DropItem异常,停止对Item数据项的处理。
(3)修改pipelinesbjhcsv.py文件,添加image_urls、image_paths字段输出。修改后的代码如下:
from itemadapter import ItemAdapter # 导入csv模块 import csv import os class BjhPipeline: # 定义构造方法,打开CSV文件 def __init__(self): # 定义CSV文件的存储路径 store_file = os.path.dirname(__file__) + '/bjh.csv' # 以a+模式创建CSV文件 self.file = open(store_file,'a+',newline='') # 实例化writer对象 self.writer = csv.writer(self.file) def process_item(self, item, spider): # 获得item数据项适配器 adapter = ItemAdapter(item) if adapter: # row为列表对象 row = [adapter['title'][0], adapter['author'][0], adapter['content'][0], adapter['image_urls'], adapter['image_paths']] # row写入CSV文件 self.writer.writerow(row) return item # 爬虫程序关闭时,该方法被调用 def close_spider(self,spider): # 关闭文件 self.file.close()
image_paths数据项存储了百家号文件内所有已下载的图片存储路径,该结构是一个列表。在后续处理数据时,可以从CSV文件中提取文章对应的全部下载图片。
(4)修改settings.py配置文件,在配置文件的尾部添加配置项IMAGES_STORE,该配置项用于存储图片下载存储路径。
IMAGES_STORE = os.path.join( os.path.dirname(os.path.dirname(__file__)),"images" )
上述配置项将下载图片存储到项目目录下images目录,images目录Scrapy会自动创建。
(5)修改spider_bjhbaidu.py爬虫文件,添加图片处理管道类,对新添加的image_urls字段赋值。修改后完整代码如下:
from newsbaidu.article_item import Article
# 导入scrapy选择器
from scrapy.selector import Selector
# 导入BaiduNewsLoader
from newsbaidu.article_itemload import BaiJiaHaoLoader
# 导入scrapy
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class SpiderBjhbaiduSpider(CrawlSpider):
name = 'spider_bjhbaidu'
allowed_domains = ['https://news.baidu.com/','baijiahao.baidu.com']
start_urls = ['https://news.baidu.com/']
# 定义爬虫内置配置项,覆盖配置文件的配置项
# 配置爬虫使用的管道类
custom_settings = {
'ITEM_PIPELINES':{
'newsbaidu.pipelinesbjhcsv.BjhPipeline': 310,
'newsbaidu.pipelinesbjhimg.BjhImagesPipeline': 303,
}
}
# 定义爬取规则,仅爬取包含baijiahao.baidu.com的链接
rules = (
Rule(LinkExtractor(allow=r'.*baijiahao.baidu.com.*'), callback='parse_item', follow=True),
)
def parse_item(self, response):
# 获取爬取下来的网页代码
html = response.text
# 实例化BaiJiaHaoLoader对象
loader = BaiJiaHaoLoader(item=Article(), selector=Selector(response=response))
# 获取标题数据项
loader.add_xpath("title","//div[@class='article-title']/h2/text()")
# 获取文章作者数据项
loader.add_xpath("author","//div[@class='author-txt']/p/text()")
# 获取文章发布时间数据项
loader.add_xpath("date","//span[@class='date']/text()")
#获取文章内容
content = response.xpath("//div[@class='article-content']/*").extract()
loader.add_value("content",content)
#获取图片路径
imgurl = response.xpath("//div[@class='article-content']//img/@src").extract()
loader.add_value("image_urls",imgurl)
#获取title数据项
value = loader.get_collected_values("title")
# 若value不为None,调用yield语句返回Item数据项
# 过滤掉无内容的链接
if value:
yield loader.load_item()custom_settings属性配置了两个管道类,处理Item数据项的顺序是BjhImagesPipeline、BjhPipeline。
在parse_item()函数内,提取文章内容所有img标签的src属性值,并赋值给Item数据项image_urls。
(6)运行SpiderBjhbaiduSpider爬虫。
3、 生成缩略图
可以在下载图像时让Scrapy自动生成缩略图,具体方法是在settings.py配置文件中添加缩略图配置项:
# 配置缩略图
IMAGES_THUMBS = {
'small': (32, 32),
'big': (160, 160),
}
IMAGES_THUMBS配置项是字典对象,small设置小缩率图的尺寸,big设置大缩率图的尺寸。
缩率图存储在下载图片目录下thumbs子目录内,thumbs目录下有big和small子目录,big目录存储大缩率图,small存储小缩率图。
4、 图片最近下载延迟调整
scrapy下载某图片后,会在一个时间段内不会重复下载该图片(该时间段也称为过期天数),默认过期天数是90天。若要调整该时间段,可以在settings.py配置文件添加配置项IMAGES_EXPIRES,该配置项设置过期天数。
# 配置过期天数为30天
IMAGES_EXPIRES = 30
项目全部代码见课程资源(unit4\ distribution_newsbaidu)。