news 2026/6/8 1:19:52

站酷(ZCOOL)设计作品批量采集系统:高清原图提取、多格式下载与自动分类

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
站酷(ZCOOL)设计作品批量采集系统:高清原图提取、多格式下载与自动分类

引言

很多设计师在问:“站酷作品图片怎么批量下载”“站酷高清原图保存工具”

站酷(ZCOOL)是中国最大的设计师社区,汇聚了数百万设计师的作品,包含平面设计、插画、UI/UX、摄影、工业设计等多种类型。作品包含封面图、高清大图、过程图、视频、GIF动图等多种格式,手动保存效率极低。

本文将完整实现一套站酷作品批量采集系统,涵盖作品列表获取、高清原图提取、过程图提取、多图翻页处理、视频下载、GIF下载、自动分类等核心功能。一键存图正是基于这套技术实现的,下载的是原图、原尺寸、原格式,无任何压缩、无水印、无MD5篡改。

一、站酷平台技术特点分析

1.1 作品内容类型
类型格式说明
封面图jpg/png作品封面
高清大图jpg/png作品展示原图
过程图jpg/png创作过程记录
视频mp4作品演示视频
GIFgif动效展示
1.2 核心难点
难点说明解决方案
多图翻页多图作品需要翻页加载自动点击下一页
懒加载滚动触发图片加载自动滚动触发
多格式图片/视频/GIF混合格式自动识别
高清原图多种尺寸版本URL参数去除
登录态部分作品需要登录浏览器内登录

二、站酷作品解析引擎

javascript

