您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在豆瓣动态旁添加“下载”按钮,一键下载所有图片。智能识别实况图(Live Photo),并同时下载其JPG和MP4视频文件。
// ==UserScript== // @name 豆瓣帖子图片和实况图下载 // @namespace http://tampermonkey.net/ // @version 1.6 // @description 在豆瓣动态旁添加“下载”按钮,一键下载所有图片。智能识别实况图(Live Photo),并同时下载其JPG和MP4视频文件。 // @author Gemini // @match https://www.douban.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=douban.com // @grant GM_download // @license MIT // ==/UserScript== (function() { 'use strict'; function log(message, ...args) { console.log('[豆瓣下载脚本]', message, ...args); } log('脚本已启动 (v1.6 - 实况图支持)'); const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.addedNodes.length) { for (const node of mutation.addedNodes) { if (node.nodeType === 1 && (node.querySelector('.status-item') || node.classList.contains('status-item'))) { addDownloadButtons(); break; } } } }); }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(addDownloadButtons, 1500); function addDownloadButtons() { const statusItems = document.querySelectorAll('.status-item'); statusItems.forEach(item => { const actionsBar = item.querySelector('.actions'); if (actionsBar && !actionsBar.querySelector('.download-btn')) { const downloadBtn = document.createElement('a'); downloadBtn.href = '#'; downloadBtn.textContent = '下载'; downloadBtn.className = 'download-btn'; downloadBtn.style.marginLeft = '10px'; downloadBtn.style.color = '#37a'; downloadBtn.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); if (downloadBtn.textContent !== '下载') return; downloadBtn.textContent = '处理中...'; try { handleDownload(item, actionsBar); } finally { setTimeout(() => { downloadBtn.textContent = '下载'; }, 1000); } }); actionsBar.appendChild(downloadBtn); } }); } function findAllImagesAsFallback(postContainer, urlSet) { log('使用备用方法查找图片。'); const images = postContainer.querySelectorAll('.pics-wrapper img, .horizontal-photos img, .block-subject .pic img'); images.forEach(img => { const url = img.getAttribute('data-original-src') || img.src; if (url) { urlSet.add(url.replace(/\/(l|m|s|sqs|albumicon|small)\//, '/raw/')); } }); } function handleDownload(postContainer, actionsBar) { const urls = new Set(); // **核心逻辑:优先解析页面内嵌的JSON数据,因为它包含了实况图信息** const scriptTag = Array.from(postContainer.querySelectorAll('.pics-wrapper script')).find(s => s.textContent.includes('var photos')); if (scriptTag) { log('找到图片数据脚本,开始解析...'); const scriptContent = scriptTag.textContent; const jsonMatch = scriptContent.match(/var photos = (.*?);/s); if (jsonMatch && jsonMatch[1]) { try { const photos = JSON.parse(jsonMatch[1]); log(`解析成功,找到 ${photos.length} 个图片对象。`); photos.forEach(photoData => { const imgUrl = (photoData.image && (photoData.image.large.url || photoData.image.normal.url)) || ''; if (imgUrl) { // 始终添加图片URL urls.add(imgUrl); // **如果 is_live 为 true,则推断并添加视频URL** if (photoData.image.is_live) { // 通常,视频文件的URL与图片URL相同,只是扩展名不同 const videoUrl = imgUrl.replace(/\.jpg/i, '.mp4'); if (videoUrl !== imgUrl) { log('发现实况图,添加视频URL:', videoUrl); urls.add(videoUrl); } } } }); } catch (e) { log('解析图片JSON失败,将使用备用方法。', e); findAllImagesAsFallback(postContainer, urls); } } else { findAllImagesAsFallback(postContainer, urls); } } else { // 如果没有找到内嵌脚本,则使用旧的备用方法 findAllImagesAsFallback(postContainer, urls); } // 确保也找到独立的 video 标签(如果有的话) postContainer.querySelectorAll('video').forEach(video => { if (video.src) urls.add(video.src); }); const urlList = Array.from(urls); log(`文件列表准备就绪,共 ${urlList.length} 个文件:`, urlList); if (urlList.length === 0) { alert('没有找到可下载的图片或视频。'); return; } // 批量发起下载 urlList.forEach((url, index) => { const postId = postContainer.dataset.sid || `douban_${Date.now()}`; let filename = url.substring(url.lastIndexOf('/') + 1).split('?')[0]; filename = filename.replace(/[^a-zA-Z0-9._-]/g, ''); if (!filename || filename.length < 4 || !filename.includes('.')) { const extension = url.includes('.mp4') ? 'mp4' : (url.includes('.gif') ? 'gif' : 'jpg'); filename = `${postId}_${index + 1}.${extension}`; } else { filename = `${postId}_${filename}`; } try { GM_download({ url: url, name: filename }); } catch (e) { log(`GM_download 调用失败 for ${filename}`, e); } }); // 更新UI提示 const oldNotification = actionsBar.querySelector('.download-notification'); if(oldNotification) oldNotification.remove(); const notification = document.createElement('span'); notification.textContent = `已发起 ${urlList.length} 个下载!`; notification.className = 'download-notification'; notification.style.marginLeft = '10px'; notification.style.color = '#008000'; actionsBar.appendChild(notification); setTimeout(() => { notification.remove(); }, 4000); } })();