导航菜单

  • 1.langchain.intro
  • 2.langchain.chat_models
  • 3.langchain.prompts
  • 4.langchain.example_selectors
  • 5.output_parsers
  • 6.Runnable
  • 7.memory
  • 8.document_loaders
  • 9.text_splitters
  • 10.embeddings
  • 11.tool
  • 12.retrievers
  • 13.optimize
  • 14.项目介绍
  • 15.启动HTTP
  • 16.数据与模型
  • 17.权限管理
  • 18.知识库管理
  • 19.设置
  • 20.文档管理
  • 21.聊天
  • 22.API文档
  • 23.RAG优化
  • 24.索引时优化
  • 25.检索前优化
  • 26.检索后优化
  • 27.系统优化
  • 28.GraphRAG
  • 29.图
  • 30.为什么选择图数据库
  • 31.什么是 Neo4j
  • 32.安装和连接 Neo4j
  • 33.Neo4j核心概念
  • 34.Cypher基础
  • 35.模式匹配
  • 36.数据CRUD操作
  • 37.GraphRAG
  • 38.查询和过滤
  • 39.结果处理和聚合
  • 40.语句组合
  • 41.子查询
  • 42.模式和约束
  • 43.日期时间处理
  • 44.Cypher内置函数
  • 45.Python操作Neo4j
  • 46.neo4j
  • 47.py2neo
  • 48.Streamlit
  • 49.Pandas
  • 50.graphRAG
  • 51.deepdoc
  • 52.deepdoc
  • 53.deepdoc
  • 55.deepdoc
  • 54.deepdoc
  • Pillow
  • 1. Word文档格式简介
    • 1.1 什么是.docx格式
    • 1.2 Word文档的内部结构
    • 1.3 为什么需要了解格式
  • 2. Doc格式转换为Docx
    • 2.1 为什么需要转换
    • 2.2 转换方法
      • 2.2.1 Windows系统转换
      • 2.2.2 Linux系统转换
  • 3. RAGFlow Word文档解析基础
    • 3.1 什么是RAGFlowDocxParser
    • 3.2 简单使用示例
    • 3.3 使用应用解析器
  • 4. 自定义Word文档解析器
    • 4.1 为什么需要自定义解析器
    • 4.2 自定义解析器的实现思路
    • 4.3 完整实现示例
    • 4.4 关键功能说明
      • 4.4.1 图片提取
      • 4.4.2 表格处理
      • 4.4.3 上下文添加
  • 5. 完整使用示例
  • 6. 总结与最佳实践
    • 6.1 Word解析 vs PDF解析
    • 6.2 常见问题处理
    • 6.3 性能优化建议

1. Word文档格式简介 #

1.1 什么是.docx格式 #

.docx是Microsoft Office 2007及以后版本使用的Word文档格式。它基于Office Open XML标准,本质上是一个压缩的XML文件集合。

简单理解:

  • 将.docx文件重命名为.zip,然后解压,可以看到内部的XML文件
  • 主要的文档内容在word/document.xml文件中
  • 图片、样式等资源存储在单独的文件夹中

1.2 Word文档的内部结构 #

Word文档主要由以下部分组成:

document.xml(主文档内容)
  ├── 段落(<w:p>):文本内容
  ├── 表格(<w:tbl>):表格数据
  ├── 图片(<pic:pic>):嵌入的图片
  └── 其他元素:目录、页眉页脚等

重要说明:作为使用者,你不需要深入了解XML结构,只需要知道Word文档是结构化的即可。RAGFlow已经帮我们处理了这些底层细节。

1.3 为什么需要了解格式 #

了解Word文档格式有助于:

  1. 理解解析过程:知道解析器在做什么
  2. 处理特殊元素:知道如何提取图片、表格等
  3. 调试问题:遇到解析问题时知道可能的原因

2. Doc格式转换为Docx #

2.1 为什么需要转换 #

