Estrai contenuto da documenti Word DOCX in Python

L’estrazione del testo dai documenti di Word viene spesso eseguita in diversi scenari. Ad esempio, per analizzare il testo, per estrarre particolari sezioni di un documento e combinarle in un unico documento, e così via. In questo articolo imparerai come estrarre testo da documenti Word a livello di codice in Python. Inoltre, tratteremo come estrarre il contenuto tra elementi specifici come paragrafi, tabelle, ecc. in modo dinamico.

Libreria Python per estrarre testo da documenti Word

Aspose.Words for Python è una potente libreria che ti consente di creare documenti MS Word da zero. Inoltre, ti consente di manipolare i documenti Word esistenti per la crittografia, la conversione, l’estrazione di testo, ecc. Useremo questa libreria per estrarre il testo dai documenti Word DOCX o DOC. Puoi installare la libreria da PyPI usando il seguente comando pip.

pip install aspose-words

Estrazione di testo in documenti Word utilizzando Python

Un documento MS Word è costituito da vari elementi che includono paragrafi, tabelle, immagini, ecc. Pertanto, i requisiti per l’estrazione del testo possono variare da uno scenario all’altro. Ad esempio, potrebbe essere necessario estrarre il testo tra paragrafi, segnalibri, commenti, ecc.

Ogni tipo di elemento in un documento di Word è rappresentato come un nodo. Pertanto, per elaborare un documento, dovrai giocare con i nodi. Quindi iniziamo e vediamo come estrarre testo da documenti Word in diversi scenari.

Estrai testo da un documento Word in Python

In questa sezione, implementeremo un estrattore di testo Python per documenti Word e il flusso di lavoro dell’estrazione del testo sarebbe il seguente:

  • In primo luogo, definiremo i nodi che vogliamo includere nel processo di estrazione del testo.
  • Quindi, estrarremo il contenuto tra i nodi specificati (includendo o escludendo i nodi iniziali e finali).
  • Infine, utilizzeremo il clone dei nodi estratti, ad esempio per creare un nuovo documento Word composto da contenuto estratto.

Scriviamo ora un metodo chiamato extractcontent a cui passeremo i nodi e alcuni altri parametri per eseguire l’estrazione del testo. Questo metodo analizzerà il documento e clonerà i nodi. Di seguito sono riportati i parametri che passeremo a questo metodo.

  1. StartNode ed EndNode rispettivamente come punti di inizio e fine per l’estrazione del contenuto. Questi possono essere nodi a livello di blocco (Paragraph, Table) o inline (ad es. Run, FieldStart, BookmarkStart ecc.).
    1. Per passare un campo devi passare l’oggetto FieldStart corrispondente.
    2. Per passare i segnalibri, è necessario passare i nodi BookmarkStart e BookmarkEnd.
    3. Per i commenti, è necessario utilizzare i nodi CommentRangeStart e CommentRangeEnd.
  2. IsInclusive definisce se i marker sono inclusi nell’estrazione o meno. Se questa opzione è impostata su false e vengono passati lo stesso nodo o nodi consecutivi, verrà restituito un elenco vuoto.

Quella che segue è l’implementazione completa del metodo extractcontent che estrae il contenuto tra i nodi che vengono passati.

