7个Python库,快速搞定公司内部工具
代码写得好,不如工具造得巧
你是否也曾经历过这样的场景?周五下午,正准备摸鱼等待周末,经理突然走过来,轻描淡写地说:
“我们市场部需要一个数据看板,监控一下活动效果,下周一晨会要用,你看能不能快速搞一个?”
或者,财务同事满脸期待地问:“这个报销审批流程,能不能做个自动化?现在手动转发邮件太麻烦了……”
你心里一紧——又是内部工具!
做吧,得写前端、搭后端、搞部署,工作量不小;不做吧,团队效率确实受影响。这种“不对外但很重要”的需求,简直是程序员日常的“甜蜜负担”。
但今天,我要告诉你一个秘密:用Python构建内部工具,可以像写脚本一样简单。
我花了4年多时间为各种团队搭建内部系统,发现了一套“神器级”的Python库组合。掌握它们,你就能在几小时(而不是几个月)内,从零到一搭建出实用、美观且稳定的内部工具。
准备好了吗?让我们一起解锁“内部工具大师”的成就!
1. Reflex:告别JavaScript,用纯Python构建全栈应用
痛点:“这个工具很好,但谁会写React前端?”
解决方案:Reflex(原名Pynecone)让你只用Python就能创建完整的Web应用。它自动生成React前端,你只需关心业务逻辑。
为什么它是内部工具的首选?
全Python栈:前后端统一语言,减少上下文切换 实时更新:内置状态管理,数据变化自动同步到UI 一键部署:支持多种部署方式,从本地到云端 现代UI:默认提供美观的组件,无需设计师介入
实战:20行代码搭建实时KPI仪表盘
import reflex as rx
classDashboardState(rx.State):
"""仪表盘状态管理"""
sales: int = 128
bugs: int = 3
users: int = 42
defincrement_sales(self):
"""模拟销售数据更新"""
self.sales += 1
deffix_bug(self):
"""修复一个bug"""
if self.bugs > 0:
self.bugs -= 1
defdashboard():
"""主仪表盘组件"""
return rx.container(
rx.heading("📊 实时业务仪表盘", size="2xl", margin_bottom="1rem"),
rx.hstack(
rx.card(
rx.stat(
rx.stat_label("今日销售额"),
rx.stat_number(f"¥{DashboardState.sales}k"),
rx.stat_help_text("↑ 12% 较昨日")
),
width="300px"
),
rx.card(
rx.stat(
rx.stat_label("待处理问题"),
rx.stat_number(DashboardState.bugs),
rx.stat_help_text("需要优先处理")
),
width="300px"
),
rx.card(
rx.stat(
rx.stat_label("活跃用户"),
rx.stat_number(DashboardState.users),
rx.stat_help_text("当前在线")
),
width="300px"
),
spacing="1rem"
),
rx.hstack(
rx.button(
"模拟新销售",
on_click=DashboardState.increment_sales,
color_scheme="green"
),
rx.button(
"修复一个问题",
on_click=DashboardState.fix_bug,
color_scheme="blue"
),
spacing="1rem",
margin_top="2rem"
),
rx.text(
"数据更新时间: ",
rx.text(DashboardState.get_current_time(), as_="span", color="gray.500"),
margin_top="2rem"
),
padding="2rem"
)
# 创建应用
app = rx.App()
app.add_page(dashboard, title="内部仪表盘")
app.compile()
运行这个程序,访问 http://localhost:3000,你将看到一个功能完整的实时仪表盘。点击按钮,数据会立即更新——全部用Python实现,一行JavaScript都不用写。
2. FastAPI:内部工具的API引擎
痛点:团队需要数据接口,但Spring Boot太重,Flask异步支持不够好。
解决方案:FastAPI是现代Python Web框架的标杆,特别适合构建内部API服务。
为什么内部工具需要它?
自动文档:Swagger UI和ReDoc自动生成,前端同事能直接查看 性能出色:基于Starlette和Pydantic,速度接近Node.js和Go 类型安全:Python类型提示的完整支持,减少运行时错误 依赖注入:灵活处理认证、数据库连接等共享资源
实战:构建审批流程API服务
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
import uuid
app = FastAPI(title="内部审批系统API", version="1.0.0")
# 数据模型
classApprovalRequest(BaseModel):
title: str
requester: str
amount: Optional[float] = None
description: Optional[str] = None
classApprovalItem(BaseModel):
id: str
title: str
requester: str
status: str = "pending"# pending, approved, rejected
created_at: datetime
approved_by: Optional[str] = None
# 模拟数据库
approval_db = {}
defget_db():
"""模拟数据库依赖注入"""
return approval_db
@app.post("/approvals/", response_model=ApprovalItem)
asyncdefcreate_approval(
request: ApprovalRequest,
db: dict = Depends(get_db)
):
"""创建新的审批请求"""
approval_id = str(uuid.uuid4())[:8]
new_item = ApprovalItem(
id=approval_id,
title=request.title,
requester=request.requester,
created_at=datetime.now()
)
db[approval_id] = new_item.dict()
return new_item
@app.get("/approvals/", response_model=List[ApprovalItem])
asyncdeflist_approvals(
status: Optional[str] = None,
db: dict = Depends(get_db)
):
"""列出审批请求(可筛选状态)"""
items = list(db.values())
if status:
items = [item for item in items if item["status"] == status]
return items
@app.post("/approvals/{approval_id}/approve")
asyncdefapprove_request(
approval_id: str,
approver: str = "系统管理员",
db: dict = Depends(get_db)
):
"""批准请求"""
if approval_id notin db:
raise HTTPException(status_code=404, detail="审批项不存在")
db[approval_id]["status"] = "approved"
db[approval_id]["approved_by"] = approver
return {
"status": "success",
"message": f"审批项 {approval_id} 已批准",
"approved_by": approver
}
@app.get("/health")
asyncdefhealth_check():
"""健康检查端点(运维最爱)"""
return {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"service": "approval-system"
}
# 运行: uvicorn main:app --reload
运行后访问 http://localhost:8000/docs,你会看到一个完整的API文档页面。市场部、财务部、HR都可以用这个统一接口对接他们的系统。
3. NiceGUI:快速构建美观的管理后台
痛点:Streamlit适合数据演示,但构建复杂交互的管理后台比较吃力。
解决方案:NiceGUI基于Vue.js,提供了丰富的Web组件,但API完全是Python的。
它适合什么场景?
管理面板:用户管理、内容审核、系统监控 数据看板:比Streamlit更灵活的自定义布局 设备控制:支持WebSocket实时控制硬件 移动端适配:响应式设计,手机也能用
实战:任务追踪看板(Trello的极简替代)
from nicegui import ui
from datetime import datetime
from typing import Dict, List
# 任务数据存储
tasks: Dict[str, List[Dict]] = {
"todo": [
{"id": 1, "title": "设计数据库架构", "assignee": "张三", "created": "2024-01-15"},
{"id": 2, "title": "编写API文档", "assignee": "李四", "created": "2024-01-16"}
],
"in_progress": [
{"id": 3, "title": "开发用户模块", "assignee": "王五", "created": "2024-01-14"}
],
"done": [
{"id": 4, "title": "项目需求评审", "assignee": "赵六", "created": "2024-01-10"}
]
}
defcreate_task_card(task: Dict, column: str):
"""创建任务卡片"""
with ui.card().classes("w-64 p-4 m-2"):
ui.label(task["title"]).classes("text-lg font-bold")
ui.separator()
with ui.row().classes("items-center justify-between"):
ui.badge(task["assignee"], color="blue")
ui.label(task["created"]).classes("text-xs text-gray-500")
with ui.row().classes("justify-end mt-2"):
if column != "todo":
ui.button("←", on_click=lambda t=task, c=column: move_task(t, c, "left"))
if column != "done":
ui.button("→", on_click=lambda t=task, c=column: move_task(t, c, "right"))
ui.button("删除", on_click=lambda t=task, c=column: delete_task(t, c), color="red").props("flat")
defmove_task(task: Dict, from_column: str, direction: str):
"""移动任务到其他列"""
column_order = ["todo", "in_progress", "done"]
current_index = column_order.index(from_column)
if direction == "left"and current_index > 0:
new_column = column_order[current_index - 1]
elif direction == "right"and current_index < 2:
new_column = column_order[current_index + 1]
else:
return
# 从原列移除
tasks[from_column] = [t for t in tasks[from_column] if t["id"] != task["id"]]
# 添加到新列
tasks[new_column].append(task)
# 刷新UI
refresh_board()
defdelete_task(task: Dict, column: str):
"""删除任务"""
tasks[column] = [t for t in tasks[column] if t["id"] != task["id"]]
refresh_board()
defadd_new_task():
"""添加新任务"""
ifnot title_input.value:
return
new_task = {
"id": max([t["id"] for col in tasks.values() for t in col], default=0) + 1,
"title": title_input.value,
"assignee": assignee_input.value or"未分配",
"created": datetime.now().strftime("%Y-%m-%d")
}
tasks["todo"].append(new_task)
title_input.value = ""
assignee_input.value = ""
refresh_board()
defrefresh_board():
"""刷新整个看板"""
board.clear()
with board:
with ui.row().classes("w-full justify-around"):
for column_name, column_tasks in tasks.items():
with ui.column().classes("items-center"):
# 列标题
status_colors = {
"todo": "bg-gray-200",
"in_progress": "bg-blue-100",
"done": "bg-green-100"
}
with ui.card().classes(f"w-80 {status_colors[column_name]}"):
ui.label(column_name.upper().replace("_", " ")).classes("text-center font-bold")
# 任务卡片
for task in column_tasks:
create_task_card(task, column_name)
# 创建UI
ui.colors(primary="#4CAF50")
# 标题
ui.label("🎯 团队任务看板").classes("text-3xl font-bold my-4")
# 添加任务表单
with ui.row().classes("items-center w-full p-4 bg-gray-50 rounded-lg"):
title_input = ui.input("任务标题").classes("w-64")
assignee_input = ui.input("负责人").classes("w-48")
ui.button("添加任务", on_click=add_new_task, icon="add").classes("ml-4")
# 看板容器
board = ui.column().classes("w-full")
# 初始渲染
refresh_board()
# 统计信息
with ui.row().classes("w-full justify-center p-4"):
total_tasks = sum(len(col) for col in tasks.values())
ui.label(f"📊 统计: 总计 {total_tasks} 个任务 | "
f"待办 {len(tasks['todo'])} | "
f"进行中 {len(tasks['in_progress'])} | "
f"已完成 {len(tasks['done'])}")
ui.run(title="任务看板", port=8080)
运行这段代码,一个功能完整的看板应用就诞生了。拖拽功能暂时用按钮替代,但已经能满足大部分团队的需求。
4. Textual:为终端爱好者打造的现代GUI
痛点:服务器管理、日志监控需要在终端操作,但命令行不够直观。
解决方案:Textual让你在终端中构建漂亮的文本用户界面(TUI)。
适合哪些内部工具?
运维仪表盘:服务器状态监控 日志查看器:实时跟踪应用日志 数据库管理:简单的查询和操作界面 CI/CD监控:构建流水线状态
实战:服务器资源监控面板
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Static, DataTable
from textual.containers import Container, Horizontal, Vertical
from textual.reactive import reactive
import psutil
import asyncio
from datetime import datetime
classResourceMonitor(Static):
"""资源监控组件"""
# 响应式数据
cpu_usage = reactive(0)
memory_usage = reactive(0)
disk_usage = reactive(0)
defon_mount(self):
"""挂载时启动定时更新"""
self.set_interval(1, self.update_resources)
defupdate_resources(self):
"""更新资源使用率"""
self.cpu_usage = psutil.cpu_percent()
self.memory_usage = psutil.virtual_memory().percent
self.disk_usage = psutil.disk_usage("/").percent
defwatch_cpu_usage(self, cpu_usage: float):
"""监控CPU使用率变化"""
self.query_one("#cpu-usage").update(f"{cpu_usage:.1f}%")
self.query_one("#cpu-bar").update(self._create_bar(cpu_usage))
defwatch_memory_usage(self, memory_usage: float):
"""监控内存使用率变化"""
self.query_one("#memory-usage").update(f"{memory_usage:.1f}%")
self.query_one("#memory-bar").update(self._create_bar(memory_usage))
defwatch_disk_usage(self, disk_usage: float):
"""监控磁盘使用率变化"""
self.query_one("#disk-usage").update(f"{disk_usage:.1f}%")
self.query_one("#disk-bar").update(self._create_bar(disk_usage))
def_create_bar(self, percentage: float) -> str:
"""创建进度条"""
width = 20
filled = int(width * percentage / 100)
bar = "█" * filled + "░" * (width - filled)
# 根据使用率设置颜色
if percentage < 70:
color = "green"
elif percentage < 90:
color = "yellow"
else:
color = "red"
returnf"[{color}]{bar}[/]"
defcompose(self) -> ComposeResult:
"""组合界面"""
# CPU监控
with Container(id="cpu-container"):
yield Static("💻 CPU使用率", classes="resource-title")
yield Static("0%", id="cpu-usage", classes="resource-value")
yield Static("", id="cpu-bar", classes="resource-bar")
# 内存监控
with Container(id="memory-container"):
yield Static("🧠 内存使用率", classes="resource-title")
yield Static("0%", id="memory-usage", classes="resource-value")
yield Static("", id="memory-bar", classes="resource-bar")
# 磁盘监控
with Container(id="disk-container"):
yield Static("💾 磁盘使用率", classes="resource-title")
yield Static("0%", id="disk-usage", classes="resource-value")
yield Static("", id="disk-bar", classes="resource-bar")
classProcessTable(Static):
"""进程表格组件"""
defcompose(self) -> ComposeResult:
"""创建进程表格"""
table = DataTable(id="process-table")
table.add_columns("PID", "名称", "CPU%", "内存%", "状态")
table.add_rows(self._get_top_processes())
yield table
defon_mount(self):
"""挂载时启动定时更新"""
self.set_interval(2, self.update_processes)
defupdate_processes(self):
"""更新进程列表"""
table = self.query_one("#process-table")
table.clear()
table.add_rows(self._get_top_processes())
def_get_top_processes(self):
"""获取占用资源最高的进程"""
processes = []
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent', 'status']):
try:
processes.append((
proc.info['pid'],
proc.info['name'][:20],
f"{proc.info['cpu_percent']:.1f}",
f"{proc.info['memory_percent']:.1f}",
proc.info['status']
))
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
# 按CPU使用率排序,取前10个
processes.sort(key=lambda x: float(x[2]), reverse=True)
return processes[:10]
classServerMonitorApp(App):
"""服务器监控主应用"""
CSS = """
#cpu-container, #memory-container, #disk-container {
border: solid $primary;
padding: 1;
margin: 1;
height: 8;
}
.resource-title {
text-style: bold;
margin-bottom: 1;
}
.resource-value {
text-style: bold;
margin: 1 0;
}
.resource-bar {
margin-top: 1;
}
#process-table {
margin: 1;
}
"""
defcompose(self) -> ComposeResult:
"""组合应用界面"""
yield Header()
with Container():
# 资源监控部分
yield Static("📊 服务器资源监控", classes="title")
yield ResourceMonitor()
# 进程列表部分
yield Static("🔄 运行中进程 (Top 10)", classes="title")
yield ProcessTable()
yield Footer()
defon_key(self, event):
"""键盘事件处理"""
if event.key == "q":
self.exit()
elif event.key == "r":
self.bell() # 刷新提示音
self.query_one(ProcessTable).update_processes()
if __name__ == "__main__":
app = ServerMonitorApp()
app.run()
运行这个应用,你会得到一个实时的终端监控面板。按 q 退出,按 r 手动刷新进程列表。
5. RQ + Redis:轻量级任务队列
痛点:需要异步处理任务(发邮件、生成报表),但Celery太复杂。
解决方案:RQ(Redis Queue)是Python最简单的任务队列,专为中小规模设计。
什么情况下选择RQ?
内部工具规模:每天几千到几万任务 开发速度优先:配置简单,快速上手 已有Redis:利用现有基础设施 调试方便:RQ Dashboard提供Web监控界面
实战:异步邮件发送系统
# worker.py - 工作进程
import time
from redis import Redis
from rq import Worker, Queue, Connection
from email_utils import send_email
# 监听名为'emails'的队列
listen = ['emails']
if __name__ == '__main__':
redis_conn = Redis(host='localhost', port=6379)
with Connection(redis_conn):
worker = Worker(list(map(Queue, listen)))
worker.work()
# email_utils.py - 邮件发送函数
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import logging
defsend_email(to_email: str, subject: str, body: str) -> bool:
"""发送邮件的任务函数"""
try:
# 这里简化了邮件配置,实际使用时需要配置SMTP
msg = MIMEMultipart()
msg['From'] = '[email protected]'
msg['To'] = to_email
msg['Subject'] = subject
msg.attach(MIMEText(body, 'html'))
# 模拟发送过程
logging.info(f"准备发送邮件给 {to_email}: {subject}")
time.sleep(2) # 模拟网络延迟
# 实际发送代码(注释掉,避免误发)
# with smtplib.SMTP('smtp.company.com', 587) as server:
# server.starttls()
# server.login('user', 'password')
# server.send_message(msg)
logging.info(f"✅ 邮件发送成功: {to_email}")
returnTrue
except Exception as e:
logging.error(f"❌ 邮件发送失败: {e}")
returnFalse
# task_sender.py - 任务提交
from redis import Redis
from rq import Queue
from datetime import datetime
defqueue_email_task(to_email: str, subject: str, body: str):
"""将邮件任务加入队列"""
redis_conn = Redis(host='localhost', port=6379)
q = Queue('emails', connection=redis_conn)
# 将任务加入队列
job = q.enqueue(
'email_utils.send_email',
to_email,
subject,
body,
# 任务配置
job_timeout=30, # 30秒超时
result_ttl=3600# 结果保存1小时
)
print(f"📧 邮件任务已加入队列:")
print(f" 任务ID: {job.id}")
print(f" 收件人: {to_email}")
print(f" 主题: {subject}")
print(f" 排队时间: {datetime.now()}")
return job.id
# 使用示例
if __name__ == "__main__":
# 启动工作进程的命令行:
# rq worker emails
# 提交任务
job_id = queue_email_task(
to_email="[email protected]",
subject="每日报告 - 2024-01-20",
body="<h1>每日业务报告</h1><p>销售额: ¥128,000</p>"
)
# 可以保存job_id到数据库,用于后续查询状态
启动命令:
# 启动Redis(如果还没运行)
redis-server
# 启动工作进程
rq worker emails
# 在另一个终端运行任务提交
python task_sender.py
6. Pandera:数据验证的守护神
痛点:不同部门传来的CSV/Excel格式不一,导致数据处理失败。
解决方案:Pandera为Pandas DataFrame提供类型和约束验证。
为什么需要数据验证?
预防错误:在数据进入系统前发现问题 统一标准:确保所有数据源符合预期格式 文档化:Schema本身就是数据格式的文档 自动化:集成到数据管道中自动验证
实战:员工考勤数据验证
import pandera as pa
from pandera import Column, Check, DataFrameSchema
import pandas as pd
import numpy as np
from datetime import datetime
# 定义数据Schema
attendance_schema = DataFrameSchema({
"employee_id": Column(
str,
checks=[
Check.str_length(6, 6), # 员工ID必须是6位
Check.str_matches(r"^EMP\d{3}$") # 格式: EMP001
],
nullable=False
),
"date": Column(
"datetime64[ns]",
checks=[
Check(lambda d: d <= pd.Timestamp.now()), # 不能是未来日期
Check(lambda d: d >= pd.Timestamp("2024-01-01")) # 2024年之后
],
coerce=True# 尝试转换数据类型
),
"check_in": Column(
"datetime64[ns]",
checks=Check(lambda t: t.strftime("%H:%M") >= "08:00"), # 8点后打卡
nullable=True, # 允许空值(缺勤)
coerce=True
),
"check_out": Column(
"datetime64[ns]",
checks=[
Check(lambda t: t.strftime("%H:%M") <= "20:00"), # 20点前下班
Check(
lambda df: df["check_out"] > df["check_in"],
element_wise=False, # 跨列检查
error="下班时间必须晚于上班时间"
) if df["check_in"].notna().all() elseNone
],
nullable=True,
coerce=True
),
"department": Column(
str,
checks=Check.isin(["技术部", "市场部", "人事部", "财务部"]), # 部门枚举
nullable=False
),
"work_hours": Column(
float,
checks=[
Check.ge(0), # 大于等于0
Check.le(12) # 小于等于12小时
]
)
})
# 测试数据
test_data = pd.DataFrame({
"employee_id": ["EMP001", "EMP002", "INVALID", "EMP003"],
"date": ["2024-01-15", "2024-01-15", "2024-01-15", "2024-01-15"],
"check_in": ["08:30:00", "09:00:00", None, "08:15:00"],
"check_out": ["17:30:00", "18:00:00", None, "19:45:00"],
"department": ["技术部", "市场部", "技术部", "技术部"],
"work_hours": [8.5, 8.0, 0, 11.5]
})
defvalidate_attendance_data(df: pd.DataFrame) -> dict:
"""验证考勤数据并返回详细结果"""
try:
# 验证数据
validated_df = attendance_schema.validate(df)
# 计算统计信息
total_records = len(df)
valid_records = len(validated_df)
invalid_records = total_records - valid_records
# 找出无效记录
invalid_data = []
for idx, row in df.iterrows():
try:
# 尝试验证单行数据
attendance_schema.validate(row.to_frame().T)
except pa.errors.SchemaError as e:
invalid_data.append({
"index": idx,
"employee_id": row.get("employee_id", "未知"),
"error": str(e).split("\n")[0] # 取第一行错误信息
})
return {
"status": "success"if invalid_records == 0else"partial",
"message": f"验证完成: {valid_records}/{total_records} 条记录有效",
"valid_data": validated_df,
"validation_errors": invalid_data,
"summary": {
"total_records": total_records,
"valid_records": valid_records,
"invalid_records": invalid_records,
"valid_percentage": round(valid_records / total_records * 100, 2)
}
}
except pa.errors.SchemaError as e:
return {
"status": "error",
"message": f"数据验证失败: {str(e)[:100]}...",
"validation_errors": [{"error": str(e)}]
}
defprocess_attendance_file(file_path: str):
"""处理考勤文件的主函数"""
print(f"📂 处理文件: {file_path}")
try:
# 读取数据
df = pd.read_csv(file_path)
print(f" 读取到 {len(df)} 条记录")
# 验证数据
result = validate_attendance_data(df)
# 输出结果
print(f"\n🔍 验证结果: {result['message']}")
if result["validation_errors"]:
print("\n❌ 发现错误记录:")
for error in result["validation_errors"][:5]: # 只显示前5个错误
print(f" 第{error['index']+1}行 - 员工{error['employee_id']}: {error['error']}")
if len(result["validation_errors"]) > 5:
print(f" ... 还有 {len(result["validation_errors"]) - 5} 个错误")
if result["status"] in ["success", "partial"]:
print("\n📊 数据统计:")
for key, value in result["summary"].items():
print(f" {key}: {value}")
# 保存有效数据
output_path = file_path.replace(".csv", "_validated.csv")
result["valid_data"].to_csv(output_path, index=False)
print(f"\n💾 有效数据已保存至: {output_path}")
return result
except Exception as e:
print(f"❌ 处理失败: {e}")
return {"status": "error", "message": str(e)}
# 模拟使用
if __name__ == "__main__":
# 创建示例文件
test_data.to_csv("attendance_sample.csv", index=False)
# 处理文件
process_attendance_file("attendance_sample.csv")
这个验证系统可以确保HR部门上传的考勤数据格式正确,避免因数据问题导致薪资计算错误。
7. Prefect:现代化工作流编排
痛点:Airflow配置复杂,Cron功能有限,需要可靠的任务调度。
解决方案:Prefect是新一代的工作流编排工具,API设计直观,调试方便。
内部工具中的典型用例:
定期报告:每天/每周自动生成并发送报表 数据同步:不同系统间的数据定时同步 监控告警:定时检查系统状态并发送通知 批量处理:夜间执行耗时任务
实战:自动化日报系统
from prefect import flow, task, get_run_logger
from prefect.task_runners import SequentialTaskRunner
from datetime import datetime, timedelta
import pandas as pd
import smtplib
from email.mime.text import MIMEText
from typing import Dict, List
import json
@task(retries=2, retry_delay_seconds=30)
deffetch_sales_data(date: datetime) -> pd.DataFrame:
"""获取销售数据"""
logger = get_run_logger()
logger.info(f"📊 获取 {date.date()} 的销售数据")
# 模拟API调用
import random
data = {
"product": [f"产品_{i}"for i in range(5)],
"quantity": [random.randint(10, 100) for _ in range(5)],
"revenue": [random.randint(1000, 5000) for _ in range(5)]
}
df = pd.DataFrame(data)
logger.info(f"获取到 {len(df)} 条销售记录")
return df
@task
deffetch_user_activity(date: datetime) -> Dict:
"""获取用户活跃数据"""
logger = get_run_logger()
logger.info(f"👥 获取 {date.date()} 的用户活跃数据")
# 模拟数据
return {
"active_users": 1245,
"new_users": 78,
"avg_session_minutes": 8.5,
"feature_usage": {
"feature_a": 890,
"feature_b": 645,
"feature_c": 432
}
}
@task
defgenerate_daily_report(
sales_data: pd.DataFrame,
user_activity: Dict,
report_date: datetime
) -> str:
"""生成日报内容"""
logger = get_run_logger()
logger.info("📝 生成日报内容")
# 计算销售统计
total_revenue = sales_data["revenue"].sum()
total_quantity = sales_data["quantity"].sum()
top_product = sales_data.loc[sales_data["revenue"].idxmax()]
# 生成报告
report = f"""
📈 每日业务报告 - {report_date.date()}
{'='*40}
🛒 销售表现:
- 总销售额: ¥{total_revenue:,}
- 总销量: {total_quantity} 件
- 最畅销产品: {top_product['product']} (¥{top_product['revenue']:,})
👥 用户活跃:
- 活跃用户: {user_activity['active_users']} 人
- 新增用户: {user_activity['new_users']} 人
- 平均使用时长: {user_activity['avg_session_minutes']} 分钟
🎯 重点功能使用:
"""
for feature, count in user_activity["feature_usage"].items():
report += f" - {feature}: {count} 次使用\n"
report += f"\n⏰ 报告生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
logger.info(f"日报生成完成,长度: {len(report)} 字符")
return report
@task
defsend_email_report(report_content: str, recipients: List[str]):
"""发送邮件报告"""
logger = get_run_logger()
logger.info(f"📧 发送报告给 {len(recipients)} 个收件人")
# 模拟发送(实际使用时需要配置SMTP)
for recipient in recipients:
logger.info(f" -> 发送给: {recipient}")
logger.info("✅ 邮件发送任务完成")
returnTrue
@task
defsave_report_to_disk(report_content: str, report_date: datetime):
"""保存报告到本地"""
logger = get_run_logger()
filename = f"daily_report_{report_date.date()}.txt"
with open(filename, "w", encoding="utf-8") as f:
f.write(report_content)
logger.info(f"💾 报告已保存至: {filename}")
return filename
@flow(
name="daily-business-report",
task_runner=SequentialTaskRunner(),
description="生成并发送每日业务报告"
)
defdaily_report_flow():
"""每日报告主工作流"""
logger = get_run_logger()
logger.info("🚀 开始执行每日报告工作流")
# 报告日期(默认为昨天)
report_date = datetime.now() - timedelta(days=1)
try:
# 并行获取数据
sales_data = fetch_sales_data(report_date)
user_activity = fetch_user_activity(report_date)
# 生成报告
report_content = generate_daily_report(sales_data, user_activity, report_date)
# 并行执行后续任务
save_task = save_report_to_disk.submit(report_content, report_date)
email_task = send_email_report.submit(
report_content,
["[email protected]", "[email protected]"]
)
# 等待所有任务完成
save_result = save_task.result()
email_result = email_task.result()
logger.info(f"✅ 工作流执行完成")
logger.info(f" 报告文件: {save_result}")
logger.info(f" 邮件发送: {'成功'if email_result else'失败'}")
return {
"status": "success",
"report_file": save_result,
"email_sent": email_result,
"report_date": report_date.isoformat()
}
except Exception as e:
logger.error(f"❌ 工作流执行失败: {e}")
return {"status": "error", "error": str(e)}
# 辅助函数:手动触发报告
deftrigger_manual_report():
"""手动触发日报生成"""
print("🎯 手动触发日报生成")
result = daily_report_flow()
print(f"结果: {json.dumps(result, indent=2, ensure_ascii=False)}")
return result
# 部署为定时任务
if __name__ == "__main__":
# 方式1:直接运行(测试用)
# trigger_manual_report()
# 方式2:部署到Prefect服务器
# 1. 启动Prefect服务: prefect server start
# 2. 部署这个flow: prefect deployment create daily_report.py:daily_report_flow
# 3. 设置定时规则: 每天上午9点运行
# 方式3:注册为Cron任务
print("📋 使用说明:")
print("1. 直接运行: python daily_report.py")
print("2. 部署到Prefect: prefect deployment create ...")
print("3. 定时执行: 每天上午9点自动运行")
这个工作流可以自动收集数据、生成报告、保存文件并发送邮件,完全自动化整个日报流程。
写在最后
回顾这7个Python库,它们各有所长,但共同点是:让内部工具开发变得简单高效。
前端展示:Reflex和NiceGUI让你摆脱JavaScript 后端服务:FastAPI提供高性能API 终端工具:Textual为命令行增添色彩 任务处理:RQ和Prefect处理异步和定时任务 数据质量:Pandera守护数据入口
真正的技术价值往往不是最炫酷的算法,而是那些每天被使用数十次、节省团队数小时的内部工具。这些工具可能永远不会出现在产品介绍里,但它们是公司高效运转的“毛细血管”。
现在,当经理再问“能不能快速搞个工具”时,你可以自信地说:“没问题,今天下班前就能用上。”
你在工作中还用过哪些“神器级”的Python库?或者有什么特别的内部工具需求?欢迎在评论区分享你的经验和想法~
🏴☠️宝藏级🏴☠️ 原创公众号『数据STUDIO』内容超级硬核。公众号以Python为核心语言,垂直于数据科学领域,包括可戳👉Python|MySQL|数据分析|数据可视化|机器学习与数据挖掘|爬虫等,从入门到进阶!
长按👇关注- 数据STUDIO -设为星标,干货速递