Linux环境下使用WPS比较word文档并生成修订

发布于:2025-06-20 ⋅ 阅读:(19) ⋅ 点赞:(0)

1. 背景与目标

1.1 背景

在Linux服务器环境中,需要实现Word文档的自动比较功能。由于服务器通常没有图形界面,而WPS是一个GUI应用程序,因此需要解决在无头(headless)环境中运行WPS的技术挑战。

1.2 目标

  • 在无图形界面的Linux服务器上运行WPS文档比较功能
  • 实现自动化批量文档比较
  • 避免X11转发依赖
  • 确保稳定可靠的文档处理流程

2. 环境要求

2.1 系统环境

  • Linux操作系统(Ubuntu/CentOS等)
  • Python 3.7+
  • WPS Office Linux版

2.2 依赖库

pip install pywpsrpc

2.3 系统依赖

# Ubuntu/Debian
sudo apt-get install -y xvfb xauth libgl1-mesa-dri libqt5core5a

# CentOS/RHEL
sudo yum install -y xorg-x11-server-Xvfb xauth mesa-libGL

3. 核心技术实现

3.1 无头环境配置

使用Xvfb创建虚拟显示环境:

# 启动虚拟显示服务器
xvfb_process = subprocess.Popen(
    ["Xvfb", ":99", "-screen", "0", "1024x768x24"],
    stderr=subprocess.DEVNULL
)
os.environ["DISPLAY"] = ":99"

# 设置无头环境变量
os.environ["QT_QPA_PLATFORM"] = "offscreen"
os.environ["LIBGL_ALWAYS_INDIRECT"] = "0"

3.2 WPS RPC控制

通过pywpsrpc库控制WPS:

# 初始化WPS RPC实例
hr, rpc = createWpsRpcInstance()
hr, app = rpc.getWpsApplication()

# 关键设置
app.DisplayAlerts = False  # 禁用警告对话框
app.ScreenUpdating = False  # 禁用屏幕更新
app.Visible = False  # 隐藏WPS窗口

3.3 文档比较流程

# 1. 打开文档
hr, doc1 = app.Documents.Open(file1, ReadOnly=True)

# 2. 比较文档
hr = doc1.Compare(file2)

# 3. 获取比较结果
compared_doc = app.ActiveDocument

# 4. 保存结果
save_hr = compared_doc.SaveAs2(
    FileName=output_file,
    FileFormat=wpsapi.wdFormatXMLDocument,
    AddToRecentFiles=False
)

# 5. 关闭文档
doc1.Close(SaveChanges=False)
compared_doc.Close(SaveChanges=False)

3.4 安全关闭机制

def close_documents(app):
    """安全关闭所有打开的文档"""
    while app.Documents.Count > 0:
        try:
            # 正确处理返回的元组 (hr, doc)
            hr, doc = app.Documents.Item(1)
            if hr == 0 and doc:
                doc.Close(SaveChanges=False)
        except Exception as e:
            print(f"关闭文档出错: {str(e)}")
            break

4. 常见问题与解决方案

4.1 共享库缺失错误

错误信息

ImportError: libQt5Core.so.5: cannot open shared object file

解决方案

# Ubuntu/Debian
sudo apt install libqt5core5a

# 添加到LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH

4.2 Xvfb警告信息

警告信息

Warning: Could not resolve keysym XF86CameraAccessEnable
...

解决方案
忽略非致命警告:

xvfb_process = subprocess.Popen(
    ["Xvfb", ":99", "-screen", "0", "1024x768x24"],
    stderr=subprocess.DEVNULL  # 忽略错误输出
)

4.3 文档对象处理错误

错误信息

'tuple' object has no attribute 'Close'

解决方案
正确处理pywpsrpc返回的元组:

# 错误方式
doc = app.Documents.Item(1)
doc.Close()  # 报错

# 正确方式
hr, doc = app.Documents.Item(1)  # 解包元组
if hr == 0 and doc:
    doc.Close()

4.4 文档保存失败

可能原因

  • 输出目录权限不足
  • 文件已存在且被锁定
  • 路径包含特殊字符

解决方案

# 确保输出目录存在
os.makedirs(output_dir, exist_ok=True)

# 删除已存在文件
if os.path.exists(output_file):
    try:
        os.remove(output_file)
    except Exception as e:
        print(f"无法删除旧文件: {str(e)}")

# 验证保存结果
if save_hr == 0:
    if not os.path.exists(output_file):
        print("警告: 保存成功但文件未找到")

5. 完整代码实现

import os
import traceback
import time
import subprocess
from pywpsrpc.rpcwpsapi import createWpsRpcInstance, wpsapi

# 设置无头环境变量
os.environ["DISPLAY"] = ":0"
os.environ["QT_QPA_PLATFORM"] = "offscreen"
os.environ["LIBGL_ALWAYS_INDIRECT"] = "0"

