使用Python编写脚本,为Excel表格添加水印

简介

这是本人实习中的一个小任务,经过无数努力,终于搞出来了。网上很多资料和博客都是lese,完全没有理清楚水印在excel中的定义是什么,插个图片就是水印吗?当然不是!如果帮助到佬们请点个赞吧。

Ecxel中水印的定义

与PDF,Word文件不同,由于Excel没有直接提供水印添加功能,所以目前而言在Excel中添加水印实际上就是为excel文件添加背景。

所以我们目前要做的其实就是如何将图片设置为excel背景。

解决方案

方案1

如果只是希望在windows环境下使用,那么可以直接使用:

python 复制代码
import win32com.client as win32
from PIL import Image, ImageDraw, ImageFont

# 水印创建
watermark_text = "123456789"
font_path = "arial.ttf"
font_size = 0.5 * 50
image_size = (1000, 800)
text_color = (0, 0, 0, 20)
watermark_image_path = r'D:\Projects\PY_Projects\wm\watermark.png'
watermark = Image.new("RGBA", image_size, (255, 255, 255, 0))
draw = ImageDraw.Draw(watermark)

# 字体选择
try:
    font = ImageFont.truetype(font_path, font_size)
except IOError:
    font = ImageFont.load_default()
bbox = draw.textbbox((0, 0), watermark_text, font=font)
text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1]
y_text = 0
while y_text < image_size[1]:
    x_text = 0
    while x_text < image_size[0]:
        draw.text((x_text, y_text), watermark_text, font=font, fill=text_color)
        x_text += int(text_width * 2)
    y_text += int(text_height * 2)

# 水印操作
watermark = watermark.rotate(45, expand=True, resample=Image.Resampling.BICUBIC, fillcolor=(255, 255, 255, 0))
watermark.save(watermark_image_path)
# 打开excel
excel = win32.Dispatch('Excel.Application')
excel.Visible = False
workbook = excel.Workbooks.Open(r'D:\Projects\PY_Projects\wm\ex1.xlsx')
sheet = workbook.Sheets('Sheet1')

# 设置背景
sheet.SetBackgroundPicture(watermark_image_path)

# 关闭
workbook.Save()
workbook.Close()
excel.Quit()

相当于在后台开了一个客户端,然后在后台进行了背景设置。

虽然可以使用,但是在实际开发环境中,多为Linux系统,这样一来这种方法就不行了。

方案2

这个解决方案需要我们明白office文件底层是什么。本质上他们都是 `.zip` 文件。所有的具体数据信息和样式信息都存储在压缩包种的xml文中。通过对加水印和不加水印的底层xml文件进行比对,我得出我们总共需要对解压后的zip文件进行如下修改:

  • Content_Types\].xml:添加图片类型: ```XML ```

  • 在worksheet目录下,添加_rels目录,其中存在一个xml文件:

    XML 复制代码
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image1.png"/></Relationships>

    用于保存图片引用

  • 然后在worksheet目录下的sheet.xml文件中添加图片引用

    XML 复制代码
    <picture r:id="rId1"/>

然后我们再对解压后zip文件进行压缩改名,即可得到添加水印的xlsx文件,

代码实现如下:

python 复制代码
import shutil
import zipfile
import os
import xml.etree.ElementTree as ET
from PIL import Image, ImageDraw, ImageFont
import argparse
from datetime import datetime
import pandas as pd
from openpyxl import load_workbook
from openpyxl.styles import Font

# csv 转xlsx
def csv_to_xlsx(csv_file_path, xlsx_file_path):
    # 读取 CSV 文件
    df = pd.read_csv(csv_file_path)
    df.to_excel(xlsx_file_path, index=False, sheet_name='Sheet1')
    # 移除样式
    workbook = load_workbook(xlsx_file_path)
    sheet = workbook['Sheet1']
    for row in sheet.iter_rows():
        for cell in row:
            cell.border = None  
    for cell in sheet[1]:  
        cell.font = Font(bold=False)  
    # 保存
    workbook.save(xlsx_file_path)

