首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >基于Python实现Word文档中图片的自动提取处理

基于Python实现Word文档中图片的自动提取处理

原创
作者头像
熊猫钓鱼
发布2025-07-27 23:37:20
发布2025-07-27 23:37:20
7420
举报

在现代办公和文档处理中,Word文档已经成为最常用的文件格式之一。

这些文档不仅包含文本内容,还经常嵌入各种图片、图表和其他媒体元素。

但是,在许多场景下,我们需要从Word文档中提取这些图片,例如进行内容分析、创建图像数据库、或者在其他应用程序中重用这些图像。有时候不太方便。

同样,将图片按照特定顺序加载到Word文档中也是一个常见需求。本文将深入探讨如何使用Python实现Word文档中图片的自动提取与加载功能,从理论基础到实际应用,提供全面的技术指南。

欢迎大家共同学习探讨!

Word文档中的图片:基础知识

在深入技术实现之前,我们需要了解Word文档中图片的存储方式和基本特性。

图片在Word文档中的存储方式

现代Word文档(.docx格式)实际上是一个ZIP压缩包,包含多个XML文件和资源文件。当我们在Word文档中插入图片时,图片会被存储在文档包的word/media/目录下,并在文档的XML结构中通过引用的方式链接。

Word文档中的图片主要有以下几种存储形式:

  1. 嵌入式图片:直接存储在文档包中,最常见的形式
  2. 链接式图片:仅存储图片的引用路径,实际图片存储在外部
  3. 嵌入式与链接式混合:存储缩略图在文档中,原图通过外部链接引用

图片格式与属性

Word文档支持多种图片格式,常见的包括:

  • 位图格式:JPEG、PNG、BMP、GIF等
  • 矢量格式:EMF、WMF等
  • 其他格式:TIFF、SVG(较新版本支持)等

每个图片在Word文档中还包含多种属性:

  • 尺寸信息:宽度、高度(原始像素和显示尺寸)
  • 位置信息:在文档中的位置、与文本的排列方式
  • 格式设置:边框、效果、裁剪信息等
  • 替代文本:为图片设置的描述性文本
  • ID与名称:系统分配的唯一标识符

图片与文档结构的关系

在Word文档的XML结构中,图片通过以下方式与文档内容关联:

  1. 文档内容XML(document.xml)包含图片的引用和位置信息
  2. 关系文件(document.xml.rels)定义了内容与媒体文件的关联关系
  3. 媒体文件夹(media)存储实际的图片文件

了解这些基础知识对于我们实现图片提取和加载功能至关重要,因为我们需要正确解析文档结构,找到图片文件,并理解它们在文档中的位置和顺序。

技术准备与环境搭建

在开始实现Word文档图片处理功能之前,我们需要准备适当的开发环境和工具。

Python环境准备

首先,我们需要安装Python环境。推荐使用Python 3.6或更高版本,因为它提供了更好的Unicode支持和更多现代特性。

代码语言:bash
复制
# 检查Python版本
python --version

# 创建虚拟环境(可选但推荐)
python -m venv word_image_env
source word_image_env/bin/activate  # Linux/Mac
word_image_env\Scripts\activate     # Windows

必要库的安装

我们将使用几个关键的Python库来处理Word文档和图片:

代码语言:bash
复制
pip install python-docx       # 处理.docx文件
pip install Pillow            # 图像处理
pip install lxml              # XML处理(python-docx的依赖,但可能需要单独安装)
pip install tqdm              # 进度条显示(可选,用于批量处理)

其中,python-docx是我们的核心库,用于读取和操作Word文档。但它在图片提取方面有一些限制,因此我们还需要直接处理文档的ZIP结构和XML内容。

测试环境验证

安装完成后,我们可以简单测试环境是否正确配置:

代码语言:python
复制
import docx
import PIL
import lxml
import zipfile
import os

print(f"python-docx version: {docx.__version__}")
print(f"Pillow version: {PIL.__version__}")
print(f"lxml version: {lxml.__version__}")
print(f"zipfile module available: {zipfile.__name__}")
print(f"os module available: {os.__name__}")

如果所有库都能正确导入并显示版本信息,说明我们的环境已经准备就绪。

项目结构设计

为了使我们的代码组织良好且易于维护,我们可以按照以下结构设计项目:

