// ==UserScript== // @name XHS-Downloader // @namespace https://github.com/JoeanAmier/XHS-Downloader // @version 2.0.1 // @description 提取小红书作品/用户链接,下载小红书无水印图文/视频作品文件 // @author JoeanAmier // @match http*://xhslink.com/* // @match http*://www.xiaohongshu.com/explore* // @match http*://www.xiaohongshu.com/user/profile/* // @match http*://www.xiaohongshu.com/search_result* // @match http*://www.xiaohongshu.com/board/* // @icon data:image/png;base64,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 // @grant GM_getValue // @grant GM_setValue // @grant unsafeWindow // @grant GM_setClipboard // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @license GNU General Public License v3.0 // @run-at document-end // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js // @downloadURL https://update.greasyfork.icu/scripts/483847/XHS-Downloader.user.js // @updateURL https://update.greasyfork.icu/scripts/483847/XHS-Downloader.meta.js // ==/UserScript== (function () { 'use strict'; const iconBase64 = "data:image/png;base64,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"; let config = { disclaimer: GM_getValue("disclaimer", false), packageDownloadFiles: GM_getValue("packageDownloadFiles", true), autoScrollSwitch: GM_getValue("autoScrollSwitch", false), maxScrollCount: GM_getValue("maxScrollCount", 50), fileNameFormat: undefined, imageFileFormat: undefined, icon: { type: 'image', // 可选: image/svg/font image: { url: iconBase64, // 图片URL或Base64 size: 64, // 图标尺寸 borderRadius: '50%' // 形状(50%为圆形) }, }, // 位置配置 position: { bottom: '8rem', left: '2rem' }, // 动画配置 animation: { duration: 0.35, // 动画时长(s) easing: 'cubic-bezier(0.4, 0, 0.2, 1)' } }; const readme = () => { const instructions = ` XHS-Downloader 用户脚本 功能清单: 1. 下载小红书无水印作品文件 2. 提取推荐页面作品链接 3. 提取账号发布作品链接 4. 提取账号收藏作品链接 5. 提取账号专辑作品链接 6. 提取账号点赞作品链接 7. 提取搜索结果作品链接 8. 提取搜索结果用户链接 XHS-Downloader 用户脚本 详细说明: 1. 下载小红书无水印作品文件时,脚本需要花费时间处理文件,请等待片刻,请勿多次点击下载按钮 2. 无水印作品文件较大,可能需要较长的时间处理,页面跳转可能会导致下载失败 3. 提取账号发布、收藏、点赞、专辑作品链接时,脚本可以自动滚动页面直至加载全部作品 4. 提取推荐作品链接、搜索作品、用户链接时,脚本可以自动滚动指定次数加载更多内容,默认滚动次数:50 次 5. 自动滚动页面功能默认关闭;用户可以自由开启,并修改滚动页面次数,修改后立即生效 6. 如果未开启自动滚动页面功能,用户需要手动滚动页面以便加载更多内容后再进行其他操作 7. 支持作品文件打包下载;该功能默认开启,多个文件的作品将会以压缩包格式下载 项目开源地址:https://github.com/JoeanAmier/XHS-Downloader ` const disclaimer_content = ` 关于 XHS-Downloader 的 免责声明: 1. 使用者对本项目的使用由使用者自行决定,并自行承担风险。作者对使用者使用本项目所产生的任何损失、责任、或风险概不负责。 2. 本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者尽力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。 3. 使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求,并在适当的地方注明使用了 GNU General Public License v3.0 的代码。 4. 使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。 5. 使用者在使用本项目的代码和功能时,必须自行研究相关法律法规,并确保其使用行为合法合规。任何因违反法律法规而导致的法律责任和风险,均由使用者自行承担。 6. 本项目的作者不会提供 XHS-Downloader 项目的付费版本,也不会提供与 XHS-Downloader 项目相关的任何商业服务。 7. 基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承担与二次开发行为或其结果相关的任何责任,使用者应自行对因二次开发可能带来的各种情况负全部责任。 在使用本项目的代码和功能之前,请您认真考虑并接受以上免责声明。如果您对上述声明有任何疑问或不同意,请不要使用本项目的代码和功能。如果您使用了本项目的代码和功能,则视为您已完全理解并接受上述免责声明,并自愿承担使用本项目的一切风险和后果。 是否已阅读 XHS-Downloader 功能说明与免责声明(YES/NO) ` alert(instructions); if (!config.disclaimer) { const answer = prompt(disclaimer_content, ""); if (!answer) { GM_setValue("disclaimer", false); config.disclaimer = false; } else { GM_setValue("disclaimer", answer.toUpperCase() === "YES" || answer.toUpperCase() === "Y"); config.disclaimer = GM_getValue("disclaimer"); } } }; if (!config.disclaimer) { readme(); } console.info("用户接受 XHS-Downloader 免责声明", config.disclaimer) GM_registerMenuCommand("阅读脚本说明和免责声明", function () { readme(); }); const updatePackageDownloadFiles = (value) => { config.packageDownloadFiles = value; GM_setValue("packageDownloadFiles", config.packageDownloadFiles); }; const updateAutoScrollSwitch = (value) => { config.autoScrollSwitch = value; GM_setValue("autoScrollSwitch", config.autoScrollSwitch); }; const updateMaxScrollCount = (value) => { config.maxScrollCount = parseInt(value) || 50; GM_setValue("maxScrollCount", config.maxScrollCount); }; const updateFileNameFormat = (value) => { config.fileNameFormat = value; GM_setValue("fileNameFormat", config.fileNameFormat); }; const about = () => { window.open('https://github.com/JoeanAmier/XHS-Downloader', '_blank'); } const abnormal = (text) => { alert(`${text}请向作者反馈!\n项目开源地址:https://github.com/JoeanAmier/XHS-Downloader`); }; const generateVideoUrl = note => { try { return [`https://sns-video-bd.xhscdn.com/${note.video.consumer.originVideoKey}`]; } catch (error) { console.error("Error generating video URL:", error); return []; } }; const generateImageUrl = note => { let images = note.imageList; const regex = /http:\/\/sns-webpic-qc\.xhscdn.com\/\d+\/[0-9a-z]+\/(\S+)!/; let urls = []; try { images.forEach((item) => { let match = item.urlDefault.match(regex); if (match && match[1]) { urls.push(`https://ci.xiaohongshu.com/${match[1]}?imageView2/format/png`); } }) return urls } catch (error) { console.error("Error generating image URLs:", error); return []; } }; const extractImageWebpUrls = (note, urls,) => { try { let items = [] let {imageList} = note; if (urls.length !== imageList.length) { console.error("图片数量不一致!") return [] } for (const [index, item] of imageList.entries()) { if (item.urlDefault) { items.push({ webp: item.urlDefault, index: index + 1, url: urls[index], }) } else { console.error("提取图片预览链接失败", item) break } } return items; } catch (error) { console.error("Error occurred in generating image object:", error); return [] } }; const download = async (urls, note) => { const name = extractName(); console.info(`文件名称 ${name}`); if (note.type === "video") { await downloadVideo(urls[0], name); } else { let items = extractImageWebpUrls(note, urls); if (items.length === 0) { console.error("解析图文作品数据失败", note) abnormal("解析图文作品数据发生异常!") } else if (urls.length > 1) { showImageSelectionModal(items, name,) } else { await downloadImage(items, name); } } }; const exploreDeal = async note => { try { let links; if (note.type === "normal") { links = generateImageUrl(note); } else { links = generateVideoUrl(note); } if (links.length > 0) { console.info("下载链接", links); await download(links, note); } else { abnormal("处理下载链接发生异常!") } } catch (error) { console.error("Error in exploreDeal function:", error); abnormal("下载作品文件发生异常!"); } }; const extractNoteInfo = () => { const regex = /\/explore\/([^?]+)/; const match = currentUrl.match(regex); if (match) { return unsafeWindow.__INITIAL_STATE__.note.noteDetailMap[match[1]] } else { console.error("从链接提取作品 ID 失败", currentUrl,); } }; const extractDownloadLinks = async () => { if (currentUrl.includes("https://www.xiaohongshu.com/explore/")) { let note = extractNoteInfo(); if (note.note) { await exploreDeal(note.note); } else { abnormal("读取作品数据发生异常!"); } } }; const triggerDownload = (name, blob) => { // 创建 Blob 对象的 URL const blobUrl = URL.createObjectURL(blob); // 创建一个临时链接元素 const tempLink = document.createElement("a"); tempLink.href = blobUrl; tempLink.download = name; // 将链接添加到 DOM 并模拟点击 document.body.appendChild(tempLink); // 避免某些浏览器安全限制 tempLink.click(); // 清理临时链接元素 document.body.removeChild(tempLink); // 从 DOM 中移除临时链接 URL.revokeObjectURL(blobUrl); // 释放 URL console.info(`文件已成功下载: ${name}`); } const downloadFile = async (link, name, trigger = true, retries = 5) => { for (let attempt = 1; attempt <= retries; attempt++) { try { // 使用 fetch 获取文件数据 const response = await fetch(link, { "headers": { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-language": "zh-SG,zh;q=0.9", }, "method": "GET", }); // 检查响应状态码 if (!response.ok) { console.error(`下载失败,状态码: ${response.status},URL: ${link},尝试次数: ${attempt}`); continue; // 继续下一次尝试 } const blob = await response.blob(); if (trigger) { triggerDownload(name, blob); return true; } else { return blob; } } catch (error) { console.error(`下载失败 (${name}),错误信息:`, error, `尝试次数: ${attempt}`); if (attempt === retries) { return false; // 如果达到最大重试次数,返回失败 } } } return false; // 如果所有尝试都失败,返回失败 }; const downloadFiles = async (items, name,) => { const downloadResults = []; // 用于存储下载结果 const downloadPromises = items.map(async (item) => { let fileName; if (item.index) { fileName = `${name}_${item.index}.png`; // 根据索引生成文件名 } else { fileName = `${name}.png`; } const result = await downloadFile(item.url, fileName, false); // 调用单个文件下载方法 if (result) { downloadResults.push({name: fileName, file: result}); return true; // 成功 } else { return false; // 失败 } }); // 等待所有下载操作完成 const results = await Promise.all(downloadPromises); if (results.every(result => result === true)) { try { const zip = new JSZip(); downloadResults.forEach((item) => { zip.file(item.name, item.file); }); const content = await zip.generateAsync({type: "blob", compression: "STORE"}); triggerDownload(`${name}.zip`, content,) return true; } catch (error) { console.error('生成 ZIP 文件或保存失败,错误信息:', error); return false; } } else { return false; } }; const truncateString = (str, maxLength) => { if (str.length > maxLength) { const halfLength = Math.floor(maxLength / 2) - 1; // 减去 1 留出省略号的空间 return str.slice(0, halfLength) + '...' + str.slice(-halfLength); } return str; }; const extractName = () => { let name = document.title.replace(/ - 小红书$/, "").replace(/[^\u4e00-\u9fa5a-zA-Z0-9 ~!@#$%&()_\-+=\[\];"',.!()【】:“”,。《》?]/g, ""); name = truncateString(name, 64,); let match = currentUrl.match(/\/([^\/]+)$/); let id = match ? match[1] : null; return name === "" ? id : name }; const downloadVideo = async (url, name) => { if (!await downloadFile(url, `${name}.mp4`)) { abnormal("下载视频作品文件发生异常!"); } }; const downloadImage = async (items, name) => { let success; if (!config.packageDownloadFiles && items.length > 1) { let result = []; for (let item of items) { result.push(await downloadFile(item.url, `${name}_${item.index}.png`)); } success = result.every(item => item === true); } else if (items.length === 1) { success = await downloadFile(items[0].url, `${name}.png`); } else { success = await downloadFiles(items, name,); } if (!success) { abnormal("下载图文作品文件发生异常!"); } }; const window_scrollBy = (x, y,) => { window.scrollBy(x, y,); } // 随机整数生成函数 const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; // 判断是否需要暂停,模拟用户的停顿行为 const shouldPause = () => Math.random() < 0.2; // 20%几率停顿 // 执行一次增量滚动 const scrollOnce = () => { const scrollDistanceMin = 100; // 最小滚动距离 const scrollDistanceMax = 300; // 最大滚动距离 const scrollDistance = getRandomInt(scrollDistanceMin, scrollDistanceMax); window_scrollBy(0, scrollDistance); // 增量滚动 }; // 检查是否已经滚动到底部 const isAtBottom = () => { const docHeight = document.documentElement.scrollHeight; const winHeight = window.innerHeight; const scrollPos = window.scrollY; return (docHeight - winHeight - scrollPos <= 10); // 如果距离底部小于10px,认为滚动到底部 }; // 自动滚动主函数 const scrollScreen = (callback, endless = false, scrollCount = 0,) => { const timeoutMin = 250; // 最小滚动间隔 const timeoutMax = 500; // 最大滚动间隔 const scrollInterval = setInterval(() => { if (shouldPause()) { // 停顿,模拟用户的休息 clearInterval(scrollInterval); setTimeout(() => { scrollScreen(callback, endless, scrollCount,); // 重新启动滚动 }, getRandomInt(timeoutMin, timeoutMax,)); // 随机停顿时间 } else if (endless) { // 无限滚动至底部模式 if (!isAtBottom()) { scrollOnce(); // 执行一次滚动 } else { // 到达底部,停止滚动 clearInterval(scrollInterval); callback(); // 调用回调函数 } } else if (scrollCount < config.maxScrollCount && !isAtBottom()) { scrollOnce(); // 执行一次滚动 scrollCount++; } else { // 如果到达底部或滚动次数已满,停止滚动 clearInterval(scrollInterval); callback(); // 调用回调函数 } }, getRandomInt(timeoutMin, timeoutMax)); // 随机滚动间隔 }; const scrollScreenEvent = (callback, endless = false) => { if (config.autoScrollSwitch) { scrollScreen(callback, endless,); } else { callback(); } }; const extractNotesInfo = order => { const notesRawValue = unsafeWindow.__INITIAL_STATE__.user.notes._rawValue[order]; return notesRawValue.map(item => [item.id, item.xsecToken,]); }; const extractBoardInfo = () => { // 定义正则表达式来匹配 URL 中的 ID const regex = /\/board\/([a-z0-9]+)\?/; // 使用 exec 方法执行正则表达式 const match = regex.exec(currentUrl); // 检查是否有匹配 if (match) { // 提取 ID const id = match[1]; // match[0] 是整个匹配的字符串,match[1] 是第一个括号内的匹配 const notesRawValue = unsafeWindow.__INITIAL_STATE__.board.boardFeedsMap._rawValue[id].notes; return notesRawValue.map(item => [item.noteId, item.xsecToken,]); } else { console.error("从链接提取专辑 ID 失败", currentUrl,); return []; } }; const extractFeedInfo = () => { const notesRawValue = unsafeWindow.__INITIAL_STATE__.feed.feeds._rawValue; return notesRawValue.map(item => [item.id, item.xsecToken,]); }; const extractSearchNotes = () => { const notesRawValue = unsafeWindow.__INITIAL_STATE__.search.feeds._rawValue; return notesRawValue.map(item => [item.id, item.xsecToken,]); } const extractSearchUsers = () => { const notesRawValue = unsafeWindow.__INITIAL_STATE__.search.userLists._rawValue; return notesRawValue.map(item => item.id); } const generateNoteUrls = data => data.map(([id, token,]) => `https://www.xiaohongshu.com/discovery/item/${id}?source=webshare&xhsshare=pc_web&xsec_token=${token}&xsec_source=pc_share`).join(" "); const generateUserUrls = data => data.map(id => `https://www.xiaohongshu.com/user/profile/${id}`).join(" "); const extractAllLinks = (callback, order) => { scrollScreenEvent(() => { let data; if (order >= 0 && order <= 2) { data = extractNotesInfo(order); } else if (order === 3) { data = extractSearchNotes(); } else if (order === 4) { data = extractSearchUsers(); } else if (order === -1) { data = extractFeedInfo() } else if (order === 5) { data = extractBoardInfo() } else { data = []; } let urlsString = order !== 4 ? generateNoteUrls(data) : generateUserUrls(data); callback(urlsString); }, [0, 1, 2, 5].includes(order)) }; const extractAllLinksEvent = (order = 0) => { extractAllLinks(urlsString => { if (urlsString) { GM_setClipboard(urlsString, "text", () => { alert('作品/用户链接已复制到剪贴板!'); }); } else { alert("未提取到任何作品/用户链接!") } }, order); }; if (typeof JSZip === 'undefined') { alert("XHS-Downloader 用户脚本依赖库 JSZip 加载失败,作品文件打包下载功能无法使用,请尝试刷新网页或者向作者反馈!"); } /* ==================== 样式定义 ==================== */ let style = document.createElement('style'); style.textContent = ` /* 弹窗基础样式 */ #SettingsOverlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.32); backdrop-filter: blur(4px); display: flex; justify-content: center; align-items: center; z-index: 10000; animation: fadeIn 0.3s; } .optimized-scroll-modal { background: white; border-radius: 16px; width: 380px; /* 缩小窗口宽度 */ max-width: 95vw; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); overflow: hidden; animation: scaleUp 0.3s; } /* 头部样式 */ .modal-header { padding: 1rem; border-bottom: 1px solid #eee; text-align: center; } .modal-header span { font-size: 1.25rem; font-weight: 500; color: #212121; } /* 内容区域 */ .modal-body { padding: 1rem; /* 减小内边距 */ } /* 设置项样式 */ .setting-item { margin: 0.5rem 0; /* 减少设置项间距 */ padding: 10px; border-radius: 8px; transition: background 0.2s; } .setting-item:hover { background: #f0f0f0; } .setting-item label { display: flex; justify-content: space-between; align-items: center; width: 100%; } /* 设置项标题 */ .setting-item label span { font-size: 1rem; /* 增大标题文字 */ font-weight: 500; color: #333; } /* 开关样式 */ .toggle-switch { position: relative; width: 40px; height: 20px; } .toggle-switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #ccc; transition: 0.4s; border-radius: 34px; } .slider:before { content: ""; position: absolute; height: 16px; width: 16px; left: 2px; bottom: 2px; background: white; border-radius: 50%; transition: 0.4s; } input:checked + .slider { background: #2196F3; } input:checked + .slider:before { transform: translateX(20px); } /* 数值输入 */ .number-input { display: flex; align-items: center; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; margin: 6px 0; } .number-input input { width: 60px; text-align: center; border: none; } .number-button { padding: 4px 8px; background: #f0f0f0; border: none; cursor: pointer; transition: all 0.2s; } /* 文本输入框 */ .text-input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 0.9rem; margin-top: 8px; /* 增加与标题的距离 */ transition: border-color 0.2s; } .text-input:focus { outline: none; border-color: #2196F3; box-shadow: 0 0 4px rgba(33, 150, 243, 0.3); } /* 设置项说明 */ .setting-description { font-size: 0.875rem; color: #757575; margin-top: 4px; line-height: 1.4; text-align: left; /* 左对齐 */ } /* 底部按钮 */ .modal-footer { padding: 1rem; border-top: 1px solid #eee; display: flex; justify-content: flex-end; gap: 12px; } .primary-btn { background: #2196F3; color: white; padding: 8px 24px; border-radius: 24px; cursor: pointer; transition: all 0.2s; } .secondary-btn { background: #f0f0f0; color: #666; padding: 8px 24px; border-radius: 24px; cursor: pointer; transition: all 0.2s; } /* 动画 */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes scaleUp { from { transform: scale(0.98); } to { transform: scale(1); } } `; document.head.appendChild(style); // 创建开关项 const createSettingItem = ({label, description, checked}) => { const item = document.createElement('div'); item.className = 'setting-item'; item.innerHTML = `