


“带你横跨办公自动化的数据江海”
@摸鱼
前言
关于摸鱼
(
ABOUT MOYU
)
闻道有先后,术业有专攻。
各位大佬朋友们好!
~我依旧是你们的老朋友摸鱼~
在职场摸爬滚打的这十多年里,我用Python悄悄干了不少“正事”——不知不觉攒下了一整套办公自动化的实用项目技巧。去年10月初创立了公众号 「码海听潮」 ,初衷很简单:把重复的劳动交给代码,把摸鱼的时间留给生活。
目前已经吭哧吭哧更新了140多篇原创文章,每一篇都是实操干货,不讲虚的,只聊怎么用代码真正解放双手,帮大家早点下班、准点摸鱼
好了,多了不说,少了不唠,今天给大佬们分享一款本人原创的【PDF批量水印添加工具】。这款工具支持批量或单个PDF文件添加水印,文本+Logo双重水印,位置、大小、透明度、旋转角度随意调,实时预览,处理效率杠杠的!特别适合文档版权保护、品牌宣传等场景,省时省力,谁用谁知道!

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


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

《该办公工具核心功能亮点:》
01
亮点1:批量处理,效率倍增⭐
支持递归遍历文件夹及其所有子文件夹,一次性处理成百上千个PDF文件
相比手动逐个打开PDF添加水印,效率提升数十倍甚至上百倍
02
亮点2:双重水印加持🎨
文本水印:自定义文字内容、字体、大小、颜色
图片Logo:支持PNG、JPG等常见图片格式
两者可同时使用,也可单独使用,灵活配置
03
亮点3:所见即所得的实时预览⚙️
右侧预览区实时显示水印效果
所有参数调整即时反馈,无需反复试错
精确模拟A4页面效果