RAGFlow使用的python-docx库只能处理.docx格式,不支持旧的.doc格式。如果你的文档是.doc格式,需要先转换为.docx。

2.2 转换方法 #

2.2.1 Windows系统转换 #

方法一:使用LibreOffice(推荐)

# 1. 下载并安装LibreOffice
# 从官网下载:https://www.libreoffice.org/

# 2. 打开命令提示符,进入LibreOffice安装目录
cd "C:\Program Files\LibreOffice\program"

# 3. 转换单个文件
soffice.exe --headless --convert-to docx "D:\YourFolder\document.doc"

# 4. 批量转换(使用PowerShell)
Get-ChildItem -Path "D:\YourFolder" -Filter "*.doc" -Recurse | ForEach-Object {
    & "C:\Program Files\LibreOffice\program\soffice.exe" --headless --convert-to docx $_.FullName
}

方法二:使用Microsoft Word(需要安装Office)

# 创建PowerShell脚本 convert-doc-to-docx.ps1
$word = New-Object -ComObject Word.Application
$word.Visible = $false

Get-ChildItem -Recurse -Filter *.doc | ForEach-Object {
    $doc = $word.Documents.Open($_.FullName)
    $newName = $_.FullName -replace '\.doc$', '.docx'
    $doc.SaveAs([ref]$newName, [ref]12)  # 12是docx的格式代码
    $doc.Close()
}

$word.Quit()

2.2.2 Linux系统转换 #

# 1. 安装LibreOffice(如果未安装)
sudo apt-get install libreoffice  # Debian/Ubuntu
sudo dnf install libreoffice      # Fedora

# 2. 转换单个文件
libreoffice --headless --convert-to docx document.doc

# 3. 批量转换当前目录及子目录所有.doc文件
find . -name "*.doc" -exec libreoffice --headless --convert-to docx {} \;

注意事项:

  • 转换后的文件默认保存在原目录
  • 确保转换时文件没有被其他程序占用
  • 旧版.doc文件可能需要Word 2003以上兼容模式

3. RAGFlow Word文档解析基础 #

3.1 什么是RAGFlowDocxParser #

RAGFlowDocxParser是RAGFlow提供的Word文档解析器基础类,它使用python-docx库来解析Word文档。

主要功能:

  • 提取文档中的段落文本
  • 提取文档中的表格
  • 获取文档的样式信息

3.2 简单使用示例 #

下面是一个完整的使用示例:

# 导入必要的库
import os
import sys

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.abspath('.'))

# 导入RAGFlow的Word解析器
from deepdoc.parser.docx_parser import RAGFlowDocxParser

# 定义Word文件路径(请替换为你的Word文件路径)
docx_file = './data/docx_fangan-2.docx'

# 创建Word解析器实例
print("正在初始化Word解析器...")
parser = RAGFlowDocxParser()
print("Word解析器初始化完成!")

# 解析Word文件
# from_page和to_page参数可以指定解析的页码范围(Word文档按段落计算)
print(f"正在解析Word文件: {docx_file}")
sections, tables = parser(docx_file, from_page=0, to_page=100000)

# 处理段落内容
# sections是一个列表,每个元素是(段落文本, 样式名称)的元组
print("\n========== 段落内容 ==========")
for idx, (text, style) in enumerate(sections, 1):
    if text.strip():  # 只显示非空段落
        print(f"\n 段落 {idx} ")
        print(f"样式: {style}")
        print(f"内容: {text[:200]}...")  # 只显示前200个字符

# 处理表格内容
# tables是一个列表,每个元素是表格转换后的文本行列表
print("\n========== 表格内容 ==========")
for idx, table_lines in enumerate(tables, 1):
    print(f"\n 表格 {idx} ")
    for line in table_lines:
        print(line)

print("\n解析完成!")

代码说明:

  1. 导入模块:导入RAGFlowDocxParser类
  2. 初始化解析器:创建解析器实例
  3. 解析Word文档:调用解析器,返回段落和表格
  4. 处理结果:
    • sections:段落列表,每个元素包含文本和样式
    • tables:表格列表,每个表格转换为文本行

