网页数据预处理
项目背景
在采集到海量网页数据后,为了方便后续利用大模型进行问答或知识检索,需要对数据进行预处理。预处理主要包括两个层次的文本分段——首先将整个网页(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 获取预处理服务实例,调用入口方法启动整个预处理流程。
package com.litongjava.college.hawaii.kapiolani;
import org.junit.Test;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.max.search.config.AdminAppConfig;
import com.litongjava.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 格式)转换为分段数据后保存到知识库中。
package com.litongjava.college.hawaii.kapiolani;
import java.util.ArrayList;
import java.util.List;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Row;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.maxkb.model.MaxKbDocument;
import com.litongjava.maxkb.service.kb.MaxKbDocumentSplitService;
import com.litongjava.maxkb.service.kb.MaxKbParagraphSplitService;
import com.litongjava.maxkb.vo.Paragraph;
import com.litongjava.maxkb.vo.ParagraphBatchVo;
import com.litongjava.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
package com.litongjava.max.search.controller;
import com.litongjava.annotation.RequestPath;
import com.litongjava.college.hawaii.kapiolani.KapiolaniWebPageEmbeddingService;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.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 两个层次的分段参数。通过单元测试入口启动预处理服务,读取数据库中的网页数据,调用分段服务生成文本片段,最后以批量方式将结果存入知识库中,为后续的向量检索与大模型问答提供坚实基础。
以上即为网页数据预处理的完整文档,所有代码均完整保留,并按照良好的组织结构重新排序和说明。