《该办公工具代码展示:》
下面,我就用python代码让各位大佬见识一下,什么叫"传统文化"遇上"赛博效率"
import sysfrom PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QLabel, QPushButton, QLineEdit,QComboBox, QSpinBox, QSlider, QProgressBar, QFileDialog,QHBoxLayout, QVBoxLayout, QSplitter, QScrollArea, QMessageBox,QGroupBox, QFormLayout, QSizePolicy, QColorDialog)from PyQt6.QtGui import QPixmap, QPainter, QFontDatabase, QFont, QFontMetrics, QColorfrom PyQt6.QtCore import Qt, QThread, pyqtSignalclass PDFWatermarkerApp(QMainWindow):def __init__(self):super().__init__()# 设置窗口标题self.setWindowTitle("PDF批量添加文本和图片水印(欢迎关注微信公众号:码海听潮)")self.resize(1000, 700)self.folder_path = ''self.logo_path = ''self.font_cache = {}self.text_size_pct = 100 # 文字大小百分比self.logo_size_pct = 100 # Logo 大小百分比self.text_color = QColor(0, 0, 0) # 默认文字颜色:黑色self.worker = Noneself._init_ui()self._connect_signals()self.update_preview()self.setAcceptDrops(True)def _init_ui(self):# ---- 全局容器 & 样式 ----central = QWidget(self)central.setStyleSheet("background-color:#f7f9fb; font-family: 'Microsoft YaHei';")self.setCentralWidget(central)main_layout = QVBoxLayout(central)main_layout.setContentsMargins(16, 16, 16, 16)main_layout.setSpacing(16)# ---- 分割器 ----splitter = QSplitter(Qt.Orientation.Horizontal)splitter.setHandleWidth(4)splitter.setStyleSheet("QSplitter::handle { background-color:#d9d9d9; }")main_layout.addWidget(splitter)# ---- 样式定义 ----group_style = """QGroupBox {font: 600 16px 'Microsoft YaHei';margin-top: 26px;border: 1px solid#a0aec0;border-radius: 10px;background-color:#e6f7ff;padding: 10px;}QGroupBox::title {subcontrol-origin: margin;subcontrol-position: top left;padding: 0 10px;color:#1f1f1f;background-color:#e6f7ff;font-weight: bold;}"""btn_style = """QPushButton {background-color:#7cb8e6;color: white;border: none;border-radius: 6px;font: bold 15px 'Microsoft YaHei';padding: 6px 14px;}QPushButton:hover {background-color:#40a9ff;}QPushButton:pressed {background-color:#096dd9;}"""slider_style = """QSlider::groove:horizontal {height: 8px;background:#e0e0e0;border-radius: 4px;}QSlider::handle:horizontal {background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0,stop:0#40a9ff, stop:1#1890ff);width: 16px;margin: -4px 0;border-radius: 8px;}"""progress_style = """QProgressBar {border: 1px solid#ccc; border-radius: 6px;background:#f5f5f5; height: 20px;}QProgressBar::chunk {background-color:#1890ff; border-radius: 6px;}"""# ---- 左侧参数面板 ----panel = QWidget()panel.setStyleSheet("background-color:#e6f7ff;""border: 1px solid#d9d9d9;""border-radius: 8px;")panel_layout = QVBoxLayout(panel)panel_layout.setContentsMargins(14, 22, 14, 14)panel_layout.setSpacing(14)# 1. 选择 PDF 文件夹gb_folder = QGroupBox("📂 选择文件夹")gb_folder.setStyleSheet(group_style)form_folder = QHBoxLayout(gb_folder)form_folder.setContentsMargins(10, 6, 10, 6)self.btn_folder = QPushButton("🔍 浏览文件夹")self.btn_folder.setFixedHeight(32)self.btn_folder.setStyleSheet(btn_style)self.lbl_folder = QLabel("⚪未选择文件夹")self.lbl_folder.setStyleSheet("color:#333333;")form_folder.addWidget(self.btn_folder)form_folder.addSpacing(8)form_folder.addWidget(self.lbl_folder)panel_layout.addWidget(gb_folder)# 📝 文本与 Logo 设置分组gb_text_logo = QGroupBox("📝 文本与Logo ")gb_text_logo.setStyleSheet(group_style)layout_text_logo = QVBoxLayout(gb_text_logo)layout_text_logo.setContentsMargins(10, 6, 10, 6)# 🔤 文本输入hlayout_text = QHBoxLayout()self.edit_text = QLineEdit("码海听潮")self.edit_text.setFixedHeight(30)self.edit_text.setStyleSheet("padding: 4px; border: 1px solid#d9d9d9; border-radius: 4px;")hlayout_text.addWidget(QLabel("🔤 文本"))hlayout_text.addWidget(self.edit_text)layout_text_logo.addLayout(hlayout_text)# 🏷️ 字体和 Logohlayout_font_logo = QHBoxLayout()# 字体选择hlayout_font_logo.addWidget(QLabel("🖋️ 字体"))self.combo_font = QComboBox()self.combo_font.addItems(["微软雅黑", "宋体", "黑体", "楷体", "仿宋", "Arial", "Times New Roman", "Courier New"])self.combo_font.setFixedHeight(30)self.combo_font.setStyleSheet("border:1px solid#d9d9d9; border-radius:4px; padding:4px;")hlayout_font_logo.addWidget(self.combo_font)# Logo 选择按钮hlayout_font_logo.addStretch()self.btn_logo = QPushButton("🖼️ 选择Logo图片")self.btn_logo.setFixedHeight(32)self.btn_logo.setStyleSheet(btn_style)hlayout_font_logo.addWidget(self.btn_logo)layout_text_logo.addLayout(hlayout_font_logo)# 添加到主布局panel_layout.addWidget(gb_text_logo)# 💬 水印样式设置分组gb_style = QGroupBox("💬 水印样式")gb_style.setStyleSheet(group_style)style_layout = QVBoxLayout(gb_style)style_layout.setContentsMargins(10, 6, 10, 6)# 文字大小滑块hlayout_size = QHBoxLayout()label_size = QLabel("🖱️ 文字大小")self.slider_text_size = QSlider(Qt.Orientation.Horizontal)self.slider_text_size.setRange(10, 200)self.slider_text_size.setValue(self.text_size_pct)hlayout_size.addWidget(label_size)hlayout_size.addWidget(self.slider_text_size)style_layout.addLayout(hlayout_size)# 文字颜色选择器hlayout_color = QHBoxLayout()self.btn_text_color = QPushButton("🎨 文字颜色")self.lbl_color_preview = QLabel()self.lbl_color_preview.setFixedSize(24, 24)self.lbl_color_preview.setStyleSheet(f"background-color: {self.text_color.name()};")hlayout_color.addWidget(self.btn_text_color)hlayout_color.addWidget(self.lbl_color_preview)style_layout.addLayout(hlayout_color)# Logo 大小滑块hlayout_logo = QHBoxLayout()label_logo_size = QLabel("🖱️ Logo 大小")self.slider_logo_size = QSlider(Qt.Orientation.Horizontal)self.slider_logo_size.setRange(10, 200)self.slider_logo_size.setValue(self.logo_size_pct)hlayout_logo.addWidget(label_logo_size)hlayout_logo.addWidget(self.slider_logo_size)style_layout.addLayout(hlayout_logo)# 透明度滑块hlayout_opacity = QHBoxLayout()label_opacity = QLabel("🌫️ 透明度")self.slider_alpha = QSlider(Qt.Orientation.Horizontal)self.slider_alpha.setRange(0, 100)self.slider_alpha.setValue(20)self.slider_alpha.setStyleSheet(slider_style)hlayout_opacity.addWidget(label_opacity)hlayout_opacity.addWidget(self.slider_alpha)style_layout.addLayout(hlayout_opacity)# 添加到主面板布局panel_layout.addWidget(gb_style)# 📐 布局与排列设置 分组gb_layout = QGroupBox("📐 布局与排列")gb_layout.setStyleSheet(group_style)layout_all = QVBoxLayout(gb_layout)layout_all.setContentsMargins(10, 6, 10, 6)# 🔢 水印数量layout_cnt = QHBoxLayout()self.spin_h = QSpinBox();self.spin_h.setRange(1, 10);self.spin_h.setValue(1);self.spin_h.setFixedWidth(60)self.spin_v = QSpinBox();self.spin_v.setRange(1, 10);self.spin_v.setValue(1);self.spin_v.setFixedWidth(60)layout_cnt.addWidget(QLabel("↔️ 横行数量"));layout_cnt.addWidget(self.spin_h)layout_cnt.addSpacing(12)layout_cnt.addWidget(QLabel("↕️ 纵行数量"));layout_cnt.addWidget(self.spin_v)layout_all.addLayout(layout_cnt)# 🚩 水印位置layout_pos = QFormLayout()self.combo_text_pos = QComboBox();self.combo_text_pos.addItems(["中心", "左上", "右上", "左下", "右下"])self.combo_logo_pos = QComboBox();self.combo_logo_pos.addItems(["中心", "左上", "右上", "左下", "右下"])self.combo_text_pos.setFixedHeight(25);self.combo_logo_pos.setFixedHeight(25)layout_pos.addRow("📝 文本位置", self.combo_text_pos)layout_pos.addRow("📊 Logo 位置", self.combo_logo_pos)layout_all.addLayout(layout_pos)# 🔄 旋转角度layout_ang = QHBoxLayout()self.spin_angle = QSpinBox();self.spin_angle.setRange(-90, 90);self.spin_angle.setValue(20);self.spin_angle.setFixedWidth(100)layout_ang.addWidget(QLabel("🔺 角度 (°):"))layout_ang.addWidget(self.spin_angle)layout_all.addLayout(layout_ang)# 添加到主布局panel_layout.addWidget(gb_layout)# 操作按钮 & 进度条self.btn_start = QPushButton("▶️ 开始添加");self.btn_start.setFixedHeight(34);self.btn_start.setStyleSheet(btn_style)self.btn_clear = QPushButton("🔙 重置设置");self.btn_clear.setFixedHeight(34);self.btn_clear.setStyleSheet(btn_style)self.progress = QProgressBar();self.progress.setFixedHeight(14);self.progress.setStyleSheet(progress_style)btn_layout = QHBoxLayout()btn_layout.setSpacing(8)btn_layout.addWidget(self.btn_clear)btn_layout.addWidget(self.btn_start)panel_layout.addLayout(btn_layout)panel_layout.addWidget(self.progress)panel_layout.addStretch()splitter.addWidget(panel)# ---- 右侧预览面板 ----preview_panel = QWidget()preview_panel.setStyleSheet("background-color:#ffffff;""border: 1px solid#d9d9d9;""border-radius: 8px;")layout_preview = QVBoxLayout(preview_panel)layout_preview.setContentsMargins(14, 14, 14, 14)layout_preview.setSpacing(14)lbl_preview = QLabel("👀 实时预览区")lbl_preview.setAlignment(Qt.AlignmentFlag.AlignCenter)lbl_preview.setStyleSheet("font: bold 18px 'Microsoft YaHei'; color:#096dd9;")layout_preview.addWidget(lbl_preview)self.scroll_area = QScrollArea()self.scroll_area.setWidgetResizable(True)self.preview_label = QLabel()self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter)self.preview_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)self.preview_label.setMinimumSize(200, 200)self.scroll_area.setWidget(self.preview_label)layout_preview.addWidget(self.scroll_area)splitter.addWidget(preview_panel)# ---- 左右面板比例 ----splitter.setStretchFactor(0, 2)splitter.setStretchFactor(1, 8)def _connect_signals(self):self.btn_folder.clicked.connect(self.browse_folder)self.btn_logo.clicked.connect(self.choose_logo)self.btn_start.clicked.connect(self.start_process)self.btn_clear.clicked.connect(self.clear_settings)self.btn_text_color.clicked.connect(self.choose_text_color)widgets = [self.edit_text, self.combo_font, self.slider_alpha,self.spin_h, self.spin_v, self.combo_text_pos,self.combo_logo_pos, self.spin_angle,self.slider_text_size, self.slider_logo_size]for w in widgets:if hasattr(w, 'textChanged'):w.textChanged.connect(self.update_preview)if hasattr(w, 'currentIndexChanged'):w.currentIndexChanged.connect(self.update_preview)if hasattr(w, 'valueChanged'):w.valueChanged.connect(self.update_preview)def browse_folder(self):folder = QFileDialog.getExistingDirectory(self, "📂 选择文件夹")if folder:self.folder_path = folderself.lbl_folder.setText(folder)def choose_text_color(self):color = QColorDialog.getColor(initial=self.text_color, parent=self, title="🎨 文字颜色")if color.isValid():self.text_color = colorself.lbl_color_preview.setStyleSheet(f"background-color: {color.name()};")self.update_preview()def choose_logo(self):path, _ = QFileDialog.getOpenFileName(self, "🖼️ 选择Logo图片", filter="Images (*.png *.jpg *.jpeg)")if path:self.logo_path = pathself.update_preview()def clear_settings(self):self.lbl_folder.setText('未选择文件夹')self.edit_text.setText('码海听潮')self.slider_alpha.setValue(20)self.spin_h.setValue(1)self.spin_v.setValue(1)self.combo_text_pos.setCurrentIndex(0)self.combo_logo_pos.setCurrentIndex(0)self.spin_angle.setValue(30)self.progress.setValue(0)self.preview_label.clear()if hasattr(self, 'dropped_file'):self.dropped_file = Nonedef update_preview(self):# 获取预览框尺寸w = self.preview_label.width()h = self.preview_label.height()if w <= 0 or h <= 0:return# 创建预览画布pixmap = QPixmap(w, h)pixmap.fill(Qt.GlobalColor.white)painter = QPainter(pixmap)painter.setOpacity(self.slider_alpha.value() / 100)painter.setPen(self.text_color)# 绘制示例文本水印text = self.edit_text.text()font = QFont("Microsoft YaHei", 20)painter.setFont(font)painter.drawText(w//2 - 50, h//2, text)# 如果有Logo,绘制示例Logoif self.logo_path:logo = QPixmap(self.logo_path)if not logo.isNull():logo = logo.scaled(80, 80, Qt.AspectRatioMode.KeepAspectRatio,Qt.TransformationMode.SmoothTransformation)painter.drawPixmap(w - 100, 20, logo)painter.end()self.preview_label.setPixmap(pixmap)def start_process(self):QMessageBox.information(self, "提示", "开始处理PDF水印...")def dragEnterEvent(self, event):if event.mimeData().hasUrls():event.acceptProposedAction()def dropEvent(self, event):url = event.mimeData().urls()[0]path = url.toLocalFile()if os.path.isdir(path):self.folder_path = pathself.lbl_folder.setText(path)elif os.path.isfile(path) and path.lower().endswith('.pdf'):self.dropped_file = pathself.folder_path = os.path.dirname(path)self.lbl_folder.setText(os.path.basename(path))self.progress.setValue(0)self.preview_label.clear()if __name__ == '__main__':app = QApplication(sys.argv)window = PDFWatermarkerApp()window.show()sys.exit(app.exec()
这款PDF批量水印添加工具,可自动完成原需手动逐页添加水印、从最初打开数百个PDF文件逐个添加水印、手动调整位置和透明度、反复预览效果、再到逐一确认输出的繁琐与崩溃,到用自动化工具实现一键批量处理、水印参数自由调节、实时预览效果、工作效率获得指数级提升,终于解放了双手和时间!
大佬们也可以举一反三,参照上面的代码思路根据自己工作中的实际情况来具体问题具体分析,实现自己定制化的需求。

