您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一键批量下载微信公众号文章中的图片、视频和音频文件,适用普通长文和小绿书
// ==UserScript== // @name 2025最新_微信公众号媒体文件批量下载器(公众号:掌心向暖) // @namespace http://tampermonkey.net/ // @version 1.4 // @description 一键批量下载微信公众号文章中的图片、视频和音频文件,适用普通长文和小绿书 // @author You // @match https://mp.weixin.qq.com/s/* // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js // ==/UserScript== (function() { 'use strict'; // 等待页面加载完成 window.addEventListener('load', function() { init(); }); function init() { // 创建按钮容器 createButtons(); } // 创建扫描媒体和一键下载按钮 function createButtons() { // 创建按钮容器 - 固定在页面右侧 const buttonContainer = document.createElement('div'); buttonContainer.id = 'mediaDownloadContainer'; buttonContainer.style.cssText = ` position: fixed; top: 50%; right: 20px; transform: translateY(-50%); z-index: 9999; background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); padding: 15px; width: 150px; text-align: center; `; // 扫描媒体按钮 const scanButton = document.createElement('button'); scanButton.textContent = '扫描媒体'; scanButton.style.cssText = ` width: 100%; margin: 8px 0; padding: 10px 15px; background: #1aad19; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: background-color 0.3s; `; scanButton.onclick = scanMedia; // 添加悬停效果 scanButton.onmouseover = function() { this.style.background = '#16941a'; }; scanButton.onmouseout = function() { this.style.background = '#1aad19'; }; // 一键下载按钮 const downloadButton = document.createElement('button'); downloadButton.textContent = '一键下载'; downloadButton.style.cssText = ` width: 100%; margin: 8px 0; padding: 10px 15px; background: #576b95; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: background-color 0.3s; `; downloadButton.onclick = downloadAllMedia; // 添加悬停效果 downloadButton.onmouseover = function() { this.style.background = '#4a5a82'; }; downloadButton.onmouseout = function() { this.style.background = '#576b95'; }; // 进度显示区域 const progressDiv = document.createElement('div'); progressDiv.id = 'downloadProgress'; progressDiv.style.cssText = ` margin: 10px 0; padding: 8px; font-size: 12px; color: #666; background: #f8f8f8; border-radius: 4px; line-height: 1.4; word-wrap: break-word; display: none; `; buttonContainer.appendChild(scanButton); buttonContainer.appendChild(downloadButton); buttonContainer.appendChild(progressDiv); // 将按钮容器添加到页面 document.body.appendChild(buttonContainer); console.log('媒体下载器按钮已创建在页面右侧'); } // 扫描媒体文件 function scanMedia() { const images = getImages(); const videos = getVideos(); const audios = getAudios(); const progressDiv = document.getElementById('downloadProgress'); progressDiv.style.display = 'block'; progressDiv.innerHTML = ` <div><strong>扫描结果:</strong></div> <div>图片:${images.length} 张</div> <div>视频:${videos.length} 个</div> <div>音频:${audios.length} 个</div> `; console.log('扫描结果:', { images, videos, audios }); // 3秒后自动隐藏扫描结果 setTimeout(() => { progressDiv.style.display = 'none'; }, 3000); } // 获取所有图片(修改后的逻辑,兼容两种类型的文章) function getImages() { const images = []; const imageUrls = []; // 用于去重 // 方式1:针对包含视频类型的文章 - 从js_content区域获取图片 const jsContent = document.getElementById('js_content'); if (jsContent) { const imgElements = jsContent.getElementsByTagName('img'); for (let i = 0; i < imgElements.length; i++) { const img = imgElements[i]; // 过滤条件 if (img.getAttribute('data-w') === '64') continue; if (img.closest('.swiper_indicator_wrp_pc')) continue; let imgUrl = ''; // 优先使用dataset.src,然后使用src if (img.dataset.src) { imgUrl = img.dataset.src; } else if (img.src && !img.src.startsWith('data:')) { imgUrl = img.src; // 处理微信资源链接 imgUrl = imgUrl.replace("//res.wx.qq.com/mmbizwap", "http://res.wx.qq.com/mmbizwap"); } // 去重检查 if (!imgUrl || imageUrls.includes(imgUrl)) continue; imageUrls.push(imgUrl); // 根据URL判断图片格式 let extension = '.jpg'; // 默认jpg if (imgUrl.indexOf('wx_fmt=gif') > 0 || imgUrl.indexOf('mmbiz_gif') > 0) { extension = '.gif'; } else if (imgUrl.indexOf('wx_fmt=png') > 0 || imgUrl.indexOf('mmbiz_png') > 0) { extension = '.png'; } else if (imgUrl.indexOf('wx_fmt=bmp') > 0 || imgUrl.indexOf('mmbiz_bmp') > 0) { extension = '.bmp'; } else if (imgUrl.indexOf('wx_fmt=webp') > 0 || imgUrl.indexOf('mmbiz_webp') > 0) { extension = '.webp'; } images.push({ url: imgUrl, filename: `image_${images.length + 1}${extension}` }); } // 如果从js_content找到了图片,直接返回 if (images.length > 0) { return images; } } // 方式2:针对小绿书类型的文章 - 查找 .swiper_item_img 下的图片 const swiperImages = document.querySelectorAll('.swiper_item_img img'); if (swiperImages.length > 0) { swiperImages.forEach(img => { if (img.src && !imageUrls.includes(img.src)) { imageUrls.push(img.src); // 获取图片扩展名 let extension = getImageExtension(img.src); images.push({ url: img.src, filename: `image_${images.length + 1}.${extension}` }); } }); // 如果从swiper找到了图片,直接返回 if (images.length > 0) { return images; } } // 方式3:通用方法 - 如果以上两种方式都没找到图片 const allImgElements = document.querySelectorAll('img'); allImgElements.forEach((img) => { // 过滤条件 if (img.getAttribute('data-w') === '64') return; if (img.closest('.swiper_indicator_wrp_pc')) return; if (!img.src || img.src.startsWith('data:')) return; // 去重检查 if (imageUrls.includes(img.src)) return; imageUrls.push(img.src); // 获取图片后缀 let extension = '.png'; // 默认png const srcUrl = img.src; if (srcUrl.includes('.jpg') || srcUrl.includes('.jpeg')) { extension = '.jpg'; } else if (srcUrl.includes('.gif')) { extension = '.gif'; } else if (srcUrl.includes('.webp')) { extension = '.webp'; } images.push({ url: srcUrl, filename: `image_${images.length + 1}${extension}` }); }); return images; } // 获取图片扩展名(从参考脚本中提取的函数) function getImageExtension(url) { const match = url.match(/\.([a-zA-Z0-9]+)(?:\?|$)/); if (match) { return match[1].toLowerCase(); } // 检查微信图片URL中的格式参数 if (url.includes('wx_fmt=')) { const formatMatch = url.match(/wx_fmt=([a-zA-Z0-9]+)/); if (formatMatch) { return formatMatch[1].toLowerCase(); } } return 'jpg'; // 默认扩展名 } // 获取所有视频 function getVideos() { const videos = []; const videoElements = document.querySelectorAll('video'); videoElements.forEach((video, index) => { let videoUrl = video.src; // 如果video标签没有src,查找source标签 if (!videoUrl) { const source = video.querySelector('source'); if (source) { videoUrl = source.src; } } // 只处理微信视频链接 if (videoUrl && videoUrl.includes('mpvideo.qpic.cn')) { videos.push({ url: videoUrl, filename: `video_${index + 1}.mp4` }); } }); return videos; } // 获取所有音频(修改后的逻辑) function getAudios() { const audios = []; const audioUrls = []; // 用于去重 // 方法1:查找具有voice_encode_fileid属性的元素 const voiceElements = document.querySelectorAll('[voice_encode_fileid]'); voiceElements.forEach((element, index) => { const voiceId = element.getAttribute('voice_encode_fileid'); if (voiceId) { const audioUrl = `https://res.wx.qq.com/voice/getvoice?mediaid=${voiceId}`; // 去重检查 if (!audioUrls.includes(audioUrl)) { audioUrls.push(audioUrl); audios.push({ url: audioUrl, filename: `audio_${audios.length + 1}.mp3` }); } } }); // 方法2:查找audio标签中的微信语音链接(作为备用方法) const audioElements = document.querySelectorAll('audio'); audioElements.forEach((audio, index) => { if (audio.src && audio.src.includes('res.wx.qq.com/voice')) { // 去重检查 if (!audioUrls.includes(audio.src)) { audioUrls.push(audio.src); audios.push({ url: audio.src, filename: `audio_${audios.length + 1}.mp3` }); } } }); console.log('音频识别结果:', audios); return audios; } // 下载单个文件 async function downloadFile(url, filename) { try { // 尝试直接fetch let response = await fetch(url); // 如果跨域失败,尝试通过代理或其他方式 if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const blob = await response.blob(); return { filename, blob, success: true }; } catch (error) { console.error(`下载失败: ${filename}`, error); // 尝试使用代理方式下载 try { const proxyUrl = `https://cors-anywhere.herokuapp.com/${url}`; const response = await fetch(proxyUrl); const blob = await response.blob(); return { filename, blob, success: true }; } catch (proxyError) { console.error(`代理下载也失败: ${filename}`, proxyError); return { filename, success: false, error: error.message }; } } } // 批量下载所有媒体文件 async function downloadAllMedia() { const progressDiv = document.getElementById('downloadProgress'); progressDiv.style.display = 'block'; progressDiv.innerHTML = '正在准备下载...'; const images = getImages(); const videos = getVideos(); const audios = getAudios(); const allFiles = [...images, ...videos, ...audios]; if (allFiles.length === 0) { progressDiv.innerHTML = '未找到可下载的媒体文件'; setTimeout(() => { progressDiv.style.display = 'none'; }, 3000); return; } // 创建JSZip实例 const zip = new JSZip(); let downloadedCount = 0; let failedCount = 0; progressDiv.innerHTML = `开始下载 ${allFiles.length} 个文件...`; // 并发下载文件 const downloadPromises = allFiles.map(async (file, index) => { const result = await downloadFile(file.url, file.filename); if (result.success) { zip.file(result.filename, result.blob); downloadedCount++; } else { failedCount++; console.error(`文件下载失败: ${file.filename}`); } // 更新进度 progressDiv.innerHTML = ` 下载进度: ${downloadedCount + failedCount}/${allFiles.length}<br> 成功: ${downloadedCount} 个<br> 失败: ${failedCount} 个 `; return result; }); // 等待所有下载完成 await Promise.all(downloadPromises); if (downloadedCount === 0) { progressDiv.innerHTML = '所有文件下载失败,请检查网络连接或文件权限'; setTimeout(() => { progressDiv.style.display = 'none'; }, 5000); return; } // 生成压缩包 progressDiv.innerHTML = '正在生成压缩包...'; try { const zipContent = await zip.generateAsync({ type: 'blob' }); // 获取页面标题作为压缩包名称 const title = document.title.replace(/[<>:"/\\|?*]/g, '_'); // 替换不合法的文件名字符 const zipFilename = `${title}.zip`; // 下载压缩包 const link = document.createElement('a'); link.href = URL.createObjectURL(zipContent); link.download = zipFilename; document.body.appendChild(link); link.click(); document.body.removeChild(link); progressDiv.innerHTML = ` <div><strong>下载完成!</strong></div> <div>成功: ${downloadedCount} 个</div> ${failedCount > 0 ? `<div>失败: ${failedCount} 个</div>` : ''} <div>压缩包: ${zipFilename}</div> `; // 5秒后自动隐藏完成信息 setTimeout(() => { progressDiv.style.display = 'none'; }, 5000); } catch (error) { console.error('生成压缩包失败:', error); progressDiv.innerHTML = '生成压缩包失败,请重试'; setTimeout(() => { progressDiv.style.display = 'none'; }, 3000); } } })();