2.PDF长文档完整读取

一、先知道什么是RAG

1. 一句话给 RAG 下定义

RAG = 给大模型(比如 ChatGPT、豆包、开源大模型)外挂了一个你自己的私有知识库,让大模型能精准、无差错地回答你私有数据里的内容,不会瞎编、不会用过时的知识。

你可以把 RAG 理解成:

  • 大模型是一个超级会说话、会总结的「秘书」,但她的知识是固定的、过时的,而且完全不知道你电脑里的 PDF、文档、笔记这些私有内容
  • RAG 就是你给这个秘书配的一个专属智能文件柜 + 精准检索神器,你把自己的所有文档、PDF、笔记都放进这个柜子里,秘书回答问题前,会先去这个柜子里精准找到对应的内容,再基于这些内容给你回答,绝对不会瞎编。
2. RAG 到底解决了什么核心痛点?

大模型本身有两个致命缺陷,RAG 就是专门解决这两个问题的:

  • 大模型的知识是「过时的、固定的」

    比如 2024 年的大模型,不知道 2025 年的新政策、新行业动态、你公司最新的产品手册,你问她,她要么瞎编,要么说「我的知识截止到 XX 时间」。

  • 大模型完全不知道你的「私有数据」

    你电脑里的公司的内部制度、项目文档、个人笔记,这些内容从来没有在互联网上出现过,大模型完全没见过,你直接问她,她 100% 会瞎编乱造。

  • RAG 的核心价值,就是让大模型能用上你的私有数据、最新的知识,回答 100% 贴合你的需求,不会出错、不会瞎编

二、实操及讲解

模块 1:前置知识与核心认知

核心基础概念认知

  • 文本块(Chunk):拆分后的每一段独立的短文本,是 RAG 系统中检索和向量化的最小单元
  • 块大小(Chunk Size):每个文本块的最大长度(通常按字符数 /token 数计算),是分块最核心的参数
  • 块重叠(Chunk Overlap):相邻两个文本块之间重复的内容长度,解决分块导致的上下文断裂问题
模块 2:文本分块核心规则与策略学习
1. 核心参数选型规则

块大小(Chunk Size)选型

场景 推荐块大小(字符数) 对应 token 数(约) 核心原则
通用知识库 / 网页文本 100-500 50-250 兼顾检索精度和上下文完整性
论文 / 长文本 / 书籍 500-1000 250-500 保证段落语义完整,避免拆分核心内容
问答 / 精准知识库 50-200 25-100 最大化提升检索匹配精度
通用红线 必须小于向量模型最大输入长度,预留 10%-20% 冗余空间 - 绝对不能超出模型输入限制

块重叠(Chunk Overlap)选型

  • 通用规则:重叠长度为块大小的10%-20%,比如块大小 500 字符,重叠 50-100 字符
  • 高连贯性长文本场景:重叠比例提升到20%-30%,彻底避免上下文断裂
  • 核心原则:重叠部分必须包含完整的句子 / 语义单元,不能只重叠单个无意义的词语
2. 主流分块策略详解
分块策略 核心逻辑 优点 缺点 适用场景
固定长度分块(最基础) 按固定的字符数 /token 数拆分文本,达到指定长度就拆分 实现简单、速度快、长度完全可控,适配绝大多数向量模型 可能拆分完整的句子 / 段落,导致语义断裂 通用知识库、网页文本、PDF 文档等绝大多数场景
语义单元分块(按段落 / 标题) 先按文本天然结构拆分(标题、段落、句子),再对超长段落二次拆分 完全保留文本语义结构,不会拆分完整句子,语义完整性拉满 拆分后块长度不均匀,可能出现超长块,需要二次处理 结构化文档、论文、书籍、带标题层级的文档
带重叠滑动窗口分块(推荐) 固定长度分块的基础上,相邻块之间保留指定长度的重叠内容 解决了上下文断裂问题,同时保留长度可控的优点,是 RAG 项目首选 少量内容冗余,轻微增加计算量 长文本、高连贯性要求的所有 RAG 场景
智能语义分块(进阶) 基于大模型 / 语义相似度模型,自动识别文本语义边界,在转折处拆分 拆分效果最贴合语义逻辑,块之间语义独立性最强 实现复杂、速度慢、需要调用模型,成本高 法律 / 医疗等专业知识库、对检索精度要求极高的场景

