养植物最难的不是技术,而是记性。多肉两周浇一次,绿萝五天一次,薄荷夏天可能两天就得补水,一旦记混了,要么旱死要么烂根。市面上的提醒App广告多、还要联网权限,其实我们完全可以自己动手,用Python加SQLite做一个本地运行的植物浇水提醒系统。SQLite是一个文件型数据库,不需要安装任何服务端程序,整个系统就是一个py脚本加一个db文件,双击就能跑,特别适合放在家里那台旧笔记本上常年运行。

一、数据库表结构设计:两张表搞定核心逻辑
设计表结构之前先想清楚业务流程:我有哪些植物,每种植物多久浇一次水,上次浇水是什么时候,下次该什么时候浇。围绕这几个问题,最简洁的方案是两张表——plants表存植物的基础信息,water_logs表存每一次的浇水记录。
plants表建议包含这些字段:id作为主键自增,name存植物名称,water_cycle_days存浇水周期(单位天),last_watered存最近一次浇水日期,created_at记录录入时间。这里有个设计取舍:last_watered其实可以从water_logs表里查出来,单独冗余一份是为了查询效率——每天跑提醒任务时只需要扫一张表,不用做联表聚合。数据量小的时候差别不明显,但养成这种权衡意识对以后做项目很有帮助。
CREATE TABLE IF NOT EXISTS plants (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
water_cycle_days INTEGER NOT NULL DEFAULT 7,
last_watered DATE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS water_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plant_id INTEGER NOT NULL,
watered_date DATE NOT NULL,
note TEXT DEFAULT '',
FOREIGN KEY (plant_id) REFERENCES plants(id)
);
-- 给plant_id建索引,查某株植物的浇水历史时速度快很多
CREATE INDEX IF NOT EXISTS idx_water_logs_plant ON water_logs(plant_id);water_logs表的作用是留存历史记录。假设你某次出差半个月,回来后想看看哪些植物被旱得最惨,或者想统计某株植物一年浇了多少次水,这些分析都依赖完整的日志表。note字段可以记一些备注,比如“加了营养液”或者“浇透了”,方便日后复盘养护习惯。
二、核心代码:增删改查与到期计算
先写基础操作。Python内置了sqlite3模块,直接import就能用,唯一要注意的是连接数据库后要记得commit,否则数据只存在内存里,程序一退出就丢了。
import sqlite3
from datetime import date, timedelta
DB_NAME = "plants.db"
def get_conn():
conn = sqlite3.connect(DB_NAME)
conn.row_factory = sqlite3.Row # 让查询结果可以按列名取值
return conn
def add_plant(name, cycle_days):
with get_conn() as conn:
conn.execute(
"INSERT INTO plants (name, water_cycle_days, last_watered) VALUES (?, ?, ?)",
(name, cycle_days, date.today())
)
def water_plant(plant_id, note=""):
today = date.today()
with get_conn() as conn:
conn.execute("UPDATE plants SET last_watered = ? WHERE id = ?", (today, plant_id))
conn.execute(
"INSERT INTO water_logs (plant_id, watered_date, note) VALUES (?, ?, ?)",
(plant_id, today, note)
)上面的代码里有两个细节值得注意。一是用with语句管理连接,代码块结束时会自动提交事务,比手动调用commit更安全;二是SQL语句全部使用参数化写法,问号占位符传值,这样可以杜绝SQL注入问题——虽然这是自己用的小工具,但好习惯从第一行代码就要养。
接下来是整个系统的核心:计算哪些植物今天该浇水。逻辑很简单,用last_watered加上water_cycle_days得到下次浇水日,和今天比较即可。这个计算既可以放在Python里做,也可以直接交给SQL完成。我更推荐SQL方案,一条查询语句就把活干了:
def get_due_plants():
"""查出今天及之前就该浇水的植物"""
sql = """
SELECT id, name, water_cycle_days, last_watered,
date(last_watered, '+' || water_cycle_days || ' days') AS next_date
FROM plants
WHERE date(last_watered, '+' || water_cycle_days || ' days') <= date('now', 'localtime')
"""
with get_conn() as conn:
return conn.execute(sql).fetchall()
def show_reminder():
due = get_due_plants()
if not due:
print("今天所有植物都不缺水,放心吧!")
return
for p in due:
overdue = (date.today() - (
date.fromisoformat(p["next_date"]))).days
print(f"[{p['name']}] 该浇水了!周期{p['water_cycle_days']}天,"
f"下次浇水日是 {p['next_date']},已超期 {overdue} 天")这里用到了SQLite的一个实用函数date(),它可以对日期做加减运算,表达式里的双竖线是SQL的字符串拼接符号。另外要提醒一点:date('now')默认返回UTC时间,和北京时间差8小时,所以一定要加上'localtime'修饰,否则晚上八点以后跑脚本,日期会差一天,这是新手最容易踩的坑之一。
三、让提醒自动跑起来:定时任务的三种方案
有了查询逻辑,还得让它每天自动执行。方案主要有三种,各有适用场景。
第一种是程序内部死循环加time.sleep,简单粗暴但不可靠,一旦程序崩溃或电脑重启,提醒就中断了。第二种是Windows下用任务计划程序,Linux或Mac下用crontab,让操作系统每天固定时间调用脚本,这是我最推荐的方式,稳定且不占常驻内存。以Linux为例,执行crontab -e后添加一行0 9 * * * python3 /home/user/plants/reminder.py,就表示每天早上九点执行一次。第三种是在Python里用schedule库做进程内调度,适合脚本本身还要提供交互功能的场景。
无论哪种方案,都建议在脚本里加一个防重复提醒的小机制:建一张reminders表,记录每次提醒的日期,当天已经提醒过就不再发。这个逻辑用SQLite实现只需要几行代码:
def should_remind_today():
today = date.today().isoformat()
with get_conn() as conn:
row = conn.execute(
"SELECT COUNT(*) AS c FROM reminders WHERE remind_date = ?", (today,)
).fetchone()
if row["c"] == 0:
conn.execute("INSERT INTO reminders (remind_date) VALUES (?)", (today,))
return True
return False最后再说说提醒的触达方式。如果在局域网内跑,可以用一个叫pushplus或者Server酱的微信推送服务,往它的接口发个HTTP请求就能收到微信消息,代码里用requests.post即可;如果不想联网,Windows下弹个消息框、Linux下发封邮件也完全够用。
四、进阶优化与数据安全
基础版跑起来之后,还有几个方向可以继续打磨。首先是季节差异:多肉夏天休眠要控水,冬天半个月浇一次也没问题,可以在plants表加一个cycle_summer和cycle_winter字段,查询时根据当前月份选择对应周期,代码里判断一下月份再拼接SQL就行。
其次是数据备份。SQLite虽然是单文件数据库,但如果程序正在写入时直接复制文件,有可能拿到损坏的副本。正确的做法是用它自带的.backup命令,或者在Python里调用conn.backup()方法,这是官方提供的在线备份接口,写入过程中也能安全复制:
import shutil
def backup_db():
src = sqlite3.connect(DB_NAME)
dst = sqlite3.connect(f"plants_backup_{date.today().isoformat()}.db")
with dst:
src.backup(dst)
src.close()
dst.close()把这个备份函数也挂到定时任务里,每周跑一次,你的浇水数据就万无一失了。做完这个项目你会发现,SQLite的建表、索引、日期函数、事务这些零散知识点,在真实需求里串起来之后理解会深刻得多。接下来你还可以给它加上命令行交互菜单、导出浇水统计报表,甚至接一个温湿度传感器实现全自动判断,这些扩展都能在这个小小的db文件上继续生长。
SQLite植物浇水提醒Python定时任务修改时间:2026-09-13 00:08:48