Python使用Scrapy下载图片的两种方式
作者:一勺菠萝丶
Scrapy是一个适用爬取网站数据、提取结构性数据的应用程序框架,它可以通过定制化的修改来满足不同的爬虫需求,本文给大家介绍了Python使用Scrapy下载图片的两种方式,需要的朋友可以参考下
在本篇博客中,我们将探讨如何使用 Scrapy 框架下载图片,并详细解释两种下载图片的方式。
一、项目结构
首先,我们假设项目结构如下:
biantu_down_pic/ ├── biantu_down_pic/ │ ├── __init__.py │ ├── items.py │ ├── middlewares.py │ ├── pipelines.py │ ├── settings.py │ └── spiders/ │ ├── __init__.py │ └── down_pic.py ├── log_file.log └── scrapy.cfg
二、settings.py 配置
在 settings.py
中,我们需要进行一些基本配置:
# Scrapy settings for biantu_down_pic project BOT_NAME = 'biantu_down_pic' SPIDER_MODULES = ['biantu_down_pic.spiders'] NEWSPIDER_MODULE = 'biantu_down_pic.spiders' ROBOTSTXT_OBEY = False LOG_LEVEL = 'WARNING' LOG_FILE = './log_file.log' USER_AGENTS_LIST = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.111 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0', # 更多 User Agent 可添加 ] DOWNLOADER_MIDDLEWARES = { 'biantu_down_pic.middlewares.BiantuDownPicDownloaderMiddleware': 543, } ITEM_PIPELINES = { 'biantu_down_pic.pipelines.BiantuDownPicSavePipeline': 300, # 先下载图片再保存到数据库 'biantu_down_pic.pipelines.BiantuDownPicPipeline': 301, 'biantu_down_pic.pipelines.MysqlPipeline': 302, } # 图片地址配置 IMAGES_STORE = 'D:/ruoyi/pic.baidu.com/uploadPath' # MySQL 配置 MYSQL_HOST = "localhost" MYSQL_USER = "drawing-bed" MYSQL_PASSWORD = "HBt5J7itWJEXe" MYSQL_DBNAME = "drawing-bed" MYSQL_PORT = 3306
三、爬虫文件(down_pic.py)
在 down_pic.py
文件中,我们定义了爬虫类 DownPicSpider
,用于从网页 https://pic.baidu.com/
下载图片。
代码示例
import imghdr import json import os import re from datetime import datetime import scrapy from biantu_down_pic.items import BiantuDownPicItem from biantu_down_pic.settings import IMAGES_STORE from biantu_down_pic.pipelines import sanitize_filename class DownPicSpider(scrapy.Spider): name = 'down_pic' start_urls = ['https://pic.baidu.com/'] def __init__(self, token=None, map_json=None, *args, **kwargs): super(DownPicSpider, self).__init__(*args, **kwargs) self.token = token self.map_json = json.loads(map_json) if map_json else {} def parse(self, response, **kwargs): # 从 map_json 获取必要的信息 key_id = self.map_json.get('id') url = self.map_json.get('url') title = self.map_json.get("title") # 设置 cookies cookies = {i.split('=')[0]: i.split('=')[1] for i in self.token.split('; ')} # 发起请求到详情页 yield scrapy.Request( url, cookies=cookies, callback=self.parse_detail, meta={'key_id': key_id, 'url': url, 'cookies': cookies, 'title': title} ) def parse_detail(self, response, **kwargs): # 从 URL 中提取图片 ID pic_id = response.url.split('/')[-1].split('.')[0] cookies = response.meta['cookies'] # 构建请求源图片 URL 的地址 source_url = f'https://pic.baidu.com/e/extend/downpic.php?id={pic_id}' # 获取缩略图 URL thumbnail_url = response.xpath('//*[@id="img"]/img/@src').extract_first() thumbnail_url = response.urljoin(thumbnail_url) # 将缩略图 URL 添加到 meta 中 response.meta['thumbnail_url'] = thumbnail_url # 发送请求获取源图片 URL yield scrapy.Request( source_url, cookies=cookies, callback=self.parse_source_url, meta=response.meta ) def parse_source_url(self, response, **kwargs): # 解析响应中的图片下载链接 pic_data = response.json() pic_url = response.urljoin(pic_data['pic']) cookies = response.meta['cookies'] # 发送请求下载图片 yield scrapy.Request( pic_url, cookies=cookies, callback=self.save_image, meta=response.meta ) def save_image(self, response, **kwargs): # 动态生成文件保存路径 current_time = datetime.now() date_path = current_time.strftime('%Y/%m/%d') download_dir = os.path.join(IMAGES_STORE, 'pic', date_path).replace('\\', '/') # 生成清理后的文件名 title = response.meta['title'] pic_name = sanitize_filename(title) pic_name = self.clean_filename(pic_name) # 根据响应头获取文件扩展名 content_type = response.headers.get('Content-Type', b'').decode('utf-8') extension = self.get_extension(content_type, response.body) if not pic_name.lower().endswith(extension): pic_name += extension file_path = os.path.join(download_dir, pic_name).replace('\\', '/') # 确保下载目录存在 os.makedirs(download_dir, exist_ok=True) # 保存图片 with open(file_path, 'wb') as f: f.write(response.body) # 构建 Item 并返回 item = BiantuDownPicItem() item['id'] = response.meta['key_id'] item['title'] = response.meta['title'] item['title_min'] = response.meta['title'] + '_min.jpg' item['url'] = response.meta['url'] item['min_url'] = response.meta['thumbnail_url'] item['download_path'] = download_dir.replace(IMAGES_STORE, '') item['max_path'] = file_path.replace(IMAGES_STORE, '/profile') yield item @staticmethod def clean_filename(filename): """清理文件名,去除无效字符""" return re.sub(r'[<>:"/\\|?*]', '', filename) @staticmethod def get_extension(content_type, body): """根据内容类型或文件内容获取扩展名""" if 'image/jpeg' in content_type: return '.jpg' elif 'image/png' in content_type: return '.png' elif 'image/jpg' in content_type: return '.jpg' else: return '.' + imghdr.what(None, body)
四、管道文件(pipelines.py)
在 pipelines.py
文件中,我们定义了两个管道类,用于处理和保存下载的图片。
代码示例
import logging import re import scrapy from scrapy.pipelines.images import ImagesPipeline log = logging.getLogger(__name__) class BiantuDownPicPipeline: """基础的 Item 处理管道,仅打印 Item""" def process_item(self, item, spider): print(item) return item def sanitize_filename(filename): """移除文件名中的非法字符,替换为 'X'""" return re.sub(r'[<>:"/\\|?*]', 'X', filename) class BiantuDownPicSavePipeline(ImagesPipeline): """自定义图片下载和保存管道""" def get_media_requests(self, item, info): """发送请求去下载缩略图""" min_url = item['min_url'] print('1. 发送请求去下载图片, min_url:', min_url) return scrapy.Request(url=min_url) def file_path(self, request, response=None, info=None, *, item=None): """定义图片的存储路径""" print('2. 图片的存储路径') filename = sanitize_filename(item['title_min']) download_path = f"{item['download_path']}/{filename}" return download_path def item_completed(self, results, item, info): """更新 Item,添加缩略图的存储路径""" print(f'3. 对 Item 进行更新, result: {results}') if results: ok, res = results[0] if ok: item['min_path'] = '/profile' + res["path"] return item class MysqlPipeline(object): """MySQL 数据库管道,暂时保留现状""" def __init__(self, host, user, password, database, port): self.host = host self.user = user self.password = password self.database = database self.port = port @classmethod def from_crawler(cls, crawler): return cls( host=crawler.settings.get("MYSQL_HOST"), user=crawler.settings['MYSQL_USER'], password=crawler.settings['MYSQL_PASSWORD'], database=crawler.settings['MYSQL_DBNAME'], port=crawler.settings['MYSQL_PORT'] ) def open_spider(self, spider): """在爬虫启动时创建数据库连接""" self.conn = pymysql.connect( host=self.host, user=self.user, password=self.password, database=self.database, charset='utf8', port=self.port ) self.cursor = self.conn.cursor() def process_item(self, item, spider): """处理 Item 并插入数据库""" dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # SQL 语句暂时保留,待更新 # insert_sql = """ # INSERT INTO biz_pic ( # url, title, source_url, category_name, size, volume, create_by, create_time # ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) # """ # try: # self.cursor.execute(insert_sql, ( # item['url'], # item['title'], # item['source_url'], # item['category_name'], # item['size'], # item['volume'], # 'admin', # dt # )) # self.conn.commit() # except Exception as e: # log.error(f"插入数据出错,{e}") def close_spider(self, spider): """在爬虫关闭时关闭数据库连接""" self.cursor.close() self.conn.close() log.warning("爬取数据结束===============================>end")
五、两种下载方式的区别和使用场景
在这篇文章中,我们介绍了使用 Scrapy 下载图片的两种不同方式:直接链接下载和通过下载链接下载。接下来,我们将详细介绍这两种方式的区别以及它们适用的场景。
1. 直接链接下载
直接链接下载是指图片的 URL 本身就指向图片文件,可以直接通过 URL 获取图片内容。
示例代码
在 pipelines.py
中的 BiantuDownPicSavePipeline
类中,我们使用直接链接下载图片:
class BiantuDownPicSavePipeline(ImagesPipeline): """自定义图片下载和保存管道""" def get_media_requests(self, item, info): """发送请求去下载缩略图""" min_url = item['min_url'] print('1. 发送请求去下载图片, min_url:', min_url) return scrapy.Request(url=min_url) def file_path(self, request, response=None, info=None, *, item=None): """定义图片的存储路径""" print('2. 图片的存储路径') filename = sanitize_filename(item['title_min']) download_path = f"{item['download_path']}/{filename}" return download_path def item_completed(self, results, item, info): """更新 Item,添加缩略图的存储路径""" print(f'3. 对 Item 进行更新, result: {results}') if results: ok, res = results[0] if ok: item['min_path'] = '/profile' + res["path"] return item
使用场景
- 简单且常见:适用于绝大多数公开访问的图片文件。
- 性能较好:直接通过 URL 获取图片,无需额外的请求和处理。
2. 通过下载链接下载
通过下载链接下载是指图片的 URL 需要通过一个中间链接来获取实际的图片文件。这种情况通常用于需要验证或有时效性的下载链接。
示例代码
在 down_pic.py
中的 DownPicSpider
类中,我们使用通过下载链接下载图片:
class DownPicSpider(scrapy.Spider): name = 'down_pic' start_urls = ['https://pic.baidu.com/'] def parse_detail(self, response, **kwargs): # 从 URL 中提取图片 ID pic_id = response.url.split('/')[-1].split('.')[0] cookies = response.meta['cookies'] # 构建请求源图片 URL 的地址 source_url = f'https://pic.baidu.com/e/extend/downpic.php?id={pic_id}' # 获取缩略图 URL thumbnail_url = response.xpath('//*[@id="img"]/img/@src').extract_first() thumbnail_url = response.urljoin(thumbnail_url) # 将缩略图 URL 添加到 meta 中 response.meta['thumbnail_url'] = thumbnail_url # 发送请求获取源图片 URL yield scrapy.Request( source_url, cookies=cookies, callback=self.parse_source_url, meta=response.meta ) def parse_source_url(self, response, **kwargs): # 解析响应中的图片下载链接 pic_data = response.json() pic_url = response.urljoin(pic_data['pic']) cookies = response.meta['cookies'] # 发送请求下载图片 yield scrapy.Request( pic_url, cookies=cookies, callback=self.save_image, meta=response.meta )
使用场景
- 需要身份验证或时效性的下载链接:适用于需要通过特定请求获取下载链接的情况。
- 安全性要求较高:适用于下载需要权限控制的图片。
总结
- 直接链接下载:适用于绝大多数公开访问的图片文件,操作简单且性能较好。
- 通过下载链接下载:适用于需要通过验证或时效性链接获取图片的情况,安全性更高但操作稍复杂。
到此这篇关于Python使用Scrapy下载图片的两种方式的文章就介绍到这了,更多相关Python Scrapy下载图片内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!