Wyodrębnij zawartość z dokumentów Word DOCX w Python

Wyodrębnianie tekstu z dokumentów programu Word jest często wykonywane w różnych scenariuszach. Na przykład, aby przeanalizować tekst, wyodrębnić określone sekcje dokumentu i połączyć je w jeden dokument i tak dalej. W tym artykule dowiesz się, jak programowo wyodrębnić tekst z dokumentów Worda w Python. Ponadto omówimy, jak dynamicznie wyodrębniać zawartość między określonymi elementami, takimi jak akapity, tabele itp.

Biblioteka Python do wyodrębniania tekstu z dokumentów Worda

Aspose.Words for Python to potężna biblioteka, która pozwala tworzyć od podstaw dokumenty MS Word. Ponadto pozwala manipulować istniejącymi dokumentami Word w celu szyfrowania, konwersji, ekstrakcji tekstu itp. Użyjemy tej biblioteki do wyodrębnienia tekstu z dokumentów Word DOCX lub DOC. Możesz zainstalować bibliotekę z PyPI za pomocą następującego polecenia pip.

pip install aspose-words

Ekstrakcja tekstu w dokumentach programu Word przy użyciu języka Python

Dokument MS Word składa się z różnych elementów, takich jak akapity, tabele, obrazy itp. Dlatego wymagania dotyczące ekstrakcji tekstu mogą się różnić w zależności od scenariusza. Na przykład może być konieczne wyodrębnienie tekstu między akapitami, zakładkami, komentarzami itp.

Każdy typ elementu w dokumencie programu Word jest reprezentowany jako węzeł. Dlatego, aby przetworzyć dokument, będziesz musiał bawić się węzłami. Zacznijmy więc i zobaczmy, jak wyodrębnić tekst z dokumentów programu Word w różnych scenariuszach.

Wyodrębnij tekst z dokumentu programu Word w Python

W tej sekcji zamierzamy zaimplementować ekstraktor tekstu w języku Python dla dokumentów programu Word, a przepływ pracy podczas wyodrębniania tekstu wyglądałby następująco:

  • Najpierw zdefiniujemy węzły, które chcemy uwzględnić w procesie ekstrakcji tekstu.
  • Następnie wyodrębnimy zawartość między określonymi węzłami (w tym lub z wyłączeniem węzłów początkowych i końcowych).
  • Na koniec wykorzystamy klon wyodrębnionych węzłów np. do stworzenia nowego dokumentu Worda składającego się z wyodrębnionej treści.

Napiszmy teraz metodę o nazwie extractcontent, do której przekażemy węzły i kilka innych parametrów w celu przeprowadzenia ekstrakcji tekstu. Ta metoda przeanalizuje dokument i sklonuje węzły. Poniżej przedstawiono parametry, które przekażemy tej metodzie.

  1. StartNode i EndNode odpowiednio jako punkty początkowe i końcowe wyodrębniania zawartości. Mogą to być zarówno węzły na poziomie bloku (Akapit , Tabela), jak i na poziomie wiersza (np. Run, FieldStart, BookmarkStart itp.).
    1. Aby przekazać pole, należy przekazać odpowiedni obiekt FieldStart.
    2. Aby przekazać zakładki, należy przekazać węzły BookmarkStart i BookmarkEnd.
    3. W przypadku komentarzy należy używać węzłów CommentRangeStart i CommentRangeEnd.
  2. IsInclusive określa, czy znaczniki są uwzględniane w ekstrakcji, czy nie. Jeśli ta opcja jest ustawiona na false i zostanie przekazany ten sam węzeł lub kolejne węzły, zwrócona zostanie pusta lista.

Poniżej przedstawiono kompletną implementację metody extractcontent, która wyodrębnia zawartość między przekazywanymi węzłami.

