1. 读取单个txt文件

# 读取单个txt文件
import os
from langchain_community.document_loaders import TextLoader
# 获取当前脚本文件所在的目录
script_dir = os.path.dirname(__file__)
print(f"获取当前脚本文件所在的目录:{script_dir}") 
# 结合相对路径构建完整路径
file_dir = os.path.join(script_dir, '../../90-文档-Data/黑悟空/设定.txt')

loader = TextLoader(file_dir, encoding='utf-8')
documents = loader.load()
print(documents)

在这里插入图片描述

2. Document 数据结构

在 LangChain 中,Document 是处理文本数据的核心数据结构,用于封装原始文本内容及相关元数据,是连接文档加载、分割、存储、检索等环节的基础载体。

2.1 Document 的核心作用

Document 的设计目的是标准化文本数据的表示形式,让不同来源(如网页、PDF、本地文件等)、不同格式的文本都能以统一的结构在 LangChain 流程中流转(例如分块、嵌入、检索等)。Document 是 LangChain 中文本数据的标准化容器,通过 page_content 存储核心文本,通过 metadata 携带额外信息,确保了从数据加载到最终生成的全流程兼容性和可追溯性,是构建 RAG 等应用的基础组件。

2.2 Document 的核心属性

Document 类的定义简化后如下(核心字段):

