ARTICLE · 1331509
你删光了正文里的公司名,Word文档属性还在替你署名
招标文件里有一句话,不少投标人第一次见都当耳边风:“投标文件出现投标人名称或者其他可以识别投标人的标记的,按无效投标处理。”
暗标评审,一个名字都不许有。多数人的应对是把落款删掉、封面换掉,再从头到尾通读一遍确认没有公司名。这些动作都对,但不够——Word会在你看不见的地方替你署名。这个脚本的由来就是一次险情:交付前做自检,正文已经逐段确认干净,抱着试试的心态右键看了眼文件属性,作者栏里躺着一个真名。从那以后,交暗标前用脚本搜身成了固定动作。
名字都藏在哪:三处重灾区
第一处,文档属性。右键文件打开“详细信息”,作者、修改者两个字段就躺在那里。公司电脑装Office时通常默认写入员工姓名,你在工位上写的每一稿,属性里都带着真名。删正文救不了它,属性跟着文件走,转成PDF都未必清得掉——很多PDF导出选项会把Word里的作者字段原样带过去。
第二处,页眉页脚。正文改得再干净,页眉里挂着的公司名、页脚里留的邮箱,你多半一页没动过。整理稿子的人十个有九个只看正文,评审专家翻文件可不管哪一页是你看漏的。
第三处,表格和路径。联系人塞在表格里:“张工 13812345678”。正文里交代交付路径:“存档于C:\Users\zhangsan\Documents\final\”——zhangsan就是张三的拼音。这类痕迹不叫公司名,却一样能识别出人。
把后果说透:暗标废标不是风格问题,是硬条款。评标委员会认定出现可识别标记,直接按无效投标处理,没有申辩空间。前期与甲方的技术沟通、方案里熬的夜、团队报的价,全跟着这一处痕迹作废。更麻烦的是暗标往往一式多份、涉及多个章节文件,一处漏了处处漏,靠眼睛逐段找,找到的永远只是你恰好看见的那些。
交给脚本:一次搜身,四类规则
我把这几年的自查动作写成了一个脚本dark_bid_scan.py,交给AI和正则去干重复劳动。它做四件事:
▸用四条规则扫全文:手机号、邮箱、统一社会信用代码、Windows本地路径。手机号是11位1开头的数字,邮箱是通用格式,统一社会信用代码按18位编码规则写正则——营业执照号塞在表格里,一样是身份痕迹;本地路径专抓C盘用户目录,路径里的用户名往往就是你的拼音;
▸按你给的姓名、公司名等关键词逐处比对,关键词可以叠多次,公司简称、曾用名都能加进来;
▸检查Word文档属性里的author、last_modified_by、title、subject、comments、category六个字段。别小看title和comments,很多人在文件属性备注里写“XX公司专用版”,一样露;
▸覆盖范围包括正文、表格、页眉、页脚——正是上面三处重灾区。
这四类只是最常踩的痕迹。凡是能识别出投标人的标记——座机号、QQ、微信、项目编号里嵌的缩写、图片里的水印——都一样按废标风险处理。脚本是筛子,不是保单。
检查结果分两档:命中和人工确认。属性字段非空但不含你给的关键词时,脚本既不放过也不冤枉,标成“人工确认”留给人判断。退出码0是干净、2是命中,可以直接接进批处理当闸门。
实跑记录
先造一份埋雷演示件,把公司名、姓名、手机号、邮箱、本地路径、文档属性author全部埋进去,再跑脚本:
=== 暗标匿名自检报告 === 文件:演示_埋雷版.docx [命中] 正文|公司关键词|华信工程咨询有限公司 [命中] 正文|本地路径泄漏|C:\Users\zhangsan\Documents\final\ [命中] 表格|手机号|13812345678 [命中] 表格|姓名关键词|张工 [命中] 页眉(节1)|公司关键词|华信工程咨询有限公司 [命中] 页脚(节1)|邮箱|zhanggong@huaxin-consult.com [命中] 文档属性|author|张工(含关键词“张工”) [命中] 文档属性|comments|张工 2026-10-20 修改(含关键词“张工”) [提醒·人工确认] 文档属性|last_modified_by|李四(华信) [提醒·人工确认] 文档属性|title|XX项目实施方案 结论:8处命中,未过匿名自检(退出码2)8处命中、2条提醒:正文和页眉的公司名、表格里的手机号和姓名、正文里的本地路径、页脚的邮箱、属性里的author和comments,全部被抓出来;last_modified_by“李四(华信)”不含完整公司名,脚本没硬判,标了人工确认。
再跑一份属性清干净的对照件,结论未命中,退出码0——它能确认干净,不是只会喊狼来了。
脚本管不到的地方
docx文本层之外还有四路要人守:
▸文件名。“XX公司技术方案.docx”这种名字,交上去就是明牌。文件名检查一行shell就够:ls *.docx | grep 公司名;
▸PDF层。定稿转PDF后要拿PDF文本再抽一遍,再右键看一次PDF属性,作者字段有没有跟过去;
▸图片。扫描章、截图水印、组织架构图里的公司名,脚本不读图,靠人在合成前过目。
▸文本框和艺术字。“投标专用”、“内部资料”这类字,标书里常放在文本框和艺术字里,脚本扫不到这一层,合成前人工过目。
四路都过,才算真干净。
你怎么用
安装依赖后一行命令:
pip install python-docx python dark_bid_scan.py 标书.docx --name 张三 --company 某某实业 --more 13800138000整个标书拆成几十份章节docx的,用循环一次扫完:
for f in *.docx; do python dark_bid_scan.py "$f" --name 张三 --company 某某实业; done退出码2的文件留下来改,改完重跑,全部退出码0再合成PDF。放进交标流程就是三步:改完稿跑一遍脚本、按报告逐处清理、全绿之后转PDF再走一遍文件名和PDF属性检查。半小时的自检,换掉的是整个项目组半个月白干的概率。
还有一个反向用法:跟别人合作联营投标、或者拿到第三方做过一半的文件时,先跑一遍搜身再签合同——对方文档属性里藏着谁、路径里写着谁,一眼看穿,免得文件本身先替人泄了底。
三点限度说清楚:一,脚本只扫docx文本层,最终交的PDF版本要拿PDF文本再跑一路;二,关键词要喂全——公司简称、曾用名、英文注册名、项目组花名,都算可识别标记;三,它替代不了人收尾那遍通读,评审席上没有脚本的位置,它只是把你从靠眼睛找针里捞出来。
暗标查的不是你写没写名字,是Word替不替你写。删得掉的是正文,删不掉的是习惯——把这次搜身变成交标前的固定动作,名字才算真正删干净。
附:完整脚本
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """暗标匿名自检:扫出Word里藏着的公司名、姓名、联系方式和本地路径。 用法: python dark_bid_scan.py 标书.docx --name 张三 --company 某某实业 --more 13800138000 退出码:0=未命中,2=有命中(可直接接进批处理做闸门) 依赖:pip install python-docx """ import argparse import re import sys from docx import Document RE_PHONE = re.compile(r'(?<!\d)1[3-9]\d{9}(?!\d)') RE_EMAIL = re.compile(r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}') RE_USCC = re.compile(r'(?<![0-9A-Z])[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}(?![0-9A-Z])') RE_PATH = re.compile(r'[A-Z]:\\(?:Users|Documents and Settings)\\[^\s,。;、"”]*') DOCX_PROPS = ('author', 'last_modified_by', 'title', 'subject', 'comments', 'category') def iter_texts(doc): """按位置逐段产出(位置标签, 文本):正文、表格、页眉、页脚。""" for p in doc.paragraphs: if p.text.strip(): yield '正文', p.text for t in doc.tables: for row in t.rows: for cell in row.cells: if cell.text.strip(): yield '表格', cell.text for i, sec in enumerate(doc.sections, 1): for label, part in (('页眉', sec.header), ('页脚', sec.footer)): for p in part.paragraphs: if p.text.strip(): yield f'{label}(节{i})', p.text for t in part.tables: for row in t.rows: for cell in row.cells: if cell.text.strip(): yield f'{label}(节{i})表格', cell.text def main(): ap = argparse.ArgumentParser(description='暗标匿名自检') ap.add_argument('docx', help='要检查的Word文件') ap.add_argument('--name', action='append', default=[], help='编写人姓名,可多次') ap.add_argument('--company', action='append', default=[], help='公司名关键词,可多次') ap.add_argument('--more', action='append', default=[], help='其他自定义关键词,可多次') args = ap.parse_args() doc = Document(args.docx) rules = [ ('手机号', RE_PHONE), ('邮箱', RE_EMAIL), ('统一社会信用代码', RE_USCC), ('本地路径泄漏', RE_PATH), ] keywords = ([('公司关键词', c) for c in args.company] + [('姓名关键词', n) for n in args.name] + [('自定义关键词', m) for m in args.more]) rules += [(label, re.compile(re.escape(v))) for label, v in keywords] hits, warns = [], [] for where, text in iter_texts(doc): for rule_name, rex in rules: for m in rex.finditer(text): hits.append((where, rule_name, m.group(0))) core = doc.core_properties name_set = set(args.name) | set(args.company) | set(args.more) for field in DOCX_PROPS: val = str(getattr(core, field, '') or '').strip() if not val: continue kw = next((k for k in name_set if k in val), None) if kw: hits.append(('文档属性', field, f'{val}(含关键词“{kw}”)')) else: warns.append(('文档属性', field, val)) print('=== 暗标匿名自检报告 ===') print(f'文件:{args.docx}') for where, rule_name, snippet in hits: print(f'[命中] {where}|{rule_name}|{snippet[:40]}') for where, field, val in warns: print(f'[提醒·人工确认] {where}|{field}|{val[:40]}') if hits: print(f'结论:{len(hits)}处命中,未过匿名自检(退出码2)') sys.exit(2) print('结论:未命中(退出码0)') if __name__ == '__main__': main()把你的姓名和公司名换进去就能跑。唯一依赖python-docx,pip装完即用。















































