代码语言:txt*斜体*
复制
word_image_processor/
│
├── word_image_extractor.py     # 图片提取核心功能
├── word_image_loader.py        # 图片加载核心功能
├── utils/
│   ├── __init__.py
│   ├── docx_utils.py           # Word文档处理工具函数
│   ├── image_utils.py          # 图像处理工具函数
│   └── metadata_utils.py       # 元数据处理工具函数
│
├── examples/
│   ├── extract_images.py       # 图片提取示例
│   └── load_images.py          # 图片加载示例
│
└── tests/
    ├── __init__.py
    ├── test_extractor.py       # 提取功能测试
    └── test_loader.py          # 加载功能测试

这种结构将核心功能、工具函数和示例代码分开,使项目更加清晰和可维护。

Word文档结构解析

要实现图片的提取和加载,我们首先需要深入理解Word文档的内部结构,特别是与图片相关的部分。

DOCX文件格式概述

如前所述,.docx文件实际上是一个ZIP压缩包,包含多个XML文件和资源文件。这种格式被称为Office Open XML (OOXML),是一种国际标准。

我们可以通过以下方式查看.docx文件的内部结构:

代码语言:python
复制
import zipfile

def explore_docx_structure(docx_path):
    """探索Word文档的内部结构"""
    with zipfile.ZipFile(docx_path) as docx_zip:
        # 列出所有文件
        file_list = docx_zip.namelist()
        print("文档内部文件列表:")
        for file in file_list:
            print(f" - {file}")
        
        # 检查是否存在图片文件夹
        media_files = [f for f in file_list if f.startswith('word/media/')]
        print(f"\n找到 {len(media_files)} 个媒体文件:")
        for media in media_files:
            print(f" - {media}")

# 使用示例
explore_docx_structure("example.docx")

文档内容与图片的关联

在Word文档中,图片与文档内容的关联主要通过以下文件实现:

  1. document.xml:主要的文档内容文件,包含文本和对图片等资源的引用
  2. document.xml.rels:定义document.xml中引用的资源(如图片)的关系
  3. Content_Types.xml:定义文档中各种内容类型

我们需要解析这些文件来理解图片在文档中的位置和顺序。

代码语言:python
复制
import xml.etree.ElementTree as ET
from zipfile import ZipFile

def analyze_document_images(docx_path):
    """分析文档中的图片引用"""
    namespaces = {
        'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
        'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
        'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'
    }
    
    with ZipFile(docx_path) as docx_zip:
        # 解析document.xml
        doc_xml = docx_zip.read('word/document.xml')
        doc_root = ET.fromstring(doc_xml)
        
        # 查找所有图片引用
        drawing_elements = doc_root.findall('.//w:drawing', namespaces)
        print(f"找到 {len(drawing_elements)} 个图形元素")
        
        # 解析关系文件
        rels_xml = docx_zip.read('word/_rels/document.xml.rels')
        rels_root = ET.fromstring(rels_xml)
        
        # 查找图片关系
        image_rels = rels_root.findall(".//*[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/image']")
        print(f"找到 {len(image_rels)} 个图片关系")
        
        # 显示图片信息
        for rel in image_rels:
            rel_id = rel.get('Id')
            target = rel.get('Target')
            print(f"关系ID: {rel_id}, 目标文件: {target}")

# 使用示例
analyze_document_images("example.docx")

图片顺序的确定

在Word文档中,图片的顺序可以通过以下几种方式确定:

  1. 文档流顺序:图片在document.xml中出现的顺序
  2. 图片ID顺序:图片的关系ID或文件名中的数字顺序
  3. 文档位置顺序:图片在文档中的实际位置(段落和字符偏移量)

对于大多数情况,文档流顺序是最可靠的,因为它反映了图片在文档中的自然排列。但在复杂文档中,我们可能需要结合多种方法来确定准确的顺序。