class Document:
    page_content: str  # 文档的核心文本内容
    metadata: dict  # 文档的元数据(额外信息)
  1. page_content

    • 类型:字符串(str
    • 作用:存储文档的原始文本内容,是后续处理(如分块、嵌入、生成回答)的核心依据。
    • 示例:
      page_content = "黑神话悟空的战斗系统融合了武侠风格,招式灵动且富有冲击力。"
      
  2. metadata

    • 类型:字典(dict
    • 作用:存储与文本相关的额外信息,辅助后续检索、过滤或溯源。
    • 常见元数据字段(按需自定义):
      • source:文本来源(如文件路径、网页 URL);
      • page:页码(适用于 PDF 等分页文档);
      • author:作者;
      • created_at:创建时间等。
    • 示例:
      metadata = {"source": "https://zh.wikipedia.org/wiki/黑神话:悟空", "page": 1}
      

2.3 为什么需要 Document

  • 标准化:无论文本来自网页、PDF 还是 TXT,都被封装为 Document 对象,确保后续处理逻辑(如分块、嵌入)无需适配不同格式。
  • 元数据跟踪:在文档分块后,元数据会被继承到子文档中,方便追溯回答的来源(例如“该信息来自某网页的第 3 段”)。
  • 扩展性:可通过自定义元数据添加业务相关信息(如标签、优先级),支持更灵活的检索和过滤。

2.4 使用示例

from langchain_core.documents import Document

# 创建一个 Document 对象
doc = Document(
    page_content="72变是黑神话悟空的核心技能,可化身不同形态探索世界。",
    metadata={"source": "游戏设定文档", "category": "技能介绍"}
)

# 访问内容和元数据
print(doc)
print(doc.page_content)  # 输出:72变是黑神话悟空的核心技能...
print(doc.metadata)      # 输出:{"source": "游戏设定文档", "category": "技能介绍"}

在这里插入图片描述

2.5 在 RAG 流程中的作用

在检索增强生成(RAG)中,Document 是数据流转的“载体”:

  1. 加载阶段:文档加载器(如 WebBaseLoaderPyPDFLoader)将原始数据解析为 Document 对象(自动填充 page_content 和基础元数据,如 source)。
  2. 分块阶段:文本分割器(如 RecursiveCharacterTextSplitter)将长 Document 拆分为多个短 Document(子文档继承原文档的元数据)。
  3. 存储阶段Documentpage_content 被转换为向量存入向量库,metadata 随向量一起存储,用于检索时的过滤或展示。
  4. 检索阶段:检索到的相关 Document 会被传入大模型,其 page_content 作为上下文,metadata 可用于标注答案来源。

3. 基于LangChain加载目录中所有文档

# 使用 LangChain 加载目录中所有文档

"""
# 需要安装 Tesseract OCR 
### Ubuntu 执行如下命令:
sudo apt update
sudo apt install tesseract-ocr -y

### 说明

DirectoryLoader 在加载目录时,会尝试为遇到的不同文件类型找到合适的加载器。
对于 .pptx (PowerPoint)、.pdf 和 .jpg 等复杂格式,DirectoryLoader 通常会依赖 unstructured 库来处理。
unstructured 库在处理某些文件类型(特别是需要进行文本提取和处理的)时,会依赖 nltk (Natural Language Toolkit) 库。
nltk 库需要下载一些额外的数据包(如分词器、词性标注器等)才能正常工作。

如果遇到错误 zipfile.BadZipFile: File is not a zip file 
这是发生发生在 nltk.data.py 内部,这强烈表明 nltk 在尝试加载其数据包时遇到了问题。
它尝试打开一个文件作为 zip 压缩包来查找数据,但该文件不是一个有效的 zip 文件。


您好!

根据您提供的错误信息和文件列表,问题出在 langchain_community.document_loaders.DirectoryLoader 尝试加载目录中的 .pptx 文件时。

根本原因分析:

DirectoryLoader 在加载目录时,会尝试为遇到的不同文件类型找到合适的加载器。
对于 .pptx (PowerPoint)、.pdf 和 .jpg 等复杂格式,DirectoryLoader 通常会依赖 unstructured 库来处理。
unstructured 库在处理某些文件类型(特别是需要进行文本提取和处理的)时,会依赖 nltk (Natural Language Toolkit) 库。
nltk 库需要下载一些额外的数据包(如分词器、词性标注器等)才能正常工作。
您的错误 zipfile.BadZipFile: File is not a zip file 发生在 nltk.data.py 内部,这强烈表明 nltk 在尝试加载其数据包时遇到了问题。它尝试打开一个文件作为 zip 压缩包来查找数据,但该文件不是一个有效的 zip 文件。
结论: 错误不是因为您的 .pptx 文件本身损坏,而是因为 unstructured 调用 nltk 时,nltk 找不到或无法正确加载其所需的数据包,导致了 BadZipFile 错误。当只有 .csv 文件时,DirectoryLoader 可能使用了一个不依赖 unstructured 或 nltk 的简单加载器,所以没有报错。

#解决方案:
# 最直接的解决方案是 
# 下载 NLTK 所需的数据包。需要在你的 Python 环境中运行以下代码一次
import nltk
nltk.download('averaged_perceptron_tagger') 
nltk.download('punkt') 
"""
import os
from langchain_community.document_loaders import DirectoryLoader

# 获取当前脚本文件所在的目录
script_dir = os.path.dirname(__file__)
print(f"获取当前脚本文件所在的目录:{script_dir}") 
# 结合相对路径构建完整路径
data_dir = os.path.join(script_dir, '../../90-文档-Data/黑悟空')

loader = DirectoryLoader(data_dir)
docs = loader.load()
print(f"文档数:{len(docs)}")  # 输出文档总数
print(docs[0])  # 输出第一个文档

批量加载多格式文档的预处理步骤,目的是将指定目录中(90-文档-Data/黑悟空)的所有文档统一转换为 LangChain 可处理的 Document 格式,为后续构建 RAG 问答系统(分块、向量存储、检索等)做准备。

3.1 读取文档时指定参数

from langchain_community.document_loaders import DirectoryLoader

import os
# 获取当前脚本文件所在的目录
script_dir = os.path.dirname(__file__)
print(f"获取当前脚本文件所在的目录:{script_dir}") 
# 结合相对路径构建完整路径
data_dir = os.path.join(script_dir, '../../90-文档-Data/黑悟空')

loader = DirectoryLoader(data_dir, 
                         glob="**/*.md", 
                         use_multithreading=True,
                         show_progress=True,
                         )
docs = loader.load()
print(f"文档数:{len(docs)}")  # 输出文档总数
print(docs[0])  # 输出第一个文档

使用 LangChain 的 DirectoryLoader 加载指定目录中的文档,但相比之前的版本增加了更具体的配置,专注于加载目录中所有 Markdown(.md)格式的文件。以下是详细解释:

  • 关键参数详解
    • glob="**/*.md"
      glob 是文件匹配模式,** 表示递归遍历所有子目录,*.md 表示只匹配后缀为 .md 的 Markdown 文件。这一步会过滤掉目录中其他格式的文件(如 .txt.pdf 等),只保留 Markdown 文档。
    • use_multithreading=True
      启用多线程加载,当目录中文档数量多或文件较大时,能显著提高加载速度(避免单线程逐个加载的等待时间)。
    • show_progress=True
      加载过程中显示进度(例如“已加载 3/10 个文件”),方便观察加载状态,避免因文件多而误以为程序未响应。

相比未指定 glob 的版本,这段代码的针对性更强:

  • 只加载 Markdown 文件:通过 glob="**/*.md" 过滤无关格式,避免加载不需要的文件(节省资源,提高效率)。
  • 优化加载体验:多线程加速和进度显示让加载过程更高效、更透明。

3.2 指定加载工具

DirectoryLoader 的设计允许通过 loader_cls 参数自定义具体文件的加载器,原因如下:

  1. 默认逻辑的补充DirectoryLoader 会默认根据文件后缀自动选择加载器(例如 .mdUnstructuredMarkdownLoader.txtTextLoader),但有时需要强制使用特定加载器(比如希望用更简单的 TextLoader 处理 .md 文件)。
  2. 灵活性需求:不同加载器对同一种文件的处理方式可能不同(例如 TextLoader 直接读取纯文本,而 UnstructuredMarkdownLoader 会解析 Markdown 格式并保留结构),通过 loader_cls 可以按需选择。
  • TextLoader 是 LangChain 中最简单的文本加载器,功能是直接读取文件的原始文本内容(忽略格式,仅提取字符)。
  • 对于 .md(Markdown)文件,默认加载器可能会解析标题、列表等格式并转换为结构化文本,而指定 TextLoader 后,会直接读取 .md 文件的原始内容(包括 #* 等 Markdown 语法符号)。
    当你:
  • 不需要解析文件格式(如 Markdown 的标题、表格),只需要纯文本内容时;
  • 发现默认加载器对某种文件处理有问题(如格式错乱),想换一种更简单的加载方式时;
  • 希望统一不同文件的加载逻辑(例如用 TextLoader 处理 .txt.md 等所有文本类文件)时。

可以通过 loader_cls 指定加载器,增强灵活性。

from langchain_community.document_loaders import DirectoryLoader, TextLoader

import os
# 获取当前脚本文件所在的目录
script_dir = os.path.dirname(__file__)
print(f"获取当前脚本文件所在的目录:{script_dir}") 
# 结合相对路径构建完整路径
data_dir = os.path.join(script_dir, '../../90-文档-Data/黑悟空')

# 加载目录下所有 Markdown 文件
loader = DirectoryLoader(data_dir,
                         glob="**/*.md",
                         loader_cls=TextLoader # 指定加载工具
                         )
docs = loader.load()
print(docs[0].page_content[:100])  # 打印第一个文档内容的前100个字符

3.3 跳过错误文件

通过设置 silent_errors=True 实现了 “跳过加载失败的文件” 的功能,专门用于处理目录中存在非文本文件(如图片、二进制文件等)导致的加载错误。

from langchain_community.document_loaders import DirectoryLoader, TextLoader
# 加载目录下所有文件,跳过出错文件,因为有些文件是图片,TextLoader 无法加载
import os
# 获取当前脚本文件所在的目录
script_dir = os.path.dirname(__file__)
print(f"获取当前脚本文件所在的目录:{script_dir}") 
# 结合相对路径构建完整路径
data_dir = os.path.join(script_dir, '../../90-文档-Data/黑悟空')

# 加载目录下所有 Markdown 文件
loader = DirectoryLoader(data_dir,
                          silent_errors=True,
                         loader_cls=TextLoader
                         )

docs = loader.load()
print(docs[0].page_content[:100])  # 打印第一个文档内容的前100个字符

在这里插入图片描述

4. 基于LlamaIndex加载文档

from llama_index.core import SimpleDirectoryReader
# 使用 SimpleDirectoryReader 加载目录中的文件
dir_reader = SimpleDirectoryReader("90-文档-Data/黑悟空")
documents = dir_reader.load_data()
# 查看加载的文档数量和内容
print(f"文档数量: {len(documents)}")
print(documents[0].text[:100])  # 打印第一个文档的前100个字符

# 仅加载某一个特定文件
dir_reader = SimpleDirectoryReader(input_files=["90-文档-Data/黑悟空/设定.txt"])
documents = dir_reader.load_data()
print(f"文档数量: {len(documents)}")
print(documents[0].text[:100])  # 打印第一个文档的前100个字符

  • SimpleDirectoryReader 作用:自动遍历指定目录(90-文档-Data/黑悟空)下的所有文件,根据文件类型(如 .txt、.pdf、.md 等)调用对应的解析器,将文件内容提取为 LlamaIndex 专用的 Document 对象。
  • 核心特性:
    支持多种常见文件格式(文本、PDF、Markdown 等),无需手动指定加载器。
    会递归读取子目录中的文件(即目录下的子文件夹里的文件也会被加载)。
    提取的内容存储在 Document 对象的 text 属性中,同时包含文件路径等元数据(如 doc_id、metadata)。

5. LlamaIndex-构建Document对象

Document 对象的作用:

  • 标准化数据格式:将文本和元数据封装成统一结构,方便 LlamaIndex 后续处理(如分块、嵌入、检索等)。
  • 元数据辅助检索:元数据可用于筛选文档(例如只检索 category=“游戏场景” 的文档),或在回答中标注信息来源(如 “该场景来自 火照深渊场景.md”)。
    兼容 LlamaIndex 流程:Document 对象是构建向量索引(VectorStoreIndex)的输入,确保后续的 RAG 流程能正常运行。
from llama_index.core import Document

# 创建多个文档对象,并为其添加元数据
documents = [
    Document(
        text="一个充满烈焰和硫磺气息的地下洞窟,火焰从地底不断喷涌,照亮整个深渊。场景中有熔岩河流穿行,燃烧的火山石在空中漂浮。悟空需要利用自己的跳跃能力和金箍棒在熔岩之间穿行,同时对抗来自地狱的火焰妖怪。",
        metadata={
            "filename": "火照深渊场景.md",
            "category": "游戏场景",
            "file_path": "/data/黑悟空/火照深渊场景.md",
            "author": "GameScience",
            "creation_date": "2024-11-20",
            "last_modified_date": "2024-11-21",
            "file_type": "markdown",
            "word_count": 28,
        },
    ),
    Document(
        text="一片高耸入云的山脉,云雾缭绕,风力强劲。悟空需要通过飞跃山崖、利用筋斗云飞行,以及在大风中保持平衡来穿越场景。敌人主要是隐匿在云层中的飞禽妖怪和岩石机关兽。",
        metadata={
            "filename": "风起长空场景.md",
            "category": "游戏场景",
            "file_path": "/data/黑悟空/风起长空场景.md",
            "author": "GameScience",
            "creation_date": "2024-11-20",
            "last_modified_date": "2024-11-21",
            "file_type": "markdown",
            "word_count": 28,
        },
    )]   

# 打印每个文档的元数据
for doc in documents:
    print(f"Metadata for {doc.metadata['filename']}:")
    for key, value in doc.metadata.items():
        print(f"  {key}: {value}")
print("-" * 40)

将原始文本转换为 LlamaIndex 专用的 Document 对象,并通过 metadata 丰富文档信息。这一步是将非结构化文本(如游戏场景描述)纳入 LlamaIndex 处理流程的基础,为后续构建索引、实现精准问答提供数据支持。

6. unstructured解析非结构化数据

unstructured 库的核心价值是将非结构化文本(如原始 TXT、PDF)转换为结构化的元素列表,每个元素包含文本内容和元数据。这种结构化处理方便后续步骤(如导入 LangChain/LlamaIndex 构建文档、按段落检索等)。 unstructured 库解析 设定.txt 文件,将其拆分为按段落划分的 Element 元素,然后展示每个元素的文本内容、类型和元数据。本质上是文本预处理的一环,为后续的自然语言处理或 RAG 系统构建提供结构化的数据基础。

from unstructured.partition.text import partition_text
text = "90-文档-Data/黑悟空/设定.txt"
elements = partition_text(text)
for element in elements:
    print(element)

# 通过vars函数查看所有可用的元数据
for i, element in enumerate(elements):
    print(f"\n--- Element {i+1} ---")
    print(f"类型: {type(element)}")
    print(f"元素类型: {element.__class__.__name__}")
    print(f"文本内容: {element.text}")
    
    # 元数据展示
    if hasattr(element, 'metadata'):
        print("元数据:")
        metadata = vars(element.metadata)
        valid_metadata = {k: v for k, v in metadata.items() 
                         if not k.startswith('_') and v is not None}
        for key, value in valid_metadata.items():
            print(f"  {key}: {value}")

解析文本文件为结构化元素,展示每个段落的文本内容、类型及元数据.

from unstructured.partition.text import partition_text
text = "90-文档-Data/黑悟空/设定.txt"
elements = partition_text(text)
for element in elements:
    print(element)

# 使用__dict__来查看所有可用的元数据
for i, element in enumerate(elements):
    print(f"\n--- Element {i+1} ---")
    print(f"类型: {type(element)}")
    print(f"元素类: {element.__class__.__name__}")
    print(f"文本内容: {element.text}")
    
    if hasattr(element, 'metadata'):
        print("元数据:")
        metadata_dict = element.metadata.__dict__
        for key, value in metadata_dict.items():
            if not key.startswith('_') and value is not None:  
                print(f"  {key}: {value}")

综上所述,总结使用,基于unstructured+langchain:

# 1. 安装依赖(如需运行,先执行以下命令)
# pip install langchain langchain-community langchain-text-splitters langchain-huggingface langchain-deepseek unstructured "unstructured[pdf]" "unstructured[docx]" python-dotenv

# 2. 导入必要的库
import os
from dotenv import load_dotenv
from langchain_community.document_loaders import UnstructuredDirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# 3. 加载环境变量(存储 API 密钥等)
load_dotenv()  # 从 .env 文件加载环境变量(需提前创建,包含 DEEPSEEK_API_KEY)

# 4. 配置路径
# 文档目录(存放 PDF、TXT、Markdown 等多格式文件)
DATA_DIR = "./90-文档-Data/黑悟空"  
# 确保目录存在
os.makedirs(DATA_DIR, exist_ok=True)

# 5. 使用 unstructured 加载目录中的多格式文档
print("===== 加载文档 =====")
loader = UnstructuredDirectoryLoader(
    directory_path=DATA_DIR,
    glob="*.*",  # 匹配目录中所有文件(可指定格式,如 "*.pdf")
    strategy="hi_res",  # 保留文档格式(如标题、表格结构)
    silent_errors=True,  # 忽略加载失败的文件(如损坏的文件)
    # 可选:指定只提取文本元素(过滤图片、表格等)
    # elements=["NarrativeText"]
)
documents = loader.load()
print(f"成功加载 {len(documents)} 个文档")
if len(documents) == 0:
    print("请在目录中放入文档后再运行")
    exit()

# 6. 文档分块(将长文档拆分为小块,优化检索精度)
print("\n===== 文档分块 =====")
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,  # 每块最大字符数
    chunk_overlap=200,  # 块之间重叠字符数(避免割裂语义)
    separators=["\n\n", "\n", "。", ",", " "]  # 中文优先分隔符
)
split_docs = text_splitter.split_documents(documents)
print(f"分块后共 {len(split_docs)} 个片段")

# 7. 初始化嵌入模型(将文本转为向量)
print("\n===== 初始化嵌入模型 =====")
embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-small-zh-v1.5",  # 中文优化的轻量级嵌入模型
    model_kwargs={'device': 'cpu'},  # 在 CPU 上运行(无 GPU 时使用)
    encode_kwargs={'normalize_embeddings': True}  # 向量归一化
)

# 8. 创建向量存储(存储分块后的文档向量)
print("\n===== 创建向量存储 =====")
vector_store = InMemoryVectorStore.from_documents(
    documents=split_docs,
    embedding=embeddings
)
print("向量存储创建完成")

# 9. 构建 RAG 链(检索 + 生成)
print("\n===== 构建问答链 =====")
# 检索器:从向量存储中查找相关文档
retriever = vector_store.as_retriever(search_kwargs={"k": 3})  # 取最相关的 3 个片段

# 提示词模板:告诉 LLM 基于检索到的上下文回答
prompt = ChatPromptTemplate.from_template("""
基于以下上下文内容,回答用户的问题。如果上下文没有相关信息,请说“无法从提供的文档中找到答案”。

上下文:
{context}

问题:{question}

回答:
""")

# 初始化大语言模型(DeepSeek)
llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=os.getenv("DEEPSEEK_API_KEY"),  # 从环境变量获取 API 密钥
    temperature=0.3  # 控制回答随机性(0 表示更确定)
)

