Skip to content

网页数据预处理 ​

项目背景 ​

在采集到海量网页数据后,为了方便后续利用大模型进行问答或知识检索,需要对数据进行预处理。预处理主要包括两个层次的文本分段——首先将整个网页(Document)拆分为较大块的 Paragraph,然后再将 Paragraph 拆分为更小的 Sentence 级文本,最后利用嵌入模型生成向量数据。整个流程依赖于开源项目 java-maxkb 提供的文本分段工具。


预处理流程概述 ​

  • Document 拆分为 Paragraph
    每个网页数据作为一个 Document,通过配置的参数(Chunk Size=2000 tokens、Chunk Overlap=400 tokens)进行初步拆分,生成多个 Paragraph。

  • Paragraph 拆分为 Sentence
    对每个 Paragraph 进行细分,按照 Chunk Size=150 tokens 和 Chunk Overlap=50 tokens 的配置生成 Sentence 级别的文本片段,保证分段后文本的上下文连续性和粒度适宜性。

  • 向量生成
    分段后的文本数据会调用指定的嵌入模型(text-embedding-3-large)生成对应的向量,便于后续检索与模型对话。


系统架构与代码组织 ​

项目主要由两部分代码组成:

单元测试入口 ​

用于启动整个预处理流程,通过测试类加载系统配置并调用预处理服务。

网页数据嵌入服务 ​

主要功能包括:

  • 分页读取数据库中存储的网页数据(每个网页作为一个 Document)
  • 判断数据是否已在知识库中(MaxKbDocument 表)保存,若未保存则进行处理
  • 利用 MaxKbDocumentSplitService 对 Markdown 格式的网页内容进行分段,生成多个 TextSegment
  • 将每个分段包装成 Paragraph,并封装为 ParagraphBatchVo
  • 调用 MaxKbParagraphSplitService 的批量处理方法将分段数据保存到知识库中

下面按照上述结构展示具体代码及说明。


详细代码解析 ​

1. 单元测试入口 ​

测试类用于加载 AdminAppConfig 配置,并通过 AOP 获取预处理服务实例,调用入口方法启动整个预处理流程。

java
package nexus.io.college.hawaii.kapiolani;

import org.junit.Test;

import nexus.io.jfinal.aop.Aop;
import nexus.io.max.search.config.AdminAppConfig;
import nexus.io.tio.boot.testing.TioBootTest;

public class KapiolaniWebPageEmbeddingServiceTest {
  @Test
  public void test() {
    TioBootTest.runWith(AdminAppConfig.class);
    KapiolaniWebPageEmbeddingService kapiolaniWebPageEmbeddingService = Aop.get(KapiolaniWebPageEmbeddingService.class);
    kapiolaniWebPageEmbeddingService.index();
  }
}

说明:

  • 使用 TioBootTest.runWith(AdminAppConfig.class) 加载系统配置。
  • 通过 AOP 获取 KapiolaniWebPageEmbeddingService 实例并调用 index() 方法启动预处理。

2. 网页数据嵌入服务 ​

该类负责处理网页数据,将网页内容(Markdown 格式)转换为分段数据后保存到知识库中。

java
package nexus.io.college.hawaii.kapiolani;

import java.util.ArrayList;
import java.util.List;

import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.jfinal.aop.Aop;
import nexus.io.maxkb.model.MaxKbDocument;
import nexus.io.maxkb.service.kb.MaxKbDocumentSplitService;
import nexus.io.maxkb.service.kb.MaxKbParagraphSplitService;
import nexus.io.maxkb.vo.Paragraph;
import nexus.io.maxkb.vo.ParagraphBatchVo;
import nexus.io.model.page.Page;

import dev.langchain4j.data.segment.TextSegment;

public class KapiolaniWebPageEmbeddingService {

  MaxKbDocumentSplitService maxKbDocumentSplitService = Aop.get(MaxKbDocumentSplitService.class);