3.3 使用应用解析器 #

RAGFlow在基础解析器之上,提供了应用解析器(位于rag/app/naive.py),它提供了更便捷的使用方式:

# 导入必要的库
import os
import sys

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.abspath('.'))

# 导入naive应用解析器
from rag.app.naive import Docx, chunk

# 定义回调函数(用于显示进度,可选)
def dummy(prog=None, msg=""):
    if msg:
        print(f"进度: {msg}")

# 定义Word文件路径
file_name = './data/docx_fangan-2.docx'

# 方法一:直接解析(不进行分块)
print("方法一:直接解析")
docx_parse = Docx()
docx_content = docx_parse(file_name)
print(f"解析结果: {len(docx_content)} 个元素")

# 方法二:解析并自动分块
print("\n方法二:解析并分块")
data_chunk = chunk(file_name, callback=dummy)

# 查看分块结果
print(f"\n共生成 {len(data_chunk)} 个chunk")
for idx, data in enumerate(data_chunk[:5], 1):  # 只显示前5个
    print(f"\n Chunk {idx} ")
    print(data['content_with_weight'][:200] + "...")  # 只显示前200个字符

代码说明:

  1. 直接解析:使用Docx类直接解析,返回原始元素列表
  2. 解析并分块:使用chunk函数,自动进行分块处理
  3. 结果格式:每个chunk包含content_with_weight(内容)和metadata(元数据)

4. 自定义Word文档解析器 #

4.1 为什么需要自定义解析器 #

虽然RAGFlow提供了基础解析器,但在实际应用中,你可能需要:

  1. 保持顺序性:确保文本、表格、图片按文档中的顺序处理
  2. 添加上下文:为表格和图片添加前后文本作为上下文
  3. 图片描述:使用视觉大模型将图片转换为文本描述
  4. 表格总结:对表格进行总结,支持分析型问题

4.2 自定义解析器的实现思路 #

我们的自定义解析器将实现以下功能:

  1. 按顺序提取:按照文档中的顺序提取段落、表格、图片
  2. 表格上下文:为表格添加前一段文本作为上下文
  3. 图片描述:使用视觉大模型将图片转换为文本描述
  4. 图片上下文:为图片添加前后文本作为上下文
  5. 表格总结:使用LLM对表格进行总结

4.3 完整实现示例 #

下面是一个完整的自定义Word解析器实现:

# 导入必要的库
import os
import sys
import re
import uuid

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.abspath('.'))

# 导入RAGFlow的基础解析器
from deepdoc.parser import DocxParser

# 导入python-docx相关
from docx import Document
from docx.text.paragraph import Paragraph
from docx.table import Table
from docx.image.exceptions import UnrecognizedImageError
from io import BytesIO

# 导入图像处理
from PIL import Image

# 导入LangChain相关
from langchain_core.documents import Document as LDocument
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 导入工具函数(需要自己实现或从utils导入)
from model import RagLLM
from utils import table_to_summary, image_to_txt


