如何防止Python大规模图像抓取过程中出现内存不足错误

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 图像抓取是一种常见的网络爬虫技术,用于从网页上下载图片并保存到本地文件夹中。然而,当需要抓取的图片数量很大时,可能会出现内存不足的错误,导致程序崩溃。本文介绍了如何使用Python进行大规模的图像抓取,并提供了一些优化内存使用的方法和技巧,以及如何计算和评估图片的质量指标。

亿牛云代理.jpg

摘要

图像抓取是一种常见的网络爬虫技术,用于从网页上下载图片并保存到本地文件夹中。然而,当需要抓取的图片数量很大时,可能会出现内存不足的错误,导致程序崩溃。本文介绍了如何使用Python进行大规模的图像抓取,并提供了一些优化内存使用的方法和技巧,以及如何计算和评估图片的质量指标。

正文

1. 导入必要的库和模块

为了实现图像抓取的功能,我们需要导入一些必要的库和模块,如pickle、logging、datetime等。其中,pickle模块用于序列化和反序列化对象,方便我们将处理结果保存到文件中;logging模块用于记录程序的运行日志,方便我们调试和监控程序的状态;datetime模块用于获取和处理日期和时间相关的信息,方便我们设置请求头部和日志格式等。

import pickle
import logging
from datetime import datetime
from dateutil.parser import parse as parse_date
from brisque import BRISQUE
import os
import cv2
import numpy as np
from PIL import Image
from io import BytesIO
import os
import requests
from skimage import color
from time import sleep
from random import choice
import concurrent.futures
from requests.exceptions import Timeout
from robots import RobotParser

from headers import HEADERS

MAX_RETRIES = 3         # Number of times the crawler should retry a URL
INITIAL_BACKOFF = 2     # Initial backoff delay in seconds
DEFAULT_SLEEP = 10      # Default sleep time in seconds after a 429 error

brisque = BRISQUE(url=False)

2. 设置日志记录器

为了方便记录程序的运行日志,我们需要设置一个日志记录器,用于将日志信息输出到文件中。我们可以使用logging模块提供的方法来创建一个名为“image-scraper”的日志记录器,并设置其日志级别为INFO。然后,我们可以创建一个文件处理器,用于将日志信息写入到指定的文件中,并设置其日志格式为包含日志级别、线程名、时间和消息内容等信息。最后,我们可以将文件处理器添加到日志记录器中,使其生效。

# --- SETUP LOGGER ---

filename = 'image-scraper.log'
filepath = os.path.dirname(os.path.abspath(__file__))

# create file path for log file
log_file = os.path.join(filepath, filename)

# create a FileHandler to log messages to the log file
handler = logging.FileHandler(log_file)
# set the log message formats
handler.setFormatter(
    logging.Formatter(
        '%(levelname)s %(threadName)s (%(asctime)s): %(message)s')
)
# create a logger with the given name and log level
logger = logging.getLogger('image-scraper')
# prevent logging from being send to the upper logger - that includes the console logging
logger.propagate = False
logger.setLevel(logging.INFO)
# add the FileHandler to the logger
logger.addHandler(handler)

3. 定义计算图片质量指标的函数

