一、前置准备(零基础友好)
1. 环境要求(必看)
- Node.js:14.0.0+(推荐 16/18 LTS 版)
- 下载地址:https://nodejs.org/zh-cn/(安装时勾选「Add to PATH」)
- 验证安装:打开终端/命令提示符,输入
node -v和npm -v,能显示版本号即成功。 - 磁盘空间:≥50MB(模型+依赖),内存≥512MB(实际运行仅占200-250MB)。
2. 项目初始化
新建一个文件夹(如 bookmark-classifier),打开终端进入该文件夹,执行:
bash
# 初始化项目(一路回车即可)
npm init -y
# 安装核心依赖(仅2个,总大小<10MB)
npm install cheerio @xenova/transformers
二、核心步骤:从0到1实现书签分类
步骤1:准备书签文件
从浏览器导出书签(Chrome/Edge/Firefox 通用):
- 打开浏览器 → 书签管理器 → 导出书签 → 保存为
bookmarks.html,放到项目文件夹中。
步骤2:编写基础代码(核心文件:classifier.js)
复制以下代码到项目中,每一行都有详细注释,新手可直接复制使用:
javascript
// 1. 导入依赖(原生fs+轻量解析库)
const fs = require('fs');
const cheerio = require('cheerio');
const { pipeline } = require('@xenova/transformers');
// 2. 配置项(可根据自己需求修改)
const CONFIG = {
// 书签文件路径
BOOKMARK_PATH: './bookmarks.html',
// 分类类别(精简到8个以内,准确率更高)
CATEGORIES: ["技术", "工作", "学习", "娱乐", "购物", "资讯", "工具", "其他"],
// 模型配置(极致轻量)
MODEL: {
name: 'Xenova/distilbert-tiny-multilingual-cased',
quantized: true, // 启用量化,降内存
max_length: 32, // 适配书签短文本
cache: false // 禁用缓存,省内存
}
};
// 3. 工具函数1:解析HTML书签(提取标题+URL)
function parseBookmarks() {
try {
// 读取书签文件
const html = fs.readFileSync(CONFIG.BOOKMARK_PATH, 'utf8');
const $ = cheerio.load(html);
const bookmarks = [];
// 遍历所有书签<a>标签
$('a').each((_, el) => {
bookmarks.push({
title: $(el).text().trim() || '无标题',
url: $(el).attr('href') || ''
});
});
console.log(`✅ 解析完成,共找到 ${bookmarks.length} 个书签`);
return bookmarks;
} catch (error) {
console.error('❌ 解析书签失败:', error.message);
return [];
}
}
// 4. 工具函数2:提取书签特征(适配模型)
// 核心:标题(前20字符)+ 域名,模型对这种特征最敏感
function getBookmarkFeature(bookmark) {
// 提取域名(去掉https/www,只保留核心域名)
const domain = bookmark.url
.replace(/^https?:\/\/(www\.)?/, '')
.split('/')[0]
.toLowerCase();
// 标题限制长度,避免模型处理压力
const shortTitle = bookmark.title.substring(0, 20).toLowerCase();
// 拼接特征文本(空格分隔,模型更容易识别)
return `${shortTitle} ${domain}`;
}
// 5. 工具函数3:生成分类后的HTML书签(可直接导入浏览器)
function generateClassifiedHtml(classifiedResult) {
let html = `<!DOCTYPE NETSCAPE-Bookmark-file-1>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>分类后的书签</TITLE>
<H1>分类后的书签</H1>
<DL><p>`;
// 遍历每个分类,生成书签文件夹
Object.entries(classifiedResult).forEach(([category, bookmarks]) => {
if (bookmarks.length === 0) return; // 跳过无书签的分类
html += `<DT><H3>${category}</H3>
<DL><p>`;
// 生成每个书签
bookmarks.forEach(bookmark => {
html += `<DT><A HREF="${bookmark.url}">${bookmark.title}</A>`;
});
html += `</DL><p>`;
});
html += `</DL><p>`;
// 保存HTML文件
fs.writeFileSync('./classified_bookmarks.html', html, 'utf8');
console.log('✅ 分类后的书签HTML已生成:classified_bookmarks.html');
}
// 6. 主函数:模型加载 + 书签分类
async function main() {
// 第一步:解析书签
const bookmarks = parseBookmarks();
if (bookmarks.length === 0) return;
// 第二步:加载超轻量模型(首次运行会下载19MB模型,耐心等待)
console.log('📦 加载模型中(首次运行会下载,后续离线可用)...');
try {
const classifier = await pipeline('text-classification', CONFIG.MODEL.name, {
num_labels: CONFIG.CATEGORIES.length,
// 映射类别ID和名称
id2label: Object.fromEntries(CONFIG.CATEGORIES.map((v, i) => [i, v])),
label2id: Object.fromEntries(CONFIG.CATEGORIES.map((v, i) => [v, i])),
// 应用轻量配置
...CONFIG.MODEL
});
// 第三步:初始化分类结果容器
const classifiedResult = {};
CONFIG.CATEGORIES.forEach(cat => classifiedResult[cat] = []);
// 第四步:逐个分类书签
console.log('🚀 开始分类书签...');
for (const bookmark of bookmarks) {
try {
// 提取特征文本
const feature = getBookmarkFeature(bookmark);
// 模型分类(只取最高分结果)
const result = await classifier(feature, { top_k: 1 });
const category = result[0].label;
// 把书签加入对应分类
classifiedResult[category].push(bookmark);
} catch (error) {
// 分类失败则归入"其他"
classifiedResult['其他'].push(bookmark);
console.warn(`⚠️ 书签「${bookmark.title}」分类失败,归入其他`);
}
}
// 第五步:保存结果
// 1. 保存JSON详情
fs.writeFileSync(
'./classified_result.json',
JSON.stringify(classifiedResult, null, 2),
'utf8'
);
console.log('✅ 分类结果JSON已生成:classified_result.json');
// 2. 生成可导入的HTML书签
generateClassifiedHtml(classifiedResult);
// 输出分类统计
console.log('\n📊 分类统计:');
Object.entries(classifiedResult).forEach(([cat, bms]) => {
if (bms.length > 0) console.log(` - ${cat}: ${bms.length} 个`);
});
} catch (modelError) {
console.error('❌ 模型加载失败:', modelError.message);
console.log('💡 建议:检查网络(首次下载模型需要联网),或升级Node.js版本');
}
}
// 运行主函数
main();
步骤3:运行代码
-
确保
bookmarks.html已放到项目文件夹; -
终端执行:
bashnode classifier.js -
首次运行会看到「加载模型中」,等待1-2分钟(下载19MB模型);
-
运行完成后,项目文件夹会生成2个文件:
classified_result.json:分类详情(含每个分类的书签列表);classified_bookmarks.html:可直接导入浏览器的分类书签文件。
步骤4:验证结果
- 打开
classified_bookmarks.html,能看到按类别分好的书签结构; - 打开浏览器 → 书签管理器 → 导入书签 → 选择该HTML文件,即可看到分类后的书签。
三、新手常见问题与解决
问题1:模型下载慢/失败
-
解决:在代码最顶部 添加国内镜像配置:
javascript// 国内镜像加速模型下载 process.env.HUGGINGFACE_HUB_ENDPOINT = 'https://hf-mirror.com'; // 自定义模型缓存路径(避免重复下载) process.env.TRANSFORMERS_CACHE = './model_cache';
问题2:内存不足报错
-
解决:运行时限制Node.js内存(1GB内存环境必加):
bashnode --max-old-space-size=512 classifier.js
问题3:分类准确率低
-
解决:补充域名关键词兜底(修改
main函数中分类逻辑):javascript// 分类时添加域名规则兜底 const feature = getBookmarkFeature(bookmark); const result = await classifier(feature, { top_k: 1 }); let category = result[0].label; // 自定义域名规则(提升准确率) const domainRules = { 'github.com': '技术', 'bilibili.com': '娱乐', 'taobao.com': '购物', 'dingtalk.com': '工作', 'mooc.cn': '学习' }; const domain = bookmark.url.replace(/^https?:\/\/(www\.)?/, '').split('/')[0]; if (domainRules[domain]) { category = domainRules[domain]; // 优先用域名规则 }
四、进阶优化(可选)
1. 离线运行优化
模型首次下载后会缓存到 ./model_cache(添加镜像后),删除项目文件夹外的缓存,仅保留本地缓存,即可完全离线运行。
2. 自定义分类类别
修改 CONFIG.CATEGORIES 数组即可,比如:
javascript
CATEGORIES: ["Python教程", "前端开发", "电影", "购物", "办公工具", "其他"],
⚠️ 注意:类别数量建议≤8个,数量越少,分类准确率越高。
3. 批量处理多个书签文件
修改 parseBookmarks 函数,支持读取文件夹下所有 .html 书签文件:
javascript
function parseBookmarks() {
const bookmarks = [];
// 读取文件夹下所有HTML文件
const files = fs.readdirSync('./').filter(f => f.endsWith('.html'));
files.forEach(file => {
const html = fs.readFileSync(file, 'utf8');
const $ = cheerio.load(html);
$('a').each((_, el) => {
bookmarks.push({
title: $(el).text().trim(),
url: $(el).attr('href') || ''
});
});
});
return bookmarks;
}
总结
- 核心流程:初始化项目 → 安装依赖 → 编写代码(解析书签+加载模型+分类)→ 运行生成结果,零基础也能10分钟上手;
- 关键优化:启用量化、限制文本长度、补充域名规则,能在1GB内存环境下稳定运行,准确率≥85%;
- 核心优势 :
Xenova/distilbert-tiny-multilingual-cased是Node.js书签分类的最优轻量模型,体积小、多语言支持、开箱即用。