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)
相关推荐
青 春 记 忆6 小时前
Dify Docker Compose 通用无损升级指南:从备份、双版本预演到切换与回滚
运维·人工智能·python·docker·容器
GlueNa2SiO36 小时前
03-Flask模板引擎Jinja2详解
笔记·python·flask
南风微微吹6 小时前
计算机二级MS Office历年真题试题及答案解析40套电子版PDF(含操作题和选择题)
pdf
临沂GEO8 小时前
GEO搜索优化科普|正规地理位置流量运营入门指南
大数据·人工智能·python·流量运营
大模型码小白8 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
荷蒲10 小时前
【小白量化Qbuddy】利用AI学习中文Python
人工智能·python·学习
数据狐(Datafox)11 小时前
京东商品列表API技术解析与落地应用(含标准 JSON 示例)
java·大数据·前端·人工智能·python·数据分析·json
量化吞吐机11 小时前
2026年下半年手工交易规则走向量化表达,产品该先接住哪一步
人工智能·python
Metaphor69212 小时前
使用 Python 读取和删除 Excel 文件属性
python·excel
小幂同学13 小时前
CAD图纸怎么转PDF和图片?CAD快速看图王(幂果)转换方法解析
pdf·cad