您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
从Dribbble页面提取并下载图片,跨页面自动重置状态
// ==UserScript== // @name Dribbble 图片批量抓取器 // @namespace http://tampermonkey.net/ // @version 1.1.1 // @description 从Dribbble页面提取并下载图片,跨页面自动重置状态 // @author eddie7x // @license MIT // @icon data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABMLAAATCwAAAAAAAAAAAADin/7/4p/+/8J43P+ILp3/iC6d/61dxP/inv7/45/+/+Oe/v/inv7/4Z/9/9+g/v/fof7/36D+/9+g/v/gn/7/45/+/+Of/v/in/7/pVO7/4gunf+HLZz/um3S/+Ke/v/jn/7/45/+/+Kf/v/goP7/36f9/9+h/v/OoOj/4J/+/+Of/v/jn/7/45/+/92Z+f+XQqz/iS6d/4gunP+3ac//4p7+/+Ke/v/jn/7/4Z/+/9+q/P/gyvb/lJye/+Gt+//jn/7/45/+/+Of/v/in/7/2JPz/5Q+qv+JLp3/iC2c/6ZTvP/clvf/4p7+/+Gp/P/ftPr/3+nu/8rU1//cqvX/45/+/+Of/v/jn/7/45/+/+Oe/v/alPX/m0aw/4kunf+ILZz/jzaj/7xv1f/gpfr/4df0/7S9wP/d5+v/rYDD/+Of/v/jn/7/45/+/+Of/v/jn/7/457+/+Cc/P+vX8b/iC6c/4gtnf+ILZz/ol+2/97d7v+hqav/ubTH/7aBzf/in/7/45/+/+Of/v/jn/7/45/+/+Oe/v/jnv7/4p7+/86G6P+cR7H/hy2c/51YsP+knLD/kZWa/3NIgP+KPp7/45/+/+Of/v/jn/7/45/+/+Of/v/jn/7/45/+/+Of/v/inv7/4p7+/8+P5/+gT7b/0tTf/087Vv9zK4T/iS2d/+Kf/v/inv7/4Z39/92Y+f/emfn/4p79/+Ke/v/inv7/4p7+/+Om/f/jqf3/p1q8/654v/+KR5z/o1+4/6lYv/+gTLf/kTqn/4gunv+ILZ3/hy2c/4kunf+SO6f/ok+5/7hq0P/Vju//0Ynr/4gtnP+ILp3/rmTE/9KU6//in/7/iTCf/4owoP+KMJ//iTCe/4ovn/+JL5//iS+f/4kvnv+JLp7/iC2c/400ov+JLp3/jjWi/92X9//Wl/D/3Zv4/6JPuP+zZcv/v3PY/8R63v/Ded3/vXHW/7FhyP+eSrT/ijCf/4kunv+JLp7/iS6e/5Q9qf/RiOv/4p7+/+Kf/v/in/7/4p/+/+Kf/v/jn/7/4p/+/+Oe/v/inv7/4p7+/8J32/+ILZz/iS6e/4kunv+JLp7/iS+e/7hr0f/inv3/45/+/+Of/v/jn/7/4p/+/+Of/v/inv7/4p7+/8h+4f+LMqH/iS+e/5E5pv+6bdL/jjWj/4gunv+ILZ3/qVjA/+Of/v/jn/7/45/+/+Oe/v/inv7/4p7+/7ls0v+KMZ//iS+e/442o//Riev/4p7+/9iS8/+bRrH/iS6e/4kunv/in/7/4p/+/+Kf/v/inv7/1I3v/6BNt/+JMJ7/ijCf/5E6p//Qiev/4p7+/+Ke/v/inv3/3pv7/6JQuf+JL57/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== // @match https://dribbble.com/* // @grant GM_download // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @require https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js // ==/UserScript== (function() { 'use strict'; // 样式 GM_addStyle(` .dribbble-scraper { position: fixed; bottom: 20px; right: 20px; z-index: 9999; background: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 15px; border: 1px solid #eee; } .scraper-btn { padding: 8px 16px; background: #4285f4; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; margin-right: 8px; transition: background 0.2s; } .scraper-btn:hover { background: #3367d6; } .scraper-status { margin-top: 10px; font-size: 13px; color: #555; } .scraper-result { margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee; font-size: 13px; } .found-count { color: #3498db; font-weight: bold; } .download-count { color: #2ecc71; font-weight: bold; } `); // 界面 const uiElement = document.createElement('div'); uiElement.className = 'dribbble-scraper'; uiElement.innerHTML = ` <button id="scrapeBtn" class="scraper-btn">抓取图片</button> <button id="downloadBtn" class="scraper-btn" disabled>下载图片</button> <div id="scraperStatus" class="scraper-status">准备就绪</div> <div id="scraperResult" class="scraper-result"></div> `; document.body.appendChild(uiElement); const scrapeBtn = document.getElementById('scrapeBtn'); const downloadBtn = document.getElementById('downloadBtn'); const statusEl = document.getElementById('scraperStatus'); const resultEl = document.getElementById('scraperResult'); // 文件名安全化 function sanitizeFilename(name, maxLen = 24) { return name.replace(/[<>:"/\\|?*\n\r]+/g, '').trim().replace(/\s+/g, '_').substring(0, maxLen); } // 提取图片 function extractImageUrls() { statusEl.textContent = "正在解析页面..."; const imageElements = document.querySelectorAll(".shot-page-container div > a > img"); const urls = []; imageElements.forEach(img => { let url = null; const attrs = ["src", "srcset", "data-srcset", "data-src"]; for (const attr of attrs) { const val = img.getAttribute(attr); if (val && val.includes("http")) { url = val.split("?")[0].trim(); if (attr.includes("set")) url = url.split(",")[0].trim(); break; } } if (url) urls.push(url); }); const title = sanitizeFilename(document.title.trim() || "Untitled"); const pageKey = location.href; // 用 URL 做唯一标识 return { title, urls, pageKey }; } // 下载图片 function downloadImages(urls, title) { return new Promise((resolve) => { if (!urls.length) { resolve([]); return; } const results = []; let completed = 0; const total = urls.length; const downloadDir = `Dribbble-${title}`; statusEl.textContent = `开始下载 ${total} 张图片...`; const downloadFile = (url, index) => { return new Promise((resolveFile) => { try { const parse = new URL(url); const base = decodeURIComponent(parse.pathname.split('/').pop()); const [name, ext] = base.split('.'); const fileName = `${name || 'image'}-${index + 1}${ext ? '.' + ext : '.bin'}`; // 改进的GM_download处理 const download = GM_download({ url: url, name: `${downloadDir}/${fileName}`, onload: () => { results.push(fileName); completed++; updateProgress(); resolveFile(); }, onerror: (err) => { console.error(`下载失败: ${url}`, err); completed++; updateProgress(); resolveFile(); }, ontimeout: () => { completed++; updateProgress(); resolveFile(); }, onabort: () => { completed++; updateProgress(); resolveFile(); } }); // 保存下载引用,用于后续状态检查 return download; } catch (err) { console.error(`下载错误: ${url}`, err); completed++; updateProgress(); resolveFile(); } }); }; function updateProgress() { const progress = Math.round((completed / total) * 100); statusEl.textContent = `下载进度: ${progress}% (${completed}/${total})`; if (completed === total) { resolve(results); } } // 使用Promise.all处理下载队列,避免过多并发 const downloadPromises = urls.map((url, i) => downloadFile(url, i)); // 全部下载完成后更新状态 Promise.all(downloadPromises).then(() => { // 确保所有下载都完成后才更新状态 if (completed === total) { resolve(results); } }); }); } // 检查缓存 function checkCache() { const cached = GM_getValue('dribbbleScraperData'); const currentKey = location.href; if (cached && cached.urls && cached.urls.length > 0) { if (cached.pageKey !== currentKey) { console.log("[Dribbble Scraper] 页面切换,清理旧缓存"); clearCache(); } else { statusEl.textContent = `已找到 ${cached.urls.length} 张图片`; resultEl.innerHTML = `<div class="found-count">缓存 ${cached.urls.length} 张图片: ${cached.title}</div>`; downloadBtn.disabled = false; } } else { statusEl.textContent = "准备就绪"; downloadBtn.disabled = true; } } // 清理缓存 function clearCache() { GM_setValue('dribbbleScraperData', { title: '', urls: [], pageKey: '' }); statusEl.textContent = "准备就绪"; resultEl.innerHTML = ''; downloadBtn.disabled = true; } checkCache(); // 按钮事件 scrapeBtn.addEventListener('click', async () => { try { scrapeBtn.disabled = true; downloadBtn.disabled = true; resultEl.innerHTML = ''; const result = extractImageUrls(); if (!result.urls.length) { statusEl.textContent = "未找到可下载的图片"; scrapeBtn.disabled = false; return; } GM_setValue('dribbbleScraperData', result); statusEl.textContent = `已找到 ${result.urls.length} 张图片`; resultEl.innerHTML = `<div class="found-count">发现 ${result.urls.length} 张图片: ${result.title}</div>`; downloadBtn.disabled = false; } catch (err) { console.error("抓取错误:", err); statusEl.textContent = "抓取过程中出错"; } finally { scrapeBtn.disabled = false; } }); downloadBtn.addEventListener('click', async () => { try { downloadBtn.disabled = true; scrapeBtn.disabled = true; resultEl.innerHTML = ''; const data = GM_getValue('dribbbleScraperData'); if (!data || !data.urls || !data.urls.length) { statusEl.textContent = "未找到图片数据,请先抓取图片"; scrapeBtn.disabled = false; return; } const results = await downloadImages(data.urls, data.title); // 使用setTimeout确保UI有机会更新 setTimeout(() => { statusEl.textContent = `下载完成: ${results.length} 张图片`; resultEl.innerHTML = `<div class="download-count">已保存 ${results.length} 个文件到下载目录</div>`; downloadBtn.disabled = false; }, 500); } catch (err) { console.error("下载错误:", err); statusEl.textContent = "下载过程中出错"; } finally { scrapeBtn.disabled = false; } }); // 监听URL变化 function setupUrlChangeListener() { let currentUrl = location.href; setInterval(() => { if (currentUrl !== location.href) { console.log("[Dribbble Scraper] URL变化,清理缓存"); currentUrl = location.href; clearCache(); } }, 500); } // 启动URL变化监听 setupUrlChangeListener(); GM_registerMenuCommand("抓取Dribbble图片", () => { scrapeBtn.click(); }); console.log("[Dribbble Scraper] 已加载"); })();