You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用C#提取Excel单元格内的图片并获取其所属单元格/位置信息

如何使用C#提取Excel单元格内的图片并获取其所属单元格/位置信息

我完全理解你的困扰:用ClosedXML能轻松抓取浮在单元格上方的图片,但遇到嵌入到单元格内部的图片就失效了;而直接用OpenXml遍历包的方式虽然能拿到所有图片,却丢失了关键的位置信息。别担心,下面我给你一套基于OpenXml的完整解决方案,既能提取所有图片,又能精准获取每个图片所属的单元格位置。

核心原理说明

Excel里无论是嵌入单元格还是浮在单元格上的图片,都会存储在工作表的DrawingsPart部件中。每个图片通过OneCellAnchor(对应单个单元格嵌入的图片)或TwoCellAnchor(对应跨单元格/固定位置的图片)元素与单元格位置绑定。我们只需要把图片部件和这些锚点信息关联起来,就能同时拿到图片内容和所属位置。

完整可运行代码

下面是封装好的工具类,直接就能用:

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Drawing.Spreadsheet;
using System;
using System.IO;
using System.Linq;

public class ExcelImageExtractor
{
    public static void ExtractImagesWithPosition(string excelFilePath, string outputFolder)
    {
        // 确保输出目录存在
        Directory.CreateDirectory(outputFolder);

        using (SpreadsheetDocument doc = SpreadsheetDocument.Open(excelFilePath, false))
        {
            WorkbookPart workbookPart = doc.WorkbookPart;
            if (workbookPart == null) return;

            // 遍历所有工作表
            foreach (WorksheetPart worksheetPart in workbookPart.WorksheetParts)
            {
                // 获取当前工作表的名称
                string sheetName = workbookPart.Workbook.Descendants<Sheet>()
                    .First(s => s.Id.Value == workbookPart.GetIdOfPart(worksheetPart)).Name;

                // 检查当前工作表是否有图片容器
                DrawingsPart drawingsPart = worksheetPart.DrawingsPart;
                if (drawingsPart == null) continue;

                // 处理单个单元格嵌入的图片(OneCellAnchor)
                foreach (var anchor in drawingsPart.WorksheetDrawing.Descendants<OneCellAnchor>())
                {
                    ProcessAnchor(anchor, sheetName, drawingsPart, outputFolder);
                }

                // 处理跨单元格或固定位置的图片(TwoCellAnchor)
                foreach (var anchor in drawingsPart.WorksheetDrawing.Descendants<TwoCellAnchor>())
                {
                    ProcessAnchor(anchor, sheetName, drawingsPart, outputFolder);
                }
            }
        }
    }

    private static void ProcessAnchor(OpenXmlElement anchor, string sheetName, DrawingsPart drawingsPart, string outputFolder)
    {
        // 获取图片关联的起始单元格
        Marker marker = anchor.Descendants<Marker>().FirstOrDefault();
        if (marker == null) return;
        string cellReference = GetCellAddress(marker);

        // 获取图片对应的二进制部件
        Blip blip = anchor.Descendants<Blip>().FirstOrDefault();
        if (blip == null) return;
        ImagePart imagePart = (ImagePart)drawingsPart.GetPartById(blip.Embed.Value);

        // 生成带位置信息的文件名
        string imageExtension = GetExtensionFromContentType(imagePart.ContentType);
        string fileName = $"{sheetName}_{cellReference}_{Guid.NewGuid()}{imageExtension}";
        string outputPath = Path.Combine(outputFolder, fileName);

        // 保存图片
        using (Stream imageStream = imagePart.GetStream())
        using (FileStream outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
        {
            imageStream.CopyTo(outputStream);
        }

        // 输出位置日志(也可以存入变量供后续使用)
        Console.WriteLine($"已提取图片:{fileName},所属位置:工作表【{sheetName}】的单元格【{cellReference}】");
    }

    // 从锚点标记解析单元格地址(如A1、B5)
    private static string GetCellAddress(Marker marker)
    {
        uint rowNum = marker.RowId.Value;
        int columnIndex = (int)marker.ColumnId.Value;
        string columnName = ConvertColumnIndexToName(columnIndex);
        return $"{columnName}{rowNum}";
    }

    // 将列索引转换为Excel列名(1→A,26→Z,27→AA)
    private static string ConvertColumnIndexToName(int index)
    {
        string columnName = string.Empty;
        while (index > 0)
        {
            int remainder = (index - 1) % 26;
            columnName = Convert.ToChar('A' + remainder) + columnName;
            index = (index - 1) / 26;
        }
        return columnName;
    }

    // 根据Content-Type获取图片扩展名
    private static string GetExtensionFromContentType(string contentType)
    {
        return contentType switch
        {
            "image/png" => ".png",
            "image/jpeg" => ".jpg",
            "image/gif" => ".gif",
            "image/bmp" => ".bmp",
            _ => ".png" // 未知类型默认用PNG
        };
    }
}

// 使用示例
// ExcelImageExtractor.ExtractImagesWithPosition(@"C:\test.xlsx", @"C:\ExtractedExcelImages");

代码关键点解析

  1. 锚点类型区分:

    • OneCellAnchor:对应完全嵌入到单个单元格的图片,是你当前问题的核心处理对象。
    • TwoCellAnchor:对应跨多个单元格或浮在单元格上方的图片,这段代码也兼容这种场景。
  2. 位置解析:
    从锚点的Marker元素中读取RowId和ColumnId,再将列索引转换为Excel的列名(如1→A),最终组合成标准的单元格地址。

  3. 图片关联:
    通过Blip元素的Embed属性,我们可以找到对应的ImagePart,从而获取图片的二进制流并保存到本地。

额外小贴士

  • 代码会自动处理多工作表场景,保存的图片文件名包含工作表名和单元格位置,方便你快速对应。
  • 如果需要对图片做进一步处理(如存入数据库),可以直接修改ProcessAnchor方法中的保存逻辑,无需额外转换。

备注:内容来源于stack exchange,提问作者MilleB

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 10:48:09