# 创建水印
def createwm(watermark_content):
    # 配置
    watermark_text = watermark_content
    font_path = "SimSun.ttc" 
    font_size = 80
    image_size = (400, 200)  
    text_color = (0, 0, 0, 128)  
    background_color = (255, 255, 255, 0)  
    # 创建空白图像
    watermark = Image.new("RGBA", image_size, background_color)
    draw = ImageDraw.Draw(watermark)
    try:
        font = ImageFont.truetype(font_path, font_size)
    except IOError:
        font = ImageFont.load_default()
    # 获取文本边界框
    bbox = draw.textbbox((0, 0), watermark_text, font=font)
    text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1]
    # 计算文本位置
    x_text = (image_size[0] - text_width) // 2
    y_text = (image_size[1] - text_height) // 2
    
    draw.text((x_text, y_text), watermark_text, font=font, fill=text_color)
    # 旋转并保存水印
    watermark = watermark.rotate(45, expand=True, resample=Image.Resampling.BICUBIC, fillcolor=background_color)
    watermark.save("watermark.png")

# 后缀修改
def cgSuffix(filePath, newSfx):
    base_name, ex_suffix = os.path.splitext(filePath)
    if not newSfx.startswith('.'):
        newSfx = '.' + newSfx
    newFilePath = base_name + newSfx    
    os.rename(filePath, newFilePath)

# 解压缩zip
def unzip_file(zip_path):
    zip_filename = os.path.basename(zip_path)
    base_name = os.path.splitext(zip_filename)[0]
    zip_dir = os.path.dirname(zip_path)
    
    # 创建同名的目录
    extract_to = os.path.join(zip_dir, base_name)
    if not os.path.exists(extract_to):
        os.makedirs(extract_to)

    # 提取所有文件到指定目录
    with zipfile.ZipFile(zip_path, 'r') as zip_ref:
        zip_ref.extractall(extract_to)

    # 删除原始ZIP文件
    try:
        os.remove(zip_path)
        print(f"已成功删除ZIP文件: {zip_path}")
    except Exception as e:
        print(f"删除ZIP文件时出错: {e}")

# 读取xml
def add_contenttype(xmlPath):
    # 解析xml
    tree = ET.parse(xmlPath)
    root = tree.getroot()
    # 设置命名空间
    ns = {'ct': 'http://schemas.openxmlformats.org/package/2006/content-types'}
    ET.register_namespace('', ns['ct'])

    new_default = ET.SubElement(root, f'{{{ns["ct"]}}}Default')
    new_default.set('Extension', 'png')
    new_default.set('ContentType', 'image/png')
    # 保存
    tree.write(xmlPath, encoding='utf-8', xml_declaration=True)

# 创建文件夹
def create_folder(folder_path):
    try:
        os.makedirs(folder_path, exist_ok=True)
        print(f"Folder '{folder_path}' created successfully.")
    except Exception as e:
        print(f"An error occurred while creating the folder: {e}")

# 移动图片文件
def move_image(image_name, media_folder):
    destination_path = os.path.join(media_folder, image_name)
    
    if os.path.exists(image_name):
        try:
            shutil.move(image_name, destination_path)
            print(f"Image '{image_name}' moved to '{destination_path}'.")
        except Exception as e:
            print(f"An error occurred while moving the image: {e}")
    else:
        print(f"Image '{image_name}' does not exist in the current directory.")

# 创建图片引用
def create_rels(relationships_folder, target_path, id):
    # 创建根元素和命名空间
    root = ET.Element('Relationships', xmlns="http://schemas.openxmlformats.org/package/2006/relationships")
    relationship = ET.SubElement(root, 'Relationship')
    relationship.set('Id', id)
    relationship.set('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image')
    relationship.set('Target', target_path)

    # 创建ElementTree对象并写入文件
    tree = ET.ElementTree(root)
    rels_file_path = os.path.join(relationships_folder, 'sheet1.xml.rels')
    tree.write(rels_file_path, encoding='utf-8', xml_declaration=True)
    print(f"File 'sheet1.xml.rels' created at '{rels_file_path}' with Target='{target_path}'.")

def modify_sheet_xml(sheet_xml_path, rid):
    # 解析 sheet.xml 文件
    tree = ET.parse(sheet_xml_path)
    root = tree.getroot()

    # 定义命名空间
    namespaces = {
        '': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',  
        'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
        'mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
        'x14ac': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac'
    }
    for prefix, uri in namespaces.items():
        ET.register_namespace(prefix, uri)

    # 插入 <picture> 元素
    new_picture = ET.Element('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}picture')
    new_picture.set('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id', rid)
    root.append(new_picture)  

    tree.write(sheet_xml_path, encoding='utf-8', xml_declaration=True)