def extract_content(startNode : aw.Node, endNode : aw.Node, isInclusive : bool):
    
    # First, check that the nodes passed to this method are valid for use.
    verify_parameter_nodes(startNode, endNode)

    # Create a list to store the extracted nodes.
    nodes = []

    # If either marker is part of a comment, including the comment itself, we need to move the pointer
    # forward to the Comment Node found after the CommentRangeEnd node.
    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

    # Keep a record of the original nodes passed to this method to split marker nodes if needed.
    originalStartNode = startNode
    originalEndNode = endNode

    # Extract content based on block-level nodes (paragraphs and tables). Traverse through parent nodes to find them.
    # We will split the first and last nodes' content, depending if the marker nodes are inline.
    startNode = get_ancestor_in_body(startNode)
    endNode = get_ancestor_in_body(endNode)

    isExtracting = True
    isStartingNode = True
    # The current node we are extracting from the document.
    currNode = startNode

    # Begin extracting content. Process all block-level nodes and specifically split the first
    # and last nodes when needed, so paragraph formatting is retained.
    # Method is a little more complicated than a regular extractor as we need to factor
    # in extracting using inline nodes, fields, bookmarks, etc. to make it useful.
    while (isExtracting) :
        
        # Clone the current node and its children to obtain a copy.
        cloneNode = currNode.clone(True)
        isEndingNode = currNode == endNode

        if (isStartingNode or isEndingNode) :
            
            # We need to process each marker separately, so pass it off to a separate method instead.
            # End should be processed at first to keep node indexes.
            if (isEndingNode) :
                # !isStartingNode: don't add the node twice if the markers are the same node.
                process_marker(cloneNode, nodes, originalEndNode, currNode, isInclusive, False, not isStartingNode, False)
                isExtracting = False

            # Conditional needs to be separate as the block level start and end markers, maybe the same node.
            if (isStartingNode) :
                process_marker(cloneNode, nodes, originalStartNode, currNode, isInclusive, True, True, False)
                isStartingNode = False
            
        else :
            # Node is not a start or end marker, simply add the copy to the list.
            nodes.append(cloneNode)

        # Move to the next node and extract it. If the next node is None,
        # the rest of the content is found in a different section.
        if (currNode.next_sibling == None and isExtracting) :
            # Move to the next section.
            nextSection = currNode.get_ancestor(aw.NodeType.SECTION).next_sibling.as_section()
            currNode = nextSection.body.first_child
            
        else :
            # Move to the next node in the body.
            currNode = currNode.next_sibling
            
    # For compatibility with mode with inline bookmarks, add the next paragraph (empty).
    if (isInclusive and originalEndNode == endNode and not originalEndNode.is_composite) :
        include_next_paragraph(endNode, nodes)

    # Return the nodes between the node markers.
    return nodes

Alcuni metodi di supporto sono richiesti anche dal metodo extractcontent per eseguire l’operazione di estrazione del testo, che sono riportati di seguito.

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

    # The order in which these checks are done is important.
    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")

    # Check the end node is after the start node in the DOM tree.
    # First, check if they are in different sections, then if they're not,
    # check their position in the body of the same section.
    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):

    # Test if the node is a descendant of a Paragraph or Table node and is not a paragraph
    # or a table a paragraph inside a comment class that is decent of a paragraph is possible.
    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 we are dealing with a block-level node, see if it should be included and add it to the list.
    if node == block_level_ancestor:
        if can_add and is_inclusive:
            nodes.append(clone_node)
        return

    # cloneNode is a clone of blockLevelNode. If node != blockLevelNode, blockLevelAncestor
    # is the node's ancestor that means it is a composite node.
    assert clone_node.is_composite

    # If a marker is a FieldStart node check if it's to be included or not.
    # We assume for simplicity that the FieldStart and FieldEnd appear in the same paragraph.
    if node.node_type == aw.NodeType.FIELD_START:
        # If the marker is a start node and is not included, skip to the end of the field.
        # If the marker is an end node and is to be included, then move to the end field so the field will not be removed.
        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

    # Support a case if the marker node is on the third level of the document body or lower.
    node_branch = fill_self_and_parents(node, block_level_ancestor)

    # Process the corresponding node in our cloned node by index.
    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)

    # After processing, the composite node may become empty if it has doesn't include it.
    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:

        # Move to the first child to include paragraphs without content.
        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()
    # Remove the first paragraph from the empty document.
    dst_doc.first_section.body.remove_all_children()

    # Import each node from the list into the new document. Keep the original formatting of the node.
    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

Ora siamo pronti per utilizzare questi metodi ed estrarre il testo da un documento di Word.

Estrai il testo tra i paragrafi in un documento di Word

Vediamo come estrarre il contenuto tra due paragrafi in un documento Word DOCX. Di seguito sono riportati i passaggi per eseguire questa operazione in Python.

  • Innanzitutto, carica il documento di Word utilizzando la classe Document.
  • Ottieni il riferimento dei paragrafi iniziale e finale in due oggetti usando il metodo Document.firstsection.body.getchild(NodeType.PARAGRAPH, int, boolean).asparagraph().
  • Chiama il metodo extractcontent(startPara, endPara, True) per estrarre i nodi in un oggetto.
  • Chiama il metodo helper generatedocument(Document, extractNodes) per creare un documento costituito dal contenuto estratto.
  • Infine, salva il documento restituito usando il metodo Document.save(string).

L’esempio di codice seguente mostra come estrarre il testo tra il settimo e l’undicesimo paragrafo in un documento di Word in Python.

# Load document.
doc = aw.Document("Extract content.docx")

# Define starting and ending paragraphs.
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()