class MyDocx(DocxParser):
    """
    自定义Word文档解析器

    功能:
    1. 按顺序提取文本、表格、图片
    2. 为表格和图片添加上下文
    3. 支持图片描述和表格总结
    """

    def __init__(self):
        # 调用父类初始化
        super().__init__()

    def __clean(self, line):
        """
        清理文本:移除全角空格等特殊字符

        参数:
            line: 原始文本行

        返回:
            str: 清理后的文本
        """
        # 将全角空格替换为普通空格,并去除首尾空白
        line = re.sub(r"\u3000", " ", line).strip()
        return line

    def get_picture(self, document, paragraph):
        """
        从段落中提取图片

        参数:
            document: Word文档对象
            paragraph: 段落对象

        返回:
            list: PIL Image对象列表,如果没有图片则返回None
        """
        # 使用XPath查找段落中的图片元素
        # .//pic:pic 表示查找所有pic:pic元素(无论嵌套层级)
        imgs = paragraph._element.xpath('.//pic:pic')

        # 如果没有找到图片,返回None
        if not imgs:
            return None

        ret_imgs = []  # 存储提取的图片列表

        # 遍历找到的所有图片
        for img in imgs:
            try:
                # 获取图片的嵌入ID
                # a:blip/@r:embed 表示获取blip元素的embed属性
                embed = img.xpath('.//a:blip/@r:embed')[0]

                # 根据ID获取图片的相关部分
                related_part = document.part.related_parts[embed]

                # 获取图片的二进制数据
                image_blob = related_part.image.blob

                # 将二进制数据转换为PIL Image对象
                image = Image.open(BytesIO(image_blob)).convert('RGB')
                ret_imgs.append(image)

            except UnrecognizedImageError:
                # 如果图片格式不被识别,跳过
                print("Unrecognized image format. Skipping image.")
            except Exception as e:
                # 其他错误也跳过
                print(f"Error extracting image: {e}")

        # 返回图片列表,如果没有图片则返回None
        return ret_imgs if ret_imgs else None

    def get_table(self, tb):
        """
        将表格转换为HTML格式

        参数:
            tb: docx表格对象

        返回:
            str: HTML格式的表格字符串
        """
        html = "<table>"

        # 遍历表格的每一行
        for r in tb.rows:
            html += "<tr>"
            i = 0

            # 处理合并单元格
            # 如果相邻单元格内容相同,认为是合并单元格
            while i < len(r.cells):
                span = 1  # 合并的单元格数量
                c = r.cells[i]

                # 检查后续单元格是否与当前单元格内容相同
                for j in range(i+1, len(r.cells)):
                    if c.text == r.cells[j].text:
                        span += 1
                        i = j
                    else:
                        break

                i += 1

                # 根据是否合并添加不同的HTML标签
                if span == 1:
                    html += f"<td>{c.text}</td>"
                else:
                    html += f"<td colspan='{span}'>{c.text}</td>"

            html += "</tr>"

        html += "</table>"
        return html

    def convert_table_to_lines(self, tb):
        """
        将表格转换为文本行列表(用于明细检索)

        参数:
            tb: docx表格对象

        返回:
            list: 表格转换后的文本行列表
        """
        lines = []

        # 如果表格为空或只有一行(表头),返回空列表
        if not tb.rows or len(tb.rows) <= 1:
            return lines

        # 获取表头(第一行)
        headers = [cell.text.strip() for cell in tb.rows[0].cells]

        # 处理数据行(从第二行开始)
        for row in tb.rows[1:]:
            # 获取当前行的所有单元格文本
            row_values = [cell.text.strip() for cell in row.cells]

            # 拼接表头和数据值,格式:【表头:值】
            row_text = []
            for i, value in enumerate(row_values):
                if i < len(headers):  # 确保有对应的表头
                    header = headers[i]
                    row_text.append(f"【{header}:{value}】")
                else:
                    row_text.append(f"【{value}】")

            # 将当前行的所有单元格拼接结果添加到结果列表
            lines.append("".join(row_text))

        return lines

    def __call__(self, filename, binary=None, from_page=0, to_page=100000):
        """
        解析Word文档,按顺序提取所有元素

        参数:
            filename: Word文件路径
            binary: Word二进制数据(可选)
            from_page: 起始页(段落计数)
            to_page: 结束页(段落计数)

        返回:
            list: 元素列表,每个元素是(文本/表格HTML, 图片对象, 类型, 额外信息)的元组
        """
        # 打开Word文档
        doc = Document(filename) if not binary else Document(BytesIO(binary))
        lines = []  # 存储所有提取的元素

        # 遍历文档主体中的每个子元素
        # doc.element.body.iterchildren() 返回文档主体中的所有子元素
        for element in doc.element.body.iterchildren():
            # 处理段落(paragraph)
            if element.tag.endswith('p'):
                # 创建段落对象
                paragraph = Paragraph(element, doc.element)

                # 尝试从段落中提取图片
                image_list = self.get_picture(doc, paragraph)

                # 清理段落文本
                clean_text = self.__clean(paragraph.text)

                # 如果有图片,标记为图片类型
                if image_list:
                    # 格式:(文本, 图片列表, "image", None)
                    lines.append((clean_text, image_list, "image", None))
                else:
                    # 格式:(文本, None, "text", None)
                    lines.append((clean_text, None, "text", None))

            # 处理表格(table)
            elif element.tag.endswith('tbl'):
                # 创建表格对象
                table = Table(element, doc.element)

                # 将表格转换为HTML
                table_html = self.get_table(table)

                # 将表格转换为文本行(用于明细检索)
                table_lines = self.convert_table_to_lines(table)

                # 格式:(表格HTML, None, "table", 表格文本行列表)
                lines.append((table_html, None, "table", table_lines))

            # 处理结构化文档标签(sdt),如目录等
            elif element.tag.endswith('sdt'):
                sdt_content = []

                # 遍历sdt元素的所有子元素
                for child in element.iterchildren():
                    if child.tag.endswith('sdtContent'):
                        # 在sdtContent中查找段落
                        for achild in child.iterchildren():
                            if achild.tag.endswith('p'):
                                # 提取段落文本并清理
                                para = Paragraph(achild, doc.element)
                                sdt_content.append(self.__clean(para.text) + "\n")

                # 格式:(目录内容, None, "sdt", None)
                lines.append(("".join(sdt_content), None, "sdt", None))

        return lines

    def _len_wo_blank(self, text):
        """
        计算文本长度(不包括空格)

        参数:
            text: 文本字符串

        返回:
            int: 去除空格后的文本长度
        """
        return len(text.replace(" ", ""))

    def pre_chunk(self, filename, binary=None, from_page=0, to_page=100000):
        """
        预处理:提取文本、表格、图片,并添加上下文

        参数:
            filename: Word文件路径
            binary: Word二进制数据
            from_page: 起始页
            to_page: 结束页

        返回:
            tuple: (文本chunks列表, 表格chunks列表, 图片chunks列表)
        """
        # 调用__call__方法解析文档
        lines = self(filename, binary, from_page, to_page)

        # 去掉空白行
        # 保留图片和有效的文本/表格/目录
        lines = [
            (text, img, atype, externel_info) 
            for text, img, atype, externel_info in lines 
            if atype == "image" or (atype in ["text", "sdt", "table"] and text.strip())
        ]

        # 初始化结果列表
        chunks = []      # 文本chunks
        images = []      # 图片chunks(包含上下文)
        tables = []      # 表格chunks(包含上下文)

        # 配置参数
        get_text_num = 2                    # 获取前几行文本作为上下文
        skip_table_images_text_cnt = 30      # 如果文本长度小于此值,可能只是标题,不单独作为chunk

        # 遍历所有元素
        i = 0
        while i < len(lines):
            # 获取前几行文本作为上下文
            prev_texts = []
            prev_index = i - 1

            # 向前查找文本元素
            while prev_index >= 0 and len(prev_texts) < get_text_num:
                if lines[prev_index][2] == "text":
                    prev_texts.append(lines[prev_index][0])
                prev_index -= 1

            # 合并前几行文本(注意要反转顺序,因为是从后往前查找的)
            prev_text = " ".join(reversed(prev_texts))

            # 处理表格
            if lines[i][2] == "table":
                # 合并表格和前文文本
                html_combined_text = f"{prev_text} \n {lines[i][0]}"

                # 保存表格信息:格式为(合并后的文本, 表格文本行列表, 前文文本)
                tables.append((html_combined_text, lines[i][3], prev_text))

                # 如果前文本很短且已经在chunks中,从chunks中移除(避免重复)
                for text in prev_texts:
                    if self._len_wo_blank(text) < skip_table_images_text_cnt and text in chunks:
                        chunks.remove(text)

            # 处理图片
            elif lines[i][2] == "image":
                # 向后查找文本元素(图片说明通常在图片下方)
                next_index = i + 1
                while next_index < len(lines) and lines[next_index][2] != "text":
                    next_index += 1

                # 获取后文文本
                next_text = lines[next_index][0] if next_index < len(lines) else ""

                # 保存图片信息:格式为(前文文本, 图片段落文本, 图片对象, 后文文本)
                images.append((prev_text, lines[i][0], lines[i][1], next_text))

                # 如果前后文本很短,从chunks中移除(避免重复)
                for text in prev_texts:
                    if self._len_wo_blank(text) < skip_table_images_text_cnt and text in chunks:
                        chunks.remove(text)

                # 如果后文本很短,标记为skip,后续不添加到chunks
                if next_index < len(lines) and self._len_wo_blank(next_text) < skip_table_images_text_cnt:
                    lines[next_index] = (lines[next_index][0], lines[next_index][1], lines[next_index][2], "skip")

                # 如果图片段落有文本,添加到chunks
                if lines[i][0] != "":
                    chunks.append(lines[i][0])

            # 处理普通文本
            else:
                # 普通文本直接添加到chunks,除非被标记为skip
                if lines[i][3] != "skip":
                    chunks.append(lines[i][0])

            i += 1

        return chunks, tables, images

    def chunk(self, filename, binary=None, from_page=0, to_page=100000, llm=None,
              chunk_size=512, chunk_overlap=30):
        """
        最终分块函数:生成LangChain Document列表

        参数:
            filename: Word文件路径
            binary: Word二进制数据
            from_page: 起始页
            to_page: 结束页
            llm: 语言模型实例(用于表格总结和图片描述)
            chunk_size: 文本chunk大小
            chunk_overlap: chunk重叠大小

        返回:
            list: LangChain Document列表
        """
        # 步骤1: 预处理,获取文本、表格、图片
        txt_chunks, table_chunks, image_chunks = self.pre_chunk(
            filename, binary, from_page, to_page
        )

        # 如果没有提供LLM,创建一个
        if llm is None:
            llm = RagLLM()

        docs = []

        # 步骤2: 处理表格(总结)
        print("正在处理表格...")
        for table_info in table_chunks:
            # table_info格式:(合并后的文本, 表格文本行列表, 前文文本)
            table_chunk, table_txts, prev_text = table_info

            # 使用LLM对表格进行总结
            table_summary = table_to_summary(table_chunk, llm)

            # 创建表格总结Document
            doc = LDocument(
                page_content=table_summary,  # 总结作为主要内容
                metadata={
                    "source": "table",
                    "content": table_chunk  # 原始表格内容保存在metadata中
                }
            )
            docs.append(doc)

            # 为表格的每一行创建明细Document(用于精确检索)
            for row in table_txts:
                doc = LDocument(
                    page_content=f" 明细项:{row}",
                    metadata={
                        "source": "table-row",
                        "content": ""
                    }
                )
                docs.append(doc)

        # 步骤3: 处理图片(描述)
        print("正在处理图片...")
        img_path = "./doc_images"
        os.makedirs(img_path, exist_ok=True)

        for image_chunk in image_chunks:
            # image_chunk格式:(前文文本, 图片段落文本, 图片对象, 后文文本)
            pre_text, txt, img, post_text = image_chunk

            # 构建图片描述提示词
            prompt = f"""
请根据图片的相关上下文和信息,用中文简要总结下图中所描述的内容,如果有数据请提取出数据,并做分析,否则只要简要描述。

上下文:
{pre_text}

{post_text}

图表信息:{txt}
"""

            # 处理图片(可能是单个图片或图片列表)
            imgs = img
            if not isinstance(img, list):
                imgs = [img]

            filenames = []      # 存储保存的图片路径
            image_descs = []    # 存储图片描述

            # 遍历所有图片
            for im in imgs:
                # 使用视觉大模型描述图片
                image_desc = image_to_txt(im, prompt, 300)

                # 保存图片到本地
                img_file_name = f"{img_path}/image_{uuid.uuid4()}.png"
                im.save(img_file_name)
                filenames.append(img_file_name)
                image_descs.append(image_desc[0] if isinstance(image_desc, list) else image_desc)

            # 合并所有图片的描述
            image_desc = "\n".join(image_descs)

            # 创建图片Document
            # 内容包含上下文和图片描述,metadata中保存图片路径
            doc = LDocument(
                page_content=f"{pre_text} {post_text} 具体信息:{image_desc}",
                metadata={
                    "source": "image",
                    "content": "##".join(filenames)  # 多个图片路径用##分隔
                }
            )
            docs.append(doc)

        # 步骤4: 处理文本(分块)
        print("正在处理文本...")
        all_texts = "\n".join(txt_chunks)

        # 使用递归字符分割器进行分块
        r_spliter = RecursiveCharacterTextSplitter(
            chunk_size=chunk_size,
            chunk_overlap=chunk_overlap,
            separators=[
                "\n\n",      # 双换行(段落分隔)
                "\n",        # 单换行
                ".",         # 英文句号
                "\uff0e",    # 全角句号
                "\u3002",    # 中文句号
                ",",         # 英文逗号
                "\uff0c",    # 全角逗号
                "\u3001",    # 中文顿号
            ]
        )

        # 分割文本
        chunks = r_spliter.split_text(all_texts)

        # 创建文本Document
        for chunk_text in chunks:
            doc = LDocument(
                page_content=chunk_text,
                metadata={
                    "source": "text",
                    "content": ""
                }
            )
            docs.append(doc)

        print(f"处理完成!共生成 {len(docs)} 个Document")
        return docs