PS: 带重叠的滑动窗口是什么意思?

带重叠滑动窗口(RAG 必懂)

一句话:切文本不一刀断开,后一块开头重复带上前一块末尾一小段文字,两块内容有重叠区域,防止一句话被劈成两半、关键信息丢失。

先对比两种切法(一眼看懂区别)

① 无重叠固定切块(普通一刀切)

设定:块长 500 字符,切完直接从 500 位置新开下一块

块 1:0~500

块 2:500~1000

块 3:1000~1500

坑:刚好一句话卡在 500 字符中间,前半句在块 1、后半句在块 2。用户提问只搜到块 2,后半句孤零零,看不懂前文,RAG 回答出错。

示例原文:订单超时未支付时,系统发送消息到RabbitMQ,消费者查询订单状态

一刀切拆分:

块 1:订单超时未支付时,系统发送消息到

块 2:RabbitMQ,消费者查询订单状态

搜 "消费者做什么" 只召回块 2,不知道什么场景下查订单。

② 带重叠滑动窗口(滑动切块)

参数:chunk_size=500(每块总长度),overlap=100(重叠100字符,重叠比例20%)

滑动步长 = 总块长−重叠 = 500−100=400,下一块不从 500 开始,从 400 开始

块 1:0~500(共 500 字)

块 2:400~900(从 400 起步,前面 400~500 这 100 个字和块 1 重复 = 重叠)

块 3:800~1300(800~900 和块 2 重复)

原来被劈断的那句话,后半句在块 2,前半句被重叠带进块 2 里,不管搜到块 1 还是块 2,整句话信息完整。

三个核心参数

  • chunk_size:窗口大小 = 每一块总字符长度(默认 500)
  • overlap:重叠字符数(块末尾复制到下一块开头,一般 10%~20%)
  • step:滑动步长 = chunk_size−overlap(每次往后挪多少)
    举例:500 块长、20% 重叠→重叠 100、步长 400。

为什么 RAG 一定要用重叠?

  • 解决语义断裂:避免关键词、完整句子被切在两块中间,检索缺上下文(最核心目的)
  • 提升检索命中率:跨边界的内容会同时存在两个块里,提问更容易匹配到完整信息
  • 通用行业规范:普通文档重叠 10%~20%;法律 / 医疗专业文档 20%~30%
3. 实操代码
python 复制代码
# -*- coding: utf-8 -*-
"""
@Created on : 2026/6/3 15:11
@creator : er_nao
@File :day_86.py
@Description :PDF长文档完整读取
"""
import pdfplumber

pdf_file_path = "C:\\Users\\hp\\Desktop\\NLP学习数据\\arctle2.pdf"


# 文本提取代码
def extract_full_text_from_pdf(pdf_file_path):
    """
       提取PDF全量文本内容,返回拼接后的完整长文本
       :param pdf_path: PDF文件路径
       :return: 完整长文本字符串
    """
    with pdfplumber.open(pdf_file_path) as pdf:
        full_text_list = []
        for page in pdf.pages:
            page_text = page.extract_text(
                x_tolerance=0.5, y_tolerance=0.5, keep_blank_chars=False
            )
            if page_text.strip():
                full_text_list.append(page_text)
        # 拼接所有页面的文本,用换行符分隔
        full_text = "\n".join(full_text_list)
        print(f"PDF文本提取完成,总字符数:{len(full_text)}")
        return full_text


# 实操任务 1:基础固定长度文本分块代码

