在 Python 中從 Word DOCX 文檔中提取內容

從 Word 文檔中提取文本通常在不同的場景中進行。例如,分析文本,提取文檔的特定部分並將它們組合成一個文檔,等等。在本文中,您將學習如何使用 Python 以編程方式從 Word 文檔中提取文本。此外,我們還將介紹如何動態提取段落、表格等特定元素之間的內容。

從 Word 文檔中提取文本的 Python 庫

Aspose.Words for Python 是一個功能強大的庫,可讓您從頭開始創建 MS Word 文檔。此外,它還允許您對現有的 Word 文檔進行加密、轉換、文本提取等操作。我們將使用該庫從 Word DOCX 或 DOC 文檔中提取文本。您可以使用以下 pip 命令從 PyPI 安裝庫。

pip install aspose-words

使用 Python 提取 Word 文檔中的文本

MS Word 文檔由各種元素組成,包括段落、表格、圖像等。因此,文本提取的要求可能因場景而異。例如,您可能需要提取段落、書籤、評論等之間的文本。

Word 文檔中的每種類型的元素都表示為一個節點。因此,要處理文檔,您將不得不使用節點。那麼讓我們開始吧,看看如何在不同的場景下從Word文檔中提取文本。

在 Python 中從 Word 文檔中提取文本

在本節中,我們將為 Word 文檔實現一個 Python 文本提取器,文本提取的工作流程如下:

  • 首先,我們將定義要包含在文本提取過程中的節點。
  • 然後,我們將提取指定節點之間的內容(包括或不包括起始和結束節點)。
  • 最後,我們將使用提取節點的克隆,例如創建一個包含提取內容的新 Word 文檔。

現在讓我們編寫一個名為 extractcontent 的方法,我們將向其傳遞節點和一些其他參數以執行文本提取。此方法將解析文檔並克隆節點。以下是我們將傳遞給此方法的參數。

  1. StartNode 和 EndNode 分別作為提取內容的起點和終點。這些可以是塊級別(段落、表格)或內聯級別(例如 Run、FieldStart、BookmarkStart 等)節點。
    1. 要傳遞字段,您應該傳遞相應的 FieldStart 對象。
    2. 要傳遞書籤,應傳遞 BookmarkStart 和 BookmarkEnd 節點。
    3. 對於註釋,應使用 CommentRangeStart 和 CommentRangeEnd 節點。
  2. IsInclusive 定義標記是否包含在提取中。如果此選項設置為 false 並傳遞相同的節點或連續的節點,則將返回一個空列表。

以下是提取傳遞的節點之間的內容的 extractcontent 方法的完整實現。

def extract_content(startNode : aw.Node, endNode : aw.Node, isInclusive : bool):
    
    # 首先,檢查傳遞給此方法的節點是否有效。
    verify_parameter_nodes(startNode, endNode)

    # 創建一個列表來存儲提取的節點。
    nodes = []

    # 如果任一標記是評論的一部分,包括評論本身,我們需要移動指針
    # 轉發到在 CommentRangeEnd 節點之後找到的 Comment 節點。
    if (endNode.node_type == aw.NodeType.COMMENT_RANGE_END and isInclusive) :
        
        node = find_next_node(aw.NodeType.COMMENT, endNode.next_sibling)
        if (node != None) :
            endNode = node

    # 保留傳遞給此方法的原始節點的記錄,以便在需要時拆分標記節點。
    originalStartNode = startNode
    originalEndNode = endNode

    # 基於塊級節點(段落和表格)提取內容。遍歷父節點以找到它們。
    # 我們將拆分第一個和最後一個節點的內容,具體取決於標記節點是否內聯。
    startNode = get_ancestor_in_body(startNode)
    endNode = get_ancestor_in_body(endNode)

    isExtracting = True
    isStartingNode = True
    # 我們從文檔中提取的當前節點。
    currNode = startNode

    # 開始提取內容。處理所有塊級節點並專門拆分第一個
    # 和需要時的最後一個節點,因此保留了段落格式。
    # 方法比常規提取器複雜一點,因為我們需要考慮因素
    # 在提取中使用內聯節點、字段、書籤等使其有用。
    while (isExtracting) :
        
        # 克隆當前節點及其子節點以獲得副本。
        cloneNode = currNode.clone(True)
        isEndingNode = currNode == endNode

        if (isStartingNode or isEndingNode) :
            
            # 我們需要單獨處理每個標記,因此將其傳遞給單獨的方法。
            # 應首先處理結束以保留節點索引。
            if (isEndingNode) :
                # !isStartingNode:如果標記是同一個節點,則不要添加該節點兩次。
                process_marker(cloneNode, nodes, originalEndNode, currNode, isInclusive, False, not isStartingNode, False)
                isExtracting = False

            # 條件需要作為塊級開始和結束標記分開,可能是同一個節點。
            if (isStartingNode) :
                process_marker(cloneNode, nodes, originalStartNode, currNode, isInclusive, True, True, False)
                isStartingNode = False
            
        else :
            # 節點不是開始或結束標記,只需將副本添加到列表中即可。
            nodes.append(cloneNode)

        # 移動到下一個節點並提取它。如果下一個節點是 None,
        # 其餘內容位於不同的部分。
        if (currNode.next_sibling == None and isExtracting) :
            # 移至下一節。
            nextSection = currNode.get_ancestor(aw.NodeType.SECTION).next_sibling.as_section()
            currNode = nextSection.body.first_child
            
        else :
            # 移動到正文中的下一個節點。
            currNode = currNode.next_sibling
            
    # 為了與內聯書籤模式兼容,添加下一段(空)。
    if (isInclusive and originalEndNode == endNode and not originalEndNode.is_composite) :
        include_next_paragraph(endNode, nodes)

    # 返回節點標記之間的節點。
    return nodes