# 使用示例
if __name__ == "__main__":
    # 定义Word文件路径
    docx_file = './data/docx_fangan-2.docx'

    # 创建自定义解析器实例
    parser = MyDocx()

    # 解析并分块
    documents = parser.chunk(
        docx_file,
        chunk_size=512,
        chunk_overlap=30
    )

    # 查看结果统计
    text_count = sum(1 for doc in documents if doc.metadata['source'] == 'text')
    table_count = sum(1 for doc in documents if doc.metadata['source'] == 'table')
    table_row_count = sum(1 for doc in documents if doc.metadata['source'] == 'table-row')
    image_count = sum(1 for doc in documents if doc.metadata['source'] == 'image')

    print(f"\n解析完成!")
    print(f"文本chunks: {text_count} 个")
    print(f"表格chunks: {table_count} 个")
    print(f"表格行chunks: {table_row_count} 个")
    print(f"图片chunks: {image_count} 个")
    print(f"总计: {len(documents)} 个Document")

    # 查看示例结果
    print("\n========== 示例结果 ==========")
    for idx, doc in enumerate(documents[:3], 1):
        print(f"\n Document {idx} ({doc.metadata['source']}) ")
        content_preview = doc.page_content[:200] + "..." if len(doc.page_content) > 200 else doc.page_content
        print(f"内容: {content_preview}")
        print(f"元数据: {doc.metadata}")

