Витяг вмісту з документів Word DOCX на Python

Вилучення тексту з документів Word часто виконується в різних сценаріях. Наприклад, щоб проаналізувати текст, виділити окремі розділи документа та об’єднати їх в один документ тощо. У цій статті ви дізнаєтеся, як програмно видобувати текст із документів Word на Python. Крім того, ми розглянемо, як динамічно витягувати вміст між певними елементами, такими як абзаци, таблиці тощо.

Бібліотека Python для вилучення тексту з документів Word

Aspose.Words for Python — це потужна бібліотека, яка дозволяє створювати документи MS Word з нуля. Крім того, це дозволяє вам маніпулювати існуючими документами Word для шифрування, перетворення, вилучення тексту тощо. Ми використовуватимемо цю бібліотеку для вилучення тексту з документів Word DOCX або DOC. Ви можете встановити бібліотеку з PyPI за допомогою такої команди pip.

pip install aspose-words

Вилучення тексту в документах Word за допомогою Python

Документ MS Word складається з різних елементів, які включають абзаци, таблиці, зображення тощо. Тому вимоги до вилучення тексту можуть відрізнятися від одного сценарію до іншого. Наприклад, вам може знадобитися витягти текст між абзацами, закладки, коментарі тощо.

Кожен тип елемента в документі Word представлено у вигляді вузла. Тому для обробки документа доведеться погратися з вузлами. Тож давайте почнемо й подивимось, як витягувати текст із документів Word у різних сценаріях.

Витягніть текст із документа Word на Python

У цьому розділі ми збираємося реалізувати екстрактор тексту Python для документів Word, і робочий процес вилучення тексту буде таким:

  • Спочатку ми визначимо вузли, які ми хочемо включити в процес вилучення тексту.
  • Потім ми витягнемо вміст між вказаними вузлами (включаючи або виключаючи початковий і кінцевий вузли).
  • Нарешті, ми використаємо клон витягнутих вузлів, наприклад, щоб створити новий документ Word, що складається з вилученого вмісту.

Давайте тепер напишемо метод з назвою extractcontent, якому ми передамо вузли та деякі інші параметри для виконання вилучення тексту. Цей метод розбере документ і клонує вузли. Нижче наведено параметри, які ми передамо цьому методу.

  1. StartNode і EndNode як початкова і кінцева точки для вилучення вмісту відповідно. Це можуть бути вузли рівня блоку (Абзац, Таблиця) або вбудованого рівня (наприклад, Виконати, 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 = []

    # Якщо будь-який маркер є частиною коментаря, включаючи сам коментар, нам потрібно перемістити вказівник
    # переслати до вузла Comment Node, який знаходиться після вузла 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

    # Зберігайте записи вихідних вузлів, переданих цьому методу, щоб за потреби розділити вузли маркерів.
    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) :
            
            # Нам потрібно обробляти кожен маркер окремо, тому натомість передайте його окремому методу.
            # Спочатку слід обробити End, щоб зберегти індекси вузлів.
            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. Якщо node != 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.

  • Спочатку завантажте документ Word за допомогою класу Document.
  • Отримайте посилання на початковий і кінцевий абзаци в два об’єкти за допомогою методу Document.firstsection.body.getchild(NodeType.PARAGRAPH, int, boolean).asparagraph().
  • Викличте метод extractcontent(startPara, endPara, True), щоб витягнути вузли в об’єкт.
  • Викличте допоміжний метод generatedocument(Document, extractedNodes), щоб створити документ із витягнутим вмістом.
  • Нарешті, збережіть повернутий документ за допомогою методу Document.save(string).

У наведеному нижче прикладі коду показано, як витягнути текст між 7-м і 11-м абзацами в документі Word у Python.

# Завантажити документ.
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. Нижче наведено кроки для виконання цієї операції.

  • Завантажте документ Word за допомогою класу Document.
  • Отримайте посилання на початковий і кінцевий вузли в два об’єкти за допомогою методу 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")

Вилучення тексту між абзацами на основі стилів

Давайте тепер перевіримо, як витягувати вміст між абзацами на основі стилів. Для демонстрації ми витягнемо вміст між першим «Заголовком 1» і першим «Заголовком 3» у документі Word. Наступні кроки демонструють, як цього досягти в Python.

  • Спочатку завантажте документ Word за допомогою класу Document.
  • Потім витягніть абзаци в об’єкт за допомогою допоміжного методу параграфівbystylename(Document, «Заголовок 1»).
  • Витягніть абзаци в інший об’єкт за допомогою допоміжного методу параграфівbystylename(Document, «Заголовок 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")

Докладніше про вилучення тексту

Ви можете вивчити інші сценарії вилучення тексту з документів Word, використовуючи цю статтю документації.

Витягніть текст із документів Word безкоштовно

Ви можете отримати безкоштовну тимчасову ліцензію, щоб видобувати текст із документів Word без обмежень оцінки.

Висновок

У цій статті ви дізналися, як видобувати текст із документів MS Word за допомогою Python. Крім того, ви бачили, як програмно видобувати вміст між подібними або різними типами вузлів у документі Word. Таким чином, ви можете створити свій власний екстрактор тексту MS Word на Python. Крім того, ви можете досліджувати інші функції Aspose.Words for Python за допомогою документації. Якщо у вас виникнуть запитання, не соромтеся повідомити нас через наш форум.

Дивись також