def extract_content(startNode : aw.Node, endNode : aw.Node, isInclusive : bool):
    
    # Najpierw sprawdź, czy węzły przekazane do tej metody są prawidłowe do użycia.
    verify_parameter_nodes(startNode, endNode)

    # Utwórz listę do przechowywania wyodrębnionych węzłów.
    nodes = []

    # Jeśli któryś ze znaczników jest częścią komentarza, w tym samego komentarza, musimy przesunąć wskaźnik
    # do węzła Comment znajdującego się za węzłem CommentRangeEnd.
    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

    # Zachowaj zapis oryginalnych węzłów przekazanych do tej metody, aby w razie potrzeby podzielić węzły znaczników.
    originalStartNode = startNode
    originalEndNode = endNode

    # Wyodrębnij zawartość na podstawie węzłów na poziomie bloku (akapity i tabele). Przechodź przez węzły nadrzędne, aby je znaleźć.
    # Podzielimy zawartość pierwszego i ostatniego węzła, w zależności od tego, czy węzły znaczników są wbudowane.
    startNode = get_ancestor_in_body(startNode)
    endNode = get_ancestor_in_body(endNode)

    isExtracting = True
    isStartingNode = True
    # Bieżący węzeł, który wyodrębniamy z dokumentu.
    currNode = startNode

    # Rozpocznij wyodrębnianie zawartości. Przetwarzaj wszystkie węzły na poziomie bloku i konkretnie podziel pierwszy
    # i ostatnie węzły w razie potrzeby, dzięki czemu formatowanie akapitu zostaje zachowane.
    # Metoda jest nieco bardziej skomplikowana niż zwykły ekstraktor, ponieważ musimy to uwzględnić
    # w wyodrębnianiu za pomocą wbudowanych węzłów, pól, zakładek itp., aby było to przydatne.
    while (isExtracting) :
        
        # Sklonuj bieżący węzeł i jego elementy podrzędne, aby uzyskać kopię.
        cloneNode = currNode.clone(True)
        isEndingNode = currNode == endNode

        if (isStartingNode or isEndingNode) :
            
            # Musimy przetworzyć każdy znacznik osobno, więc zamiast tego przekaż go osobnej metodzie.
            # Koniec powinien zostać przetworzony jako pierwszy, aby zachować indeksy węzłów.
            if (isEndingNode) :
                # !isStartingNode: nie dodawaj węzła dwa razy, jeśli znaczniki są tym samym węzłem.
                process_marker(cloneNode, nodes, originalEndNode, currNode, isInclusive, False, not isStartingNode, False)
                isExtracting = False

            # Warunek musi być oddzielny, ponieważ znaczniki początku i końca na poziomie bloku, być może ten sam węzeł.
            if (isStartingNode) :
                process_marker(cloneNode, nodes, originalStartNode, currNode, isInclusive, True, True, False)
                isStartingNode = False
            
        else :
            # Węzeł nie jest znacznikiem początkowym ani końcowym, po prostu dodaj kopię do listy.
            nodes.append(cloneNode)

        # Przejdź do następnego węzła i wyodrębnij go. Jeśli następnym węzłem jest Brak,
        # reszta treści znajduje się w innej sekcji.
        if (currNode.next_sibling == None and isExtracting) :
            # Przejdź do następnej sekcji.
            nextSection = currNode.get_ancestor(aw.NodeType.SECTION).next_sibling.as_section()
            currNode = nextSection.body.first_child
            
        else :
            # Przejdź do następnego węzła w ciele.
            currNode = currNode.next_sibling
            
    # Aby zapewnić zgodność z trybem z wbudowanymi zakładkami, dodaj następny akapit (pusty).
    if (isInclusive and originalEndNode == endNode and not originalEndNode.is_composite) :
        include_next_paragraph(endNode, nodes)

    # Zwróć węzły między znacznikami węzłów.
    return nodes