# Extract the content between these paragraphs in the document. Include these markers in the extraction.
extractedNodes = extract_content(startPara, endPara, True)

# Generate document containing extracted content.
dstDoc = generate_document(doc, extractedNodes)

# Save document.
dstDoc.save("extract_content_between_paragraphs.docx")

Estrai testo tra diversi tipi di nodi in un documento di Word

Puoi anche estrarre contenuto tra diversi tipi di nodi. A scopo dimostrativo, estraiamo il contenuto tra un paragrafo e una tabella e lo salviamo in un nuovo documento di Word. Di seguito sono riportati i passaggi per eseguire questa operazione.

  • Carica il documento di Word usando la classe Document.
  • Ottieni il riferimento dei nodi iniziale e finale in due oggetti usando il metodo Document.firstsection.body.getchild(NodeType, int, boolean).
  • Chiama il metodo extractcontent(startPara, endPara, True) per estrarre i nodi in un oggetto.
  • Chiama il metodo helper generatedocument(Document, extractNodes) per creare un documento costituito dal contenuto estratto.
  • Salva il documento restituito utilizzando il metodo Document.save(string).

L’esempio di codice seguente mostra come estrarre il testo tra un paragrafo e una tabella in Python.

# Load document
doc = aw.Document("Extract content.docx")

# Define starting and ending nodes.
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()

# Extract the content between these nodes in the document. Include these markers in the extraction.
extracted_nodes = extract_content(start_para, end_table, True)

# Generate document containing extracted content.
dstDoc = generate_document(doc, extractedNodes)

# Save document.
dstDoc.save("extract_content_between_nodes.docx")

Estrai il testo tra i paragrafi in base agli stili

Diamo ora un’occhiata a come estrarre il contenuto tra i paragrafi in base agli stili. A scopo dimostrativo, estrarremo il contenuto tra il primo “Titolo 1” e il primo “Titolo 3” nel documento di Word. I seguenti passaggi mostrano come ottenere ciò in Python.

  • Innanzitutto, carica il documento di Word utilizzando la classe Document.
  • Quindi, estrai i paragrafi in un oggetto usando il metodo di supporto paragrafipernomestile(Documento, “Intestazione 1”).
  • Estrai i paragrafi in un altro oggetto usando il metodo di supporto paragrafipernomestile(Documento, “Intestazione 3”).
  • Chiama il metodo extractcontent(startPara, endPara, True) e passa i primi elementi in entrambi gli array di paragrafi come primo e secondo parametro.
  • Chiama il metodo helper generatedocument(Document, extractNodes) per creare un documento costituito dal contenuto estratto.
  • Infine, salva il documento restituito usando il metodo Document.save(string).

Nell’esempio di codice seguente viene illustrato come estrarre il contenuto tra i paragrafi in base agli stili.

# Load document
doc = aw.Document("Extract content.docx")

# Gather a list of the paragraphs using the respective heading styles.
parasStyleHeading1 = paragraphs_by_style_name(doc, "Heading 1")
parasStyleHeading3 = paragraphs_by_style_name(doc, "Heading 3")

# Use the first instance of the paragraphs with those styles.
startPara1 = parasStyleHeading1[0]
endPara1 = parasStyleHeading3[0]

# Extract the content between these nodes in the document. Don't include these markers in the extraction.
extractedNodes = extract_content(startPara1, endPara1, False)

# Generate document containing extracted content.
dstDoc = generate_document(doc, extractedNodes)

# Save document.
dstDoc.save("extract_content_between_paragraphs_based_on-Styles.docx")

Leggi di più

Puoi esplorare altri scenari di estrazione di testo da documenti di Word usando questo articolo della documentazione.

Ottieni una licenza API gratuita

Puoi ottenere una licenza temporanea per utilizzare Aspose.Words per Python senza limitazioni di valutazione.

Conclusione

In questo articolo, hai imparato come estrarre testo da documenti MS Word usando Python. Inoltre, hai visto come estrarre il contenuto tra tipi simili o diversi di nodi in un documento di Word a livello di codice. Pertanto, puoi creare il tuo estrattore di testo MS Word in Python. Inoltre, puoi esplorare altre funzionalità di Aspose.Words per Python usando la documentazione. In caso di domande, non esitare a farcelo sapere tramite il nostro forum.

Guarda anche

Informazioni: se hai bisogno di ottenere un documento Word da una presentazione PowerPoint, puoi utilizzare il convertitore Aspose Presentazione in documento Word.