LlamaIndex 深度集成:常用 Reader 全解析与自定义 Reader 实战


LlamaIndex 深度集成:常用 Reader 全解析与自定义 Reader 实战

摘要:在 RAG 应用中,LlamaIndex 的 Reader 是数据入口。本文结合官方架构与项目实战,从源码层面解析常用 Reader,并给出自定义 Reader 的完整方案。


一、LlamaIndex Reader 体系架构

1.1 BaseReader:所有 Reader 的基座

# llama_index/core/readers/base.py
from abc import ABC, abstractmethod
from typing import List
from llama_index.core.schema import Document

class BaseReader(ABC):
    @abstractmethod
    def load_data(self, *args: Any, **load_kwargs: Any) -> List[Document]:
        """加载数据并返回 Document 列表"""

源码解读

  • BaseReader 是抽象基类,只约束一个 load_data 方法
  • Document 是 LlamaIndex 的核心数据结构,包含 textmetadata
  • 任何自定义 Reader 只需实现 load_data,返回 List[Document]

1.2 SimpleDirectoryReader 的核心调度逻辑

SimpleDirectoryReader 是目录级入口,它的核心作用是:

  1. 扫描目录:递归或非递归收集文件
  2. 扩展名匹配:根据文件后缀选择 Reader
  3. Reader 分发:通过 file_extractor 映射执行具体 Reader
  4. Document 组装:合并所有 Reader 结果

简化源码逻辑如下:

class SimpleDirectoryReader(BaseReader):
    def __init__(self, input_dir, file_extractor=None, ...):
        self.input_dir = input_dir
        # 用户自定义的 Reader 映射
        self.file_extractor = file_extractor or {}
    
    def _get_default_file_extractor(self) -> dict:
        """默认的扩展名-Reader 映射"""
        return {
            ".pdf": PDFReader(),
            ".docx": DocxReader(),
            ".txt": TextReader(),
            # ... 更多默认映射
        }
    
    def load_data(self, *args, **kwargs) -> List[Document]:
        # 1. 收集文件
        files = self._get_files()
        
        # 2. 合并默认 + 自定义 file_extractor
        extractor = {**self._get_default_file_extractor(), **self.file_extractor}
        
        documents = []
        for file in files:
            ext = os.path.splitext(file)[1]
            reader = extractor.get(ext) or self.default_reader
            # 3. 调用 Reader.load_data
            docs = reader.load_data(file)
            documents.extend(docs)
        
        return documents

关键结论

  • file_extractor 优先级高于默认 Reader
  • 扩展名小写匹配,如 .docx.pdf
  • 自定义 Reader 必须继承 BaseReader

二、LlamaIndex 常用 Reader 盘点

2.1 SimpleDirectoryReader —— 目录批量读取

概念

SimpleDirectoryReader 是 LlamaIndex 最常用的本地文件加载器,支持自动识别多种格式。

源码位置

llama-index-core/llama_index/core/readers/file/base.py

核心参数

参数 类型 说明
input_dir str 输入目录
input_files List[str] 指定文件列表
required_exts List[str] 只读取指定后缀
exclude List[str] 排除文件,支持通配符
recursive bool 是否递归子目录
filename_as_id bool 文件名作为 doc_id
file_extractor dict 自定义 Reader 映射

使用方法

from llama_index.core import SimpleDirectoryReader

documents = SimpleDirectoryReader(
    input_dir="./knowledge_base",
    recursive=True,
    required_exts=[".pdf", ".docx", ".txt"],
    exclude=["*.tmp", "*.log"],
    filename_as_id=True,
).load_data()

源码级建议

# file_extractor 的本质是一个字典
# key: 文件扩展名(带点,如 .pdf)
# value: BaseReader 实例
file_extractor = {
    ".pdf": PDFReader(),
    ".docx": DocxReader(),
    ".doc": MyCustomDocReader(),  # 自定义 Reader
}

2.2 DocxReader —— Word 文档读取

源码位置

llama-index-readers-file/llama_index/readers/file/docs/base.py