代码语言:python
复制
def get_images_in_order(docx_path):
    """获取文档中图片的顺序"""
    namespaces = {
        'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
        'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
        'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'
    }
    
    with ZipFile(docx_path) as docx_zip:
        # 解析document.xml
        doc_xml = docx_zip.read('word/document.xml')
        doc_root = ET.fromstring(doc_xml)
        
        # 解析关系文件
        rels_xml = docx_zip.read('word/_rels/document.xml.rels')
        rels_root = ET.fromstring(rels_xml)
        
        # 创建关系ID到目标文件的映射
        rel_map = {rel.get('Id'): rel.get('Target') 
                  for rel in rels_root.findall("*")}
        
        # 按文档流顺序查找图片引用
        image_refs = []
        for drawing in doc_root.findall('.//w:drawing', namespaces):
            # 查找blip元素(包含图片引用)
            blip = drawing.find('.//a:blip', 
                              {'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'})
            if blip is not None:
                rel_id = blip.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed')
                if rel_id in rel_map:
                    target = rel_map[rel_id]
                    image_refs.append({
                        'rel_id': rel_id,
                        'target': target,
                        'filename': target.split('/')[-1]
                    })
        
        return image_refs

# 使用示例
images_in_order = get_images_in_order("example.docx")
for i, img in enumerate(images_in_order):
    print(f"图片 {i+1}: {img['filename']} (关系ID: {img['rel_id']})")

通过这种方式,我们可以确定图片在文档中的准确顺序,为后续的提取和处理奠定基础。

图片提取核心技术

在了解了Word文档的结构后,我们可以开始实现图片提取的核心功能。

基本提取方法

最直接的图片提取方法是从Word文档的ZIP结构中提取media文件夹中的所有图片:

代码语言:python
复制
import os
import zipfile
from pathlib import Path

def extract_all_images(docx_path, output_dir):
    """
    从Word文档中提取所有图片
    
    Args:
        docx_path: Word文档路径
        output_dir: 图片输出目录
    
    Returns:
        提取的图片文件路径列表
    """
    # 确保输出目录存在
    os.makedirs(output_dir, exist_ok=True)
    
    extracted_images = []
    
    with zipfile.ZipFile(docx_path) as docx_zip:
        # 查找所有媒体文件
        media_files = [f for f in docx_zip.namelist() 
                      if f.startswith('word/media/')]
        
        # 提取每个媒体文件
        for media_file in media_files:
            # 获取文件名
            filename = os.path.basename(media_file)
            # 构建输出路径
            output_path = os.path.join(output_dir, filename)
            
            # 提取文件
            with docx_zip.open(media_file) as source, open(output_path, 'wb') as target:
                target.write(source.read())
            
            extracted_images.append(output_path)
            print(f"已提取: {filename}")
    
    return extracted_images

# 使用示例
images = extract_all_images("example.docx", "extracted_images")
print(f"共提取了 {len(images)} 个图片")

这种方法简单直接,但它有一个主要缺点:无法保证提取的图片与文档中的顺序一致。

按文档顺序提取图片

为了按照文档中的顺序提取图片,我们需要结合前面分析的文档结构:

代码语言:python
复制
import os
import zipfile
import xml.etree.ElementTree as ET
from pathlib import Path

def extract_images_in_order(docx_path, output_dir):
    """
    按文档顺序提取Word文档中的图片
    
    Args:
        docx_path: Word文档路径
        output_dir: 图片输出目录
    
    Returns:
        按顺序提取的图片文件路径列表
    """
    # 确保输出目录存在
    os.makedirs(output_dir, exist_ok=True)
    
    extracted_images = []
    
    # 定义命名空间
    namespaces = {
        'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
        'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
        'a': 'http://schemas.openxmlformats.org/drawingml/2006/main',
        'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'
    }
    
    with zipfile.ZipFile(docx_path) as docx_zip:
        # 解析关系文件
        rels_xml = docx_zip.read('word/_rels/document.xml.rels')
        rels_root = ET.fromstring(rels_xml)
        
        # 创建关系ID到目标文件的映射
        rel_map = {rel.get('Id'): rel.get('Target') 
                  for rel in rels_root.findall("*")}
        
        # 解析document.xml
        doc_xml = docx_zip.read('word/document.xml')
        doc_root = ET.fromstring(doc_xml)
        
        # 查找所有图片引用
        image_count = 0
        for drawing in doc_root.findall('.//w:drawing', namespaces):
            # 查找blip元素(包含图片引用)
            blip = drawing.find('.//a:blip', namespaces)
            if blip is not None:
                rel_id = blip.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed')
                if rel_id in rel_map:
                    target = rel_map[rel_id]
                    image_path = f"word/{target}"
                    
                    # 检查文件是否存在于ZIP中
                    if image_path in docx_zip.namelist():
                        # 生成序号化的文件名
                        image_count += 1
                        original_filename = os.path.basename(target)
                        file_ext = os.path.splitext(original_filename)[1]
                        new_filename = f"image_{image_count:03d}{file_ext}"
                        output_path = os.path.join(output_dir, new_filename)
                        
                        # 提取图片
                        with docx_zip.open(image_path) as source, open(output_path, 'wb') as target:
                            target.write(source.read())
                        
                        # 记录提取信息
                        extracted_images.append({
                            'original_path': image_path,
                            'original_filename': original_filename,
                            'new_path': output_path,
                            'new_filename': new_filename,
                            'rel_id': rel_id,
                            'order': image_count
                        })
                        
                        print(f"已提取图片 {image_count}: {new_filename} (原文件: {original_filename})")
    
    return extracted_images