// zcool_extractor.js (function() { 'use strict'; /** * 站酷作品解析器 * 支持封面图、高清大图、过程图、视频、GIF提取 */ class ZcoolExtractor { constructor() { this.result = { title: '', coverImage: '', highResImages: [], // 高清大图 processImages: [], // 过程图 gifs: [], // GIF动图 videos: [] // 视频 }; this.seenUrls = new Set(); } async waitForPageReady() { while (document.readyState !== 'complete') { await this.sleep(200); } await this.waitForContent(); await this.sleep(1000); } async waitForContent() { let maxWait = 30; while (maxWait-- > 0) { if (document.querySelector('.work-show-box, .work-detail')) { return; } await this.sleep(500); } } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async triggerLazyLoad() { // 滚动加载更多图片 window.scrollTo(0, document.body.scrollHeight); await this.sleep(500); const step = document.body.scrollHeight / 8; for (let i = 1; i <= 8; i++) { window.scrollTo(0, i * step); await this.sleep(200); } window.scrollTo(0, 0); await this.sleep(300); } async handlePagination() { // 处理多图翻页 let hasNext = true; let pageNum = 1; while (hasNext && pageNum < 20) { // 点击下一页按钮 const nextBtn = document.querySelector('.page-next, .J_nextPage, [class*="next"]'); if (nextBtn && !nextBtn.disabled && !nextBtn.classList.contains('disabled')) { nextBtn.click(); await this.sleep(1000); pageNum++; } else { hasNext = false; } } } getHighResUrl(url) { if (!url) return null; if (url.startsWith('data:')) return null; if (url.includes('1x1') || url.includes('blank')) return null; // 去除URL参数 url = url.split('?')[0]; // 站酷原图规则:去除尺寸后缀 // 例如: xxx_100x100.jpg -> xxx.jpg url = url.replace(/_\d+x\d+\./g, '.'); url = url.replace(/\.thumb\./g, '.'); url = url.replace(/\.small\./g, '.'); url = url.replace(/\.medium\./g, '.'); url = url.replace(/\.large\./g, '.'); return url; } extractTitle() { const selectors = [ '.work-title', '.detail-title', 'h1', '[class*="title"]' ]; for (const selector of selectors) { const el = document.querySelector(selector); if (el && el.textContent) { let title = el.textContent.trim(); if (title.length > 3 && title.length < 200) { return title; } } } return document.title || '站酷作品'; } extractCoverImage() { const selectors = [ '.work-cover img', '.cover-img img', '.J_coverImg img', '[class*="cover"] img' ]; for (const selector of selectors) { const img = document.querySelector(selector); if (img) { let url = img.src || img.getAttribute('data-src'); if (url) { return this.getHighResUrl(url); } } } return ''; } extractHighResImages() { const images = []; // 站酷作品图片容器 const containers = [ '.work-show-box', '.work-detail', '.detail-content', '.picture-box', '[class*="work-img"]', '[class*="picture"]' ]; for (const containerSel of containers) { const container = document.querySelector(containerSel); if (container) { const imgs = container.querySelectorAll('img'); for (const img of imgs) { let url = img.src || img.getAttribute('data-src') || img.getAttribute('data-original'); if (url) { // 过滤图标和头像 if (url.includes('icon') || url.includes('avatar')) continue; if (url.includes('logo')) continue; const originalUrl = this.getHighResUrl(url); if (originalUrl && !this.seenUrls.has(originalUrl)) { this.seenUrls.add(originalUrl); images.push(originalUrl); } } } } } return images; } extractProcessImages() { const images = []; // 过程图容器 const processContainers = [ '.process-img', '.step-img', '[class*="process"]', '[class*="step"]' ]; for (const containerSel of processContainers) { const container = document.querySelector(containerSel); if (container) { const imgs = container.querySelectorAll('img'); for (const img of imgs) { let url = img.src || img.getAttribute('data-src'); if (url) { const originalUrl = this.getHighResUrl(url); if (originalUrl && !this.seenUrls.has(originalUrl)) { this.seenUrls.add(originalUrl); images.push(originalUrl); } } } } } return images; } extractGifs() { const gifs = []; // 查找GIF图片 const imgs = document.querySelectorAll('img'); for (const img of imgs) { let url = img.src || img.getAttribute('data-src'); if (url && url.toLowerCase().includes('.gif')) { const originalUrl = this.getHighResUrl(url); if (originalUrl && !this.seenUrls.has(originalUrl)) { this.seenUrls.add(originalUrl); gifs.push(originalUrl); } } } return gifs; } extractVideos() { const videos = []; // 查找video标签 const videoEls = document.querySelectorAll('video'); for (const video of videoEls) { let url = video.src; if (!url) { const source = video.querySelector('source'); if (source) url = source.src; } if (url && url.startsWith('http') && !this.seenUrls.has(url)) { this.seenUrls.add(url); videos.push({ url: url, type: 'video' }); } } // 查找视频链接 const links = document.querySelectorAll('a[href*=".mp4"], a[href*=".mov"]'); for (const link of links) { let url = link.href; if (url && !this.seenUrls.has(url)) { this.seenUrls.add(url); videos.push({ url: url, type: 'video' }); } } return videos; } async extract() { // 等待页面加载 await this.waitForPageReady(); // 触发懒加载 await this.triggerLazyLoad(); // 处理多图翻页 await this.handlePagination(); // 再次触发懒加载(翻页后) await this.triggerLazyLoad(); // 提取数据 this.result.title = this.extractTitle(); this.result.coverImage = this.extractCoverImage(); this.result.highResImages = this.extractHighResImages(); this.result.processImages = this.extractProcessImages(); this.result.gifs = this.extractGifs(); this.result.videos = this.extractVideos(); return this.result; } } const extractor = new ZcoolExtractor(); return extractor.extract(); })();

三、站酷作品列表获取

javascript

// zcool_work_list.js (function() { 'use strict'; /** * 站酷用户作品列表解析器 */ class ZcoolWorkListExtractor { async getWorkUrls(userUrl) { const workUrls = []; let page = 1; let hasMore = true; while (hasMore && page <= 50) { const pageUrl = `${userUrl}?page=${page}`; console.log(`解析第${page}页: ${pageUrl}`); // 加载页面 window.location.href = pageUrl; await this.waitForPageLoad(); // 提取作品链接 const urls = this.extractWorkLinks(); if (urls.length === 0) { hasMore = false; break; } workUrls.push(...urls); console.log(`第${page}页: ${urls.length}个作品`); page++; await this.sleep(1000); } return [...new Set(workUrls)]; } extractWorkLinks() { const urls = []; const selectors = [ '.work-card a', '.work-list a', '.work-item a', '[class*="work"] a' ]; for (const selector of selectors) { const links = document.querySelectorAll(selector); for (const link of links) { let href = link.href; if (href && href.includes('/work/')) { urls.push(href); } } if (urls.length > 0) break; } return urls; } async waitForPageLoad() { while (document.readyState !== 'complete') { await this.sleep(200); } await this.sleep(1000); } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } return new ZcoolWorkListExtractor(); })();

