夜雨聆风学习资料网

ARTICLE · 1298231

「告别手动清理Word文档元数据,python批量一键搞定,外发前必备!」

「告别手动清理Word文档元数据,python批量一键搞定,外发前必备!」

“带你横跨办公自动化的数据江海”

@码海听潮@摸鱼

前言

关于摸鱼

(

ABOUT MOYU

)

闻道有先后,术业有专攻。

各位大佬朋友们好!

~我依旧是你们的老朋友摸鱼~

在职场摸爬滚打的这十多年里,我用Python悄悄干了不少“正事”——不知不觉攒下了一整套办公自动化的实用项目技巧。去年10月初创立了公众号 「码海听潮」 ,初衷很简单:把重复的劳动交给代码,把摸鱼的时间留给生活。

目前已经吭哧吭哧更新了近180篇原创文章,每一篇都是实操干货,不讲虚的,只聊怎么用代码真正解放双手,帮大家早点下班、准点摸鱼。

好了,多了不说,少了不唠,今天给大佬们分享一款本人原创的【Word 文档元数据批量清理工具】。日常做资料归档、文档外发的朋友,常遇到一个痛点:手头积攒的 Word 文档,摘要页里标题、作者、公司、备注等元数据五花八门,手动一个个打开属性面板清空,几十上百个文件弄下来又费眼又累手,稍不注意还漏清几项,外发后极易泄露内部信息、暴露作者身份,埋下安全隐患。

这款工具正是为了守住文档外发的"最后一道安全关"。它支持 .docx / .docm,COM 模式兼容 .doc,一键批量清空勾选的摘要页字段,可开启处理前自动备份,原始文件不丢、清理有据可查;提供 XML 模式(推荐,纯 Python、无需 Word,直接删除元数据节点、彻底移除不留痕)与 COM 模式(需 Windows + Word,每个文件独立 Word 实例、处理完彻底释放,防止作者被回填)两种方式,全程后台运行不卡界面,日志实时反馈,成功失败一目了然,让每一份外发文档都干净可控、安心交付!

《该办公工具成品展示:》

《该办公工具操作演示视频:》

关注
重播 分享 赞

《该办公工具核心功能亮点:》