# 使用示例
images = extract_images_in_order("example.docx", "extracted_images")
print(f"按顺序提取了 {len(images)} 个图片")

这个实现确保了图片按照它们在文档中出现的顺序被提取,并使用序号化的文件名保存,便于后续处理。

处理特殊情况

在实际应用中,我们可能会遇到一些特殊情况,如:

  1. 链接式图片:图片不在文档包中,而是通过外部链接引用
  2. 重复图片:同一图片在文档中多次使用
  3. 嵌入对象中的图片:如SmartArt、图表等对象中的图片

我们需要扩展我们的代码来处理这些情况:

代码语言:python
复制
def extract_images_advanced(docx_path, output_dir):
    """增强版图片提取,处理特殊情况"""
    # 基本设置与前面相同
    os.makedirs(output_dir, exist_ok=True)
    extracted_images = []
    processed_targets = set()  # 跟踪已处理的图片,避免重复
    
    namespaces = {
        'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
        'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
        'a': 'http://schemas.openxmlformats.org/drawingml/2006/main',
        'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing',
        'pic': 'http://schemas.openxmlformats.org/drawingml/2006/picture',
        'v': 'urn:schemas-microsoft-com:vml'
    }
    
    with zipfile.ZipFile(docx_path) as docx_zip:
        # 解析关系文件
        rels_xml = docx_zip.read('word/_rels/document.xml.rels')
        rels_root = ET.fromstring(rels_xml)
        
        # 创建关系映射
        rel_map = {}
        for rel in rels_root.findall("*"):
            rel_id = rel.get('Id')
            target = rel.get('Target')
            rel_type = rel.get('Type')
            rel_map[rel_id] = {
                'target': target,
                'type': rel_type,
                'is_external': target.startswith('http') or target.startswith('file:')
            }
        
        # 解析document.xml
        doc_xml = docx_zip.read('word/document.xml')
        doc_root = ET.fromstring(doc_xml)
        
        # 图片计数器
        image_count = 0
        
        # 处理常规图片 (w:drawing)
        for drawing in doc_root.findall('.//w:drawing', namespaces):
            blip = drawing.find('.//a:blip', namespaces)
            if blip is not None:
                # 处理嵌入图片
                embed_rel_id = blip.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed')
                if embed_rel_id and embed_rel_id in rel_map:
                    rel_info = rel_map[embed_rel_id]
                    target = rel_info['target']
                    
                    # 跳过已处理的图片
                    if target in processed_targets:
                        continue
                    
                    processed_targets.add(target)
                    
                    # 处理内部图片
                    if not rel_info['is_external']:
                        image_path = f"word/{target}"
                        if image_path in docx_zip.namelist():
                            image_count += 1
                            file_ext = os.path.splitext(target)[1]
                            new_filename = f"image_{image_count:03d}{file_ext}"
                            output_path = os.path.join(output_dir, new_filename)
                            
                            with docx_zip.open(image_path) as source, open(output_path, 'wb') as target_file:
                                target_file.write(source.read())
                            
                            extracted_images.append({
                                'original_path': image_path,
                                'new_path': output_path,
                                'new_filename': new_filename,
                                'rel_id': embed_rel_id,
                                'order': image_count,
                                'type': 'embedded'
                            })
                    
                    # 处理外部链接图片
                    else:
                        image_count += 1
                        link_info = f"external_link_{image_count:03d}.txt"
                        link_path = os.path.join(output_dir, link_info)
                        
                        with open(link_path, 'w') as f:
                            f.write(f"External image link: {target}\n")
                        
                        extracted_images.append({
                            'original_path': target,
                            'new_path': link_path,
                            'new_filename': link_info,
                            'rel_id': embed_rel_id,
                            'order': image_count,
                            'type': 'external_link'
                        })
                
                # 处理链接图片
                link_rel_id = blip.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}link')
                if link_rel_id and link_rel_id in rel_map:
                    # 类似处理链接图片...
                    pass
        
        # 处理VML图片 (v:imagedata) - 通常用于兼容性模式
        for img_data in doc_root.findall('.//v:imagedata', namespaces):
            rel_id = img_data.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id')
            if rel_id and rel_id in rel_map:
                # 处理VML图片...
                pass
        
        # 处理嵌入对象中的图片
        # 这需要更复杂的处理,可能需要解析其他关系文件
    
    return extracted_images