《该办公工具的应用场景:》
1. 企业文档版权保护
公司内部机密文档、合同、协议添加"机密"、"内部资料"水印
防止敏感信息外泄,明确文档归属权
离职员工资料交接时批量添加公司Logo水印
2. 品牌宣传与营销
产品手册、宣传册批量添加公司Logo和品牌口号
提升品牌曝光度,防止竞品盗用资料
展会资料、客户提案统一品牌形象
3. 教育培训行业
课件、讲义、习题集添加"仅供内部使用"水印
防止学生资料外传,保护知识产权
在线课程教材批量添加机构Logo和版权声明
4. 法律与金融行业
合同草案、审计报告添加"草稿"、"审核中"水印
律师函、法律文书加密防护
防止文档被篡改或冒用
5. 政府与事业单位
红头文件、公告通知添加"内部文件"水印
会议材料、汇报PPT转PDF后加标识
档案数字化管理中的版权保护
6. 设计院与建筑行业
设计图纸、施工方案批量添加公司水印
防止设计成果被他人盗用
投标文件品牌标识强化
7. 自媒体与内容创作者
电子书、原创文章PDF添加个人品牌Logo
防止内容被搬运、抄袭
付费资料版权保护
8. 电商与网店运营
产品说明书、用户手册批量添加店铺水印
防止同行盗用产品资料
提升店铺专业形象
9. 科研与学术领域
论文、研究报告添加"草稿"、"审阅中"水印
保护科研成果不被提前泄露
学术交流资料版权声明
10. 批量文档归档管理
大量历史文档统一添加归档标识
部门资料规范化管理
电子档案安全防护
一句话总结:只要是PDF文档,只要是版权保护、品牌宣传、文档管理的场景,这款工具都能大显身手!
结语
当Python遇见办公,牛马打工人终于笑出了猪叫声
【职场人必看】每天早上一睁眼,想到又要面对:
1.📊 堆积如山的Excel表格
2.📑 机械重复的复制粘贴
3.✍️ 永远改不完的各类文档
4.诸如此类的更多........
是不是连Ctrl+Alt+Delete的心都有了?
别慌!别急,摸鱼这位“职场外挂”已经带着Python代码来拯救你了!
另外,本篇文章的exe已上传nas私有云盘,有需要的大佬可私信摸鱼君获取!

感谢各位大佬观看,还望各位大佬抬抬贵手一键三连,多多关注点赞转发评论,大佬们的支持才是摸鱼孜孜不倦更新原创干货的动力!
夜雨聆风