为了评估图片的质量,我们需要计算一些图片质量指标,如亮度、清晰度、对比度、色彩度等。我们可以定义一个函数get_image_quality_metrics,接受一个包含图片数据的响应对象作为参数,并返回一个包含各种质量指标的字典。在这个函数中,我们首先使用PIL库和numpy库将图片数据转换为数组形式,并使用cv2库和skimage库对图片进行处理和计算。具体来说:

  • 计算亮度:我们将图片转换为灰度图,并计算其像素值的平均值。
  • 计算清晰度:我们使用拉普拉斯算子对灰度图进行边缘检测,并计算其方差值。
  • 计算对比度:我们使用均方根对比度的公式,计算灰度图像素值与其平均值的差的平方的平均值的平方根。
  • 计算噪声:我们使用高斯滤波或中值绝对偏差(MAD)的方法,计算图片的方差值。
  • 计算饱和度:我们将图片转换为HSV颜色空间,并计算其饱和度通道的平均值。
  • 计算色彩度:我们将图片转换为LAB颜色空间,并计算其a和b通道的平方和的平方根的平均值。
  • 获取图片的尺寸:我们获取图片的高度和宽度,并将其添加到字典中。

    def get_image_quality_metrics(response):
      """
      Calculate various image quality metrics for an image.
    
      Args:
          response (requests.Response): The response object containing the image data.
    
      Returns:
          dict: A dict of image quality metrics including brightness, sharpness, contrast, and colorfulness.
      """
      image_array = np.frombuffer(response.content, np.uint8)
      image = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
    
      metrics = dict()
    
      # Calculate brightness
      gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
      metrics['brightness'] = np.mean(gray)
    
      # Calculate sharpness using variance of Laplacian
      metrics['sharpness'] = cv2.Laplacian(gray, cv2.CV_64F).var()
    
      # Calculate contrast using root mean squared contrast
      metrics['contrast'] = np.sqrt(np.mean((gray - np.mean(gray)) ** 2))
    
      # Calculate image noise using variance of Gaussian or median absolute deviation (MAD)
      metrics['noise'] = np.var(image)
    
      # Calculate saturation using average saturation of pixels or histogram analysis
      hsv = color.rgb2hsv(image)
      saturation = hsv[:, :, 1]
      metrics['saturation'] = np.mean(saturation)
    
      # Calculate colorfulness
      lab = color.rgb2lab(image)
      a, b = lab[:, :, 1], lab[:, :, 2]
      metrics['colorfulness'] = np.sqrt(np.mean(a ** 2 + b ** 2))
    
      # Get dimenstions of the image
      height, width, _ = image.shape
      metrics['height'] = height
      metrics['width'] = width
    
      return metrics
    

    4. 定义发送请求的函数

    为了从网页上下载图片,我们需要发送GET请求到图片的URL,并获取响应对象。我们可以定义一个函数send_request,接受一个URL作为参数,并返回一个响应对象。在这个函数中,我们需要处理一些可能出现的异常和错误,如超时、状态码不为200、429等。为了避免被网站屏蔽或限制,我们需要使用代理服务器和随机选择的请求头部。具体来说:

  • 我们使用requests库提供的方法来创建一个代理服务器对象,使用亿牛云提供的代理服务器信息。

  • 我们使用一个while循环来重试请求,设置一个最大重试次数和一个初始退避延迟时间。
  • 我们从headers模块中随机选择一个请求头部,并将其添加到请求中。
  • 我们使用try-except语句来捕获可能出现的异常和错误,并根据不同的情况进行处理:

    • 如果出现超时错误,我们记录日志信息,并增加重试次数和退避延迟时间。
    • 如果出现状态码不为200的错误,我们记录日志信息,并根据状态码进行处理:
      • 如果状态码为429,表示请求过于频繁,我们需要等待一段时间后再重试,我们可以使用time模块提供的sleep方法来暂停程序运行,并设置一个默认的睡眠时间。
      • 如果状态码为403或404,表示请求被拒绝或资源不存在,我们可以直接跳出
      • 如果状态码为其他值,表示请求出现其他错误,我们可以直接抛出异常,并记录日志信息。
    • 如果没有出现异常或错误,我们返回响应对象,并记录日志信息。

      def send_request(url: str) -> requests.Response:
      """
      Sends a GET request to the specified URL, checks whether the link is valid,
      and returns a response object.
      
      Args:
         url (str): The URL to send the GET request to
      """
      retry_count = 0
      backoff = INITIAL_BACKOFF
      header = choice(HEADERS)
      
      # 亿牛云 爬虫代理加强版
      proxyHost = "www.16yun.cn"
      proxyPort = "31111"
      
      # 代理验证信息
      proxyUser = "16YUN"
      proxyPass = "16IP"
      
      # create a proxy server object using the proxy information    
      proxyMeta = "http://%(user)s:%(pass)s@%(host)s:%(port)s" % {
             
             
         "host": proxyHost,
         "port": proxyPort,
         "user": proxyUser,
         "pass": proxyPass,
      }
      proxies = {
             
             
         "http": proxyMeta,
         "https": proxyMeta,
      }
      
      while retry_count < MAX_RETRIES:
         try:
             # Send a GET request to the website and return the response object
             req = requests.get(url, headers=header, proxies=proxies, timeout=20)
             req.raise_for_status()
             logger.info(f"Successfully fetched {url}")
             return req
      
         except Timeout:
             # Handle timeout error: log the error and increase the retry count and backoff delay
             logger.error(f"Timeout error for {url}")
             retry_count += 1
             backoff *= 2
      
         except requests.exceptions.HTTPError as e:
             # Handle HTTP error: log the error and check the status code
             logger.error(f"HTTP error for {url}: {e}")
             status_code = e.response.status_code
      
             if status_code == 429:
                 # Handle 429 error: wait for some time and retry
                 logger.info(f"Waiting for {DEFAULT_SLEEP} seconds after 429 error")
                 sleep(DEFAULT_SLEEP)
                 retry_count += 1
      
             elif status_code == 403 or status_code == 404:
                 # Handle 403 or 404 error: break the loop and return None
                 logger.info(f"Skipping {url} due to {status_code} error")
                 break
      
             else:
                 # Handle other errors: raise the exception and log the error
                 logger.error(f"Other HTTP error for {url}: {e}")
                 raise e
      
      # Return None if the loop ends without returning a response object
      return None
      

      5. 定义处理图片的函数

      为了从响应对象中提取图片的数据,并计算其质量指标和BRISQUE分数,我们可以定义一个函数process_image,接受一个响应对象和一个URL作为参数,并返回一个包含图片信息的字典。在这个函数中,我们需要使用“with”语句来管理文件和图片对象的打开和关闭,以及使用“del”语句来释放不再需要的变量,从而优化内存使用。具体来说:

  • 我们使用PIL库提供的方法来打开响应对象中的图片数据,并将其转换为RGBA格式。

  • 我们使用os模块提供的方法来创建一个名为“images”的文件夹,用于存储下载的图片。
  • 我们使用datetime模块提供的方法来获取当前的日期和时间,并将其转换为字符串格式,作为图片的文件名。
  • 我们使用“with”语句来打开一个以日期和时间命名的文件,并将图片数据写入到文件中。
  • 我们使用brisque模块提供的方法来计算图片的BRISQUE分数,并将其添加到字典中。
  • 我们使用前面定义的get_image_quality_metrics函数来计算图片的其他质量指标,并将其添加到字典中。
  • 我们使用“del”语句来删除不再需要的变量,如响应对象、图片对象等。
  • 我们返回包含图片信息的字典。

    def process_image(response, url):
      """
      Process an image from a response object and calculate its quality metrics and BRISQUE score.
    
      Args:
          response (requests.Response): The response object containing the image data.
          url (str): The URL of the image.
    
      Returns:
          dict: A dict of image information including quality metrics and BRISQUE score.
      """
      # Open the image data from the response object and convert it to RGBA format
      image = Image.open(BytesIO(response.content)).convert('RGBA')
    
      # Create a folder named "images" to store the downloaded images
      os.makedirs('images', exist_ok=True)
    
      # Get the current date and time and convert it to a string format as the image file name
      date_time = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
    
      # Open a file with the date and time as the file name and write the image data to it
      with open(f'images/{date_time}.png', 'wb') as f:
          image.save(f, 'PNG')
    
      # Calculate the BRISQUE score of the image and add it to the dict
      image_info = dict()
      image_info['brisque'] = get_brisque_score(response)
    
      # Calculate the other quality metrics of the image and add them to the dict
      image_info.update(get_image_quality_metrics(response))
    
      # Delete the response object and the image object to free up memory
      del response
      del image
    
      # Return the dict of image information
      return image_info
    

    6. 使用线程池来处理多个网站的图片抓取任务

    为了提高程序的效率和并发性,我们可以使用线程池来处理多个网站的图片抓取任务,并将处理结果保存到文件中。我们可以使用concurrent.futures模块提供的方法来创建一个线程池对象,并使用submit方法来提交每个网站的图片抓取任务。具体来说:

  • 我们创建一个名为“websites”的列表,用于存储需要抓取图片的网站的URL。

  • 我们创建一个名为“results”的列表,用于存储每个网站的图片抓取结果。
  • 我们使用“with”语句来创建一个线程池对象,并设置其最大线程数为10。
  • 我们遍历每个网站的URL,并使用submit方法来提交一个图片抓取任务,传入send_request函数和URL作为参数,并将返回的future对象添加到results列表中。
  • 我们遍历results列表中的每个future对象,并使用result方法来获取其结果,即响应对象。
  • 我们判断响应对象是否为None,如果不为None,表示请求成功,我们则使用process_image函数来处理响应对象,并将返回的图片信息字典添加到results列表中;如果为None,表示请求失败,我们则跳过该网站。
  • 我们使用pickle模块提供的方法来将results列表序列化并保存到一个名为“results.pkl”的文件中。
    ```python

    Create a list of websites to scrape images from

    websites = [
    'https://unsplash.com/',
    'https://pixabay.com/',
    'https://www.pexels.com/',
    'https://www.freeimages.com/',
    'https://stocksnap.io/',
    ]

Create a list to store the results of each website

results = []

Create a thread pool with 10 threads and submit tasks for each website

with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
for website in websites:

    # Submit a task to send a request to the website and get a response object
    future = executor.submit(send_request, website)
    # Add the future object to the results list
    results.append(future)

Iterate over the results list and get the result of each future object

for future in results:

# Get the response object from the future object
response = future.result()
# Check if the response object is None or not
if response is not None:
    # Process the response object and get the image information dict
    image_info = process_image(response, website)
    # Add the image information dict to the results list
    results.append(image_info)
else:
    # Skip the website if the response object is None
    continue

Serialize and save the results list to a file using pickle module

with open('results.pkl', 'wb') as f:
pickle.dump(results, f)
```

