夸克网盘目录树生成:
效果演示:
一、打开链接,开始爬取
测试:对13.5TB共81000个文件夹的网课视频目录扫描,爬取4层目录树举例
若只需要导出3层修改 const MAX_DEPTH=4; 后面参数为3即可
3层仅需20秒左右
4层需5-10分钟
(async () => {
/* 自动读取分享 */
const raw=JSON.parse(sessionStorage.getItem("_share_args"))?.value;
if(!raw){alert("请在分享文件列表页运行");return;}
const pwd=raw.pwd_id;
const st=encodeURIComponent(raw.stoken);
console.log("扫描分享:",pwd);
/* 参数 */
const MAX_DEPTH=4;
const CONCURRENCY=8;
const RETRY=4;
let visited=new Set();
let queue=[{fid:"0",path:"",depth:0}];
let results=[];
function sleep(t){return new Promise(r=>setTimeout(r,t));}
async function fetchPage(fid,page){
for(let i=0;i<RETRY;i++){
try{
const r=await fetch(
`https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail?pr=ucpro&fr=pc&pwd_id=${pwd}&stoken=${st}&pdir_fid=${fid}&force=0&_page=${page}&_size=50`,
{credentials:"include"});
if(r.status===400) throw "参数失效";
return await r.json();
}catch(e){
await sleep(800+Math.random()*1200);
}
}
return null;
}
async function scanDir(task){
if(task.depth>MAX_DEPTH)return;
let page=1;
while(true){
const j=await fetchPage(task.fid,page);
if(!j?.data?.list?.length)break;
for(const f of j.data.list){
const full=task.path+"/"+f.file_name;
results.push(full);
if(f.dir&&!visited.has(f.fid)){
visited.add(f.fid);
queue.push({fid:f.fid,path:full,depth:task.depth+1});
}
}
page++;
await sleep(120+Math.random()*180);
}
}
async function worker(id){
while(queue.length){
const task=queue.shift();
if(!task)break;
await scanDir(task);
if(id===0)
console.log(`已扫:${results.length} | 目录:${visited.size} | 队列:${queue.length}`);
}
}
console.log("开始扫描…");
await Promise.all(Array.from({length:CONCURRENCY},(_,i)=>worker(i)));
console.log("完成:",results.length);
const txt=results.join("\n");
try{await navigator.clipboard.writeText(txt);}catch(e){}
const blob=new Blob([txt]);
const a=document.createElement("a");
a.href=URL.createObjectURL(blob);
a.download="quark目录.txt";
a.click();
})();
二、将.txt文件转成.html
2.1、thonny-merger-3tree-G
此步thonny代码作用:将多个 从夸克分享链接爬取的.txt目录树文件 合并成1个森林并且转换成html文件
合并几棵树就粘贴进去几棵树的文件路径
下面以3棵树举列
import json
import os
# ====== 配置区域 ======
# 输入文件列表
files = [
r"C:\Users\JinRC\Downloads\quark目录 (1).txt",
r"C:\Users\JinRC\Downloads\quark目录 (2).txt",
r"C:\Users\JinRC\Downloads\quark目录 (3).txt"
]
# 输出文件路径
output_file = r"C:\Users\JinRC\Downloads\directory_tree_ultra.html"
# ====================
# 1. 高效构建目录树字典
# ====================
def build_tree(file_paths):
tree = {}
for file_path in file_paths:
if not os.path.exists(file_path):
print(f"警告:找不到文件 {file_path}")
continue
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
path = line.strip().strip("/")
if not path:
continue
parts = path.split("/")
current = tree
for part in parts:
if part not in current:
current[part] = {}
current = current[part]
return tree
print("正在解析文件结构,请稍候...")
dir_structure = build_tree(files)
print("结构解析完成,正在生成压缩数据...")
# 将字典转换为压缩的 JSON 字符串 (ensure_ascii=False 保证中文不转码,减少体积)
json_data = json.dumps(dir_structure, ensure_ascii=False, separators=(',', ':'))
# ====================
# 2. 注入现代化前端模板
# ====================
html_template = f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Resource Navigator</title>
<style>
:root {{
--bg: #0f172a; /* 深蓝背景 */
--panel: #1e293b; /* 面板颜色 */
--text: #e2e8f0; /* 主文字 */
--text-dim: #94a3b8; /* 次级文字 */
--accent: #3b82f6; /* 提亮蓝 */
--hover: #334155; /* 悬停色 */
--border: #334155; /* 边框线 */
--line: #475569; /* 树状连线 */
}}
* {{ box-sizing: border-box; outline: none; }}
body {{
margin: 0; padding: 0;
background: var(--bg);
color: var(--text);
font-family: 'Segoe UI', -apple-system, Roboto, sans-serif;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}}
/* 顶部导航栏 (Mac 风格) */
.header {{
padding: 12px 20px;
background: rgba(30, 41, 59, 0.8);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
}}
.window-controls {{
display: flex;
gap: 8px;
}}
.dot {{ width: 12px; height: 12px; border-radius: 50%; }}
.dot.red {{ background: #ef4444; }}
.dot.yellow {{ background: #f59e0b; }}
.dot.green {{ background: #22c55e; }}
.search-box {{
position: relative;
width: 300px;
}}
.search-input {{
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
padding: 6px 12px 6px 35px;
border-radius: 6px;
font-size: 13px;
transition: all 0.2s;
}}
.search-input:focus {{
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}}
.search-icon {{
position: absolute;
left: 10px; top: 50%;
transform: translateY(-50%);
width: 14px; height: 14px;
fill: var(--text-dim);
}}
/* 目录树容器 */
.tree-container {{
flex: 1;
overflow-y: auto;
padding: 20px;
scrollbar-width: thin;
scrollbar-color: var(--border) var(--bg);
}}
.tree-container::-webkit-scrollbar {{ width: 8px; }}
.tree-container::-webkit-scrollbar-thumb {{ background: var(--border); border-radius: 4px; }}
/* 树节点样式 */
ul {{
list-style: none;
padding-left: 20px; /* 缩进 */
margin: 0;
position: relative;
}}
/* 连线系统 */
ul::before {{
content: '';
position: absolute;
left: 0px; top: 0; bottom: 0;
width: 1px;
background: var(--line);
opacity: 0.5;
}}
/* 根节点不需要左侧长线 */
#root-ul::before {{ display: none; }}
li {{ position: relative; }}
.row {{
display: flex;
align-items: center;
padding: 5px 8px;
border-radius: 4px;
cursor: pointer;
user-select: none;
transition: background 0.15s;
margin-bottom: 2px;
position: relative;
}}
.row:hover {{ background: var(--hover); }}
/* 节点横线 */
.has-line::before {{
content: '';
position: absolute;
left: -20px; top: 18px;
width: 20px; height: 1px;
background: var(--line);
opacity: 0.5;
}}
/* 图标 */
.icon {{
width: 18px; height: 18px;
margin-right: 8px;
flex-shrink: 0;
}}
.icon-folder {{ fill: #fbbf24; }} /* 黄色文件夹 */
.icon-file {{ fill: #94a3b8; }} /* 灰色文件 */
.arrow {{
width: 14px; height: 14px;
margin-right: 4px;
fill: var(--text-dim);
transition: transform 0.2s;
}}
.expanded .arrow {{ transform: rotate(90deg); }}
.name {{
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}}
/* 搜索高亮 */
.highlight {{
color: var(--accent);
font-weight: bold;
}}
.hidden {{ display: none !important; }}
</style>
</head>
<body>
<svg style="display:none">
<symbol id="icon-arrow" viewBox="0 0 24 24"><path d="M10 17l5-5-5-5v10z"/></symbol>
<symbol id="icon-folder" viewBox="0 0 24 24"><path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></symbol>
<symbol id="icon-file" viewBox="0 0 24 24"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>
<symbol id="icon-search" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></symbol>
</svg>
<div class="header">
<div class="window-controls">
<div class="dot red"></div>
<div class="dot yellow"></div>
<div class="dot green"></div>
</div>
<div style="font-size: 14px; font-weight: 600; color: var(--text-dim); margin-left:12px;">资源资源库 Pro</div>
<div style="flex:1"></div>
<div class="search-box">
<svg class="search-icon"><use href="#icon-search"></use></svg>
<input type="text" class="search-input" id="searchInput" placeholder="搜索文件...">
</div>
</div>
<div class="tree-container" id="treeRoot">
<ul id="root-ul"></ul>
</div>
<script>
// 1. 接收 Python 注入的数据
const treeData = {json_data};
const rootUl = document.getElementById('root-ul');
const searchInput = document.getElementById('searchInput');
// 2. 核心渲染函数:只生成 HTML 字符串,不频繁操作 DOM
function createNodeHtml(name, data, isRoot = false) {{
const isFolder = Object.keys(data).length > 0;
const iconId = isFolder ? '#icon-folder' : '#icon-file';
const arrowHtml = isFolder
? `<svg class="arrow"><use href="#icon-arrow"></use></svg>`
: `<span style="width:18px; display:inline-block"></span>`; // 占位符
// 将数据暂存在 DOM 属性中,方便后续点击时读取
// 注意:这里我们不直接把 data 写入 HTML 属性,因为太大了
// 我们用路径索引的方式(但在简单实现中,我们可以直接利用 JS 的闭包特性)
return `
<li class="${{isFolder ? 'folder' : 'file'}}">
<div class="row ${{isRoot ? '' : 'has-line'}}" onclick="toggle(this)">
${{arrowHtml}}
<svg class="icon ${{isFolder ? 'icon-folder' : 'icon-file'}}"><use href="${{iconId}}"></use></svg>
<span class="name">${{name}}</span>
</div>
${{isFolder ? '<ul style="display:none"></ul>' : ''}}
</li>
`;
}}
// 3. 懒加载逻辑
function renderLevel(container, data) {{
// 对键名排序:文件夹在前,文件在后
const keys = Object.keys(data).sort((a, b) => {{
const aIsFolder = Object.keys(data[a]).length > 0;
const bIsFolder = Object.keys(data[b]).length > 0;
if (aIsFolder && !bIsFolder) return -1;
if (!aIsFolder && bIsFolder) return 1;
return a.localeCompare(b, 'zh-CN');
}});
let html = '';
keys.forEach(key => {{
html += createNodeHtml(key, data[key], container.id === 'root-ul');
}});
container.innerHTML = html;
// 绑定数据到 DOM 元素,以便点击时能找到对应的子数据
Array.from(container.children).forEach(li => {{
const name = li.querySelector('.name').textContent;
li._childData = data[name];
}});
}}
// 初始化渲染第一层
renderLevel(rootUl, treeData);
// 4. 交互逻辑
window.toggle = function(rowDiv) {{
const li = rowDiv.parentElement;
if (!li.classList.contains('folder')) return; // 是文件就不管
const ul = li.querySelector('ul');
const arrow = rowDiv.querySelector('.arrow');
// 如果是第一次展开,需要渲染子内容
if (ul.innerHTML === '') {{
renderLevel(ul, li._childData);
}}
const isHidden = ul.style.display === 'none';
ul.style.display = isHidden ? 'block' : 'none';
if (isHidden) {{
rowDiv.classList.add('expanded');
}} else {{
rowDiv.classList.remove('expanded');
}}
}};
// 5. 简单搜索功能 (纯前端过滤)
searchInput.addEventListener('input', (e) => {{
const term = e.target.value.toLowerCase();
if(!term) {{
// 清空搜索时,简单起见,刷新页面恢复初始状态(或者重新渲染根目录)
if(rootUl._isSearching) {{
rootUl.innerHTML = '';
renderLevel(rootUl, treeData);
rootUl._isSearching = false;
}}
return;
}}
// 搜索模式:暴力递归搜索并扁平化显示
rootUl._isSearching = true;
const results = [];
function search(node, path) {{
for (const key in node) {{
const currentPath = path ? path + '/' + key : key;
if (key.toLowerCase().includes(term)) {{
// 找到匹配
const isFolder = Object.keys(node[key]).length > 0;
results.push({{ name: key, path: currentPath, isFolder: isFolder }});
}}
if (Object.keys(node[key]).length > 0) {{
search(node[key], currentPath);
}}
}}
}}
search(treeData, '');
// 渲染搜索结果
let html = '';
if(results.length === 0) {{
html = '<div style="padding:20px; text-align:center; color:#64748b">未找到相关文件</div>';
}} else {{
// 限制显示前100条防止卡顿
results.slice(0, 100).forEach(item => {{
const iconId = item.isFolder ? '#icon-folder' : '#icon-file';
html += `
<li>
<div class="row" style="padding-left:0">
<svg class="icon ${{item.isFolder ? 'icon-folder' : 'icon-file'}}"><use href="${{iconId}}"></use></svg>
<div>
<div class="name highlight">${{item.name}}</div>
<div style="font-size:12px; color:#64748b">${{item.path}}</div>
</div>
</div>
</li>`;
}});
if(results.length > 100) {{
html += '<div style="padding:10px; text-align:center; color:#64748b">结果过多,仅显示前100条...</div>';
}}
}}
rootUl.innerHTML = html;
}});
</script>
</body>
</html>
"""
# 写入文件
with open(output_file, "w", encoding="utf-8") as f:
f.write(html_template)
print(f"完成!已生成极速版目录树:{output_file}")
print(f"文件大小优化预计已减少 90%,且界面已升级为 Pro 版。")
2.2、thonny-mk-1tree-C
此步thonny代码作用:将单个.txt目录树文件 转换成html文件
import html
input_file = r"C:\Users\JinRC\Downloads\quark目录 (5).txt"
output_file = r"C:\Users\JinRC\Downloads\quark目录 (5)tree.html"
# ---------- 构建树 ----------
tree = {}
with open(input_file, "r", encoding="utf-8") as f:
for line in f:
path = line.strip().strip("/")
if not path:
continue
parts = path.split("/")
node = tree
for p in parts:
node = node.setdefault(p, {})
# ---------- 生成HTML ----------
def render(node):
html_parts = ["<ul>"]
for name, child in sorted(node.items()):
safe = html.escape(name)
if child:
html_parts.append(
f'<li class="folder"><span class="toggle">📁 {safe}</span>'
)
html_parts.append(render(child))
html_parts.append("</li>")
else:
html_parts.append(f"<li class='file'>📄 {safe}</li>")
html_parts.append("</ul>")
return "\n".join(html_parts)
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>目录树</title>
<style>
body {{
font-family: Consolas, monospace;
background:#0f172a;
color:#e5e7eb;
}}
ul {{
list-style:none;
padding-left:18px;
}}
.folder > ul {{
display:none;
}}
.toggle {{
cursor:pointer;
color:#60a5fa;
}}
.file {{
color:#d1d5db;
}}
</style>
<script>
document.addEventListener("click",function(e){{
if(e.target.classList.contains("toggle")){{
let ul=e.target.parentNode.querySelector("ul");
if(ul) ul.style.display=ul.style.display=="none"?"block":"none";
}}
}});
</script>
</head>
<body>
<h2>资源目录</h2>
{render(tree)}
</body>
</html>
"""
with open(output_file, "w", encoding="utf-8") as f:
f.write(html_content)
print("完成 →", output_file)
三、在halo的编辑器中嵌入html代码
3.1、森林代码(涵盖搜素)
执行的是2.1的多棵树,下载这串代码粘贴进halo编辑器
3.2、单颗树代码(无搜素功能)
执行的是2.2的单棵树,下载这串代码粘贴进halo编辑器
评论区