01

 亮点一:双模式处理,场景自适应

        • XML 模式:纯 Python 实现,无需安装 Word,跨平台可用;直接删除word文档的元数据节点,彻底移除不留痕,比"置空"更干净。

        • COM 模式:需 Windows + Word,兼容 .doc 老格式,覆盖 XML 模式无法处理的文件类型。

        02

         亮点二:字段精细可控,默认全选

        • 标题、主题、作者、关键字、备注、类别、公司、经理、超链接基址、上次保存者、修订号、创建程序共 12 项摘要字段,勾选即清,支持全选/全不选,按需定制。

          03

          亮点三:批量 + 递归,一次搞定

          • 自动扫描所选文件夹,支持递归子目录,.docx / .docm(COM 模式含 .doc)批量处理,几十上百个文件一键完成,告别逐个打开属性面板的手动搬砖。

          《该办公工具代码展示:》

          下面,我就奉上python代码让各位大佬见识一下,什么叫"传统文化"遇上"赛博效率"

          # -*- coding: utf-8 -*-import osimport sysfrom PyQt6.QtWidgets import (    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,    QLabel, QLineEdit, QPushButton, QCheckBox, QGroupBox,    QTextEdit, QFileDialog, QStatusBar, QGridLayout, QComboBox,)from PyQt6.QtGui import QFontdef _ensure_check_icon():    """把勾选标记 SVG 写到临时文件并返回路径。"""    svg = (        "<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'>"        "<path d='M2.5 7.5 L5.5 10.5 L11.5 3.5' stroke='white' stroke-width='2.2' "        "fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>"    )    path = os.path.join(tempfile.gettempdir(), "word_meta_clean_check.svg")    try:        with open(path, "w", encoding="utf-8") as f:            f.write(svg)    except Exception:        return ""    return path.replace("\\", "/")CHECK_ICON = _ensure_check_icon()class MainWindow(QMainWindow):    def __init__(self):        super().__init__()        self.setWindowTitle("Word文档元数据清理工具(欢迎关注微信公众号:码海听潮 )")        self.resize(780, 760)        self._build_ui()        self._apply_style()        self._center()    # ---------- 界面构建    def _build_ui(self):        central = QWidget()        self.setCentralWidget(central)        root = QVBoxLayout(central)        root.setContentsMargins(0, 0, 0, 0)        root.setSpacing(0)        # 头部        header = QWidget()        header.setObjectName("header")        hbox = QVBoxLayout(header)        hbox.setContentsMargins(28, 22, 28, 22)        title = QLabel("Word文档元数据清理工具")        title.setObjectName("headerTitle")        sub = QLabel("批量清空摘要页字段 · 作者 / 公司 / 备注 等")        sub.setObjectName("headerSub")        hbox.addWidget(title)        hbox.addWidget(sub)        # 主体卡片        body = QWidget()        body.setObjectName("body")        blay = QVBoxLayout(body)        blay.setContentsMargins(26, 22, 26, 22)        blay.setSpacing(18)        # 源目录行        dir_row = QHBoxLayout()        dir_label = QLabel("源目录")        dir_label.setFixedWidth(56)        self.dir_edit = QLineEdit()        self.dir_edit.setPlaceholderText("选择要处理的文件夹(留空则使用当前目录)")        self.browse_btn = QPushButton("浏览…")        self.browse_btn.setFixedWidth(96)        self.browse_btn.clicked.connect(self._browse)        dir_row.addWidget(dir_label)        dir_row.addWidget(self.dir_edit, 1)        dir_row.addWidget(self.browse_btn)        # 处理方式行        mode_row = QHBoxLayout()        mode_label = QLabel("处理方式")        mode_label.setFixedWidth(56)        self.mode_combo = QComboBox()        self.mode_combo.addItem("XML 模式(推荐 · 无需 Word · 跨平台,彻底移除)", "xml")        self.mode_combo.addItem("COM 模式(需 Windows + Word,兼容 .doc)", "com")        self.mode_combo.setFixedWidth(420)        mode_row.addWidget(mode_label)        mode_row.addWidget(self.mode_combo, 1)        mode_row.addStretch(1)        # 选项行        opt_row = QHBoxLayout()        self.rec_chk = QCheckBox("递归子目录")        self.rec_chk.setChecked(True)        self.bak_chk = QCheckBox("处理前备份到 backup 子目录")        opt_row.addWidget(self.rec_chk)        opt_row.addSpacing(18)        opt_row.addWidget(self.bak_chk)        opt_row.addStretch(1)        # 字段组        group = QGroupBox("要清空的字段")        glay = QVBoxLayout(group)        glay.setSpacing(12)        btn_row = QHBoxLayout()        self.sel_all_btn = QPushButton("全选")        self.sel_none_btn = QPushButton("全不选")        self.sel_all_btn.setFixedWidth(80)        self.sel_none_btn.setFixedWidth(80)        self.sel_all_btn.clicked.connect(lambda: self._set_all(True))        self.sel_none_btn.clicked.connect(lambda: self._set_all(False))        btn_row.addWidget(self.sel_all_btn)        btn_row.addWidget(self.sel_none_btn)        btn_row.addStretch(1)        grid = QGridLayout()        grid.setSpacing(12)        self.field_boxes = {}        cols = 3        for idx, (en, zh) in enumerate(FIELDS):            cb = QCheckBox(f"{zh}  ({en})")            cb.setObjectName(en)            cb.setChecked(True)            self.field_boxes[en] = cb            r, c = divmod(idx, cols)            grid.addWidget(cb, r, c)        glay.addLayout(btn_row)        glay.addLayout(grid)        # 开始按钮        self.start_btn = QPushButton("开始清理")        self.start_btn.setObjectName("startBtn")        self.start_btn.setFixedHeight(46)        self.start_btn.clicked.connect(self._start)        # 日志        log_label = QLabel("处理日志")        self.log_view = QTextEdit()        self.log_view.setObjectName("log")        self.log_view.setReadOnly(True)        self.log_view.setMinimumHeight(180)        blay.addLayout(dir_row)        blay.addLayout(mode_row)        blay.addLayout(opt_row)        blay.addWidget(group)        blay.addWidget(self.start_btn)        blay.addWidget(log_label)        blay.addWidget(self.log_view, 1)        root.addWidget(header)        root.addWidget(body, 1)        self.status_bar = QStatusBar()        self.setStatusBar(self.status_bar)        self.status_bar.showMessage("就绪")    # ---------- 交互    def _browse(self):        d = QFileDialog.getExistingDirectory(            self, "选择文件夹", self.dir_edit.text() or os.getcwd()        )        if d:            self.dir_edit.setText(d)    def _set_all(self, state):        for cb in self.field_boxes.values():            cb.setChecked(state)    def _start(self):        folder = self.dir_edit.text().strip() or os.getcwd()        if not os.path.isdir(folder):            self._log(f"✘ 目录不存在: {folder}")            return        fields = [en for en, cb in self.field_boxes.items() if cb.isChecked()]        if not fields:            self._log("✘ 请至少选择一个要清空的字段。")            return        self._log(f"目标目录: {folder}")        self._log("(界面演示:清理逻辑未包含,请接入后台处理)")    def _log(self, msg):        self.log_view.append(msg)        self.log_view.verticalScrollBar().setValue(            self.log_view.verticalScrollBar().maximum()        )    # ---------- 样式    def _apply_style(self):        self.setStyleSheet(f"""            QMainWindow {{ background: #eef1f7; }}#header {{                background: qlineargradient(x1:0, y1:0, x2:1, y2:1,                    stop:0 #6366f1, stop:1 #8b5cf6);            }}#headerTitle {{ color: white; font-size: 22px; font-weight: 700; }}#headerSub {{ color: rgba(255,255,255,0.88); font-size: 13px; margin-top: 4px; }}            QWidget#body {{ background: #eef1f7; }}            QLabel {{ color: #1f2937; font-size: 13px; }}            QGroupBox {{                background: white;                border: 1px solid #e5e7eb;                border-radius: 12px;                padding: 18px 16px 16px 16px;                margin-top: 12px;                font-size: 14px;                font-weight: 600;                color: #111827;            }}            QGroupBox::title {{                subcontrol-origin: margin;                left: 16px;                padding: 0 6px;            }}            QLineEdit {{                background: white;                border: 1px solid #d1d5db;                border-radius: 8px;                padding: 9px 12px;                font-size: 13px;                color: #1f2937;            }}            QLineEdit:focus {{ border: 1px solid #6366f1; }}            QPushButton {{                background: white;                border: 1px solid #d1d5db;                border-radius: 8px;                padding: 8px 16px;                font-size: 13px;                color: #374151;            }}            QPushButton:hover {{ border-color: #6366f1; color: #6366f1; }}            QPushButton:pressed {{ background: #f3f4f6; }}            QPushButton:disabled {{ color: #9ca3af; border-color: #e5e7eb; }}            QPushButton#startBtn {{                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,                    stop:0 #6366f1, stop:1 #8b5cf6);                color: white;                border: none;                border-radius: 10px;                font-size: 15px;                font-weight: 600;            }}            QPushButton#startBtn:hover {{ background: qlineargradient(x1:0,y1:0,x2:1,y2:0,                stop:0 #5457e6, stop:1 #7c4df0); }}            QPushButton#startBtn:disabled {{ background: #c7c9d6; }}            QCheckBox {{                spacing: 8px;                font-size: 13px;                color: #374151;            }}            QCheckBox::indicator {{                width: 18px; height: 18px;                border: 1px solid #cbd5e1;                border-radius: 5px;                background: white;            }}            QCheckBox::indicator:checked {{                border: 1px solid #6366f1;                background: #6366f1;                image: url({CHECK_ICON});            }}            QCheckBox::indicator:unchecked:hover {{ border-color: #6366f1; }}            QTextEdit#log {{                background: #0f172a;                color: #e2e8f0;                border: 1px solid #1e293b;                border-radius: 10px;                padding: 12px;                font-family: "Consolas", "Menlo", "Courier New", monospace;                font-size: 12px;            }}            QStatusBar {{                background: white;                color: #6b7280;                font-size: 12px;                padding: 4px 10px;            }}            QStatusBar::item {{ border: none; }}        """)    def _center(self):        screen = QApplication.primaryScreen().availableGeometry()        g = self.frameGeometry()        g.moveCenter(screen.center())        self.move(g.topLeft())if __name__ == "__main__":    app = QApplication(sys.argv)    app.setFont(QFont("Microsoft YaHei UI", 10))    win = MainWindow()    win.show()    sys.exit(app.exec())

          这款【Word 文档元数据批量清理工具】,彻底告别逐份打开属性面板、手动清空标题作者公司备注的繁琐。只需选定文件夹,自动扫描(可递归子目录)、勾选字段(默认全选),XML 模式直接删除元数据节点、彻底不留痕,COM 模式兼容 .doc、每文件独立 Word 实例处理完彻底释放,并自动备份原始文件。全程后台运行不卡界面,日志实时反馈,成功失败一目了然,效率指数级提升,轻松解放双手!

          大佬们也可以举一反三,参照上面的代码思路根据自己工作中的实际情况来具体问题具体分析,实现自己定制化的需求。

          《该办公工具的应用场景:》

          1.文档外发前脱敏

          对外发送合同、方案、报告前,清空作者、公司、经理、备注等字段,避免暴露内部人员信息、组织架构或编辑痕迹,防止信息泄露。

          2.资料归档与整理

          归档历史文档时统一清空摘要页元数据,让归档库干净规范,避免不同来源文档的作者、修订号、上次保存者等杂乱信息混入。

          3.投标 / 交付物提交

          投标文件、项目交付文档通常要求匿名或统一署名,批量清理作者与创建程序信息,避免因元数据暴露而影响评审或验收。

          4.法务 / 合规审查

          对外披露材料、证据文件需去除作者身份与编辑历史痕迹,XML 模式直接删除节点,比单纯置空更彻底,满足不留痕的合规要求。

          5.企业知识库 / 共享盘入库

          批量收回的文档入库前统一清洗元数据,避免公司名、经理、超链接基址等内部信息随文档扩散。

          6.期刊投稿 / 盲审材料

          盲审或匿名评审场景需隐藏作者与单位信息,一键批量清空相关字段,省去逐份手动处理。

          7.教学 / 模板分发

          分发文档模板、教学资料时清理作者与上次保存者,避免学生或使用者看到模板制作者信息。

          8.批量文档迁移 / 格式转换前后

          在系统迁移、格式转换(如 .doc 转 .docx)前后统一清洗元数据,保证迁移后文档属性一致、干净。

          9.个人隐私保护

          个人简历、证明材料等外发前清空作者与备注,避免设备用户名、编辑软件信息随文档泄露。

          10.审计 / 安全自查

          批量检查并清理目录下所有 Word 文档的元数据,作为数据安全自查的一环,降低信息泄露风险。

          结语

          当Python遇见办公,牛马打工人终于笑出了猪叫声~~~

          【职场人必看】每天早上一睁眼,想到又要面对:

          • 1.📊 堆积如山的Excel表格

          • 2.📑 机械重复的复制粘贴

          • 3.✍️ 永远改不完的各类文档

          • 4.诸如此类的更多........

          是不是连Ctrl+Alt+Delete的心都有了?

          别慌!别急,摸鱼这位“职场外挂”已经带着Python代码来拯救你了!

          臣无大佬,无以至今日;大佬无臣,无以摸大鱼。感谢各位大佬阅读,还望各位大佬抬抬贵手一键三连,多多关注、点赞、转发、评论——大佬们的支持,才是摸鱼孜孜不倦更新原创干货的动力

          回首页,搜索你想要的文档

          在首页搜索框输入关键词,快速定位资料

          文库首页搜索指引 立即搜索

          相关学习资料