  //
  public void index() {
    int pageNumber = 1;
    int pageSize = 100;
    while (true) {
      Page<Row> page = Db.paginate(pageNumber, pageSize, "select id, url, title, type, markdown", "from hawaii_kapiolani_web_page");
      List<Row> list = page.getList();
      if (list.isEmpty()) {
        break;
      }

      for (Row row : list) {
        Long id = row.getLong("id");
        if (!Db.exists(MaxKbDocument.tableName, "id", id)) {
          one(row, id);
        }
      }

      pageNumber++;
    }
  }

  private void one(Row row, Long id) {
    MaxKbDocument maxKbDocument = new MaxKbDocument();
    String name = row.getString("title");
    String markdown = row.getString("markdown");
    long userId = 1L;
    long dataseetId = 1L;
    maxKbDocument.setId(id).set("url", row.getString("url")).set("title", name)
        //
        .set("name", name).set("type", row.getString("type")).set("content", markdown)
        //
        .set("dataset_id", dataseetId).set("user_id", userId);
    maxKbDocument.save();

    List<TextSegment> segments = maxKbDocumentSplitService.split(markdown);
    List<Paragraph> paragraphs = new ArrayList<>();
    for (TextSegment textSegment : segments) {
      paragraphs.add(new Paragraph(textSegment.text()));
    }

    ParagraphBatchVo paragraphBatchVo = new ParagraphBatchVo().setId(id).setName(name);
    paragraphBatchVo.setParagraphs(paragraphs);

    List<ParagraphBatchVo> list = new ArrayList<>();
    list.add(paragraphBatchVo);

    Aop.get(MaxKbParagraphSplitService.class).batch(userId, dataseetId, list);

  }
}

说明:

  • 数据分页读取:利用 Db.paginate(...) 分页查询 hawaii_kapiolani_web_page 表中的数据,每页最多 100 条记录。
  • 数据去重:对于每一条记录,根据 ID 判断是否已在 MaxKbDocument 表中存在,避免重复预处理。
  • Document 保存:将网页数据封装为 MaxKbDocument 对象,并保存网页的 URL、标题、类型、Markdown 内容以及其他附加信息。
  • 文本分段:调用 maxKbDocumentSplitService.split(markdown) 将整篇 Markdown 内容拆分为多个文本段,每个段落对应一个 Paragraph。
  • 段落批量处理:将所有 Paragraph 封装到一个 ParagraphBatchVo 中,并通过 MaxKbParagraphSplitService.batch(...) 批量将分段数据保存到知识库中。

在 java-maxkb 项目中,Document 拆分为 Paragraph 时所采用的分段参数(Chunk Size 2000 tokens、Chunk Overlap 400 tokens)和 Paragraph 拆分为 Sentence 的参数(Chunk Size 150 tokens、Chunk Overlap 50 tokens)均在该项目中进行配置,本文仅展示如何调用这些服务完成预处理工作。

EmbeddingController ​

java
package nexus.io.max.search.controller;

import nexus.io.annotation.RequestPath;
import nexus.io.college.hawaii.kapiolani.KapiolaniWebPageEmbeddingService;
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.body.RespBodyVo;
import nexus.io.tio.utils.thread.TioThreadUtils;

@RequestPath("/embedding")
public class EmbeddingController {

  public RespBodyVo kcc() {

    TioThreadUtils.execute(() -> {
      try {
        KapiolaniWebPageEmbeddingService kapiolaniWebPageEmbeddingService = Aop.get(KapiolaniWebPageEmbeddingService.class);
        kapiolaniWebPageEmbeddingService.index();
      } catch (Exception e) {
        e.printStackTrace();
      }
    });

    return RespBodyVo.ok();
  }
}

curl http://127.0.0.1/embedding/kcc

总结 ​

本文档详细描述了网页数据预处理的整体流程,包括将一个 Webpage(作为一个 Document)拆分为 Paragraph,再将 Paragraph 拆分为 Sentence,并生成对应的向量数据。预处理工作利用了开源项目 java-maxkb 提供的文本分段能力,配置了 Document 和 Sentence 两个层次的分段参数。通过单元测试入口启动预处理服务,读取数据库中的网页数据,调用分段服务生成文本片段,最后以批量方式将结果存入知识库中,为后续的向量检索与大模型问答提供坚实基础。

以上即为网页数据预处理的完整文档,所有代码均完整保留,并按照良好的组织结构重新排序和说明。

MIT Licensed