watchdog 是监听文件系统事件最可靠的跨平台方案,它基于各系统原生 API(Windows 的 ReadDirectoryChangesW、Linux 的 inotify、macOS 的 FSEvents),避免轮询,延迟低、资源省。

用 watchdog 监听文件系统事件最可靠
Windows 的 ReadDirectoryChangesW、Linux 的 inotify、macOS 的 FSEvents 底层机制差异大,自己封装容易漏事件或卡死。直接用 watchdog 这个库,它已做好跨平台抽象,且默认使用原生 API(不是轮询),延迟低、资源省。
安装只需:
pip install watchdog
关键点:
-
FileSystemEventHandler子类里重写on_created、on_modified等方法,别只监听on_any_event——它会把临时文件、编辑器备份等无关事件也抛进来,干扰上传逻辑 - 对
.tmp、~、.swp这类后缀的文件,应在事件回调里先if event.is_directory or event.src_path.endswith(('.tmp', '~')): return过滤掉 - 文本编辑器(如 VS Code)保存时常先写临时文件再原子替换,可能触发两次事件:一次
on_created(临时文件),一次on_moved(重命名成目标名)。建议只处理on_moved里的dest_path,并确认event.dest_path是目标路径且存在
上传前必须做文件状态校验
监听到事件不等于文件已写完。尤其大文件(如视频、数据库导出)可能还在写入中,直接上传会得到截断内容。不能依赖“事件触发即上传”。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 对
on_modified或on_moved事件,用os.stat(path).st_size记录初始大小,隔 100ms 检查是否变化,连续 3 次不变才认为写入完成(避免误判) - 更稳妥的做法是调用
os.path.getmtime(path),对比上次修改时间戳,若 500ms 内未更新,再读取文件——比单纯看大小更能应对某些编辑器的分块写入行为 - 跳过正在被其他进程占用的文件:用
try: open(path, 'rb', buffering=0) except OSError: return快速探测,避免上传卡住
用 requests 分块上传防超时和内存爆掉
上传大文件时,如果用 requests.post(..., files={'file': open(...)}) 会一次性读进内存,1GB 文件直接 OOM;HTTP 超时也会中断。
正确做法是流式分块上传:
def upload_file_chunked(filepath, url):
with open(filepath, 'rb') as f:
# 用生成器逐块 yield,requests 自动处理 chunked encoding
def file_generator():
while True:
chunk = f.read(8192)
if not chunk:
break
yield chunk
requests.put(url, data=file_generator(), timeout=(30, 300))注意点:
-
timeout=(connect_timeout, read_timeout)必须设,尤其read_timeout要大于预期上传时间,否则网络抖动就失败 - 服务端需支持接收流式 body(比如 Flask 用
request.stream.read(),不是request.files) - 上传失败时,记录失败路径和错误码(如
response.status_code),不要静默吞掉,否则文件丢了都不知道
守护进程要防重复启动和崩溃退出
脚本常被加到开机自启或 systemd 里,但没锁机制会导致多个实例同时监听同一目录,引发重复上传甚至文件锁冲突。
简单可靠的进程互斥方案:
- 在脚本开头创建一个带 PID 的锁文件,如
/tmp/folder_sync.lock,写入os.getpid();每次启动先检查该文件是否存在且对应进程仍在运行(os.kill(pid, 0)),存在则直接退出 - 用
atexit.register()在程序退出时自动删锁文件,但 SIGKILL 无法捕获,所以锁文件需配合超时判断:若锁文件存在超过 5 分钟且进程不存在,视为残留,直接覆盖 - 主循环用
try/except Exception as e:包裹,打印完整 traceback 到日志文件(别只 print),然后time.sleep(1)后继续——避免某次异常导致整个监控停止
真实场景里,文件句柄泄漏、网络 DNS 暂时不可达、远程存储配额满,都可能让上传卡住或失败。这些细节不处理,脚本跑两天就失联。


