4.4 关键功能说明 #

4.4.1 图片提取 #

图片提取是Word解析中的一个难点,因为图片嵌入在文档的XML结构中。我们的实现:

  1. 使用XPath查找:paragraph._element.xpath('.//pic:pic')查找段落中的图片元素
  2. 获取图片数据:通过嵌入ID获取图片的二进制数据
  3. 转换为PIL Image:将二进制数据转换为PIL Image对象,方便后续处理

4.4.2 表格处理 #

表格处理包括:

  1. 转换为HTML:保持表格的结构,方便显示
  2. 转换为文本行:将表格转换为"表头:值"的格式,方便精确检索
  3. 处理合并单元格:识别合并的单元格,正确生成HTML

4.4.3 上下文添加 #

为表格和图片添加上下文:

  1. 表格上下文:向前查找最近的文本段落(通常是表格标题)
  2. 图片上下文:向前和向后查找文本段落(图片说明可能在前后)

5. 完整使用示例 #

下面是一个完整的示例,演示如何使用自定义解析器:

# 导入必要的库
import os
import sys

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.abspath('.'))

# 导入自定义解析器(假设已经保存为my_docx_parser.py)
from my_docx_parser import MyDocx
from model import RagLLM

# 定义回调函数(可选)
def progress_callback(prog=None, msg=""):
    """进度回调函数"""
    if msg:
        print(f"[进度] {msg}")

