Created
May 16, 2026 16:05
-
-
Save huiliu/48ef5a9a2e490e7d48cf3b5da7a54f09 to your computer and use it in GitHub Desktop.
知乎收藏夹导出
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name 知乎收藏夹导出 | |
| // @namespace http://tampermonkey.net/ | |
| // @version 4.1 | |
| // @description 静默请求原生 API,并按照“名字+ID+数量+时间”的格式化文件名导出 | |
| // @author Gemini | |
| // @match https://www.zhihu.com/collection/* | |
| // @grant none | |
| // @run-at document-end | |
| // ==/UserScript== | |
| // Gemini 对话:https://gemini.google.com/app/e641a19324d20907?hl=zh-CN | |
| (function() { | |
| 'use strict'; | |
| let collectionId = window.location.pathname.split('/').pop(); | |
| let isWorking = false; | |
| // --- 1. 模拟网络请求函数 --- | |
| async function fetchAllCollectionData() { | |
| let allItems = []; | |
| let offset = 0; | |
| let limit = 20; | |
| let isEnd = false; | |
| updateBtnStatus(`准备开始...`); | |
| while (!isEnd && isWorking) { | |
| const apiUrl = `https://www.zhihu.com/api/v4/collections/${collectionId}/items?offset=${offset}&limit=${limit}`; | |
| console.log(`[API模拟] 正在请求: ${apiUrl}`); | |
| try { | |
| const response = await window.fetch(apiUrl, { | |
| headers: { | |
| 'accept': 'application/json, text/plain, */*', | |
| 'x-requested-with': 'XMLHttpRequest' | |
| } | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP 错误! 状态码: ${response.status}`); | |
| } | |
| const resBody = await response.json(); | |
| if (resBody && resBody.data) { | |
| allItems.push(...resBody.data); | |
| updateBtnStatus(`已下载: ${allItems.length} 条`); | |
| if (resBody.paging && resBody.paging.is_end === false) { | |
| offset += limit; | |
| // 随机延迟防风控 | |
| await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 500)); | |
| } else { | |
| isEnd = true; | |
| } | |
| } else { | |
| isEnd = true; | |
| } | |
| } catch (error) { | |
| console.error('[API模拟] 请求出错:', error); | |
| alert(`请求失败。当前已抓取 ${allItems.length} 条。`); | |
| break; | |
| } | |
| } | |
| if (allItems.length > 0) { | |
| triggerDownload(allItems); | |
| } | |
| isWorking = false; | |
| updateBtnStatus('🚀 导出全量 JSON'); | |
| } | |
| // --- 2. 格式化当前时间 (YYYYMMDD_HHmmss) --- | |
| function getFormattedTime() { | |
| const now = new Date(); | |
| const year = now.getFullYear(); | |
| const month = String(now.getMonth() + 1).padStart(2, '0'); | |
| const day = String(now.getDate()).padStart(2, '0'); | |
| const hours = String(now.getHours()).padStart(2, '0'); | |
| const minutes = String(now.getMinutes()).padStart(2, '0'); | |
| const seconds = String(now.getSeconds()).padStart(2, '0'); | |
| return `${year}${month}${day}_${hours}${minutes}${seconds}`; | |
| } | |
| // --- 3. 动态组装文件名并下载 --- | |
| function triggerDownload(finalData) { | |
| // 动态获取收藏夹名称(兼容两种知乎常见的类名) | |
| const titleEl = document.querySelector('.CollectionDetailPageHeader-title') || | |
| document.querySelector('.CollectionDetailHeader-title'); | |
| const folderName = titleEl ? titleEl.innerText.trim() : '知乎收藏夹'; | |
| const recordCount = finalData.length; | |
| const timeStr = getFormattedTime(); | |
| // 核心:按照“收藏夹名_收藏夹Id_记录数_导出时间.json”拼接 | |
| const filename = `${folderName}_${collectionId}_${recordCount}条_${timeStr}.json`; | |
| const blob = new Blob([JSON.stringify(finalData, null, 2)], { type: 'application/json' }); | |
| const a = document.createElement('a'); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| setTimeout(() => { | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(a.href); | |
| alert(`导出成功!\n文件已保存为:${filename}`); | |
| }, 500); | |
| } | |
| // --- 4. UI 注入与状态控制 --- | |
| function injectButton() { | |
| if (document.getElementById('tm-export-btn')) return; | |
| const target = document.querySelector('.CollectionDetailPageHeader-actions') || | |
| document.querySelector('.CollectionDetailHeader-actions'); | |
| if (!target) return; | |
| const btn = document.createElement('button'); | |
| btn.id = 'tm-export-btn'; | |
| btn.innerText = '🚀 导出全量 JSON'; | |
| btn.style = `margin-left:10px; padding:6px 16px; background:#0066ff; color:#fff; border:none; border-radius:999px; cursor:pointer; font-weight:600; vertical-align: middle;`; | |
| btn.onclick = () => { | |
| if (isWorking) { | |
| if (confirm("正在抓取中,是否强行停止并保存当前已下载的数据?")) { | |
| isWorking = false; | |
| } | |
| return; | |
| } | |
| if (confirm("开始在后台静默请求知乎 API 获取最完整的无损数据,确定吗?")) { | |
| isWorking = true; | |
| fetchAllCollectionData(); | |
| } | |
| }; | |
| target.appendChild(btn); | |
| } | |
| function updateBtnStatus(text) { | |
| const btn = document.getElementById('tm-export-btn'); | |
| if (btn) btn.innerText = text; | |
| } | |
| setInterval(injectButton, 1000); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment