🚀 Python + Selenium 打造网页图片抓取器(带可视化选择与 ZIP 打包)

📢 免责声明

本文内容仅供 学习与技术交流 之用,禁止任何形式的非法使用。

使用本文代码或相关技术抓取的内容,如涉及版权或法律问题,责任由使用者本人承担

作者不鼓励、也不支持任何违反法律法规及网站使用条款的行为。

✅ 合规指引

  1. 尊重版权

    • 不要抓取带有版权的图片、视频或数据。
    • 商业网站的图片、新闻、影视资源几乎都受保护,未经授权下载、传播可能违法。
  2. 查看 robots.txt 和用户协议

    • 合法网站通常会通过 robots.txt 或服务条款明确是否允许爬虫访问。
    • 如果明确禁止,就不要去爬。
  3. 设置合理的限制

    • 控制并发线程,避免对目标服务器造成压力。
    • 不要绕过反爬虫策略,否则可能涉及"恶意访问"。
  4. 🟢 合法示例网站(学习推荐)

    • Unsplash - 免费高质量摄影图片
    • Pexels - 免费照片和视频素材
    • Pixabay - 免费插画、矢量图、视频
    • ⚠️ 请注意:即便在这些网站,仍需遵守各自的授权协议。

✨ 功能亮点

  • 支持动态网页 :通过 Selenium 控制 Chrome 浏览器,处理懒加载图片。
  • 可视化界面 :基于 Tkinter,图片抓取后可预览、选择需要的图片。
  • 批量打包下载:一键保存所选图片,自动压缩为 ZIP 文件。
  • 多线程下载ThreadPoolExecutor 提升抓取速度。

环境准备

  1. Python 版本 :推荐使用 Python 3.10+ (本文基于 macOS + Python 3.10 测试)。

  2. 依赖库

    pip install pillow requests selenium beautifulsoup4

Chrome 浏览器与驱动

  • 下载对应版本的 ChromeDriver
  • 修改脚本中的路径:
ini 复制代码
CHROMEDRIVER_PATH = "/你的路径/chromedriver"
CHROME_BINARY_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"

📦 完整代码

支持启动 Chrome、滚动加载、懒加载修复、图片下载、多选保存。

python 复制代码
import os
import time
import requests
from io import BytesIO
from tkinter import Tk, Label, Button, Entry, Canvas, Scrollbar, Frame, filedialog, messagebox, Checkbutton, IntVar, StringVar
from tkinter import ttk
from PIL import Image, ImageTk, UnidentifiedImageError
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import threading
import zipfile
from concurrent.futures import ThreadPoolExecutor

CHROMEDRIVER_PATH = "xxxxx/chromedriver"
CHROME_BINARY_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"

MAX_DOWNLOAD = 1000  # 支持最大下载数量
MAX_THREADS = 10  # 最大线程数


