1. 项目初始化 #
首先,让我们创建一个新的Node.js项目并安装必要的依赖。
步骤 1.1: 创建项目目录 #
mkdir juejin-crawler
cd juejin-crawler步骤 1.2: 初始化项目 #
npm init -y这将创建一个基本的package.json文件。
步骤 1.3: 安装依赖 #
npm install axios @zilliz/milvus2-sdk-node node-fetch我们安装的依赖包括:
axios: 用于发送HTTP请求@zilliz/milvus2-sdk-node: Milvus向量数据库的Node.js客户端node-fetch: 用于发送HTTP请求(用于向量化API调用)
步骤 1.4: 更新package.json #
编辑package.json文件,添加类型模块配置和启动脚本:
{
"name": "juejin-crawler",
"version": "1.0.0",
"description": "掘金文章爬虫并保存到Milvus数据库",
"main": "index.js",
"scripts": {
+ "start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@zilliz/milvus2-sdk-node": "^2.2.10",
"axios": "^1.5.0",
"node-fetch": "^3.3.2"
},
"type": "module"
}注意添加了"type": "module",这让我们可以使用ES模块语法。
2. 创建配置文件 #
接下来,我们创建一个配置文件来存储项目的各种配置参数。这样可以集中管理配置,便于后期修改。
步骤 2.1: 创建config.js文件 #
export const config = {
// Milvus数据库配置
milvus: {
address: '192.168.2.106', // 替换为您的Milvus服务器地址
port: '19530', // Milvus服务端口
database: 'juejin', // 数据库名称
collection: 'articles', // 集合名称
vectorDimension: 1024 // 向量维度
},
// 向量化API配置
embedding: {
url: 'https://api.siliconflow.cn/v1/embeddings',
apiKey: 'sk-kgahvlalrbfjyftxrciniliopeblhxsgrxebrwgiqwwxwxth', // 替换为您的API密钥
model: 'BAAI/bge-large-zh-v1.5' // 使用的模型
},
// 爬虫配置
crawler: {
url: 'https://juejin.cn/hot/articles/1',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
};这个配置文件包含三个主要部分:
- Milvus数据库连接配置
- 文本向量化API配置
- 爬虫配置
您需要将Milvus的地址和向量化API的密钥替换为您自己的值。
3. 实现API爬虫功能 #
现在,我们来实现爬虫功能,从掘金获取热门文章。
步骤 3.1: 创建crawler.js文件 #
import axios from 'axios';
import { config } from './config.js';
/**
* 掘金文章爬虫类
*/
export class JuejinCrawler {
constructor() {
this.apiUrl = 'https://api.juejin.cn/recommend_api/v1/article/recommend_all_feed';
this.headers = {
'User-Agent': config.crawler.userAgent,
'Accept': 'application/json',
'Content-Type': 'application/json',
'Origin': 'https://juejin.cn',
'Referer': 'https://juejin.cn/'
};
}
/**
* 爬取掘金热门文章
* @returns {Promise<Array<Object>>} - 包含排名和标题的文章列表
*/
async crawlArticles() {
return await this.getArticlesFromAPI();
}
/**
* 通过API获取掘金热门文章
*/
async getArticlesFromAPI() {
const payload = {
id_type: 2,
sort_type: 3, // 热门
cursor: "0",
limit: 20
};
const response = await axios.post(this.apiUrl, payload, { headers: this.headers });
const articles = [];
const items = response.data.data;
items.forEach((item, index) => {
if (item.item_type === 2 && item.item_info && item.item_info.article_info) {
const articleInfo = item.item_info.article_info;
const title = articleInfo.title;
if (title) {
articles.push({
rank: index + 1,
title: title
});
}
}
});
return articles;
}
}这个爬虫类使用掘金的API来获取热门文章列表。我们使用axios发送POST请求,并解析响应数据来提取文章标题和排名。
我们设计了几个关键方法:
constructor: 初始化API URL和请求头crawlArticles: 主方法,调用API获取文章getArticlesFromAPI: 具体实现API调用和数据解析
4. 文本向量化功能 #
接下来,我们实现将文本转换为向量的功能。这是将文本存储到向量数据库的关键步骤。
步骤 4.1: 创建utils.js文件 #
import fetch from 'node-fetch';
import { config } from './config.js';
/**
* 将文本转换为向量
* @param {string} text - 需要转换为向量的文本
* @returns {Promise<Array<number>>} - 返回向量数组
*/
export async function textToVector(text) {
const response = await fetch(config.embedding.url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${config.embedding.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: config.embedding.model,
input: text,
encoding_format: 'float'
})
});
const data = await response.json();
return data.data[0].embedding;
}
/**
* 简单的日志输出函数
* @param {string} message - 日志消息
*/
export function log(message) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${message}`);
}在这个文件中,我们实现了两个功能:
textToVector: 使用外部API将文本转换为向量log: 一个简单的日志函数,为输出添加时间戳
textToVector函数使用node-fetch向配置中指定的API发送请求,将文本转换为向量表示。这个API需要您提供有效的API密钥,并且返回结果包含文本的向量表示。
5. Milvus数据库连接与集合管理 #
现在,我们将实现Milvus数据库的连接和集合管理功能。
步骤 5.1: 创建milvus.js文件 #
import { MilvusClient, DataType } from '@zilliz/milvus2-sdk-node';
import { config } from './config.js';
/**
* Milvus数据库操作类
*/
export class MilvusDB {
constructor() {
// 创建Milvus客户端实例
this.client = new MilvusClient({
address: `${config.milvus.address}:${config.milvus.port}`,
username: '', // 如果有用户名和密码,在这里添加
password: ''
});
this.database = config.milvus.database;
this.collection = config.milvus.collection;
this.dimension = config.milvus.vectorDimension;
}
/**
* 初始化数据库和集合
*/
async init() {
await this.createDatabase();
await this.client.use({ db_name: this.database });
}
/**
* 创建数据库
*/
async createDatabase() {
try {
await this.client.createDatabase({
db_name: this.database
});
} catch (error) {
if (error.message.includes('already exists')) {
return;
}
throw error;
}
}
/**
* 检查集合是否存在
*/
async hasCollection() {
const response = await this.client.hasCollection({
collection_name: this.collection
});
return response.value === true;
}
}这是Milvus数据库操作类的基本框架,包含连接设置和数据库初始化功能。我们实现了:
constructor: 初始化Milvus客户端init: 初始化数据库连接createDatabase: 创建数据库(如果不存在)hasCollection: 检查集合是否存在
6. 数据插入操作 #
接下来,我们在MilvusDB类中添加集合创建和数据插入的功能。
步骤 6.1: 完善milvus.js文件 #
继续编辑milvus.js,添加以下方法:
/**
* 创建集合
*/
async createCollection() {
const fields = [
{
name: 'id',
data_type: DataType.Int64,
is_primary_key: true,
autoID: true
},
{
name: 'rank',
data_type: DataType.Int64,
description: '文章排名'
},
{
name: 'title',
data_type: DataType.VarChar,
max_length: 512,
description: '文章标题'
},
{
name: 'title_vector',
data_type: DataType.FloatVector,
dim: this.dimension,
description: '标题向量'
}
];
await this.client.createCollection({
collection_name: this.collection,
fields: fields
});
await this.client.createIndex({
collection_name: this.collection,
field_name: 'title_vector',
index_type: 'HNSW',
metric_type: 'COSINE',
params: { M: 8, efConstruction: 64 }
});
await this.client.loadCollection({
collection_name: this.collection
});
}
/**
* 插入数据
* @param {Array<Object>} data - 要插入的数据数组
*/
async insertData(data) {
if (!data || data.length === 0) {
return;
}
const processedData = data.map(item => {
return {
rank: item.rank,
title: item.title,
title_vector: item.title_vector
};
});
const insertData = {
collection_name: this.collection,
fields_data: processedData
};
await this.client.insert(insertData);
}
/**
* 删除集合(如果存在)
*/
async dropCollection() {
const exists = await this.hasCollection();
if (!exists) {
return;
}
await this.client.dropCollection({
collection_name: this.collection
});
}
/**
* 关闭数据库连接
*/
async close() {
if (this.client) {
await this.client.closeConnection();
}
}我们添加了几个重要方法:
createCollection: 创建集合并定义字段结构insertData: 将数据插入到集合中dropCollection: 删除现有集合close: 关闭数据库连接
集合结构包含四个字段:
id: 自动生成的主键rank: 文章排名title: 文章标题title_vector: 标题的向量表示
我们还为向量字段创建了索引,以加快相似性搜索。
7. 主程序流程实现 #
现在,我们将把所有部分连接起来,实现主程序的流程。
步骤 7.1: 创建index.js文件 #
import { JuejinCrawler } from './crawler.js';
import { MilvusDB } from './milvus.js';
import { textToVector } from './utils.js';
/**
* 简单的日志输出函数
* @param {string} message - 日志消息
*/
function log(message) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${message}`);
}
/**
* 主函数 - 爬取掘金热门文章并保存到Milvus
*/
async function main() {
log('程序开始执行');
const crawler = new JuejinCrawler();
const db = new MilvusDB();
let milvusConnected = false;
try {
log('正在初始化Milvus数据库连接...');
await db.init();
log('正在清空现有集合...');
await db.dropCollection();
log('正在创建集合...');
await db.createCollection();
milvusConnected = true;
log('Milvus数据库连接和集合准备完成');
} catch (dbError) {
milvusConnected = false;
log(`Milvus数据库连接失败: ${dbError.message}`);
}
log('开始爬取掘金热门文章...');
const articles = await crawler.crawlArticles();
log(`爬取完成,获取到 ${articles.length} 篇文章`);
if (articles.length === 0) {
log('未获取到任何文章,程序结束');
return;
}
log('开始处理文章数据和生成向量...');
const data = [];
for (let i = 0; i < articles.length; i++) {
const article = articles[i];
try {
log(`正在处理第 ${i + 1}/${articles.length} 篇文章: ${article.title.substring(0, 30)}...`);
const titleVector = await textToVector(article.title);
data.push({
rank: article.rank,
title: article.title,
title_vector: titleVector
});
} catch (error) {
log(`文章"${article.title.substring(0, 30)}..."向量化失败: ${error.message}`);
// 向量化失败的文章将被跳过
}
}
log(`向量生成完成,共处理 ${data.length} 篇文章`);
if (milvusConnected && data.length > 0) {
log('开始将数据插入Milvus数据库...');
await db.insertData(data);
log('数据插入完成');
}
if (milvusConnected) {
log('关闭Milvus数据库连接');
await db.close();
}
log('程序执行完毕');
}
// 执行主函数
main().catch(error => {
log(`程序执行出错: ${error.message}`);
process.exit(1);
});主程序的流程包括:
- 初始化爬虫和数据库连接
- 连接Milvus数据库,清空并创建集合
- 爬取掘金热门文章
- 将文章标题转换为向量
- 将文章数据和向量存储到Milvus数据库
- 关闭数据库连接
每个步骤都有日志输出,方便追踪程序执行状态。
8. 日志与错误处理 #
我们已经在主程序中添加了基本的日志和错误处理。日志记录了程序的执行过程,错误处理确保即使某些操作失败,程序也能继续执行。
主要的错误处理包括:
- 数据库连接失败处理
- 文章向量化失败处理
- 全局异常捕获
这种设计使程序更加健壮,能够应对各种异常情况。
9. 项目完整运行测试 #
现在,让我们运行项目并测试整个流程。
步骤 9.1: 运行项目 #
npm start这将执行以下流程:
- 连接到Milvus数据库
- 爬取掘金热门文章
- 将文章标题转换为向量
- 将数据存储到Milvus数据库
如果一切顺利,您将看到类似以下的输出:
[2023-08-10T12:34:56.789Z] 程序开始执行
[2023-08-10T12:34:56.890Z] 正在初始化Milvus数据库连接...
[2023-08-10T12:34:57.123Z] 正在清空现有集合...
[2023-08-10T12:34:57.456Z] 正在创建集合...
[2023-08-10T12:34:58.789Z] Milvus数据库连接和集合准备完成
[2023-08-10T12:34:58.901Z] 开始爬取掘金热门文章...
[2023-08-10T12:35:00.234Z] 爬取完成,获取到 20 篇文章
[2023-08-10T12:35:00.345Z] 开始处理文章数据和生成向量...
[2023-08-10T12:35:00.456Z] 正在处理第 1/20 篇文章: 标题1...
...
[2023-08-10T12:35:30.789Z] 向量生成完成,共处理 20 篇文章
[2023-08-10T12:35:30.901Z] 开始将数据插入Milvus数据库...
[2023-08-10T12:35:31.234Z] 数据插入完成
[2023-08-10T12:35:31.345Z] 关闭Milvus数据库连接
[2023-08-10T12:35:31.456Z] 程序执行完毕步骤 9.2: 验证数据 #
您可以使用Milvus提供的工具(如AttuneGPT、Attu或Milvus CLI)查询数据库,验证数据是否成功插入。例如,您可以尝试搜索与特定标题相似的文章。
总结 #
在本教程中,我们循序渐进地实现了一个完整的Node.js爬虫项目,包括:
- 从掘金API爬取热门文章
- 使用第三方API将文章标题转换为向量
- 将数据存储到Milvus向量数据库
这个项目展示了如何将传统的爬虫技术与现代的向量数据库结合,为构建智能搜索和推荐系统奠定基础。
您可以进一步扩展这个项目,例如:
- 添加定时任务,定期更新数据
- 实现基于标题相似度的文章搜索功能
- 添加更多文章元数据,如作者、链接等
- 改进错误处理和重试机制
希望本教程对您有所帮助!