1. 从手动拉表半小时到一键跑完全部我为什么要做这个Python自动化事情的起因很朴素。我所在的部门每天上午都要从公司几个不同的系统里导出数据然后手工清洗、合并、算指标最后整理成一张Excel报表发给领导。数据源包括Oracle数据库、一个内部管理系统网页、还有两三个Excel模板里的历史数据。一开始数据量小每天二十分钟能搞定但随着业务量涨上来单是登录两三个系统、逐一点击导出、再复制粘贴到总表里就要花掉将近四十分钟而且经常因为漏掉某一行或者公式没拉对而被返工。我当时的想法很直接能不能让每天早上的这四十分钟变得不需要人盯着于是就有了这次Python自动化开发的小项目。背景交代清楚之后我先把目标定下来每天早上8点自动登录内部系统、从Oracle查数据、拉取网页报表、汇总生成Excel、发到企业IM群。整个项目从设计到落地用了大概五天之后运行了两个月稳定性和收益都超出了预期。这篇文章就把完整过程和关键代码整理出来适合想用Python解决日常工作重复劳动、但还没系统上手的读者参考。先说结论Python做这类内部自动化最大的优势不是语法简单而是它的库生态几乎覆盖了所有连系统、读数据、写报表、发通知的环节。requests管接口调用、pandas管数据处理、openpyxl管Excel写入、apscheduler管定时调度每个环节都有成熟的轮子。真正花时间的不是写代码而是梳理业务流程和排查环境问题。2. 需求盘点与技术选型先搞清楚自动化到底要自动什么2.1 流程拆解把手动操作翻译成代码步骤很多人一上来就写代码这是最容易翻车的方式。自动化的本质是把人的操作流程翻译成计算机能执行的步骤前提就是先把流程拆细。我把我这边的场景拆成了五步访问内部管理系统A输入账号密码拿到登录态。登录后跳转到报表页面根据当天的日期参数下载数据文件。连接Oracle数据库执行一段已经写好的查询SQL取出前一天的数据。把下载的文件和数据库查询结果在pandas里清洗、合并计算同比、环比和几个指标。把最终结果写入Excel模板通过企业IM的机器人接口发送到指定群同时把文件归档到共享目录。每拆一步就要确认一个关键问题这步操作有没有不受人工干预的入口比如系统A有没有APIOracle能不能远程直连企业IM有没有机器人Webhook。确认完这些自动化方案才谈得上可行。2.2 技术栈选择哪些库负责哪件事根据上面的步骤我做了选型步骤工具理由登录网页系统requests 手动维护Cookie内部系统没有开放API登录协议简单用requests模拟表单登录即可下载报表文件requests.get session保持会话保持登录态按参数请求文件地址查询Oracle数据库cx_Oracle现在叫oracledbPython连接Oracle最成熟的方案数据清洗合并pandas numpyDataFrame处理表格数据几乎是标配写入Excelopenpyxl pandas.ExcelWriter写入格式可控能沿用原模板样式定时调度apscheduler Windows计划任务/cron脚本本身支持手动触发调度交给系统级定时器更稳定消息通知requests调用Webhook企微/钉钉/飞书都有机器人接口本质就是POST一个JSON这里有一个很重要的问题为什么不用Selenium因为我评估了一下目标系统是纯表单登录没有复杂的JS验证码直接用requests模拟登录更轻量、更稳定。Selenium要额外启动浏览器进程在服务器上没有图形界面的时候还得装虚拟显示器成本高。只有当系统有复杂前端渲染或点击事件的时候才值得用Selenium。选型要跟着稳定性和维护成本走不是为了酷炫。2.3 Python环境准备别小看这一步项目跑在公司Windows服务器上我直接装了Python 3.10版本。这里要给新手提醒一个容易踩的坑不要直接装最新版Python先确认你依赖的库是否支持该版本。我当时吃过一个教训前置版本装的是Python 3.12结果有个内部依赖包还没适配只好回退。通用的做法是装3.10或3.11这种次新稳定版。环境方面我用的是虚拟环境venv每装一个库都在虚拟环境里装而不是全局环境。原因很简单服务器上可能还有别的项目不同的项目依赖不同版本的pandas、requests全局装的话迟早冲突。动手之前先建立好virtualenv后面部署到另一台机器时只要导出requirements.txt目标机器一条pip install -r requirements.txt就能复现。另外要提一下pip换源这件事。公司内网访问PyPI官方源经常超时我用的是国内镜像源比如清华源或者阿里源。命令很简单pip config set global.index-url 指向镜像地址就行。这个问题不解决很多新人在安装numpy库或安装sklearn库这种依赖包体积大的场景下光等下载就等到怀疑人生。3. 核心代码实现登录、查数、清洗、落表全流程3.1 登录态保持与报表下载requests模拟登录登录内部系统这一步我先用浏览器的开发者工具抓了一下登录请求发现就是POST到/login接口参数是username和password成功后返回一个sessionid的Cookie。于是代码就有了底。注意企业内部系统往往有登录失败次数限制所以代码里务必加上失败重试和异常通知不然账号被锁定了很麻烦。import requests login_url http://内部系统地址/login report_url http://内部系统地址/report/export session requests.Session() login_data { username: your_name, password: your_password } resp session.post(login_url, datalogin_data, timeout10) if resp.status_code ! 200 or 登录失败 in resp.text: raise RuntimeError(登录失败请检查账号或网络) params { date: 2025-03-18, type: daily_report } file_resp session.get(report_url, paramsparams, timeout60) if file_resp.status_code 200: with open(report.xlsx, wb) as f: f.write(file_resp.content)这段代码的关键在于session对象的复用。用requests的SessionCookie会被自动保存下来下载文件时不需要重新登录。文件保存用二进制写模式b因为Excel文件本质是二进制格式这一步错了会导致文件打不开。3.2 Oracle查询链接、游标、DataFrameOracle连接部分我用的是cx_Oracle库。首先公司提供的连接字符串通常是这样的格式host:port/service_name。代码里用cx_Oracle.connect创建连接然后通过pandas.read_sql直接把查询结果读成DataFrame。这里有个很好的实践SQL语句不要直接写在业务代码里单独放在一个sql文件或配置表里这样业务人员调整口径时不用动代码。import cx_Oracle import pandas as pd cx_Oracle.init_oracle_client(lib_dirrD:\instantclient_21_x64) conn cx_Oracle.connect( userusername, passwordpassword, dsn192.168.1.10:1521/ORCLPDB1 ) sql SELECT dept, SUM(amount) AS total_amount FROM sales_data WHERE stat_date TO_DATE(:dt, YYYY-MM-DD) GROUP BY dept df_sales pd.read_sql(sql, conconn, params{dt: 2025-03-18}) conn.close()这里要解释一个容易踩的坑cx_Oracle需要Oracle客户端库Instant Client才能工作。如果你在服务器上直接pip install cx_Oracle然后连数据库大概率会报DPI-1047错误。解决办法有两个一是把instantclient解压出来用init_oracle_client指定到解压目录二是装oracledb的thin模式不需要任何本地客户端库。我后来换成了oracledb thin模式部署时省了非常多事。import oracledb import pandas as pd conn oracledb.connect(userusername, passwordpassword, dsn192.168.1.10:1521/ORCLPDB1) df_sales pd.read_sql(sql, conconn) conn.close()pandas.read_sql这个接口对数据库类型的适配做得很好不管是从Oracle还是MySQL查询拿到的都是DataFrame后面的处理流程完全不需要关心数据来源。这也是我选择pandas做中间层的原因统一了后续所有操作的数据结构。3.3 pandas数据清洗合并、去重、计算指标数据拿到之后就是最常见的pandas操作。我一般是这么处理的第一步检查空值和重复值第二步统一字段名称和格式第三步合并两张表做计算。import pandas as pd import numpy as np # 两张表一张是网页下载的明细表一张是Oracle查询的汇总表 df_detail pd.read_excel(report.xlsx, sheet_name明细) df_sales pd.read_sql(sql, conconn) # 统一日期字段为datetime类型 df_detail[日期] pd.to_datetime(df_detail[日期]) df_sales[stat_date] pd.to_datetime(df_sales[stat_date]) # 按部门合并 df_merged pd.merge(df_detail, df_sales, left_on部门, right_ondept, howleft) # 计算环比本期/上期-1 df_merged[环比] df_merged[本期金额] / df_merged[上期金额] - 1 df_merged[同比] df_merged[本期金额] / df_merged[去年同期金额] - 1 # 处理除数为0的情况 df_merged[[环比, 同比]] df_merged[[环比, 同比]].replace([np.inf, -np.inf], np.nan) df_merged df_merged.fillna(0)这里有一个关于数据可靠性的重要习惯每次合并之后打印一下数据量信息和几个关键统计量。比如打印len(df_merged)和df_merged.isnull().sum()确认没有出现大批量空值。如果某天上游数据延迟或者下载到的文件是空的这一步就能直接暴露问题而不是等到报表发出去之后被人发现错误。我还用了一个技巧把校验规则写进代码。例如数据行数必须大于0、合计数不能为0、环比绝对值不能大于10倍如果校验不通过就中止发送并给人发告警。这个校验机制极大提高了自动化跑批的可信度否则第一次代码跑通很简单长期稳定运行很难。3.4 Excel写入沿用模板保留样式写到Excel这一步我用的是openpyxl引擎。pandas默认的ExcelWriter引擎在直接df.to_excel时是没办法保留一个现成模板里的表头样式和公式的。我的做法是先复制一份模板文件再用openpyxl.load_workbook加载它把指定单元格区域写入数据。from openpyxl import load_workbook from openpyxl.utils.dataframe import dataframe_to_rows template_path daily_report_template.xlsx output_path daily_report_20250318.xlsx # 复制模板文件 import shutil shutil.copyfile(template_path, output_path) wb load_workbook(output_path) ws wb[每日报表] # 从第5行开始写入数据保留前4行的标题和说明 for r_idx, row in enumerate(dataframe_to_rows(df_merged, indexFalse, headerFalse), start5): for c_idx, value in enumerate(row, start1): ws.cell(rowr_idx, columnc_idx, valuevalue) # 对金额列设置数字格式 for row in ws.iter_rows(min_row5, max_row5 len(df_merged), min_col2, max_col4): for cell in row: cell.number_format #,##0.00 wb.save(output_path)这里就有个细节如果直接用to_excel会把原有模板的样式都抹掉而我们公司的报表是有固定格式和公式的不能丢。openpyxl在处理既有工作簿时不会重排其他内容可以精准写入指定位置。如果报表还要继续保留模板里的求和公式只要在模板里预留好公式行openpyxl就会保留。3.5 发到企业IM一个POST请求搞定企业微信、钉钉、飞书都有机器人Webhook本质就是往URL post一个JSON。在自动化项目里这个环节同时承担着两个职责发送报表文件和发送异常告警。import requests import json webhook_url https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key你的key def send_report(file_path): # 先上传文件拿到media_id with open(file_path, rb) as f: upload_resp requests.post( https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key你的keytypefile, files{media: f}, timeout30 ) media_id upload_resp.json().get(media_id) payload { msgtype: file, file: {media_id: media_id} } requests.post(webhook_url, datajson.dumps(payload), timeout10) def send_alert(message): payload { msgtype: text, text: {content: f自动化报表任务异常{message}} } requests.post(webhook_url, datajson.dumps(payload), timeout10)虽然代码不复杂但这一步是整个系统无人值守的关键。没有通知环节脚本半夜挂了没人知道第二天早上一看报表没发等于自动化失去了意义。我后来还加了try/except任何异常都先发告警然后抛出确保宁可让人看到错误也不能让错误被静默吞掉。4. 让脚本每天自动跑APScheduler与系统定时任务的选择4.1 为什么不用Python内部sleep循环写常驻进程很多人想到定时任务第一反应是while True: time.sleep(60)然后检查时间。这个方案有两个问题一是进程常驻如果内存泄漏或网络异常跑几天就挂了二是进程崩溃没人知道没发报表也不知道。我一开始也考虑过APScheduler挂常驻进程但后来还是放弃了。最靠谱的方案是把主流程写成一个可以被一次调用的脚本调度交给操作系统。Windows用计划任务、Linux用crontab每天8点触发一次python脚本。Max这样的好处是脚本本身无状态、跑完即退出出问题只需要重跑一次。这不依赖Python进程的稳定性也方便监测。当然如果你需要在脚本内部跑多个子任务APScheduler也是个好选择。它支持cron表达式代码写起来很方便。我在这套系统里用的是系统定时任务调度单脚本方式简单、易排查、不易出现僵尸进程。def main(): try: run_daily_report() send_report(daily_report_20250318.xlsx) except Exception as e: send_alert(str(e)) raise if __name__ __main__: main()4.2 Windows计划任务的配置要点Windows服务器上配置计划任务我给三条实战经验一定要把使用最高权限运行打开避免Python脚本因为权限不足无法读写共享目录。程序填python解释器的绝对路径参数填脚本绝对路径工作目录也填脚本所在目录。因为脚本中如果有相对路径引用配置文件工作目录不对会直接报FileNotFoundError。计划任务触发器设置每天时间填8:00但如果运行时间较长建议再加一个如果任务运行时间超过30分钟强制停止的选项避免异常卡死。题外话如果你是Linux服务器cron表达式一行搞定0 8 * * * cd /opt/python_project /usr/bin/python3 run_daily.py /var/log/auto_task.log 21。重定向日志很重要排查问题全靠它。5. 我踩过的几个坑环境、编码、路径、三方库兼容性问题5.1 Python环境层面的坑写代码用了两天排查环境问题用了一天半。我遇到最大的坑就是前面提到的cx_Oracle在Windows服务器上缺Oracle Instant Client。另一个坑是pandas和numpy版本不匹配导致DataFrame某些操作报奇怪的内部错误。这类问题的通用排查思路是先看报错堆栈到底指向哪个库然后把那个库单独升级或降级到兼容版本。还需要提醒的是不要在系统自带Python里瞎装库。Windows上有个App execution alias命令行里敲python可能会打开微软商店而不是真正的Python环境。解决办法是在系统设置里关掉应用执行别名或者安装Python时选择Add Python to PATH。5.2 数据上的坑数据自动化最核心的坑是你以为数据没问题它偏偏给你出幺蛾子。我遇到过网页下载的Excel文件带了前导空格导致merge对不上遇到过Oracle查询出来有id为NULL的脏数据遇到过下载下来的文件实际是HTML错误页而不是Excel文件。所以我用了一个通用检查函数替代人工目视检查def check_file_valid(file_path): with open(file_path, rb) as f: head f.read(4) return head bPK\x03\x04 # xlsx文件的zip头xlsx文件本质上是一个zip包文件头应该是PK如果检查到文件头不是这个可以直接判定下载失败。这种文件内容校验的思路看着简单却省了我好几次把坏文件发给领导的尴尬。另一个数据坑是Excel里日期格式不统一有的单元格是日期类型有的存成了文本。处理的办法是统一用pd.to_datetime并加上format参数必要时用errorscoerce把非法值变成NaT然后再统一清洗。5.3 路径与目录结构代码里写死路径是自动化脚本的大忌。我第一版直接在脚本里写了D:\my_script\data\report.xlsx后来脚本被挪到另一个目录全都炸了。后来我改成脚本自动识别目录import os import sys BASE_DIR os.path.dirname(os.path.abspath(__file__)) DATA_DIR os.path.join(BASE_DIR, data) CONFIG_PATH os.path.join(BASE_DIR, config.yaml)用os.path.abspath(file)获取脚本真正所在目录再基于这个目录拼装所有路径。同时把登录账号、数据库连接串、Webhook地址这些敏感信息和环境相关的配置都放到config.yaml或者环境变量里脚本本身不掺杂具体环境配置。这样项目从开发机迁移到服务器只需要改配置文件代码一行不动。6. 进阶扩展从自动报表到数据服务的一点延伸项目稳定跑了两个礼拜之后我开始琢磨扩展。既然数据每天都能自动取那能不能让业务人员自己按条件查询指标于是我在同一个项目里加了两个额外的能力第一把同类的查询逻辑封装成一个函数对外提供简单的命令行参数接口。比如python query.py --date 2025-03-18 --dept 销售部它能直接查询指定日期的指标并输出表格这样非技术人员通过一个简单的批处理文件就能自助查数。第二加入了一点协程和并发处理的思路。比如每天要下载多个省份的文件用requests一个个下载比较慢我就用concurrent.futures.ThreadPoolExecutor开了4个线程并发下载整体时间几乎缩短到原来的四分之一。不过这里也要提醒一句并发下载要注意目标服务器的承受能力公司内部系统通常没问题如果是对外网站就要注意控制频率别把别人服务器搞崩了主要也是避免给自己惹麻烦。第三很多热词里提到了python爬虫和量化交易策略代码虽然和我的场景不完全一样但底层逻辑是通的。只要是定期从某个数据源获取数据→清洗→按规则执行动作都可以套用这同一套骨架数据获取层、数据处理层、任务调度层、通知层。很多人觉得爬虫或者量化交易很高端拆开看核心还是requests拿数据、pandas算信号、定时任务触发。我对这套自动化架构的体会是不要为了自动化而自动化自动化是为了把人的精力释放到更有价值的判断工作上去。手拉报表四十分钟浪费的其实是每天早上最清醒的时间。而把流程交给脚本之后人只需要在收到异常告警时介入其余时间该干嘛干嘛这才是工具应该有的样子。项目再往后走我打算把Excel报表往更轻量的方向演一下比如直接在网页上做一个看板数据还是由这套定时任务推送到数据库表再由前端展示。不过那是另一个项目了当前这套Python自动化已经把我从每天的重复劳动里彻底解放出来了。
阅读完成 · 觉得有帮助?