extractcontent 方法還需要一些輔助方法來完成文本提取操作,下面給出。

def verify_parameter_nodes(start_node: aw.Node, end_node: aw.Node):

    # 這些檢查的執行順序很重要。
    if start_node is None:
        raise ValueError("Start node cannot be None")
    if end_node is None:
        raise ValueError("End node cannot be None")

    if start_node.document != end_node.document:
        raise ValueError("Start node and end node must belong to the same document")

    if start_node.get_ancestor(aw.NodeType.BODY) is None or end_node.get_ancestor(aw.NodeType.BODY) is None:
        raise ValueError("Start node and end node must be a child or descendant of a body")

    # 檢查結束節點是否在 DOM 樹中的開始節點之後。
    # 首先,檢查它們是否在不同的部分,如果不在,
    # 檢查它們在同一部分正文中的位置。
    start_section = start_node.get_ancestor(aw.NodeType.SECTION).as_section()
    end_section = end_node.get_ancestor(aw.NodeType.SECTION).as_section()

    start_index = start_section.parent_node.index_of(start_section)
    end_index = end_section.parent_node.index_of(end_section)

    if start_index == end_index:

        if (start_section.body.index_of(get_ancestor_in_body(start_node)) >
            end_section.body.index_of(get_ancestor_in_body(end_node))):
            raise ValueError("The end node must be after the start node in the body")

    elif start_index > end_index:
        raise ValueError("The section of end node must be after the section start node")

 
def find_next_node(node_type: aw.NodeType, from_node: aw.Node):

    if from_node is None or from_node.node_type == node_type:
        return from_node

    if from_node.is_composite:

        node = find_next_node(node_type, from_node.as_composite_node().first_child)
        if node is not None:
            return node

    return find_next_node(node_type, from_node.next_sibling)

 
def is_inline(node: aw.Node):

    # 測試節點是否是段落或表節點的後代而不是段落
    # 或表格 評論類中的段落是一個段落的體面是可能的。
    return ((node.get_ancestor(aw.NodeType.PARAGRAPH) is not None or node.get_ancestor(aw.NodeType.TABLE) is not None) and
            not (node.node_type == aw.NodeType.PARAGRAPH or node.node_type == aw.NodeType.TABLE))

 
