ai
  • index
  • cursor
  • vector
  • crawl
  • crawl-front
  • DrissionPage
  • logging
  • mysql
  • pprint
  • sqlalchemy
  • contextmanager
  • dotenv
  • Flask
  • python
  • job
  • pdfplumber
  • python-docx
  • redbook
  • douyin
  • ffmpeg
  • json
  • numpy
  • opencv-python
  • pypinyin
  • re
  • requests
  • subprocess
  • time
  • uuid
  • watermark
  • milvus
  • pymilvus
  • search
  • Blueprint
  • flash
  • Jinja2
  • secure_filename
  • url_for
  • Werkzeug
  • chroma
  • HNSW
  • pillow
  • pandas
  • beautifulsoup4
  • langchain-community
  • langchain-core
  • langchain
  • langchain_unstructured
  • libreoffice
  • lxml
  • openpyxl
  • pymupdf
  • python-pptx
  • RAGFlow
  • tabulate
  • sentence_transformers
  • jsonl
  • collections
  • jieba
  • rag_optimize
  • rag
  • rank_bm25
  • Hugging_Face
  • modelscope
  • all-MiniLM-L6-v2
  • ollama
  • rag_measure
  • ragas
  • ASGI
  • FastAPI
  • FastChat
  • Jupyter
  • PyTorch
  • serper
  • uvicorn
  • markdownify
  • NormalizedLevenshtein
  • raq-action
  • CrossEncoder
  • Bi-Encoder
  • neo4j
  • neo4j4python
  • matplotlib
  • Plotly
  • Streamlit
  • py2neo
  • abc
  • read_csv
  • neo4jinstall
  • APOC
  • neo4jproject
  • uv
  • GDS
  • heapq
  • 1. 项目初始化
    • 步骤 1.1: 创建项目目录
    • 步骤 1.2: 初始化项目
    • 步骤 1.3: 安装依赖
    • 步骤 1.4: 更新package.json
  • 2. 创建配置文件
    • 步骤 2.1: 创建config.js文件
  • 3. 实现API爬虫功能
    • 步骤 3.1: 创建crawler.js文件
  • 4. 文本向量化功能
    • 步骤 4.1: 创建utils.js文件
  • 5. Milvus数据库连接与集合管理
    • 步骤 5.1: 创建milvus.js文件
  • 6. 数据插入操作
    • 步骤 6.1: 完善milvus.js文件
  • 7. 主程序流程实现
    • 步骤 7.1: 创建index.js文件
  • 8. 日志与错误处理
  • 9. 项目完整运行测试
    • 步骤 9.1: 运行项目
    • 步骤 9.2: 验证数据
  • 总结

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'
  }
};

这个配置文件包含三个主要部分:

  1. Milvus数据库连接配置
  2. 文本向量化API配置
  3. 爬虫配置

您需要将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}`);
}

在这个文件中,我们实现了两个功能:

  1. textToVector: 使用外部API将文本转换为向量
  2. 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数据库操作类的基本框架,包含连接设置和数据库初始化功能。我们实现了:

  1. constructor: 初始化Milvus客户端
  2. init: 初始化数据库连接
  3. createDatabase: 创建数据库(如果不存在)
  4. 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();
        }
    }

我们添加了几个重要方法:

  1. createCollection: 创建集合并定义字段结构
  2. insertData: 将数据插入到集合中
  3. dropCollection: 删除现有集合
  4. 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);
});

主程序的流程包括:

  1. 初始化爬虫和数据库连接
  2. 连接Milvus数据库,清空并创建集合
  3. 爬取掘金热门文章
  4. 将文章标题转换为向量
  5. 将文章数据和向量存储到Milvus数据库
  6. 关闭数据库连接

每个步骤都有日志输出,方便追踪程序执行状态。


8. 日志与错误处理 #

我们已经在主程序中添加了基本的日志和错误处理。日志记录了程序的执行过程,错误处理确保即使某些操作失败,程序也能继续执行。

主要的错误处理包括:

  1. 数据库连接失败处理
  2. 文章向量化失败处理
  3. 全局异常捕获

这种设计使程序更加健壮,能够应对各种异常情况。


9. 项目完整运行测试 #

现在,让我们运行项目并测试整个流程。

步骤 9.1: 运行项目 #

npm start

这将执行以下流程:

  1. 连接到Milvus数据库
  2. 爬取掘金热门文章
  3. 将文章标题转换为向量
  4. 将数据存储到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爬虫项目,包括:

  1. 从掘金API爬取热门文章
  2. 使用第三方API将文章标题转换为向量
  3. 将数据存储到Milvus向量数据库

这个项目展示了如何将传统的爬虫技术与现代的向量数据库结合,为构建智能搜索和推荐系统奠定基础。

您可以进一步扩展这个项目,例如:

  • 添加定时任务,定期更新数据
  • 实现基于标题相似度的文章搜索功能
  • 添加更多文章元数据,如作者、链接等
  • 改进错误处理和重试机制

希望本教程对您有所帮助!

访问验证

请输入访问令牌

Token不正确,请重新输入