源码解读

from llama_index.readers.file import DocxReader

class DocxReader(BaseReader):
    def load_data(
        self,
        file: Path,
        extra_info: Optional[Dict[str, Any]] = None,
        **load_kwargs: Any,
    ) -> List[Document]:
        # 使用 python-docx 解析 docx
        # 将段落文本拼接为 Document
        ...

使用方法

from llama_index.readers.file import DocxReader

reader = DocxReader()
docs = reader.load_data(file="report.docx")

深度集成建议

  • 简单 Word 文档可用
  • 复杂表格、图文混排建议使用 LlamaParseReader
  • 可通过 file_extractor 注入

2.3 PDFReader —— PDF 文件读取

源码位置

llama-index-readers-file/llama_index/readers/file/docs/base.py

源码解读

from llama_index.readers.file import PDFReader

class PDFReader(BaseReader):
    def load_data(
        self,
        file: Path,
        extra_info: Optional[Dict[str, Any]] = None,
        **load_kwargs: Any,
    ) -> List[Document]:
        # 基于 PyPDF2 / pypdf 解析 PDF
        # 默认按页拆分 Document
        ...

使用方法

from llama_index.readers.file import PDFReader

reader = PDFReader()
docs = reader.load_data(file="paper.pdf")

使用建议

  • 文字型 PDF 效果好
  • 扫描版、复杂排版效果差
  • 生产环境建议用 LlamaParseMinerU

2.4 LlamaParseReader / LlamaParse —— 云端高质量解析

源码位置

llama-parse/llama_parse/__init__.py
llama-cloud-services/llama_cloud_services/parse/__init__.py

源码解读

from llama_parse import LlamaParse

class LlamaParse:
    def __init__(self, api_key: str, result_type: str = "markdown", ...):
        self.api_key = api_key
        self.result_type = result_type
    
    def load_data(self, file_path: str) -> List[Document]:
        # 上传文件到 LlamaParse 云端
        # 返回 Markdown/Text 结构

使用方法

from llama_parse import LlamaParse
from llama_cloud_services.parse import ResultType

parser = LlamaParse(
    api_key="your-api-key",
    result_type=ResultType.MD,
    verbose=True,
)

documents = parser.load_data("complex.pdf")

深度集成建议

  • 本项目将其包装为 DocxLlamaParseReaderDocLlamaParseReader
  • 通过 file_extractor 注入 SimpleDirectoryReader,实现自动调用

2.5 BeautifulSoupWebReader —— 网页正文提取

源码位置

llama-index-readers-web/llama_index/readers/web/beautiful_soup_web/base.py

源码解读

from llama_index.readers.web import BeautifulSoupWebReader

class BeautifulSoupWebReader(BaseReader):
    def load_data(
        self,
        urls: List[str],
        custom_hostname: Optional[str] = None,
        **kwargs: Any,
    ) -> List[Document]:
        # requests 获取 HTML
        # BeautifulSoup 提取正文

使用方法

from llama_index.readers.web import BeautifulSoupWebReader

reader = BeautifulSoupWebReader()
docs = reader.load_data(urls=["https://example.com/article"])

使用建议

  • 网页 RAG 首选
  • 可配合爬虫框架批量抓取
  • 注意请求频率和反爬

2.6 DatabaseReader —— 数据库读取

源码位置

llama-index-readers-database/llama_index/readers/database/base.py

源码解读

from llama_index.readers.database import DatabaseReader

class DatabaseReader(BaseReader):
    def __init__(self, uri: str, query: str):
        self.uri = uri
        self.query = query
    
    def load_data(self) -> List[Document]:
        # 使用 SQLAlchemy 执行 SQL
        # 每行转换为 Document

使用方法

from llama_index.readers.database import DatabaseReader

reader = DatabaseReader(
    uri="mysql+pymysql://user:pass@localhost/db",
    query="SELECT id, content FROM articles",
)
docs = reader.load_data()

2.7 其他常用 Reader