Niektóre metody pomocnicze są również wymagane przez metodę extractcontent do wykonania operacji wyodrębniania tekstu, które podano poniżej.

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

    # Kolejność przeprowadzania tych kontroli jest ważna.
    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")

    # Sprawdź, czy węzeł końcowy znajduje się za węzłem początkowym w drzewie DOM.
    # Najpierw sprawdź, czy są w różnych sekcjach, a jeśli nie,
    # sprawdzić ich położenie w treści tej samej sekcji.
    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):

    # Sprawdź, czy węzeł jest potomkiem węzła akapitu lub tabeli i czy nie jest akapitem
    # lub tabela akapit wewnątrz klasy komentarza, która jest przyzwoita w akapicie, jest możliwa.
    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):

    # Jeśli mamy do czynienia z węzłem na poziomie bloku, sprawdź, czy powinien zostać uwzględniony i dodaj go do listy.
    if node == block_level_ancestor:
        if can_add and is_inclusive:
            nodes.append(clone_node)
        return

    # cloneNode jest klonem blockLevelNode. Jeśli węzeł != blockLevelNode, blockLevelAncestor
    # jest przodkiem węzła, co oznacza, że jest to węzeł złożony.
    assert clone_node.is_composite

    # Jeśli znacznik jest węzłem FieldStart, sprawdź, czy ma być uwzględniony, czy nie.
    # Dla uproszczenia zakładamy, że FieldStart i FieldEnd pojawiają się w tym samym akapicie.
    if node.node_type == aw.NodeType.FIELD_START:
        # Jeśli znacznik jest węzłem początkowym i nie jest uwzględniony, przejdź do końca pola.
        # Jeśli znacznik jest węzłem końcowym i ma być uwzględniony, należy przejść do pola końcowego, aby pole nie zostało usunięte.
        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

    # Obsługuje przypadek, gdy węzeł znacznika znajduje się na trzecim lub niższym poziomie treści dokumentu.
    node_branch = fill_self_and_parents(node, block_level_ancestor)

    # Przetwórz odpowiedni węzeł w naszym sklonowanym węźle według indeksu.
    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)

    # Po przetworzeniu węzeł złożony może stać się pusty, jeśli go nie zawiera.
    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:

        # Przejdź do pierwszego elementu podrzędnego, aby uwzględnić akapity bez treści.
        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()
    # Usuń pierwszy akapit z pustego dokumentu.
    dst_doc.first_section.body.remove_all_children()

    # Zaimportuj każdy węzeł z listy do nowego dokumentu. Zachowaj oryginalne formatowanie węzła.
    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

Teraz jesteśmy gotowi do wykorzystania tych metod i wyodrębnienia tekstu z dokumentu Word.

Wyodrębnij tekst między akapitami w dokumencie programu Word

Zobaczmy, jak wyodrębnić zawartość między dwoma akapitami w dokumencie Word DOCX. Poniżej przedstawiono kroki, aby wykonać tę operację w języku Python.

  • Najpierw załaduj dokument programu Word przy użyciu klasy Document.
  • Uzyskaj odwołanie do akapitu początkowego i końcowego do dwóch obiektów za pomocą metody Document.firstsection.body.getchild(NodeType.PARAGRAPH, int, boolean).asparagraph() .
  • Wywołaj metodę extractcontent(startPara, endPara, True), aby wyodrębnić węzły do obiektu.
  • Wywołaj metodę pomocnika generowaneocument(Document, extractedNodes), aby utworzyć dokument składający się z wyodrębnionej zawartości.
  • Na koniec zapisz zwrócony dokument przy użyciu metody Document.save(string).

Poniższy przykładowy kod pokazuje, jak wyodrębnić tekst między 7. a 11. akapitem w dokumencie programu Word w języku Python.

# Załaduj dokument.
doc = aw.Document("Extract content.docx")

# Zdefiniuj początkowe i końcowe akapity.
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()

# Wyodrębnij zawartość między tymi akapitami w dokumencie. Uwzględnij te znaczniki w ekstrakcji.
extractedNodes = extract_content(startPara, endPara, True)

# Wygeneruj dokument zawierający wyodrębnioną treść.
dstDoc = generate_document(doc, extractedNodes)

# Zapisz dokument.
dstDoc.save("extract_content_between_paragraphs.docx")

Wyodrębnij tekst między różnymi typami węzłów w dokumencie programu Word

Możesz także wyodrębniać zawartość między różnymi typami węzłów. Dla demonstracji wyodrębnijmy zawartość między akapitem a tabelą i zapiszmy ją w nowym dokumencie programu Word. Poniżej przedstawiono kroki, aby wykonać tę operację.

  • Załaduj dokument programu Word przy użyciu klasy Document.
  • Uzyskaj odniesienie początkowego i końcowego węzła do dwóch obiektów za pomocą metody Document.firstsection.body.getchild(NodeType, int, boolean).
  • Wywołaj metodę extractcontent(startPara, endPara, True), aby wyodrębnić węzły do obiektu.
  • Wywołaj metodę pomocnika generowaneocument(Document, extractedNodes), aby utworzyć dokument składający się z wyodrębnionej zawartości.
  • Zapisz zwrócony dokument przy użyciu metody Document.save(string).