def process_marker(clone_node: aw.Node, nodes, node: aw.Node, block_level_ancestor: aw.Node,
    is_inclusive: bool, is_start_marker: bool, can_add: bool, force_add: bool):

    # 如果我們正在處理塊級節點,請查看是否應該包含它並將其添加到列表中。
    if node == block_level_ancestor:
        if can_add and is_inclusive:
            nodes.append(clone_node)
        return

    # cloneNode 是 blockLevelNode 的克隆。如果節點 != blockLevelNode, blockLevelAncestor
    # 是節點的祖先,這意味著它是一個複合節點。
    assert clone_node.is_composite

    # 如果標記是 FieldStart 節點,請檢查它是否包含在內。
    # 為簡單起見,我們假設 FieldStart 和 FieldEnd 出現在同一段落中。
    if node.node_type == aw.NodeType.FIELD_START:
        # 如果標記是起始節點且未包含在內,則跳至該字段的末尾。
        # 如果標記是結束節點並且要包含在內,則移動到結束字段,這樣該字段就不會被刪除。
        if is_start_marker and not is_inclusive or not is_start_marker and is_inclusive:
            while node.next_sibling is not None and node.node_type != aw.NodeType.FIELD_END:
                node = node.next_sibling

    # 如果標記節點位於文檔主體的第三層或更低,則支持一個案例。
    node_branch = fill_self_and_parents(node, block_level_ancestor)

    # 通過索引處理我們克隆節點中的相應節點。
    current_clone_node = clone_node
   for i in range(len(node_branch) - 1, -1):

        current_node = node_branch[i]
        node_index = current_node.parent_node.index_of(current_node)
        current_clone_node = current_clone_node.as_composite_node.child_nodes[node_index]

        remove_nodes_outside_of_range(current_clone_node, is_inclusive or (i > 0), is_start_marker)

    # 處理後,如果不包含複合節點,複合節點可能會變為空。
    if can_add and (force_add or clone_node.as_composite_node().has_child_nodes):
        nodes.append(clone_node)

 
def remove_nodes_outside_of_range(marker_node: aw.Node, is_inclusive: bool, is_start_marker: bool):

    is_processing = True
    is_removing = is_start_marker
    next_node = marker_node.parent_node.first_child

    while is_processing and next_node is not None:

        current_node = next_node
        is_skip = False

        if current_node == marker_node:
            if is_start_marker:
                is_processing = False
                if is_inclusive:
                    is_removing = False
            else:
                is_removing = True
                if is_inclusive:
                    is_skip = True

        next_node = next_node.next_sibling
        if is_removing and not is_skip:
            current_node.remove()

 
def fill_self_and_parents(node: aw.Node, till_node: aw.Node):

    nodes = []
    current_node = node

    while current_node != till_node:
        nodes.append(current_node)
        current_node = current_node.parent_node

    return nodes

 
def include_next_paragraph(node: aw.Node, nodes):

    paragraph = find_next_node(aw.NodeType.PARAGRAPH, node.next_sibling).as_paragraph()
    if paragraph is not None:

        # 移動到第一個孩子以包含沒有內容的段落。
        marker_node = paragraph.first_child if paragraph.has_child_nodes else paragraph
        root_node = get_ancestor_in_body(paragraph)

        process_marker(root_node.clone(True), nodes, marker_node, root_node,
            marker_node == paragraph, False, True, True)

 
def get_ancestor_in_body(start_node: aw.Node):

    while start_node.parent_node.node_type != aw.NodeType.BODY:
        start_node = start_node.parent_node
    return start_node
def generate_document(src_doc: aw.Document, nodes):

    dst_doc = aw.Document()
    # 從空文檔中刪除第一段。
    dst_doc.first_section.body.remove_all_children()

    # 將列表中的每個節點導入到新文檔中。保留節點的原始格式。
    importer = aw.NodeImporter(src_doc, dst_doc, aw.ImportFormatMode.KEEP_SOURCE_FORMATTING)

   for node in nodes:
        import_node = importer.import_node(node, True)
        dst_doc.first_section.body.append_child(import_node)

    return dst_doc

 
def paragraphs_by_style_name(doc: aw.Document, style_name: str):

    paragraphs_with_style = []
    paragraphs = doc.get_child_nodes(aw.NodeType.PARAGRAPH, True)

   for paragraph in paragraphs:
        paragraph = paragraph.as_paragraph()
        if paragraph.paragraph_format.style.name == style_name:
            paragraphs_with_style.append(paragraph)

    return paragraphs_with_style

現在我們已準備好使用這些方法並從 Word 文檔中提取文本。

提取 Word 文檔中段落之間的文本

讓我們看看如何提取 Word DOCX 文檔中兩個段落之間的內容。以下是在 Python 中執行此操作的步驟。

  • 首先,使用 Document 類加載 Word 文檔。
  • 使用 Document.firstsection.body.getchild(NodeType.PARAGRAPH, int, boolean).asparagraph() 方法將開始和結束段落引用到兩個對像中。
  • 調用 extractcontent(startPara, endPara, True) 方法將節點提取到對像中。
  • 調用 generatedocument(Document, extractedNodes) 輔助方法來創建包含提取內容的文檔。
  • 最後,使用 Document.save(string) 方法保存返回的文檔。

以下代碼示例顯示瞭如何使用 Python 提取 Word 文檔中第 7 段和第 11 段之間的文本。

# 加載文檔。
doc = aw.Document("Extract content.docx")