# 定义Word文件路径
docx_file = './data/docx_fangan-2.docx'

# 创建LLM实例(用于表格总结和图片描述)
llm = RagLLM()

# 创建自定义解析器实例
parser = MyDocx()

# 解析Word文档并生成Document列表
print("开始解析Word文档...")
documents = parser.chunk(
    docx_file,
    llm=llm,
    chunk_size=512,
    chunk_overlap=30
)

# 查看结果统计
text_count = sum(1 for doc in documents if doc.metadata['source'] == 'text')
table_count = sum(1 for doc in documents if doc.metadata['source'] == 'table')
table_row_count = sum(1 for doc in documents if doc.metadata['source'] == 'table-row')
image_count = sum(1 for doc in documents if doc.metadata['source'] == 'image')

print(f"\n解析完成!")
print(f"文本chunks: {text_count} 个")
print(f"表格chunks: {table_count} 个")
print(f"表格行chunks: {table_row_count} 个")
print(f"图片chunks: {image_count} 个")
print(f"总计: {len(documents)} 个Document")

# 查看示例结果
print("\n========== 示例结果 ==========")
for idx, doc in enumerate(documents[:5], 1):
    print(f"\n Document {idx} ({doc.metadata['source']}) ")
    content_preview = doc.page_content[:200] + "..." if len(doc.page_content) > 200 else doc.page_content
    print(f"内容: {content_preview}")
    if doc.metadata.get('content'):
        print(f"额外内容: {doc.metadata['content'][:100]}...")

