🚀 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)

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

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

📢 再次声明

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

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

相关推荐
2301_8135995512 小时前
mysql为什么不要在索引列上做运算_mysql函数索引使用场景
jvm·数据库·python
好家伙VCC12 小时前
**发散创新:基于FFmpeg的视频编码优化实践与实战代码解析**在现代多媒体系统中,
java·python·ffmpeg·音视频
人工干智能12 小时前
科普:CountVectorizer、TF、TF-IDF,三者层层递进
python·tf-idf
qq_3422958212 小时前
如何监控集群 interconnect_ping与traceroute验证心跳通畅
jvm·数据库·python
qq_3422958212 小时前
Go语言错误处理如何做_Go语言error错误处理教程【实用】
jvm·数据库·python
qq_3345635512 小时前
如何在phpMyAdmin中执行多条SQL语句_分号分隔与批量执行解析
jvm·数据库·python
2401_8971905512 小时前
Golang如何做Clean Architecture_Golang整洁架构教程【详解】
jvm·数据库·python
qq_1898070313 小时前
如何在网页中实现国际象棋棋子的拖拽与格点吸附功能
jvm·数据库·python
C系语言13 小时前
ONNX Runtime安装
人工智能·python·深度学习
2402_8548083713 小时前
如何管理微服务下Oracle的数据库连接数_调整应用节点的MaxActive汇总以防止超processes
jvm·数据库·python