Python自动化操作滴答清单全攻略:从API封装到实战技巧
滴答清单作为国内用户基数庞大的任务管理工具,其网页版API的开放为开发者提供了丰富的自动化可能。本文将带你从零构建一个高可用的Python封装库,不仅能实现基础的增删改查,还会深入探讨异常处理、性能优化和实际工作流整合等进阶话题。
1. 环境准备与基础配置
在开始编写自动化脚本前,需要完成几个关键准备工作。首先确保你的开发环境已安装Python 3.7+版本,这是运行现代HTTP库的最低要求。推荐使用虚拟环境隔离依赖:
python -m venv ticktick_env source ticktick_env/bin/activate # Linux/Mac ticktick_env\Scripts\activate # Windows安装必要的依赖库:
pip install requests python-dotenv关键配置步骤:
- 登录滴答清单网页版,通过浏览器开发者工具获取Cookie
- 创建
.env文件安全存储凭证:TICKTICK_COOKIE=your_actual_cookie_value USER_AGENT=Mozilla/5.0... - 初始化基础请求会话:
import os from dotenv import load_dotenv from requests import Session load_dotenv() class TicktickClient: def __init__(self): self.session = Session() self.base_url = "https://api.dida365.com" self._setup_headers() def _setup_headers(self): self.session.headers.update({ "User-Agent": os.getenv("USER_AGENT"), "Cookie": f"t={os.getenv('TICKTICK_COOKIE')}", "X-Device": "web_app" })2. 核心API方法封装实战
2.1 智能任务创建系统
基础的创建任务功能可以扩展为支持多种场景的智能系统。以下是一个增强版的create_task方法:
def create_task(self, title, content="", due_date=None, priority=0, project_id="inbox", tags=None, reminders=None): """ 创建任务并返回任务ID :param due_date: 支持datetime对象或ISO格式字符串 :param reminders: 提醒时间列表,如["-PT30M", "-PT1H"] """ task_data = { "title": title, "content": content, "priority": min(max(priority, 0), 3), # 限制优先级范围 "projectId": self._validate_project(project_id), "tags": tags or [] } if due_date: task_data["dueDate"] = self._format_datetime(due_date) if reminders: task_data["reminders"] = [ {"id": self._generate_id(), "trigger": f"TRIGGER:{r}"} for r in reminders ] response = self.session.post( f"{self.base_url}/api/v2/task", json=task_data ) return response.json().get("id")配套的辅助方法:
import uuid from datetime import datetime def _generate_id(self): return str(uuid.uuid4()).replace("-", "")[:24] def _format_datetime(self, dt): if isinstance(dt, str): return dt return dt.strftime("%Y-%m-%dT%H:%M:%S.000+0000") def _validate_project(self, project_id): # 实现项目ID校验逻辑 return project_id2.2 高级查询功能实现
滴答清单的搜索API功能强大但参数复杂,我们可以构建更友好的查询接口:
def search_tasks(self, keyword=None, project=None, status="active", limit=100, start_date=None): """ 多功能任务搜索 :param status: active/completed/all :return: 匹配的任务列表 """ params = { "limit": limit, "status": status } if keyword: params["query"] = keyword if project: params["projectId"] = self._validate_project(project) if start_date: params["from"] = self._format_date(start_date) response = self.session.get( f"{self.base_url}/api/v2/search/task", params=params ) return self._process_search_results(response.json())典型查询示例:
# 查询"开发"相关的未完成任务 tasks = client.search_tasks(keyword="开发", status="active") # 获取"项目A"中本周创建的所有任务 from datetime import datetime, timedelta week_start = datetime.now() - timedelta(days=7) tasks = client.search_tasks(project="项目A", start_date=week_start)3. 批处理与性能优化
当需要操作大量任务时,单次API调用的效率会成为瓶颈。滴答清单提供了批量操作接口,我们可以利用它实现高效处理:
def batch_update_tasks(self, task_updates): """ 批量更新任务 :param task_updates: 包含id和更新字段的字典列表 :return: 操作结果 """ batch_data = { "update": [ { "id": task["id"], **{k:v for k,v in task.items() if k != "id"} } for task in task_updates ], "add": [], "delete": [] } response = self.session.post( f"{self.base_url}/api/v2/batch/task", json=batch_data ) return response.status_code == 200性能优化技巧:
- 使用连接池:
requests.Session()会自动保持连接 - 合理设置超时:
timeout=(3.05, 27)避免长时间阻塞 - 实现本地缓存:对不常变的数据如项目列表进行缓存
from functools import lru_cache @lru_cache(maxsize=1) def get_projects(self): """带缓存的获取项目列表""" response = self.session.get(f"{self.base_url}/api/v2/projects") return { proj["name"]: proj["id"] for proj in response.json() }4. 异常处理与日志系统
稳定的自动化脚本需要完善的错误处理机制。以下是推荐的异常处理模式:
import logging from requests.exceptions import RequestException logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger("TicktickAPI") def safe_api_call(self, method, endpoint, **kwargs): try: response = getattr(self.session, method)( f"{self.base_url}{endpoint}", **kwargs ) response.raise_for_status() return response.json() except RequestException as e: logger.error(f"API请求失败: {str(e)}") if hasattr(e, "response"): logger.debug(f"响应内容: {e.response.text}") raise TicktickAPIError(f"API操作失败: {str(e)}")自定义异常类:
class TicktickAPIError(Exception): """滴答清单API异常基类""" pass class AuthenticationError(TicktickAPIError): """认证失败异常""" pass class RateLimitExceeded(TicktickAPIError): """API调用频率限制""" pass5. 实际应用场景示例
5.1 晨间工作流自动化
def morning_routine(client): # 创建当日重点任务 client.create_task( "晨会准备", "检查昨日进度和今日计划", due_date=datetime.now().replace(hour=9, minute=30), priority=2, project_id="工作" ) # 查询逾期任务 overdue = client.search_tasks( status="active", start_date=datetime.now() - timedelta(days=1) ) if overdue: # 自动延期一天 updates = [{ "id": task["id"], "dueDate": (datetime.now() + timedelta(days=1)) } for task in overdue] client.batch_update_tasks(updates)5.2 项目进度跟踪器
def project_report(client, project_name): tasks = client.search_tasks(project=project_name) stats = { "total": len(tasks), "completed": sum(1 for t in tasks if t.get("status") == 1), "high_priority": sum(1 for t in tasks if t.get("priority", 0) > 1) } print(f""" {project_name}项目状态报告: - 总任务数: {stats['total']} - 已完成: {stats['completed']} ({stats['completed']/stats['total']:.0%}) - 高优先级任务: {stats['high_priority']} """) return [t for t in tasks if t.get("priority", 0) > 1]5.3 智能提醒系统
def setup_intelligent_reminders(client): # 获取所有无提醒的高优先级任务 tasks = [ t for t in client.search_tasks() if t.get("priority", 0) > 1 and not t.get("reminders") ] for task in tasks: due_date = datetime.fromisoformat(task["dueDate"]) # 根据截止时间设置智能提醒 if (due_date - datetime.now()) < timedelta(hours=24): reminders = ["-PT1H"] # 1小时前提醒 else: reminders = ["-PT12H", "-PT1H"] # 12小时和1小时前 client.update_task( task["id"], reminders=reminders )6. 安全最佳实践
凭证管理:
- 永远不要将Cookie硬编码在脚本中
- 使用环境变量或加密存储
- 定期轮换API凭证
请求限制:
import time class RateLimitedClient: def __init__(self, client, max_calls=30, period=60): self.client = client self.max_calls = max_calls self.period = period self.calls = [] def call_api(self, method, *args, **kwargs): now = time.time() # 移除超过时间窗口的记录 self.calls = [t for t in self.calls if t > now - self.period] if len(self.calls) >= self.max_calls: sleep_time = self.period - (now - self.calls[0]) time.sleep(sleep_time) result = getattr(self.client, method)(*args, **kwargs) self.calls.append(time.time()) return result数据备份:
def backup_tasks(client, file_path): tasks = client.search_tasks(status="all") with open(file_path, "w") as f: json.dump(tasks, f, indent=2)
7. 扩展功能开发
7.1 与日历系统集成
from icalendar import Calendar, Event def export_to_ical(client, project_name): cal = Calendar() tasks = client.search_tasks(project=project_name) for task in tasks: event = Event() event.add("summary", task["title"]) event.add("description", task.get("content", "")) if task.get("dueDate"): event.add("dtstart", datetime.fromisoformat(task["dueDate"])) event.add("dtend", datetime.fromisoformat(task["dueDate"]) + timedelta(hours=1)) cal.add_component(event) with open(f"{project_name}.ics", "wb") as f: f.write(cal.to_ical())7.2 命令行界面开发
使用Click库创建用户友好的CLI:
import click @click.group() def cli(): pass @cli.command() @click.option("--project", help="指定项目名称") def list_tasks(project): client = TicktickClient() tasks = client.search_tasks(project=project) for task in tasks: click.echo(f"{task['id']}: {task['title']}") @cli.command() @click.argument("title") @click.option("--due", help="截止日期(YYYY-MM-DD)") def add_task(title, due): client = TicktickClient() task_id = client.create_task(title, due_date=due) click.echo(f"创建成功! 任务ID: {task_id}") if __name__ == "__main__": cli()使用示例:
python ticktick_cli.py list-tasks --project "工作" python ticktick_cli.py add-task "完成API文档" --due 2023-08-15