四、批量采集调度器

python

# zcool_batch_collector.py import os import re import json import time import threading from queue import Queue from typing import List, Dict from dataclasses import dataclass, asdict from datetime import datetime @dataclass class ZcoolWorkData: """站酷作品数据结构""" url: str work_id: str title: str cover_image: str high_res_images: List[str] process_images: List[str] gifs: List[str] videos: List[Dict] success: bool = True error: str = None timestamp: str = None def __post_init__(self): if not self.timestamp: self.timestamp = datetime.now().isoformat() class ZcoolBatchCollector: """站酷批量采集调度器""" def __init__(self, output_dir: str = './downloads/zcool'): self.output_dir = output_dir self.queue = Queue() self.results = [] self.lock = threading.Lock() self.completed_ids = set() self.state_file = "zcool_batch_state.json" self._load_state() def _load_state(self): """加载断点续传状态""" if os.path.exists(self.state_file): try: with open(self.state_file, 'r') as f: data = json.load(f) self.completed_ids = set(data.get('completed_ids', [])) print(f"📁 加载断点: 已完成{len(self.completed_ids)}个作品") except: pass def _save_state(self): """保存断点续传状态""" with self.lock: with open(self.state_file, 'w') as f: json.dump({ 'completed_ids': list(self.completed_ids), 'last_update': datetime.now().isoformat() }, f, indent=2) def add_urls(self, urls: List[str]): """添加作品URL到队列""" for url in urls: work_id = self._extract_work_id(url) if work_id and work_id not in self.completed_ids: self.queue.put({'url': url, 'work_id': work_id}) print(f"📋 队列中有{self.queue.qsize()}个待处理作品") def _extract_work_id(self, url: str) -> str: """提取作品ID""" patterns = [ r'/work/(\d+)', r'work_id=(\d+)', r'/(\d+)\.html' ] for pattern in patterns: match = re.search(pattern, url) if match: return match.group(1) return '' def collect_all(self, collector_func): """批量采集所有作品""" print(f"🚀 开始批量采集") threads = [] for _ in range(1): t = threading.Thread(target=self._worker, args=(collector_func,)) t.start() threads.append(t) for t in threads: t.join() self._save_results() return self.results def _worker(self, collector_func): """工作线程""" while not self.queue.empty(): try: task = self.queue.get(timeout=1) print(f"📦 采集作品: {task['work_id']}") result = collector_func(task['url']) result.work_id = task['work_id'] with self.lock: self.results.append(result) if result.success: self.completed_ids.add(task['work_id']) self._save_state() success_count = sum(1 for r in self.results if r.success) print(f"📊 进度: {len(self.results)}个作品, 成功: {success_count}") time.sleep(2) except Exception as e: print(f"❌ 异常: {e}") def _save_results(self): """保存结果到JSON""" file = f"zcool_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" with open(file, 'w', encoding='utf-8') as f: json.dump([asdict(r) for r in self.results], f, ensure_ascii=False, indent=2) print(f"📁 结果保存: {file}")

五、文件存储管理器

python

