Logo

郎哥编程

下载网页图片

2020-12-19 235

百度新闻爬虫项目的SpiderBjhbaiduSpider爬虫仅是爬取了文章作者、发布时间、标题和文章内容。在一些情况下,还需要爬取文章中的图片,把图片下载到本地。

1、 图片管道类

Scrapy专门提供了一个用于下载网络图片的管道类ImagesPipeline,它可以将图片下载到本地,并将图片格式转换为JPG格式,也可以生成缩略图,并能避免重复下载已有的图片。

图像管道类需要Pillow 4.0.0或更高版本,Pillow是Python的图形处理库,图像管道类使用该库将下载的图片生成缩略图和转换为JPEG格式。

ImagesPipeline类继承FilesPipeline类,下表给出了ImagesPipeline类及父类的主要方法:

29.PNG

注释(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的字段名称也不相同,具体命名如下表所示:

30.PNG

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)。

代码在线纠错(通义千问 qwen-max)

支持粘贴多个代码文件,提交后由阿里云通义千问自动分析代码漏洞、语法错误、逻辑问题并给出修改建议。
您已解锁 AI 代码纠错功能,可正常使用!

评论区

登录 后发表评论
暂无评论