Poniższy przykładowy kod pokazuje, jak wyodrębnić tekst między akapitem a tabelą w języku Python.

# Załaduj dokument
doc = aw.Document("Extract content.docx")

# Zdefiniuj węzły początkowe i końcowe.
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()

# Wyodrębnij zawartość między tymi węzłami w dokumencie. Uwzględnij te znaczniki w ekstrakcji.
extracted_nodes = extract_content(start_para, end_table, True)

# Wygeneruj dokument zawierający wyodrębnioną treść.
dstDoc = generate_document(doc, extractedNodes)

# Zapisz dokument.
dstDoc.save("extract_content_between_nodes.docx")

Wyodrębnij tekst między akapitami na podstawie stylów

Sprawdźmy teraz, jak wyodrębnić treść między akapitami na podstawie stylów. W celu demonstracji wyodrębnimy zawartość między pierwszym „Nagłówkiem 1” a pierwszym „Nagłówkiem 3” w dokumencie programu Word. Poniższe kroki pokazują, jak to osiągnąć w języku Python.

  • Najpierw załaduj dokument programu Word przy użyciu klasy Document.
  • Następnie wyodrębnij akapity do obiektu, używając metody pomocniczejparagrafybystylename(Document, “Heading 1”).
  • Wyodrębnij akapity do innego obiektu, używając metody pomocniczejparagrafybystylename(Document, “Heading 3”).
  • Wywołaj metodę extractcontent(startPara, endPara, True) i przekaż pierwsze elementy w obu tablicach akapitów jako pierwszy i drugi parametr.
  • Wywołaj metodę pomocnika generowaneocument(Document, extractedNodes), aby utworzyć dokument składający się z wyodrębnionej zawartości.
  • Na koniec zapisz zwrócony dokument przy użyciu metody Document.save(string).

Poniższy przykład kodu pokazuje, jak wyodrębnić zawartość między akapitami na podstawie stylów.

# Załaduj dokument
doc = aw.Document("Extract content.docx")

# Zbierz listę akapitów, używając odpowiednich stylów nagłówków.
parasStyleHeading1 = paragraphs_by_style_name(doc, "Heading 1")
parasStyleHeading3 = paragraphs_by_style_name(doc, "Heading 3")

# Użyj pierwszego wystąpienia akapitów z tymi stylami.
startPara1 = parasStyleHeading1[0]
endPara1 = parasStyleHeading3[0]

# Wyodrębnij zawartość między tymi węzłami w dokumencie. Nie uwzględniaj tych znaczników w ekstrakcji.
extractedNodes = extract_content(startPara1, endPara1, False)

# Wygeneruj dokument zawierający wyodrębnioną treść.
dstDoc = generate_document(doc, extractedNodes)

# Zapisz dokument.
dstDoc.save("extract_content_between_paragraphs_based_on-Styles.docx")

Czytaj więcej

Możesz zapoznać się z innymi scenariuszami wyodrębniania tekstu z dokumentów programu Word, korzystając z tego artykułu w dokumentacji.

Uzyskaj bezpłatną licencję API

Możesz uzyskać tymczasową licencję na używanie Aspose.Words for Python bez ograniczeń ewaluacyjnych.

Wniosek

W tym artykule nauczyłeś się, jak wyodrębniać tekst z dokumentów MS Word za pomocą Python. Ponadto widziałeś, jak programowo wyodrębniać zawartość między podobnymi lub różnymi typami węzłów w dokumencie programu Word. W ten sposób możesz zbudować własny ekstraktor tekstu MS Word w Python. Poza tym możesz poznać inne funkcje Aspose.Words for Python, korzystając z dokumentacji. Jeśli masz jakieś pytania, daj nam znać za pośrednictwem naszego forum.

Zobacz też

Info: Jeśli kiedykolwiek będziesz potrzebował pobrać dokument Word z prezentacji PowerPoint, możesz użyć konwertera Aspose Presentation to Word Document.