# 构建 LCEL 链:检索 → 格式化上下文 → 生成回答 → 解析输出
rag_chain = (
    {
        "context": retriever | (lambda docs: "\n\n".join(doc.page_content for doc in docs)),
        "question": RunnablePassthrough()
    }
    | prompt
    | llm
    | StrOutputParser()
)

# 10. 运行问答
print("\n===== 开始问答 =====")
while True:
    question = input("\n请输入问题(输入 'exit' 退出):")
    if question.lower() == "exit":
        break
    response = rag_chain.invoke(question)
    print(f"\n回答:{response}")
  1. 文档加载:UnstructuredDirectoryLoader 调用 unstructured 解析目录中所有文件
  2. 支持多格式,自动提取文本和元数据。
  3. 文档分块:将长文档拆分为小块,避免因文本过长导致检索精度下降。
  4. 向量存储:用嵌入模型将文本转为向量,存储在内存向量库中,方便快速检索。
  5. 问答链:用户提问时,先检索相关文档片段,再让 LLM 基于这些片段生成回答,确保答案基于文档内容。
  6. 特点
    多格式支持:通过 unstructured 处理 PDF、Word、图片(需额外安装 OCR 依赖)等格式。
    可扩展性:可替换嵌入模型(如 text2vec)或 LLM(如本地模型)。
    容错性:silent_errors=True 确保个别文件加载失败不影响整体流程。

备注:langchain 集成各种Unstructured Loader

在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