结论

本文介绍了如何使用Python进行大规模的图像抓取,并提供了一些优化内存使用的方法和技巧,以及如何计算和评估图片的质量指标。我们使用requests库来发送GET请求到图片的URL,并使用代理服务器和随机选择的请求头部来避免被网站屏蔽或限制。我们使用PIL库和cv2库来处理图片数据,并使用brisque模块和自定义的函数来计算图片的质量指标和BRISQUE分数。我们使用logging模块来记录程序的运行日志,并使用pickle模块来将处理结果保存到文件中。我们使用“with”语句来管理文件和图片对象的打开和关闭,以及使用“del”语句来释放不再需要的变量,从而优化内存使用。我们使用concurrent.futures模块来创建一个线程池,并使用submit方法来提交每个网站的图片抓取任务,从而提高程序的效率和并发性。通过这些方法和技巧,我们可以实现一个高效、稳定、可扩展的大规模图像抓取程序。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
14天前
|
机器学习/深度学习 算法 TensorFlow
动物识别系统Python+卷积神经网络算法+TensorFlow+人工智能+图像识别+计算机毕业设计项目
动物识别系统。本项目以Python作为主要编程语言,并基于TensorFlow搭建ResNet50卷积神经网络算法模型,通过收集4种常见的动物图像数据集(猫、狗、鸡、马)然后进行模型训练,得到一个识别精度较高的模型文件,然后保存为本地格式的H5格式文件。再基于Django开发Web网页端操作界面,实现用户上传一张动物图片,识别其名称。
46 1
动物识别系统Python+卷积神经网络算法+TensorFlow+人工智能+图像识别+计算机毕业设计项目
|
14天前
|
机器学习/深度学习 人工智能 算法
植物病害识别系统Python+卷积神经网络算法+图像识别+人工智能项目+深度学习项目+计算机课设项目+Django网页界面
植物病害识别系统。本系统使用Python作为主要编程语言,通过收集水稻常见的四种叶片病害图片('细菌性叶枯病', '稻瘟病', '褐斑病', '稻瘟条纹病毒病')作为后面模型训练用到的数据集。然后使用TensorFlow搭建卷积神经网络算法模型,并进行多轮迭代训练,最后得到一个识别精度较高的算法模型,然后将其保存为h5格式的本地模型文件。再使用Django搭建Web网页平台操作界面,实现用户上传一张测试图片识别其名称。
65 21
植物病害识别系统Python+卷积神经网络算法+图像识别+人工智能项目+深度学习项目+计算机课设项目+Django网页界面
|
14天前
|
机器学习/深度学习 人工智能 算法
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
鸟类识别系统。本系统采用Python作为主要开发语言,通过使用加利福利亚大学开源的200种鸟类图像作为数据集。使用TensorFlow搭建ResNet50卷积神经网络算法模型,然后进行模型的迭代训练,得到一个识别精度较高的模型,然后在保存为本地的H5格式文件。在使用Django开发Web网页端操作界面,实现用户上传一张鸟类图像,识别其名称。
60 12
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
|
9天前
|
数据采集 存储 JavaScript
构建您的第一个Python网络爬虫:抓取、解析与存储数据
【9月更文挑战第24天】在数字时代,数据是新的金矿。本文将引导您使用Python编写一个简单的网络爬虫,从互联网上自动抓取信息。我们将介绍如何使用requests库获取网页内容,BeautifulSoup进行HTML解析,以及如何将数据存储到文件或数据库中。无论您是数据分析师、研究人员还是对编程感兴趣的新手,这篇文章都将为您提供一个实用的入门指南。拿起键盘,让我们开始挖掘互联网的宝藏吧!
|
9天前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
20 1
|
16天前
|
并行计算 开发者 Python
高效利用Python中的生成器提高内存管理
在处理大量数据或执行复杂计算时,内存管理成为关键问题。Python中的生成器(Generators)提供了一种优雅的解决方案,通过惰性计算和节省内存的方式显著提高程序的效率。本文将探讨生成器的基本概念,实际应用场景,以及如何利用生成器优化内存使用和提高程序性能。
|
18天前
|
监控 Ubuntu API
Python脚本监控Ubuntu系统进程内存的实现方式
通过这种方法,我们可以很容易地监控Ubuntu系统中进程的内存使用情况,对于性能分析和资源管理具有很大的帮助。这只是 `psutil`库功能的冰山一角,`psutil`还能够提供更多关于系统和进程的详细信息,强烈推荐进一步探索这个强大的库。
29 1
|
8天前
|
Python
python对电脑的操作,获取几核,获取操作系统,获取内存
python对电脑的操作,获取几核,获取操作系统,获取内存
|
1月前
|
数据采集 JavaScript 前端开发
构建简易Python爬虫:抓取网页数据入门指南
【8月更文挑战第31天】在数字信息的时代,数据抓取成为获取网络资源的重要手段。本文将引导你通过Python编写一个简单的网页爬虫,从零基础到实现数据抓取的全过程。我们将一起探索如何利用Python的requests库进行网络请求,使用BeautifulSoup库解析HTML文档,并最终提取出有价值的数据。无论你是编程新手还是有一定基础的开发者,这篇文章都将为你打开数据抓取的大门。
|
2月前
|
机器学习/深度学习 人工智能 TensorFlow
利用Python和TensorFlow实现简单图像识别
【8月更文挑战第31天】在这篇文章中,我们将一起踏上一段探索人工智能世界的奇妙之旅。正如甘地所言:“你必须成为你希望在世界上看到的改变。” 通过实践,我们不仅将学习如何使用Python和TensorFlow构建一个简单的图像识别模型,而且还将探索如何通过这个模型理解世界。文章以通俗易懂的方式,逐步引导读者从基础到高级,体验从编码到识别的整个过程,让每个人都能在AI的世界中看到自己的倒影。
下一篇
无影云桌面