简述

万圣节快到了,需要一些万圣节主题的图片制作成海报,到网站一张一张下载太过麻烦,就想着实现一个简单的Python爬虫工具来获取万圣节相关的图片,由于一些网站可能会通过robots.txt或其他手段禁止爬虫行为,你应当首先确认该网站是否允许被爬取,爬取内容时应当遵守网站的robots.txt规则以及相关的使用条款,避免非法爬取。我今天要爬取的网站是https://www.istockphoto.com/, 该站是允许爬取的。好了,开始了,可以使用requests和BeautifulSoup来爬取网页内容。另外,istockphoto网站通常会使用JavaScript加载图片,这意味着仅使用requests和BeautifulSoup可能无法抓取动态加载的内容。在这种情况下,可以考虑使用Selenium或Playwright来处理JavaScript渲染。

我今天实现的是基于Selenium的爬虫,它能够访问https://www.istockphoto.com/并查找与“万圣节”相关的图片。

准备:安装Selenium库和ChromeDriver

pip install selenium 安装Selenium库, 用于模拟浏览器行为,特别是加载JavaScript内容。

安装一个WebDriver, 我使用的是ChromeDriver(注意控制Chrome浏览器,你需要下载对应版本的ChromeDriver并设置其路径)ChromeDriver的安装不再赘述,下面简短的说一下windows系统的安装与设置吧:

步骤 1: 检查你的Chrome浏览器版本

ChromeDriver的版本必须与已安装的Chrome浏览器版本匹配。

  1. 打开Chrome浏览器。
  2. 在地址栏中输入 chrome://settings/help,然后按下回车。
  3. 你会看到当前的Chrome版本号。

步骤 2: 下载与Chrome版本匹配的ChromeDriver

  1. 访问ChromeDriver官网。
  2. 查找与你的Chrome浏览器版本匹配的ChromeDriver版本(对于版本 115 及更高版本,)您可以在 Chrome for Testing (CfT) 可用性信息中心查看各个发布渠道(稳定版、Beta 版、开发者版、Canary 版)的最新 ChromeDriver 版本。。
  3. 下载适合你操作系统的文件(Windows, Mac, 或 Linux,我用的是win的)。

步骤 3: 安装ChromeDriver

Windows用户

  1. 下载的chromedriver_win32.zip文件解压缩后,你会得到chromedriver.exe。
  2. 将这个文件放置在一个路径中,例如:C:\chromedriver\chromedriver.exe。
  3. 将这个路径加入系统的环境变量:

    右键点击“此电脑”,选择“属性”。

    选择“高级系统设置” → “环境变量”。

    在“系统变量”区域中,找到“Path”,选择它并点击“编辑”。

    点击“新建”,输入C:\chromedriver\,然后点击“确定”。

步骤 4: 测试ChromeDriver

打开终端或命令提示符,输入

chromedriver --version

如果成功安装,你应该会看到类似于 ChromeDriver 117.0.5938.62 这样的版本号信息。

步骤 5: 使用Selenium调用ChromeDriver

在Python代码中,确保你的Selenium脚本能够找到ChromeDriver。例如,如果没有将ChromeDriver添加到系统路径,可以在代码中直接提供其路径。

from selenium import webdriver
from selenium.webdriver.chrome.service import Service

# 指定chromedriver的路径
service = Service(executable_path='你的chromedriver所在路径')

# 启动浏览器
driver = webdriver.Chrome(service=service)

代码实现

直接上代码(注释写的很清楚,我相信你们可以看明白)代码实现主要的点:

  1. 中途提示:防止无休止,每下载完20张图片后(可根据实际情况自行修改这个数值),程序会暂停,等待用户的输入。
  2. 并行下载:提高下载速度,使用多线程下载来并行处理多个图片的下载任务。每次批次处理后重新创建线程池:通过在 while 循环中每次处理完一批图片后,使用 with ThreadPoolExecutor 来创建一个新的线程池。线程池在每一批次结束后会自动关闭,下一次处理时重新创建一个新的线程池。
  3. 使用断点续传:如果网络中断或者长时间运行,可以记录已下载的图片,避免重复下载。
# Author: Bruce Kevin Chen
# Create code: 2024/10/1 21:22
import os
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from concurrent.futures import ThreadPoolExecutor
import time

# 配置ChromeDriver
chrome_options = Options()
chrome_options.add_argument("--headless")  # 在后台运行
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")

# 替换为你自己的ChromeDriver路径
service = Service(executable_path='你的chromedriver.exe路径')

# 创建halloween目录,如果不存在
save_dir = 'halloween'
if not os.path.exists(save_dir):
    os.makedirs(save_dir)

# 下载图片的函数
def download_image(url, idx):
    try:
        img_data = requests.get(url).content
        file_path = os.path.join(save_dir, f'halloween_image_{idx + 1}.jpg')
        with open(file_path, 'wb') as handler:
            handler.write(img_data)
        print(f"Image {idx + 1} downloaded: {url}")
    except Exception as e:
        print(f"Failed to download image {idx + 1}: {e}")


# 启动浏览器
driver = webdriver.Chrome(service=service, options=chrome_options)

# 打开目标网站
driver.get("https://www.istockphoto.com/photos/halloween")

# 确保页面加载完成
time.sleep(3)

# 找到图片元素
image_elements = driver.find_elements(By.CSS_SELECTOR, 'img')

# 提取图片的src属性
image_urls = []
for idx, img in enumerate(image_elements):
    src = img.get_attribute('src')
    if src and 'https://media.istockphoto.com' in src:
        image_urls.append((src, idx))

# 关闭浏览器
driver.quit()

# 使用线程池并行下载
def download_in_batches(image_urls, batch_size=20):
    total_images = len(image_urls)
    current_batch = 0

    while current_batch * batch_size < total_images:
        start = current_batch * batch_size
        end = min((current_batch + 1) * batch_size, total_images)
        batch = image_urls[start:end]

        # 每批次创建一个新的线程池
        # 在每一批次的下载中创建一个新的ThreadPoolExecutor,而不是在整个过程中只使用一个线程池。
        # 这样每次询问用户是否继续下载时,都会创建一个新的线程池来处理下载任务,防止由于 executor.shutdown(wait=True) 方法           调用后,
        # 线程池被关闭,导致无法提交新的任务。在关闭线程池之后,不能再使用同一个线程池提交新的任务。
        with ThreadPoolExecutor(max_workers=5) as executor:
            # 下载当前批次的图片
            for url, idx in batch:
                executor.submit(download_image, url, idx)

        current_batch += 1

        # 如果下载了20张,询问用户是否继续
        if current_batch * batch_size < total_images:
            user_input = input(f"{end} images downloaded. Do you want to download more? (y/n): ")
            if user_input.lower() != 'y':
                print("Download stopped by user.")
                break

# 每批次下载20张图片,并询问用户是否继续
download_in_batches(image_urls, batch_size=20)