Python使用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 中,我们需要进行一些基本配置: # 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 文件中,我们定义了爬虫类 DownPicSpider,用于从网页 https://pic.baidu.com/ 下载图片。
  • 在 pipelines.py 文件中,我们定义了两个管道类,用于处理和保存下载的图片。
  • 在这篇文章中,我们介绍了使用 Scrapy 下载图片的两种不同方式:直接链接下载和通过下载链接下载。接下来,我们将详细介绍这两种方式的区别以及它们适用的场景。
  • 直接链接下载:适用于绝大多数公开访问的图片文件,操作简单且性能较好。 通过下载链接下载:适用于需要通过验证或时效性链接获取图片的情况,安全性更高但操作稍复杂。 到此这篇关于Python使用Scrapy下载图片的两种方式的文章就介绍到这了,更多相关Python Scrapy下载图片内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: python编程scrapy简单代码实现搜狗图片下载器 Python中scrapy下载保存图片的示例 Python爬虫实战之使用Scrapy爬取豆瓣图片 Python爬虫之教你利用Scrapy爬取图片
  • 目录
    • 一、项目结构
    • 二、settings.py 配置
    • 三、爬虫文件(down_pic.py)
      • 代码示例
    • 四、管道文件(pipelines.py)
      • 代码示例
    • 五、两种下载方式的区别和使用场景
      • 1. 直接链接下载
        • 示例代码
        • 使用场景
      • 2. 通过下载链接下载
        • 示例代码
        • 使用场景
    • 总结

      在本篇博客中,我们将探讨如何使用 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 中,我们需要进行一些基本配置:

      # 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 文件中,我们定义了爬虫类 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 文件中,我们定义了两个管道类,用于处理和保存下载的图片。

      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 下载图片的两种不同方式:直接链接下载和通过下载链接下载。接下来,我们将详细介绍这两种方式的区别以及它们适用的场景。

      直接链接下载是指图片的 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 获取图片,无需额外的请求和处理。

      通过下载链接下载是指图片的 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下载图片内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

      您可能感兴趣的文章:

      • python编程scrapy简单代码实现搜狗图片下载器
      • Python中scrapy下载保存图片的示例
      • Python爬虫实战之使用Scrapy爬取豆瓣图片
      • Python爬虫之教你利用Scrapy爬取图片

      站内搜索