class ImageScraperApp:
    def __init__(self, master):
        self.root = master

        if isinstance(master, Tk):
            self.root.title("网页图片抓取器(动态网页+选择下载+ZIP)")
            self.root.geometry("1400x800")

        Label(master, text="输入网页 URL:").pack(pady=5)
        self.url_entry = Entry(master, width=100)
        self.url_entry.pack(pady=5)

        Button(master, text="抓取图片", command=self.start_fetch).pack(pady=5)
        Button(master, text="全选/取消全选", command=self.toggle_select_all).pack(pady=5)
        Button(master, text="保存选中图片", command=self.save_selected).pack(pady=5)

        self.status_var = StringVar()
        self.status_var.set("状态:等待输入 URL")
        Label(master, textvariable=self.status_var).pack(pady=5)

        # 进度条
        self.progress = ttk.Progressbar(master, orient="horizontal", length=500, mode="determinate")
        self.progress.pack(pady=5)

        self.frame = Frame(master)
        self.frame.pack(fill="both", expand=True)

        self.canvas = Canvas(self.frame, bg="white")
        self.scrollbar = Scrollbar(self.frame, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = Frame(self.canvas, bg="white")

        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        )

        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)

        self.canvas.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")

        self.images = []         # (PIL.Image, 原始字节)
        self.photo_images = []   # 缩略图缓存
        self.image_labels = []   # Checkbutton 控件
        self.check_vars = []     # 选中状态

        # 窗口宽度变化时,重新排布图片
        self.canvas.bind("<Configure>", lambda e: self.rearrange_images())

    def start_fetch(self):
        threading.Thread(target=self.fetch_images, daemon=True).start()

    def fetch_images(self):
        url = self.url_entry.get()
        if not url:
            messagebox.showwarning("提示", "请输入网页 URL")
            return

        for lbl in self.image_labels:
            lbl.destroy()
        self.images.clear()
        self.photo_images.clear()
        self.image_labels.clear()
        self.check_vars.clear()

        self.status_var.set("状态:启动 ChromeDriver...")
        chrome_options = Options()
        chrome_options.binary_location = CHROME_BINARY_PATH
        chrome_options.add_argument("--headless=new")  # 新版本 headless 模式

        service = Service(CHROMEDRIVER_PATH)
        try:
            driver = webdriver.Chrome(service=service, options=chrome_options)
        except Exception as e:
            messagebox.showerror("错误", f"ChromeDriver 或 Chrome 浏览器无法启动:\n{e}")
            self.status_var.set("状态:启动失败")
            return

        try:
            self.status_var.set("状态:加载网页...")
            driver.get(url)
            driver.implicitly_wait(5)

            # 滚动加载,触发懒加载
            last_height = driver.execute_script("return document.body.scrollHeight")
            while True:
                driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
                time.sleep(2)
                new_height = driver.execute_script("return document.body.scrollHeight")
                if new_height == last_height:
                    break
                last_height = new_height

            # 修复 lazyload,把 data-src / data-original 填到 src
            driver.execute_script("""
                document.querySelectorAll('img').forEach(img => {
                    if (img.getAttribute('data-src')) {
                        img.setAttribute('src', img.getAttribute('data-src'));
                    }
                    if (img.getAttribute('data-original')) {
                        img.setAttribute('src', img.getAttribute('data-original'));
                    }
                });
            """)

            html = driver.page_source
            self.status_var.set("状态:网页加载完成")
        except Exception as e:
            messagebox.showerror("错误", f"网页加载失败:\n{e}")
            driver.quit()
            self.status_var.set("状态:网页加载失败")
            return
        finally:
            driver.quit()

        self.status_var.set("状态:解析图片...")
        soup = BeautifulSoup(html, "html.parser")
        img_tags = soup.find_all("img")
        img_urls = [urljoin(url, img.get("src")) for img in img_tags if img.get("src")]
        img_urls = [u for u in img_urls if not u.lower().endswith(".gif")]

        if not img_urls:
            messagebox.showinfo("提示", "没有抓取到图片")
            self.status_var.set("状态:未抓取到图片")
            return

        self.status_var.set(f"状态:开始下载 {len(img_urls)} 张图片...")

        # 初始化进度条
        total = min(len(img_urls), MAX_DOWNLOAD)
        self.progress["maximum"] = total
        self.progress["value"] = 0

        with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
            futures = [executor.submit(self.download_image, img_url, i) for i, img_url in enumerate(img_urls[:MAX_DOWNLOAD])]
            for future in futures:
                future.result()

        self.rearrange_images()
        self.status_var.set(f"状态:抓取完成,共抓取 {len(self.images)} 张图片")
        messagebox.showinfo("提示", f"抓取完成,共抓取 {len(self.images)} 张图片")

    def download_image(self, img_url, idx):
        try:
            r = requests.get(img_url, timeout=10)
            r.raise_for_status()
            img = Image.open(BytesIO(r.content))
            if img.format and img.format.lower() == "gif":
                return

            self.images.append((img, r.content))

            var = IntVar(value=1)
            chk = Checkbutton(self.scrollable_frame, variable=var, bg="white", relief="flat")
            self.check_vars.append(var)
            self.image_labels.append(chk)

            # 更新进度条
            self.progress["value"] = idx + 1
            self.root.update_idletasks()

        except UnidentifiedImageError:
            print(f"无法识别图片: {img_url}")
        except Exception as e:
            print(f"加载图片失败 {img_url}: {e}")

    def rearrange_images(self):
        if not self.image_labels or not self.images:
            return

        col_count = 6  # 固定 6 列
        thumb_width = 200  # 固定宽度 200

        self.photo_images.clear()
        for idx, (chk, (img, _)) in enumerate(zip(self.image_labels, self.images)):
            ratio = thumb_width / img.width
            h = int(img.height * ratio)
            img_preview = img.copy().resize((thumb_width, h))
            photo = ImageTk.PhotoImage(img_preview)
            chk.config(image=photo)
            chk.image = photo
            self.photo_images.append(photo)

            chk.grid(row=idx // col_count, column=idx % col_count, padx=5, pady=5)

    def toggle_select_all(self):
        if not self.check_vars:
            return
        if all(var.get() == 1 for var in self.check_vars):
            for var in self.check_vars:
                var.set(0)
        else:
            for var in self.check_vars:
                var.set(1)

    def save_selected(self):
        selected = [i for i, var in enumerate(self.check_vars) if var.get() == 1]
        if not selected:
            messagebox.showwarning("提示", "没有选择图片")
            return
        if len(selected) > MAX_DOWNLOAD:
            messagebox.showwarning("提示", f"最多支持下载 {MAX_DOWNLOAD} 张图片")
            return

        folder = filedialog.askdirectory(title="选择保存 ZIP 文件目录")
        if not folder:
            return
        zip_path = os.path.join(folder, "images.zip")
        with zipfile.ZipFile(zip_path, "w") as zf:
            for idx in selected:
                img, content = self.images[idx]
                ext = img.format.lower() if img.format else "jpg"
                zf.writestr(f"image_{idx+1}.{ext}", content)

        messagebox.showinfo("提示", f"选中图片已打包保存到 {zip_path}")


def run(master):
    app = ImageScraperApp(master)
    return app


if __name__ == "__main__":
    root = Tk()
    app = ImageScraperApp(root)
    root.mainloop()

🎨 使用效果

运行后会弹出一个 GUI 窗口:

  1. 输入网页 URL → 点击"抓取图片"。
  2. 界面展示缩略图,可逐一选择或全选。
  3. 点击"保存选中图片" → 自动打包为 ZIP 文件。

注意事项与风险提示

  • 版本兼容性

    • macOS 上 --headless=new 参数对老版本 Chrome 可能不兼容,必要时改成 --headless
    • Windows/Linux 用户需要修改 CHROME_BINARY_PATH
  • 访问权限:部分网站可能屏蔽 Selenium 模拟浏览器的访问。

  • 法律合规

    • 仅可抓取开放版权或你有权访问的图片资源。
    • 商业网站图片通常受版权保护,未经授权下载可能带来法律风险。

📌 总结

这份脚本算是一个 学习型小项目,融合了:

  • 爬虫基础(Requests + BeautifulSoup)
  • 动态网页处理(Selenium)
  • Python GUI 开发(Tkinter)
  • 多线程下载与压缩(ThreadPoolExecutor + ZipFile)

你可以在此基础上继续扩展:

  • 支持视频/音频抓取
  • 增加代理池绕过反爬
  • 增加任务日志与断点续传

📢 再次声明

本文所有内容仅限 学习与交流 ,请严格遵守法律法规。

抓取的资源请确保 版权合法,不要用于商业或非法用途。

相关推荐
XiaoMu_00112 小时前
【Flask + Vue3 前后端分离管理系统】
python·flask
BYSJMG12 小时前
计算机Python毕业设计推荐:基于Django+Vue用户评论挖掘旅游系统
大数据·vue.js·hadoop·python·spark·django·课程设计
hvinsion12 小时前
零依赖每月工作计划备忘录:高效管理你的每一天
javascript·css·python·开源·html
二闹12 小时前
别再混淆了 is 和 ==的区别
后端·python
多恩Stone12 小时前
【3D 入门-4】trimesh 极速上手之 3D Mesh 数据结构解析(Vertices / Faces)
数据结构·人工智能·python·3d
多恩Stone13 小时前
【3D 入门-3】常见 3D 格式对比,.glb / .obj / .stl / .ply
人工智能·pytorch·python·深度学习·3d
先做个垃圾出来………13 小时前
242. 有效的字母异位词| 349. 两个数组的交集
开发语言·python
雷达学弱狗13 小时前
通用的二叉数迭代方法
python
青铜发条13 小时前
【python】python进阶——pip命令
python