# 压缩目录为zip
def zip_contents_of_directory(folder_path, output_zip_path):
    with zipfile.ZipFile(output_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for root, dirs, files in os.walk(folder_path):
            for file in files:
                file_path = os.path.join(root, file)
                arcname = os.path.relpath(file_path, start=folder_path)
                zipf.write(file_path, arcname)


def main():

    # cmd解析
    parser = argparse.ArgumentParser(description="Process some files.")
    # 添加参数
    parser.add_argument('-i', '--input', type=str, required=True, help='The input CSV file.')
    parser.add_argument('-u', '--user', type=str, required=True, help='The role of user.')
    parser.add_argument('-n', '--name', type=str, required=True, help='The real name associated with the user.')
    parser.add_argument('-o', '--output', type=str, required=True, help='The output XLSX file.')
    # 解析命令行参数
    args = parser.parse_args()
    input = args.input
    role = args.user
    name = args.name
    output = args.output


    strlist = input.split('.')
    path = strlist[0]
    csv_to_xlsx(input, path+'.xlsx')

    if os.path.exists(path+'.xlsx'):
        cgSuffix(path + '.xlsx', "zip")
    unzip_file(path + '.zip')
    # 分为6步:
    #   1. [Content_Types].xml:添加图片类型
    content_types_path = os.path.join(path, '[Content_Types].xml')
    add_contenttype(content_types_path)
    #   2. 添加media目录
    mediaFolder = path + '/xl/media'
    cur_time = datetime.now()
    fmted_time = cur_time.strftime("%Y-%m-%d %H:%M:%S")
    create_folder(mediaFolder)
    wm_content = role + '\n' + name + '\n' + fmted_time
    createwm(wm_content)
    image_name = 'watermark.png'
    move_image(image_name, mediaFolder)
    #   3. 在worksheet目录下,添加_ref目录,在其中添加一个xml文件的引用
    refFolder = path + '/xl/worksheets/_rels'
    rid = 'rId1'
    create_folder(refFolder)
    create_rels(refFolder, "../media/"+image_name, rid)
    #   4. 在worksheet目录下的sheet.xml文件中添加图片引用
    sheetPath = path + '/xl/worksheets/sheet1.xml'
    modify_sheet_xml(sheetPath, rid)
    #   5. 压缩成zip,并改名为xlsx
    folder_to_zip = path
    output_zip_file = path +'.zip'
    zip_contents_of_directory(folder_to_zip, output_zip_file)
    #   6. 改写后缀为xlsx
    if os.path.exists(output_zip_file):
            os.rename(output_zip_file, output)
    shutil.rmtree(path)

if __name__ == "__main__":
    main()

我是将csv转为xlsx然后再添加水印,如果是希望直接使用xlsx使用者自行修改即可(IG野区栓条🐕都能赢)。

相关推荐
星期天要睡觉5 分钟前
深度学习——基于 ResNet18 的图像分类训练
pytorch·python·机器学习
林炳然6 分钟前
Python-Basic Day-1 基本元素(数字、字符串)
python
weixin_307779139 分钟前
在Linux服务器上使用Jenkins和Poetry实现Python项目自动化
linux·开发语言·python·自动化·jenkins
今天没有盐10 分钟前
内置基础类型之布尔值类型(bool)与时间与日期类型
python·编程语言
Empty_77713 分钟前
Python编程之常用模块
开发语言·网络·python
Q_Q51100828538 分钟前
python+uniapp基于微信小程序的学院设备报修系统
spring boot·python·微信小程序·django·flask·uni-app
蓝色空白的博客1 小时前
自动化测试脚本-->集成测试部署思路整理(1)
python·集成测试
Blossom.1181 小时前
把AI“绣”进丝绸:生成式刺绣神经网络让古装自带摄像头
人工智能·pytorch·python·深度学习·神经网络·机器学习·fpga开发
星星也在雾里2 小时前
【管理多版本Python环境】Anaconda安装及使用
python·anaconda
用户3721574261352 小时前
使用 Python 将 CSV 文件转换为 PDF 的实践指南
python