# 定義開始和結束段落。
startPara = doc.first_section.body.get_child(aw.NodeType.PARAGRAPH, 6, True).as_paragraph()
endPara = doc.first_section.body.get_child(aw.NodeType.PARAGRAPH, 10, True).as_paragraph()

# 提取文檔中這些段落之間的內容。在提取中包括這些標記。
extractedNodes = extract_content(startPara, endPara, True)

# 生成包含提取內容的文檔。
dstDoc = generate_document(doc, extractedNodes)

# 保存文檔。
dstDoc.save("extract_content_between_paragraphs.docx")

提取Word文檔中不同類型節點之間的文本

您還可以提取不同類型節點之間的內容。為了演示,讓我們提取段落和表格之間的內容並將其保存到一個新的 Word 文檔中。以下是執行此操作的步驟。

  • 使用 Document 類加載 Word 文檔。
  • 使用 Document.firstsection.body.getchild(NodeType, int, boolean) 方法將開始和結束節點的引用獲取到兩個對像中。
  • 調用 extractcontent(startPara, endPara, True) 方法將節點提取到對像中。
  • 調用 generatedocument(Document, extractedNodes) 輔助方法來創建包含提取內容的文檔。
  • 使用 Document.save(string) 方法保存返回的文檔。

以下代碼示例顯示瞭如何在 Python 中提取段落和表格之間的文本。

# 載入文件
doc = aw.Document("Extract content.docx")

# 定義開始和結束節點。
start_para = doc.last_section.get_child(aw.NodeType.PARAGRAPH, 2, True).as_paragraph()
end_table = doc.last_section.get_child(aw.NodeType.TABLE, 0, True).as_table()

# 提取文檔中這些節點之間的內容。在提取中包括這些標記。
extracted_nodes = extract_content(start_para, end_table, True)

# 生成包含提取內容的文檔。
dstDoc = generate_document(doc, extractedNodes)

# 保存文件。
dstDoc.save("extract_content_between_nodes.docx")

根據樣式提取段落之間的文本

現在讓我們看看如何根據樣式提取段落之間的內容。為了演示,我們將提取 Word 文檔中第一個“標題 1”和第一個“標題 3”之間的內容。以下步驟演示瞭如何在 Python 中實現此目的。

  • 首先,使用 Document 類加載 Word 文檔。
  • 然後,使用 paragraphsbystylename(Document, “Heading 1”) 輔助方法將段落提取到一個對像中。
  • 使用 paragraphsbystylename(Document, “Heading 3”) 輔助方法將段落提取到另一個對像中。
  • 調用 extractcontent(startPara, endPara, True) 方法並將兩個段落數組中的第一個元素作為第一個和第二個參數傳遞。
  • 調用 generatedocument(Document, extractedNodes) 輔助方法來創建包含提取內容的文檔。
  • 最後,使用 Document.save(string) 方法保存返回的文檔。

以下代碼示例顯示瞭如何根據樣式提取段落之間的內容。

# 載入文件
doc = aw.Document("Extract content.docx")

# 使用各自的標題樣式收集段落列表。
parasStyleHeading1 = paragraphs_by_style_name(doc, "Heading 1")
parasStyleHeading3 = paragraphs_by_style_name(doc, "Heading 3")

# 使用具有這些樣式的段落的第一個實例。
startPara1 = parasStyleHeading1[0]
endPara1 = parasStyleHeading3[0]

# 提取文檔中這些節點之間的內容。不要在提取中包含這些標記。
extractedNodes = extract_content(startPara1, endPara1, False)

# 生成包含提取內容的文檔。
dstDoc = generate_document(doc, extractedNodes)

# 保存文檔。
dstDoc.save("extract_content_between_paragraphs_based_on-Styles.docx")

閱讀更多

您可以使用 this 文檔文章探索從 Word 文檔中提取文本的其他方案。

獲取免費的 API 許可證

您可以獲得 臨時許可 以在沒有評估限制的情況下使用 Aspose.Words for Python。

結論

在本文中,您學習瞭如何使用 Python 從 MS Word 文檔中提取文本。此外,您還了解瞭如何以編程方式提取 Word 文檔中相似或不同類型節點之間的內容。因此,您可以在 Python 中構建自己的 MS Word 文本提取器。此外,您可以使用 文檔 探索 Aspose.Words for Python 的其他功能。如果您有任何疑問,請隨時通過我們的 論壇 告訴我們。

也可以看看

信息:如果您需要從 PowerPoint 演示文稿中獲取 Word 文檔,您可以使用 Aspose Presentation to Word Document 轉換器。