这个增强版的实现能够处理更多特殊情况,并避免重复提取相同的图片。

按序提取图片的实现

现在,我们将前面的技术整合成一个完整的、可用的图片提取类。这个类将提供更多功能和更好的错误处理。

代码语言:python
复制
import os
import zipfile
import xml.etree.ElementTree as ET
from pathlib import Path
import shutil
from datetime import datetime
import json
from PIL import Image
import io

class WordImageExtractor:
    """Word文档图片提取器"""
    
    def __init__(self, docx_path):
        """
        初始化提取器
        
        Args:
            docx_path: Word文档路径
        """
        self.docx_path = docx_path
        self.namespaces = {
            'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
            'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
            'a': 'http://schemas.openxmlformats.org/drawingml/2006/main',
            'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing',
            'pic': 'http://schemas.openxmlformats.org/drawingml/2006/picture',
            'v': 'urn:schemas-microsoft-com:vml',
            'mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
            'wps': 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape'
        }
        
        # 验证文件存在
        if not os.path.exists(docx_path):
            raise FileNotFoundError(f"找不到Word文档: {docx_path}")
        
        # 验证文件格式
        if not docx_path.lower().endswith('.docx'):
            raise ValueError(f"不支持的文件格式: {docx_path}. 仅支持.docx格式")
        
        # 初始化关系映射
        self.rel_map = {}
        self.image_info = []
        
        # 解析文档结构
        self._parse_document_structure()
    
    def _parse_document_structure(self):
        """解析文档结构,建立关系映射"""
        try:
            with zipfile.ZipFile(self.docx_path) as docx_zip:
                # 检查是否是有效的Word文档
                if 'word/document.xml' not in docx_zip.namelist():
                    raise ValueError(f"无效的Word文档: {self.docx_path}")
                
                # 解析关系文件
                if 'word/_rels/document.xml.rels' in docx_zip.namelist():
                    rels_xml = docx_zip.read('word/_rels/document.xml.rels')
                    rels_root = ET.fromstring(rels_xml)
                    
                    # 建立关系映射
                    for rel in rels_root.findall("*"):
                        rel_id = rel.get('Id')
                        target = rel.get('Target')
                        rel_type = rel.get('Type')
                        self.rel_map[rel_id] = {
                            'target': target,
                            'type': rel_type,
                            'is_external': target.startswith('http') or target.startswith('file:')
                        }
                
                # 解析文档内容,查找图片引用
                doc_xml = docx_zip.read('word/document.xml')
                doc_root = ET.fromstring(doc_xml)
                
                # 查找所有图片引用并记录顺序
                self._find_image_references(doc_root)
                
        except zipfile.BadZipFile:
            raise ValueError(f"文件不是有效的ZIP格式: {self.docx_path}")
        except ET.ParseError as e:
            raise ValueError(f"XML解析错误: {e}")
    
    def _find_image_references(self, doc_root):
        """查找文档中的所有图片引用"""
        image_order = 0
        
        # 处理常规图片 (w:drawing)
        for drawing in doc_root.findall('.//w:drawing', self.namespaces):
            blip = drawing.find('.//a:blip', self.namespaces)
            if blip is not None:
                embed_rel_id = blip.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed')
                if embed_rel_id and embed_rel_id in self.rel_map:
                    image_order += 1
                    rel_info = self.rel_map[embed_rel_id]
                    
                    # 获取图片尺寸信息
                    extent = drawing.find('.//wp:extent', self.namespaces)
                    width = height = None
                    if extent is not None:
                        width = extent.get('cx')  # EMU单位
                        height = extent.get('cy')  # EMU单位
                    
                    # 获取替代文本
                    alt_text = ""
                    doc_pr = drawing.find('.//wp:docPr', self.namespaces)
                    if doc_pr is not None:
                        alt_text = doc_pr.get('descr', '')
                    
                    self.image_info.append({
                        'order': image_order,
                        'rel_id': embed_rel_id,
                        'target': rel_info['target'],
                        'type': 'embedded' if not rel_info['is_external'] else 'external',
                        'width_emu': width,
                        'height_emu': height,
                        'alt_text': alt_text,
                        'element_type': 'drawing'
                    })
        
        # 处理VML图片 (v:imagedata) - 兼容性模式
        for img_data in doc_root.findall('.//v:imagedata', self.namespaces):
            rel_id = img_data.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id')
            if rel_id and rel_id in self.rel_map:
                image_order += 1
                rel_info = self.rel_map[rel_id]
                
                self.image_info.append({
                    'order': image_order,
                    'rel_id': rel_id,
                    'target': rel_info['target'],
                    'type': 'embedded' if not rel_info['is_external'] else 'external',
                    'width_emu': None,
                    'height_emu': None,
                    'alt_text': img_data.get('title', ''),
                    'element_type': 'vml'
                })
    
    def get_image_count(self):
        """获取文档中的图片数量"""
        return len(self.image_info)
    
    def get_image_info(self):
        """获取所有图片的信息"""
        return self.image_info.copy()
    
    def extract_images(self, output_dir, preserve_names=False, include_metadata=True):
        """
        提取所有图片
        
        Args:
            output_dir: 输出目录
            preserve_names: 是否保留原始文件名
            include_metadata: 是否包含元数据文件
        
        Returns:
            提取结果列表
        """
        # 确保输出目录存在
        os.makedirs(output_dir, exist_ok=True)
        
        extracted_images = []
        processed_targets = set()
        
        with zipfile.ZipFile(self.docx_path) as docx_zip:
            for img_info in self.image_info:
                target = img_info['target']
                
                # 跳过重复图片
                if target in processed_targets:
                    continue
                processed_targets.add(target)
                
                # 处理嵌入图片
                if img_info['type'] == 'embedded':
                    image_path = f"word/{target}"
                    if image_path in docx_zip.namelist():
                        # 确定输出文件名
                        if preserve_names:
                            filename = os.path.basename(target)
                        else:
                            file_ext = os.path.splitext(target)[1]
                            filename = f"image_{img_info['order']:03d}{file_ext}"
                        
                        output_path = os.path.join(output_dir, filename)
                        
                        # 提取图片
                        with docx_zip.open(image_path) as source:
                            image_data = source.read()
                            
                        with open(output_path, 'wb') as target_file:
                            target_file.write(image_data)
                        
                        # 获取图片实际尺寸
                        actual_width = actual_height = None
                        try:
                            with Image.open(io.BytesIO(image_data)) as pil_img:
                                actual_width, actual_height = pil_img.size
                        except Exception:
                            pass
                        
                        extracted_images.append({
                            'order': img_info['order'],
                            'original_path': image_path,
                            'output_path': output_path,
                            'filename': filename,
                            'rel_id': img_info['rel_id'],
                            'type': 'embedded',
                            'width_emu': img_info['width_emu'],
                            'height_emu': img_info['height_emu'],
                            'actual_width': actual_width,
                            'actual_height': actual_height,
                            'alt_text': img_info['alt_text'],
                            'element_type': img_info['element_type'],
                            'file_size': len(image_data)
                        })
                        
                        print(f"已提取图片 {img_info['order']}: {filename}")
                
                # 处理外部链接图片
                elif img_info['type'] == 'external':
                    link_filename = f"external_link_{img_info['order']:03d}.txt"
                    link_path = os.path.join(output_dir, link_filename)
                    
                    with open(link_path, 'w', encoding='utf-8') as f:
                        f.write(f"外部图片链接: {target}\n")
                        f.write(f"替代文本: {img_info['alt_text']}\n")
                        f.write(f"关系ID: {img_info['rel_id']}\n")
                    
                    extracted_images.append({
                        'order': img_info['order'],
                        'original_path': target,
                        'output_path': link_path,
                        'filename': link_filename,
                        'rel_id': img_info['rel_id'],
                        'type': 'external',
                        'alt_text': img_info['alt_text']
                    })
                    
                    print(f"已记录外部链接 {img_info['order']}: {target}")
        
        # 生成元数据文件
        if include_metadata:
            metadata_path = os.path.join(output_dir, 'extraction_metadata.json')
            metadata = {
                'source_document': os.path.basename(self.docx_path),
                'extraction_time': datetime.now().isoformat(),
                'total_images': len(extracted_images),
                'embedded_images': len([img for img in extracted_images if img['type'] == 'embedded']),
                'external_links': len([img for img in extracted_images if img['type'] == 'external']),
                'images': extracted_images
            }
            
            with open(metadata_path, 'w', encoding='utf-8') as f:
                json.dump(metadata, f, ensure_ascii=False, indent=2)
            
            print(f"已生成元数据文件: {metadata_path}")
        
        return extracted_images
    
    def extract_single_image(self, image_order, output_path):
        """
        提取单个图片
        
        Args:
            image_order: 图片序号(从1开始)
            output_path: 输出文件路径
        
        Returns:
            提取结果信息
        """
        # 查找指定序号的图片
        target_image = None
        for img_info in self.image_info:
            if img_info['order'] == image_order:
                target_image = img_info
                break
        
        if not target_image:
            raise ValueError(f"找不到序号为 {image_order} 的图片")
        
        if target_image['type'] != 'embedded':
            raise ValueError(f"图片 {image_order} 是外部链接,无法提取")
        
        # 确保输出目录存在
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        
        with zipfile.ZipFile(self.docx_path) as docx_zip:
            image_path = f"word/{target_image['target']}"
            if image_path in docx_zip.namelist():
                with docx_zip.open(image_path) as source:
                    image_data = source.read()
                
                with open(output_path, 'wb') as target_file:
                    target_file.write(image_data)
                
                print(f"已提取图片 {image_order} 到: {output_path}")
                return {
                    'order': image_order,
                    'output_path': output_path,
                    'file_size': len(image_data),
                    'success': True
                }
            else:
                raise FileNotFoundError(f"在文档中找不到图片文件: {image_path}")