# 可以保存到向量数据库
# from langchain_chroma import Chroma
# from model import RagEmbedding
# 
# embedding_model = RagEmbedding()
# vector_db = Chroma.from_documents(
#     documents,
#     embedding_model.get_embedding_fun(),
#     collection_name="my_docx_collection"
# )

6. 总结与最佳实践 #

6.1 Word解析 vs PDF解析 #

| 特性 | Word解析 | PDF解析 | |||| | 数据格式 | 结构化(XML) | 渲染后的图片 | | OCR需求 | 不需要 | 需要 | | 图片提取 | 相对简单 | 需要OCR识别 | | 表格处理 | 直接读取 | 需要表格识别 | | 格式信息 | 丰富(样式等) | 有限 |

6.2 常见问题处理 #

问题1:图片提取失败

  • 原因:图片格式不支持或文档损坏
  • 解决方案:添加异常处理,跳过无法提取的图片

问题2:表格结构复杂

  • 原因:合并单元格、嵌套表格等
  • 解决方案:使用更复杂的表格解析逻辑,或转换为Markdown格式

问题3:文档格式不标准

  • 原因:文档可能包含特殊元素或格式
  • 解决方案:添加更多的元素类型处理,或使用try-except跳过未知元素

6.3 性能优化建议 #

  1. 批量处理:对于大量文档,考虑并行处理
  2. 缓存结果:解析结果可以缓存,避免重复解析
  3. 选择性处理:如果不需要图片,可以跳过图片提取步骤
← 上一节 52.deepdoc 下一节 54.deepdoc →

访问验证

请输入访问令牌

Token不正确,请重新输入