📢 免责声明
本文内容仅供 学习与技术交流 之用,禁止任何形式的非法使用。
使用本文代码或相关技术抓取的内容,如涉及版权或法律问题,责任由使用者本人承担 。
作者不鼓励、也不支持任何违反法律法规及网站使用条款的行为。
✅ 合规指引
-
尊重版权
- 不要抓取带有版权的图片、视频或数据。
- 商业网站的图片、新闻、影视资源几乎都受保护,未经授权下载、传播可能违法。
-
查看 robots.txt 和用户协议
- 合法网站通常会通过
robots.txt
或服务条款明确是否允许爬虫访问。 - 如果明确禁止,就不要去爬。
- 合法网站通常会通过
-
设置合理的限制
- 控制并发线程,避免对目标服务器造成压力。
- 不要绕过反爬虫策略,否则可能涉及"恶意访问"。
-
🟢 合法示例网站(学习推荐)
✨ 功能亮点
- 支持动态网页 :通过
Selenium
控制 Chrome 浏览器,处理懒加载图片。 - 可视化界面 :基于
Tkinter
,图片抓取后可预览、选择需要的图片。 - 批量打包下载:一键保存所选图片,自动压缩为 ZIP 文件。
- 多线程下载 :
ThreadPoolExecutor
提升抓取速度。
环境准备
-
Python 版本 :推荐使用 Python 3.10+ (本文基于 macOS + Python 3.10 测试)。
-
依赖库:
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 窗口:
- 输入网页 URL → 点击"抓取图片"。
- 界面展示缩略图,可逐一选择或全选。
- 点击"保存选中图片" → 自动打包为 ZIP 文件。

注意事项与风险提示
-
版本兼容性:
- macOS 上
--headless=new
参数对老版本 Chrome 可能不兼容,必要时改成--headless
。 - Windows/Linux 用户需要修改
CHROME_BINARY_PATH
。
- macOS 上
-
访问权限:部分网站可能屏蔽 Selenium 模拟浏览器的访问。
-
法律合规:
- 仅可抓取开放版权或你有权访问的图片资源。
- 商业网站图片通常受版权保护,未经授权下载可能带来法律风险。
📌 总结
这份脚本算是一个 学习型小项目,融合了:
- 爬虫基础(Requests + BeautifulSoup)
- 动态网页处理(Selenium)
- Python GUI 开发(Tkinter)
- 多线程下载与压缩(ThreadPoolExecutor + ZipFile)
你可以在此基础上继续扩展:
- 支持视频/音频抓取
- 增加代理池绕过反爬
- 增加任务日志与断点续传
📢 再次声明
本文所有内容仅限 学习与交流 ,请严格遵守法律法规。
抓取的资源请确保 版权合法,不要用于商业或非法用途。