def fixed_length_chunking(text, chunk_size=500):
    """
       基础固定长度文本分块函数
       :param text: 输入的完整长文本
       :param chunk_size: 每个文本块的最大字符数,默认500
       :return: 分块后的文本块列表
    """
    # 初始化结果列表
    chunk_list = []
    # 计算文本总长度
    text_length = len(text)
    # 按固定步长遍历文本,拆分块
    for start_index in range(0, text_length, chunk_size):
        # 计算当前块的结束索引
        end_index = min(start_index + chunk_size, text_length)
        # 截取当前块的文本
        current_chunk = text[start_index:end_index]
        # 过滤掉空块
        if current_chunk.strip():
            chunk_list.append(current_chunk)

    # 输出分块结果统计
    print(f"===== 固定长度分块完成 =====")
    print(f"总文本长度:{text_length} 字符")
    print(f"预设块大小:{chunk_size} 字符")
    print(f"最终分块数量:{len(chunk_list)} 个")
    print(f"单个块最大长度:{max([len(chunk) for chunk in chunk_list])} 字符")
    print("=============================")

    return chunk_list


# 实操任务 2:按段落 / 语义单元结构化分块代码
def paragraph_based_chunking(text, max_chunk_size=500):
    """
        按段落/语义单元的结构化分块函数
        核心逻辑:先按天然段落拆分,再对超长段落二次拆分,保证语义完整性
        :param text: 输入的完整长文本
        :param max_chunk_size: 单个块的最大字符数,默认1000
        :return: 分块后的文本块列表
    """
    # 第一步:按换行符拆分天然段落,过滤掉空段落
    raw_paragraphs = [p.strip() for p in text.split("\n") if p.strip()]
    print(f"原始段落数量:{len(raw_paragraphs)}个")

    # 第二步:遍历所有段落,拆分超长段落,生成最终块
    chunk_list = []
    for paragraph in raw_paragraphs:
        paragraph_length = len(paragraph)
        # 如果段落长度小于等于最大块大小,直接作为一个块
        if paragraph_length <= max_chunk_size:
            chunk_list.append(paragraph)
        # 如果段落超长,进行二次固定长度拆分
        else:
            for start in range(0, paragraph_length, max_chunk_size):
                end = min(start + max_chunk_size, paragraph_length)
                sub_chunk = paragraph[start:end]
                if sub_chunk.strip():
                    chunk_list.append(sub_chunk)

    # 输出分块结果统计
    print(f"===== 段落结构化分块完成 =====")
    print(f"最终分块数量:{len(chunk_list)} 个")
    print(f"单个块最大长度:{max([len(chunk) for chunk in chunk_list])} 字符")
    print(f"单个块最小长度:{min([len(chunk) for chunk in chunk_list])} 字符")
    print("=============================")

    return chunk_list


# 实操任务 3:带重叠的滑动窗口分块代码(RAG 项目推荐)
def sliding_window_chunking(text, chunk_size=500, overlap_ratio=0.2):
    """
       带重叠的滑动窗口分块函数(RAG项目首选方案)
       核心逻辑:固定长度分块的基础上,相邻块之间保留指定比例的重叠内容,解决上下文断裂问题
       :param text: 输入的完整长文本
       :param chunk_size: 每个文本块的最大字符数,默认500
       :param overlap_ratio: 相邻块的重叠比例,默认20%(0.2),推荐10%-30%
       :return: 分块后的文本块列表
   """
    # 计算重叠的字符数
    overlap_length = int(chunk_size * overlap_ratio)
    # 计算每次滑动的步长(块大小 - 重叠长度)
    step_length = chunk_size - overlap_length
    # 初始化结果列表
    chunk_list = []
    text_length = len(text)

    for start_index in range(0, text_length, step_length):
        # 计算当前块的结束索引
        end_index = min(start_index+chunk_size, text_length)
        # 截取当前块的文本
        current_chunk = text[start_index:end_index]
        # 过滤掉空块
        if current_chunk.strip():
            chunk_list.append(current_chunk)

    # 输出分块结果统计
    print(f"===== 滑动窗口分块完成 =====")
    print(f"预设块大小:{chunk_size}字符")
    print(f"重叠比例:{overlap_ratio*100}%,重叠长度:{overlap_length}字符")
    print(f"滑动步长:{step_length}字符")
    print(f"总文本长度:{text_length}字符")
    print(f"最终分块数量:{len(chunk_list)}个")
    print(f"单个快最大长度:{max([len(chunk) for chunk in chunk_list])}字符")
    print("=============================")

    return chunk_list


