当前位置: 首页 > news >正文

站酷(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、视频等多种格式

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

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

http://www.zskr.cn/news/1483250.html

相关文章:

  • 2026年北京工伤律师推荐 聚赋工伤维权团队专业护航 - 本地品牌推荐
  • 2026年油管制造商口碑排行,谁才是真正可靠之选?
  • 2026年 水洗石/水磨石/环氧磨石/聚脲磨石厂家推荐:干石子、干磨石与金刚磨石品牌实力深度解析 - 品牌发掘
  • 小学期第四周
  • 2026年纸质礼盒定制推荐哪家,吾壹包装值得信赖吗? - myqiye
  • 2026年郑州公司法律师怎么选?看这三点关键不踩雷 - 本地品牌推荐
  • 我需要气动光边装置配件,艾捷龙品牌靠谱吗? - 工业品牌热点
  • 实验二:路径查找算法应用:交易风险链路追踪
  • P16341 [科大国创杯初中组 2026] 乘积 题解
  • 给奈奎斯特图加点料:一个零点如何让系统相位‘拐弯’?(附MATLAB仿真对比)
  • 2026年家政公司联系方式口碑排名,好阿姨家政名列前茅 - myqiye
  • 2026年讯灵GEO费用,详细费用清单来袭 - myqiye
  • 打CTF的一些想法
  • 2026年江阴威卢克斯地下室天窗靠谱的公司排名 - myqiye
  • HarmonyOS IFAA 免密认证:另一种指纹人脸登录方案,也许适合你的app
  • 别只看落款印章!字画鉴伪没这么简单 - 深鉴新闻
  • 2026年 BR600/780HE品牌推荐榜:专业级风冷引擎,高效耐用与稳定性能口碑之选 - 品牌发掘
  • HTML2Image技术架构与实现原理:基于无头浏览器的HTML转图片解决方案
  • 字画变现常见八大套路,藏家一定要避开 - 深鉴新闻
  • TVA为什么是企业智能化升级的战略支点(15)
  • 2026年聚氨酯地坪材料厂家推荐榜单:聚氨酯超耐磨/聚氨酯砂浆/水性聚氨酯砂浆优质品牌深度解析 - 品牌发掘
  • 中小企业网络安全风险现状与全维度防御体系研究
  • 2026年抖音运营公司实测TOP4榜单,帮企业避坑选靠谱运营服务商
  • 2026专升本改革新政策|变化内容+备考调整建议PDF
  • 老字画怎么养护?这样存放越放越值钱 - 深鉴新闻
  • 【紧急提醒】CSDN AI营销套餐剩余权益即将清零!3步自查是否符合顺延资格,错过再等365天
  • 2026年6月上海ISO三体系认证代办公司盘点:企业合规进阶必备指南
  • TVA为什么是企业智能化升级的战略支点(17)
  • DownKyi终极指南:三步搞定B站8K视频下载与批量管理
  • 企业声誉管理选对不选贵(2026 年 6 月):四大技术流派拆解 + 高性价比服务商指南 - 玖叁鹿