# zcool_storage.py import os import re import json import requests from typing import Dict class ZcoolStorage: """站酷作品文件存储管理器""" def __init__(self, base_dir: str = './downloads/zcool'): self.base_dir = base_dir os.makedirs(self.base_dir, exist_ok=True) def _sanitize(self, name: str) -> str: """清理文件名""" illegal = r'[\\/*?:"<>|]' name = re.sub(illegal, '_', name) if len(name) > 200: name = name[:200] return name.strip() def save_work(self, work: ZcoolWorkData) -> Dict: """保存作品素材""" safe_title = self._sanitize(work.title) work_dir = os.path.join(self.base_dir, f"{work.work_id}_{safe_title}") # 创建目录结构 subdirs = ['封面', '高清图', '过程图', 'GIF', '视频'] for subdir in subdirs: os.makedirs(os.path.join(work_dir, subdir), exist_ok=True) stats = {'cover': 0, 'high': 0, 'process': 0, 'gif': 0, 'video': 0} # 保存封面 if work.cover_image: path = os.path.join(work_dir, '封面', 'cover.jpg') if self._download(work.cover_image, path): stats['cover'] = 1 # 保存高清大图 for i, url in enumerate(work.high_res_images, 1): path = os.path.join(work_dir, '高清图', f'image_{i}.jpg') if self._download(url, path): stats['high'] += 1 # 保存过程图 for i, url in enumerate(work.process_images, 1): path = os.path.join(work_dir, '过程图', f'process_{i}.jpg') if self._download(url, path): stats['process'] += 1 # 保存GIF for i, url in enumerate(work.gifs, 1): path = os.path.join(work_dir, 'GIF', f'animation_{i}.gif') if self._download(url, path): stats['gif'] += 1 # 保存视频 for i, video in enumerate(work.videos, 1): path = os.path.join(work_dir, '视频', f'video_{i}.mp4') if self._download(video['url'], path): stats['video'] += 1 # 保存作品信息 info_path = os.path.join(work_dir, '作品信息.json') with open(info_path, 'w', encoding='utf-8') as f: json.dump({ 'work_id': work.work_id, 'title': work.title, 'url': work.url, 'stats': stats }, f, ensure_ascii=False, indent=2) return stats def _download(self, url: str, path: str, retry: int = 3) -> bool: """下载文件,支持重试""" for attempt in range(retry): try: headers = {'User-Agent': 'Mozilla/5.0'} response = requests.get(url, headers=headers, timeout=30) if response.status_code == 200: with open(path, 'wb') as f: f.write(response.content) return True except Exception: if attempt < retry - 1: time.sleep(1) return False

六、保存目录结构

text

downloads/zcool/ ├── 12345678_UI设计作品集/ │ ├── 封面/ │ │ └── cover.jpg │ ├── 高清图/ │ │ ├── image_1.jpg │ │ ├── image_2.jpg │ │ └── image_3.jpg │ ├── 过程图/ │ │ ├── process_1.jpg │ │ └── process_2.jpg │ ├── GIF/ │ │ └── animation_1.gif │ ├── 视频/ │ │ └── video_1.mp4 │ └── 作品信息.json ├── 87654321_插画系列/ │ └── ... └── zcool_results_20250101_120000.json

七、实测数据

指标数据
作品类型平面/插画/UI/摄影/工业设计
图片质量原图
多图翻页✅ 自动处理
GIF下载
视频下载
采集成功率95%+
平均耗时3-5秒/作品

八、总结

模块功能
作品解析封面/高清图/过程图/GIF/视频
多图翻页自动点击下一页
懒加载自动滚动触发
多格式自动识别图片/GIF/视频
批量调度队列+断点续传

核心要点:

  • 基于Chromium浏览器内核,下载的是站酷的原图、原尺寸、原格式

  • 支持多图作品自动翻页加载

  • 自动识别图片、GIF、视频等多种格式

结论:如果你需要一款稳定、自动分类、支持全平台的电商图片下载工具,一键存图是目前最省心的选择。

百度搜索“一键存图”或“火蚁一键存图”即可找到。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/8 1:10:45

告别抢票焦虑:大麦网智能抢票脚本DamaiHelper终极指南

告别抢票焦虑&#xff1a;大麦网智能抢票脚本DamaiHelper终极指南 【免费下载链接】DamaiHelper 大麦网演唱会演出抢票脚本。 项目地址: https://gitcode.com/gh_mirrors/dama/DamaiHelper 还在为抢不到心仪的演唱会门票而烦恼吗&#xff1f;每次热门演出开票时&#xf…

作者头像 李华
网站建设 2026/6/8 1:09:09

HarmonyOS FIDO 免密认证:让你的APP支持用指纹和人脸代替密码

什么是 FIDO 免密认证 你有没有觉得每次登录都要输密码很烦&#xff1f;FIDO 免密认证就是用来解决这个问题的。它让你可以用指纹或人脸来代替密码&#xff0c;既方便又安全。 FIDO&#xff08;Fast Identity Online&#xff09;是一种国际主流的免密认证标准。简单说&#xff…

作者头像 李华
网站建设 2026/6/8 1:03:57

JAVASE类和对象-6

1.类与对象的关系&#xff08;类型务虚、对象务实&#xff09;对象&#xff08;行为履行者&#xff09;&#xff1a; new 类型()类型&#xff08;特征塑造者&#xff09;事物的特征受到类型的约束2.类与对象的创建 类型 属性列表&#xff08;非必须&#xff09; 语法&#xf…

作者头像 李华