#  分块结果保存与校验代码
import json

# -------------------------- 结果保存路径 --------------------------
output_chunk_path = "C:\\Users\\hp\\Desktop\\NLP学习数据\\RAG文本分块结果.json"
# -----------------------------------------------------------------
def save_chunks_to_file(chunk_list, save_path):
    """
        将分块结果保存为JSON文件,方便后续RAG系统使用
        :param chunk_list: 分块后的文本块列表
        :param save_path: 保存路径
    """
    # 结构化保存,包含块序号、块内容、块长度
    chunk_result = []
    for idx, chunk in enumerate(chunk_list):
        chunk_result.append({
            "chunk_id": idx +1,
            "chunk_text": chunk,
            "chunk_length": len(chunk)
        })

    with open(save_path, 'w', encoding='utf-8') as f:
        json.dump(chunk_result, f, ensure_ascii=False, indent=2)

    print(f"分块结果已保存至:{save_path}")
    print(f"共保存 {len(chunk_result)} 个文本块")



if __name__ == "__main__":
    full_text = extract_full_text_from_pdf(pdf_file_path)

    # 1:基础固定长度文本分块代码
    # fixed_chunks = fixed_length_chunking(full_text, chunk_size=500)
    # 打印前3个块,预览结果
    # for i, chunk in enumerate(fixed_chunks[:3]):
    #     print(f"\n--- 第{i + 1}个文本块 ---")
    #     print(f"长度:{len(chunk)} 字符")
    #     print(f"内容:{chunk[:200]}...")  # 只打印前200个字符预览


    # 2:按段落 / 语义单元结构化分块代码
    # paragraph_chunks = paragraph_based_chunking(full_text, chunk_size=500)
    # 打印前3个块,预览结果
    # for i, chunk in enumerate(paragraph_chunks[:3]):
    #     print(f"\n--- 第{i + 1}个文本块 ---")
    #     print(f"长度:{len(chunk)} 字符")
    #     print(f"内容:{chunk[:200]}...")

    # 3:带重叠的滑动窗口分块代码(RAG项目推荐)
    sliding_chunks = sliding_window_chunking(full_text, chunk_size=500, overlap_ratio=0.2)
    # 打印前4个块,预览相邻块的重叠效果
    for i, chunk in enumerate(sliding_chunks[:4]):
        print(f"\n---第{i+1}个文件块---")
        print(f"长度:{len(chunk)}字符")
        print(f"内容:{chunk}...")


    #  分块结果保存与校验代码
    save_chunks_to_file(sliding_chunks, output_chunk_path)
相关推荐
装不满的克莱因瓶29 分钟前
掌握感知器的学习原理
人工智能·python·神经网络·算法·ai·卷积神经网络
慈云数据33 分钟前
【免费开源】多格式文件转换工具 Pro:图片、PDF、文档、批量重命名一站式转换
pdf
py小王子33 分钟前
Nature 期刊图复现|Python 实现双轴高维直方图与重叠分布图
python·nature·期刊图复现
小熊Coding38 分钟前
从零打造一款回合制 RPG 游戏:基于 Pygame 的《塔影守卫》全解析
python·游戏·计算机专业·pygame·rpg·2d游戏
myenjoy_11 小时前
串口采集与 Modbus RTU——字节流里的时间敏感博弈
网络·python·网络协议·tcp/ip
易舟云财务软件1 小时前
财务 AI Python 实战:从自动化报表到智能风控的应用场景
人工智能·python·自动化
武雄(小星Ai)1 小时前
一个模型干五件事:拆解 NVIDIA Cosmos 3 的物理 AI 全模态架构
人工智能·python·agent
Mr.Daozhi2 小时前
跨境电商选品完整流水线:Google Trends筛词+Meta广告分析,CLI工具设计实战
开发语言·爬虫·python·跨境电商·工具链·选品
装不满的克莱因瓶2 小时前
掌握典型卷积神经网络的搭建
人工智能·python·深度学习·神经网络·机器学习·ai·cnn