def compare_word_docs(dir1, dir2, output_dir):
    """比较两个目录中的Word文档"""
    # 初始化WPS RPC
    hr, rpc = createWpsRpcInstance()
    if hr != 0:
        print("无法创建WPS RPC实例")
        return
    
    hr, app = rpc.getWpsApplication()
    if hr != 0:
        print("无法获取WPS应用程序")
        return
    
    # WPS配置
    app.DisplayAlerts = False
    app.ScreenUpdating = False
    app.Visible = False
    
    # 确保输出目录存在
    os.makedirs(output_dir, exist_ok=True)
    
    # 获取公共文件列表
    files1 = {f.lower() for f in os.listdir(dir1) if f.lower().endswith(('.doc', '.docx'))}
    files2 = {f.lower() for f in os.listdir(dir2) if f.lower().endswith(('.doc', '.docx'))}
    common_files = files1 & files2
    
    print(f"找到 {len(common_files)} 个需要比较的公共文件")
    
    for filename in common_files:
        orig_filename = next((f for f in os.listdir(dir1) if f.lower() == filename), filename)
        file1 = os.path.join(dir1, orig_filename)
        file2 = os.path.join(dir2, orig_filename)
        output_filename = os.path.splitext(orig_filename)[0] + ".docx"
        output_file = os.path.join(output_dir, output_filename)
        
        print(f"\n处理文件: {orig_filename}")
        print(f"源文件1: {file1}")
        print(f"源文件2: {file2}")
        print(f"输出文件: {output_file}")
        
        doc1 = None
        compared_doc = None
        
        try:
            # 打开并比较文档
            hr, doc1 = app.Documents.Open(file1, ReadOnly=True)
            if hr != 0:
                print(f"无法打开文件: {file1} (错误码: {hr})")
                continue
            
            hr = doc1.Compare(file2)
            if hr != 0:
                print(f"比较失败 (错误码: {hr})")
                continue
            
            time.sleep(3)  # 等待比较完成
            
            # 获取比较结果文档
            compared_doc = app.ActiveDocument
            if compared_doc is None and app.Documents.Count > 1:
                hr, compared_doc = app.Documents.Item(2)
                if hr != 0 or not compared_doc:
                    print("无法获取比较文档")
                    continue
            
            # 准备保存路径
            os.makedirs(os.path.dirname(output_file), exist_ok=True)
            if os.path.exists(output_file):
                try:
                    os.remove(output_file)
                except Exception as e:
                    print(f"无法删除旧文件: {str(e)}")
            
            # 保存结果
            save_hr = compared_doc.SaveAs2(
                FileName=output_file,
                FileFormat=wpsapi.wdFormatXMLDocument,
                AddToRecentFiles=False
            )
            
            if save_hr == 0 and os.path.exists(output_file):
                print(f"保存成功: {output_file} ({os.path.getsize(output_file)} 字节)")
            else:
                print(f"保存失败 (错误码: {save_hr})")
            
            # 关闭文档
            if compared_doc:
                compared_doc.Close(SaveChanges=False)
            if doc1:
                doc1.Close(SaveChanges=False)
                
        except Exception as e:
            print(f"处理 {orig_filename} 时出错: {str(e)}")
            traceback.print_exc()
        finally:
            close_documents(app)
    
    app.Quit()
    print("\n处理完成")

def close_documents(app):
    """安全关闭所有文档"""
    try:
        while app.Documents.Count > 0:
            try:
                hr, doc = app.Documents.Item(1)
                if hr == 0 and doc:
                    doc.Close(SaveChanges=False)
                    print("已关闭文档")
                else:
                    break
            except Exception as e:
                print(f"关闭文档出错: {str(e)}")
                break
    except Exception as e:
        print(f"关闭文档时出错: {str(e)}")

def setup_xvfb():
    """设置虚拟显示环境"""
    try:
        print("检查虚拟显示环境...")
        result = subprocess.run(["which", "Xvfb"], capture_output=True, text=True)
        if result.returncode != 0:
            print("安装Xvfb...")
            subprocess.run(["sudo", "apt-get", "update"], check=True)
            subprocess.run(["sudo", "apt-get", "install", "-y", "xvfb", "xauth", "libgl1-mesa-dri"], check=True)
        
        print("启动虚拟显示...")
        xvfb_process = subprocess.Popen(
            ["Xvfb", ":99", "-screen", "0", "1024x768x24"],
            stderr=subprocess.DEVNULL
        )
        os.environ["DISPLAY"] = ":99"
        return xvfb_process
    except Exception as e:
        print(f"虚拟显示设置失败: {str(e)}")
        return None

if __name__ == "__main__":
    # 配置路径
    dir1 = "/path/to/first/directory"
    dir2 = "/path/to/second/directory"
    output_dir = "/path/to/output"
    
    # 验证路径
    for path in [dir1, dir2]:
        if not os.path.exists(path):
            print(f"错误: 目录不存在: {path}")
            exit(1)
    
    os.makedirs(output_dir, exist_ok=True)
    
    # 权限验证
    test_file = os.path.join(output_dir, "permission_test.tmp")
    try:
        with open(test_file, "w") as f:
            f.write("test")
        os.remove(test_file)
    except Exception as e:
        print(f"输出目录权限错误: {e}")
        exit(1)
    
    # 设置虚拟显示
    xvfb_process = setup_xvfb()
    
    # 执行比较
    compare_word_docs(dir1, dir2, output_dir)
    
    # 清理
    if xvfb_process:
        print("停止虚拟显示...")
        xvfb_process.terminate()

6. 总结

本文档详细介绍了在无头Linux环境中使用WPS进行文档比较的技术实现方案。核心解决方案包括:

  1. 虚拟显示技术:使用Xvfb创建虚拟显示环境
  2. 无头模式配置:通过环境变量强制WPS以无头模式运行
  3. RPC控制:使用pywpsrpc库程序化控制WPS
  4. 健壮的错误处理:针对常见错误提供解决方案

该方案成功解决了以下关键挑战:

  • 在无图形界面的服务器环境中运行GUI应用程序
  • 实现WPS文档比较的完全自动化
  • 处理WPS RPC接口的特殊返回结构
  • 确保长时间运行的稳定性

此技术方案适用于需要批量处理Office文档的服务器环境,如文档管理系统、自动化报告生成等场景。


网站公告

今日签到

点亮在社区的每一天
去签到