# 使用示例
def main():
    """主函数示例"""
    try:
        # 创建提取器实例
        extractor = WordImageExtractor("example.docx")
        
        # 显示图片信息
        print(f"文档中共有 {extractor.get_image_count()} 个图片")
        
        # 获取图片详细信息
        for img_info in extractor.get_image_info():
            print(f"图片 {img_info['order']}: {img_info['target']} ({img_info['type']})")
        
        # 提取所有图片
        results = extractor.extract_images("extracted_images", preserve_names=False)
        
        print(f"\n提取完成,共处理 {len(results)} 个图片")
        
        # 提取单个图片示例
        if results:
            extractor.extract_single_image(1, "single_image/first_image.jpg")
        
    except Exception as e:
        print(f"错误: {e}")

if __name__ == "__main__":
    main()

总结与展望

在提取图片的过程中,保存完整的元数据信息对于后续的处理和分析非常重要。我们需要记录图片的各种属性,包括尺寸、格式、在文档中的位置等信息。

我们设计一个完整的元数据结构来存储图片信息。除了从Word文档中提取图片,我们还经常需要将图片按照特定顺序插入到Word文档中。这在批量处理、模板生成等场景中非常有用。使用python-docx库,我们可以实现基本的图片插入功能。

在处理大量Word文档或大型文档时,性能优化也非常重要。我们需要考虑内存使用、处理速度和错误处理等方面。

以上,就是本篇内容,感谢大家阅读!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Word文档中的图片:基础知识
    • 图片在Word文档中的存储方式
    • 图片格式与属性
    • 图片与文档结构的关系
  • 技术准备与环境搭建
    • Python环境准备
    • 必要库的安装
    • 测试环境验证
    • 项目结构设计
  • Word文档结构解析
    • DOCX文件格式概述
    • 文档内容与图片的关联
    • 图片顺序的确定
  • 图片提取核心技术
    • 基本提取方法
    • 按文档顺序提取图片
    • 处理特殊情况
  • 按序提取图片的实现
  • 总结与展望
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档