Reader 包名 用途
JSONReader llama-index-readers-file 读取 JSON 文件
CSVReader / PandasCSVReader llama-index-readers-file 读取 CSV 文件
MarkdownReader llama-index-readers-file 读取 Markdown 文件
UnstructuredReader llama-index-readers-file 多格式复杂解析
WikipediaReader llama-index-readers-wikipedia 维基百科
SimpleWebPageReader llama-index-readers-web 简单网页读取
NotionPageReader llama-index-readers-notion Notion 页面
GithubRepositoryReader llama-index-readers-github GitHub 仓库

三、Reader 对比与选型建议

Reader 数据源 优点 缺点 适用场景
SimpleDirectoryReader 本地目录 批量、自动识别 复杂版式弱 本地文档批量入库
DocxReader .docx 轻量本地 复杂排版差 简单 Word
PDFReader .pdf 轻量本地 扫描版差 文字型 PDF
LlamaParse 多格式 版式解析强 需 API Key 复杂文档
BeautifulSoupWebReader 网页 自动正文提取 受反爬限制 网页 RAG
DatabaseReader 数据库 对接业务数据 需写 SQL 企业数据
JSONReader/CSVReader 结构化文件 简单易用 需规范格式 表格数据

四、自定义 Reader 深度实战

4.1 为什么需要自定义 Reader?

  1. 默认 Reader 不支持某些格式(如 .doc 旧版 Word)
  2. 需要统一元数据(如注入 file_pathsource
  3. 需要接入外部解析服务(如 LlamaParse、MinerU)
  4. 需要预处理文本(如过滤页眉页脚、提取特定章节)

4.2 项目实战:封装 LlamaParse 为 DOC/DOCX Reader

本项目中的完整实现:

"""
DOCX 文件 LlamaParse 读取器(适配 SimpleDirectoryReader)
基于 llama_index.core.readers.base.BaseReader 实现
"""

from pathlib import Path
from typing import List

from llama_cloud_services.parse import ResultType
from llama_index.core.readers.base import BaseReader
from llama_index.core.schema import Document
from llama_parse import LlamaParse

from utils.log_util import logger


def create_llama_parser(result_type: ResultType = ResultType.MD, split_by_page: bool = False):
    """工厂函数:创建 LlamaParse 解析器"""
    return LlamaParse(
        api_key="your-api-key",
        result_type=result_type,
        verbose=True,
    )


class DocxLlamaParseReader(BaseReader):
    """
    DOCX 文件读取器
    通过 LlamaParse 官方 API 解析,可被 SimpleDirectoryReader 通过 file_extractor 注入
    """

    def __init__(self, split_by_page: bool = True, result_type: ResultType = ResultType.MD):
        self.split_by_page = split_by_page
        self.result_type = result_type
        self._parser: LlamaParse | None = None

    def _get_parser(self) -> LlamaParse:
        """懒加载解析器实例"""
        if self._parser is None:
            self._parser = create_llama_parser(
                result_type=self.result_type,
                split_by_page=self.split_by_page,
            )
        return self._parser

    def load_data(self, file: Path, extra_info: dict | None = None, **load_kwargs) -> List[Document]:
        file_path = str(file)
        parser = self._get_parser()

        logger.info(f"DocxLlamaParseReader 开始读取: {file_path}")
        documents = parser.load_data(file_path)

        # 注入元数据
        metadata = extra_info or {}
        metadata["file_path"] = file_path
        metadata["source"] = "llamaparse"

        for doc in documents:
            doc.metadata.update(metadata)

        logger.info(f"DocxLlamaParseReader 读取完成: {file_path}, 共 {len(documents)} 个 Document")
        return documents

4.3 注入 SimpleDirectoryReader 使用

from llama_index.core import SimpleDirectoryReader
from module_rag.rag_common.file_read.llamaParse_realize.docx_reader import DocxLlamaParseReader

# 自定义 file_extractor
file_extractor = {
    ".doc": DocLlamaParseReader(split_by_page=True),
    ".docx": DocxLlamaParseReader(split_by_page=True),
}

reader = SimpleDirectoryReader(
    input_dir="./docs",
    required_exts=[".doc", ".docx"],
    file_extractor=file_extractor,
)

documents = reader.load_data()

4.4 更简单的自定义 Reader 模板

from pathlib import Path
from typing import List

from llama_index.core.readers.base import BaseReader
from llama_index.core.schema import Document


class MyCustomReader(BaseReader):
    def __init__(self, encoding: str = "utf-8"):
        self.encoding = encoding

    def load_data(
        self,
        file: Path,
        extra_info: dict | None = None,
        **load_kwargs,
    ) -> List[Document]:
        file_path = Path(file)

        # 1. 读取/处理文件
        with open(file_path, "r", encoding=self.encoding) as f:
            text = f.read()

        # 2. 构建元数据
        metadata = extra_info or {}
        metadata.update({
            "file_path": str(file_path),
            "file_name": file_path.name,
            "file_ext": file_path.suffix,
        })

        # 3. 返回 Document
        return [Document(text=text, metadata=metadata)]


# 使用
reader = MyCustomReader()
docs = reader.load_data("example.txt")

五、自定义 Reader 的规范与最佳实践

5.1 必须继承 BaseReader

from llama_index.core.readers.base import BaseReader

class MyReader(BaseReader):
    ...

5.2 load_data 签名规范

def load_data(
    self,
    file: Path,
    extra_info: Optional[dict] = None,
    **load_kwargs,
) -> List[Document]:
    ...

5.3 元数据必须包含 file_path

metadata = extra_info or {}
metadata["file_path"] = str(file_path)
metadata["source"] = "my_reader"

5.4 懒加载昂贵资源

def _get_parser(self):
    if self._parser is None:
        self._parser = create_parser()
    return self._parser

5.5 异常处理与日志

try:
    documents = parser.load_data(file_path)
except Exception as e:
    logger.error(f"读取失败: {file_path}, 错误: {e}")
    return []

5.6 通过 file_extractor 注入

file_extractor = {
    ".doc": DocLlamaParseReader(),
    ".docx": DocxLlamaParseReader(),
    ".pdf": PDFReader(),
}

六、本项目文件读取架构总结

module_rag/rag_common/file_read/
├── __init__.py              # 统一入口,支持多种解析方式
├── base_reader.py           # 基于 SimpleDirectoryReader 的封装
├── self_define/             # 本地 Reader 配置
└── llamaParse_realize/      # 基于 LlamaParse 的自定义 Reader
    ├── __init__.py          # file_extractor 配置
    ├── doc_reader.py        # .doc 自定义 Reader
    └── docx_reader.py       # .docx 自定义 Reader

核心调用链:

common_read_file_content(file_path, read_method="LlamaParse")
    ↓
_get_file_extractor_by_method("LlamaParse")
    ↓
get_file_extractor()  # 返回 {".doc": DocLlamaParseReader(), ".docx": DocxLlamaParseReader()}
    ↓
base_read_file(file_path, file_extractor)
    ↓
SimpleDirectoryReader(input_files=[file_path], file_extractor=file_extractor).load_data()
    ↓
DocxLlamaParseReader.load_data(file)  # 自定义 Reader
    ↓
返回 List[Document]

七、总结

Reader 定位
SimpleDirectoryReader 本地目录批量加载入口
DocxReader/PDFReader 轻量本地文档解析
LlamaParse 复杂版式云端解析
BeautifulSoupWebReader 网页知识库
DatabaseReader 企业业务库
自定义 Reader 特殊格式和业务需求

LlamaIndex 的 Reader 机制非常清晰:继承 BaseReader,实现 load_data,通过 file_extractor 注入 SimpleDirectoryReader。掌握这一点,就能无缝扩展任意数据源,深度集成 LlamaIndex。


如果你需要,我可以把文中示例整理成一个可运行的 module_rag/rag_common/file_read/custom_reader_demo.py 文件,并接入你现有的 file_read 模块。

Logo

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

更多推荐