1. 项目概述:用AI重新理解你的生活记录
你有没有过这样的感觉?豆瓣上标记了几百本书和电影,微信读书里躺着一堆划线笔记,Flomo里随手记下的日常灵感,还有各种App里的日记和想法……这些数据散落在各处,就像一座座孤岛。我们每天都在产生大量的生活记录,但它们大多只是沉睡的数据,很少被真正回顾、分析和理解。LifeInk AI这个项目,正是为了解决这个痛点而生。
相关服务:关于我们
简单来说,LifeInk AI是一个 个人专属的AI智能体 。它的核心目标,是把你分散在各个平台(目前主要是豆瓣,未来会扩展)的书、影、音、日记等个人数据聚合起来,然后利用AI的能力,帮你重新“理解”和“回顾”自己的生活。你可以和它对话,问它“我这个月看了哪些悬疑片?”、“去年我最喜欢的书是哪本?为什么?”,它也能自动为你生成周报、月报甚至年度总结,用可视化的方式呈现你的阅读趋势、兴趣偏好。这不仅仅是一个数据同步工具,更是一个基于你个人数据训练的、懂你的数字生活助手。
这个项目目前处于活跃开发阶段,由社区驱动。它最初的设计思路,是围绕
Notion
这个强大的知识管理工具来构建数据中枢,并利用
Playwright
和
Requests
等工具从源头抓取数据。技术栈选择了现代的Python 3,并开始采用
uv
作为新一代的Python包管理器和项目运行器,以提高依赖管理和开发体验。对于开发者而言,项目结构清晰,模块化程度高,特别是新版
src/feature
目录下的设计,体现了向更健壮、可扩展的AI Agent架构演进的思路。
2. 核心设计思路与技术选型解析
2.1 为什么选择“数据聚合+AI分析”的路径?
在构思个人数据管理方案时,我们通常面临几个选择:手动整理、使用现成的年度报告功能,或者自己搭建系统。手动整理耗时耗力,难以坚持;平台自带的报告往往维度单一,且数据被锁在各自的围墙花园里。LifeInk AI选择的第三条路,其核心逻辑在于 数据主权 和 深度个性化 。
首先,通过爬虫技术将数据“拿回来”,存储在你完全可控的介质中(如Notion数据库),解决了数据分散和平台依赖的问题。其次,引入AI进行分析和对话,是为了解决“数据沉默”的问题。单纯的数据罗列没有意义,AI能够充当一个永不疲倦的“数据分析师”和“记忆伙伴”,帮你从数据中发现模式、建立联系、激发回忆。这种“采集-存储-分析-交互”的闭环,是构建真正有用个人知识库的关键。
2.2 技术栈深度剖析:从Requests到Playwright,再到AI Agent
项目在技术选型上经历了明显的演进,这反映了对可靠性、可维护性和未来扩展性的考量。
1. 数据抓取层:Requests与Playwright的取舍
早期的
spider.py
使用
requests
库,这是一种轻量级、高效的HTTP库,适合处理结构清晰的API或简单网页。对于豆瓣某些无需复杂交互的页面(如同步数据到Notion),
requests
足矣。然而,豆瓣等现代网站大量依赖JavaScript动态渲染内容,直接使用
requests
获取到的可能是空壳HTML。因此,新版
feature/community/douban/
模块引入了
Playwright
。
注意 :
Playwright是一个浏览器自动化框架,可以模拟真实用户操作,完美解决动态加载问题。但它的代价是更重的资源消耗和更复杂的部署环境(需要安装浏览器)。项目采用两者并存的方式,是根据不同任务场景做的合理权衡:对可靠性要求高的全量抓取用Playwright,对性能要求高的简单同步用requests。
2. 数据存储与中枢:为什么是Notion? Notion在这里扮演了“数据枢纽”和“呈现界面”的双重角色。其优势非常明显:
- 结构化与灵活性兼备 :数据库(Database)提供了严格的结构化存储,而页面(Page)又能容纳富文本、图片等,非常适合存储书影音条目及其评论、日记。
- 强大的API :Notion官方API成熟稳定,支持增删改查,便于程序化操作。
- 卓越的可视化 :通过Notion自身的数据看板、画廊视图,可以快速实现项目Roadmap中提到的“可视化看板”部分原型。
- 最终用户友好 :即使不打开LifeInk AI的界面,用户也可以在熟悉的Notion环境中浏览、搜索自己的数据。
3. 项目结构与未来架构:
uv
与模块化
项目根目录的
main.py
和旧模块代表了早期快速原型。而
src/feature
目录则代表了面向未来的重构方向。使用
uv
(一个用Rust写的极速Python包管理工具)管理依赖,比传统的
pip
+
venv
组合更快、更一致。模块化设计(如
douban/
,
weread/
,
flomo/
)明确了数据源的边界,
models/
目录使用
Pydantic
进行数据验证和序列化,确保了内部数据流的类型安全和清晰。这种结构为后续接入AI Agent(Phase 2)打下了坚实基础——每个数据源模块提供干净、结构化的数据,AI模块消费这些数据提供服务。
4. AI能力的集成前瞻 虽然当前代码中AI部分尚未实现,但从Roadmap可以看出,其构想并非简单的调用ChatGPT API。它计划构建一个“基于个人数据上下文的AI对话接口”。这意味着需要:
- 构建个人数据向量库 :将书影音标题、标签、评论等内容嵌入(Embedding)成向量,存储到向量数据库(如Chroma、Qdrant)。
- 实现检索增强生成(RAG) :当用户提问时,先从其个人向量库中检索最相关的记录,再将记录作为上下文提供给大语言模型(LLM),生成个性化回答。
- 报告生成引擎 :利用LLM的总结和归纳能力,结合模板,将一段时间内的数据转化为结构化的叙事性报告。
3. 核心模块实操与代码解析
3.1 豆瓣数据抓取:Playwright实战详解
新版豆瓣抓取模块位于
src/feature/community/douban/
,是项目的核心数据入口。我们深入看一下其实现。
scrapers/
目录下的抓取器
:这里通常会按数据类型细分,例如
book_scraper.py
,
movie_scraper.py
。以抓取“已读图书”为例,一个健壮的抓取器需要考虑以下要点:
# 示例代码结构 (src/feature/community/douban/scrapers/book_scraper.py)
from playwright.sync_api import Page, TimeoutError as PlaywrightTimeoutError
from ..models import BookItem # 导入Pydantic数据模型
import logging
import time
logger = logging.getLogger(__name__)
class BookScraper:
def __init__(self, page: Page):
self.page = page
def scrape_collection(self, collection_url: str, max_items: int = None) -> list[BookItem]:
"""抓取某个收藏夹(如‘已读’)的图书列表"""
books = []
self.page.goto(collection_url)
time.sleep(2) # 初始等待,可优化为等待特定元素
while True:
# 1. 定位当前页所有图书条目
item_elements = self.page.locator("div.subject-item") # 豆瓣图书条目选择器
count = item_elements.count()
for i in range(count):
if max_items and len(books) >= max_items:
logger.info(f"已达到最大抓取数量 {max_items}")
return books
item = item_elements.nth(i)
try:
# 2. 解析单个条目信息
title_elem = item.locator("h2 a")
title = title_elem.inner_text().strip()
url = title_elem.get_attribute("href")
rating_elem = item.locator(".rating-stars")
rating = self._parse_rating(rating_elem) if rating_elements.count() > 0 else None
# ... 解析出版信息、作者、标签等
# 3. 封装成数据模型
book = BookItem(
title=title,
url=url,
rating=rating,
# ... 其他字段
)
books.append(book)
logger.debug(f"已抓取: {title}")
except Exception as e:
logger.error(f"解析第{i}个条目时出错: {e}")
continue # 跳过当前错误条目,继续下一个
# 4. 翻页逻辑
next_button = self.page.locator("span.next a")
if next_button.is_enabled():
next_button.click()
# 等待新内容加载,推荐使用 wait_for_selector 而非固定 sleep
self.page.wait_for_selector("div.subject-item", state="attached", timeout=10000)
time.sleep(1) # 礼貌性延迟,避免请求过快
else:
logger.info("已到达最后一页")
break
return books
def _parse_rating(self, rating_elem):
"""解析评分星级,例如将 'class="rating-stars allstar40"' 解析为 4.0"""
# 实现细节...
pass
关键点解析与避坑指南 :
-
选择器稳定性
:豆瓣的HTML结构可能变动。
div.subject-item这类选择器是脆弱的。更稳健的做法是同时使用多个特征定位,或定期检查更新。 -
等待策略
:
time.sleep是脆弱的。Playwright提供了page.wait_for_selector,page.wait_for_load_state等方法,应优先使用这些方法等待特定元素或状态,这样更高效且抗干扰。 -
错误处理与日志
:循环内每个条目的解析必须用
try...except包裹,防止因单个条目解析失败导致整个任务崩溃。详细的日志(logging)对于调试爬虫至关重要。 -
反爬虫应对
:过于频繁的请求会导致IP被限制。需要在翻页和请求间增加随机延迟(如
time.sleep(random.uniform(1, 3)))。Playwright可以模拟真人鼠标移动,本身比简单请求更不易被识别,但仍需保持礼貌。
login.py
登录管理
:豆瓣登录可能涉及验证码。
Playwright
可以处理二维码登录(如项目描述中提到的“session过期弹二维码”)。思路是:先尝试使用持久化的
cookies
恢复会话;如果失败,则暂停脚本,弹出二维码让用户扫描;用户扫描登录后,
Playwright
捕获新的
cookies
并保存。
session.py
会话持久化
:这是保证用户体验的关键。将
Playwright
上下文(Context)的
cookies
定期保存到文件(如JSON),下次启动时加载,可以避免每次运行都需重新登录。
3.2 与Notion同步:构建个人数据库
这是项目早期就实现的功能,也是数据流动的终点之一。核心是利用Notion API将抓取到的数据写入指定的Notion数据库。
步骤拆解 :
-
获取凭证
:在Notion官网创建集成(Integration),获取
INTERNAL_TOKEN。邀请该集成到你的目标数据库,获取数据库的DATABASE_ID。 -
配置环境变量
:在项目根目录创建
.env文件,安全地存储NOTION_TOKEN和DATABASE_ID。 -
数据模型映射
:定义你的Notion数据库属性(Properties),如“书名”(Title)、“评分”(Number)、“状态”(Select)、“链接”(URL)等。在代码中,需要将
BookItem等数据模型映射到Notion API要求的JSON格式。 -
增量同步
:这是生产级应用必须考虑的。简单的实现是,在插入新数据前,根据唯一标识(如豆瓣URL)查询是否已存在。Notion API支持通过
filter进行查询。更复杂的方案可以记录每条数据的同步时间戳。
# 简化的Notion数据同步示例 (基于 notion-client 库)
from notion_client import Client
import os
from dotenv import load_dotenv
load_dotenv()
notion = Client(auth=os.environ["NOTION_TOKEN"])
def create_book_page_in_notion(database_id: str, book: BookItem):
"""在Notion数据库中创建一本书的页面"""
properties = {
"书名": {"title": [{"text": {"content": book.title}}]},
"评分": {"number": book.rating},
"状态": {"select": {"name": book.status}}, # 如“已读”、“想读”
"链接": {"url": book.url},
"作者": {"rich_text": [{"text": {"content": book.author}}]},
"我的评分": {"number": book.my_rating}, # 个人评分
"标签": {"multi_select": [{"name": tag} for tag in book.tags]},
# ... 其他属性
}
# 检查是否已存在(根据链接)
existing = notion.databases.query(
database_id=database_id,
filter={"property": "链接", "url": {"equals": book.url}}
)
if existing["results"]:
page_id = existing["results"][0]["id"]
# 更新现有页面
notion.pages.update(page_id=page_id, properties=properties)
print(f"已更新: {book.title}")
else:
# 创建新页面
notion.pages.create(parent={"database_id": database_id}, properties=properties)
print(f"已创建: {book.title}")
实操心得 :
- Notion API对属性类型要求严格,确保你传递的数据类型(字符串、数字、日期等)与数据库属性定义完全匹配,否则会报错。
-
Notion页面内容(Content)和属性(Properties)是分开的。上述代码只设置了属性。如果你想在页面里添加详细的评论、笔记,需要使用
children参数来添加块(Blocks),如段落、引述、图片等。 - API有速率限制,在批量同步大量数据时,需要在请求间添加延迟。
3.3 项目初始化与依赖管理:使用uv
新版模块推荐使用
uv
,这是一个趋势。它与
pip
命令兼容,但速度极快,并且集成了虚拟环境管理。
# 进入新版模块目录
cd src/feature
# uv sync 类似于 pip install -r requirements.txt,但更快且能解析锁文件
uv sync
# 运行项目主模块
uv run python -m feature
# 也可以直接使用uv运行脚本
uv run main.py
优势 :
-
极速安装
:依赖解析和安装过程比
pip快一个数量级。 -
可复现性
:
uv lock会生成一个uv.lock文件,精确锁定所有依赖的版本,确保在任何地方都能安装完全相同的环境。 -
一体化
:无需单独操作
venv或virtualenv,uv在uv sync时会自动处理环境。
4. 扩展开发与未来功能实现指南
4.1 接入微信读书(weread)
微信读书的数据抓取比豆瓣更具挑战性,因为其反爬机制更严格,且大量内容在App内。思路有以下几种:
-
模拟移动端请求(推荐)
:使用
mitmproxy等抓包工具,分析微信读书App的API请求。通常可以找到获取书架、阅读进度、笔记等数据的接口。然后使用requests库模拟这些请求,需要携带有效的Cookie或Token。这需要一定的逆向工程能力。 -
浏览器自动化
:在桌面端登录微信读书网页版,使用
Playwright抓取。但网页版功能可能有限,且登录状态可能不稳定。 - 官方API(不存在) :微信读书没有开放个人数据的官方API。
实现要点 :
-
登录态维持
:微信读书的登录态(通常是一个
wr_skey或类似的cookie)有效期较长,但需要妥善保管。可以设计一个类似的session.py来管理。 -
数据模型设计
:定义
WereadBook模型,包含书名、作者、阅读进度、划线笔记、想法等字段。需要与豆瓣的BookItem模型协调,考虑未来统一数据模型的可能性。 - 增量同步 :重点关注“笔记”和“想法”的同步,这些是动态产生的。可以根据笔记的更新时间戳进行增量抓取。
4.2 构建AI对话能力(RAG架构雏形)
这是Phase 2的核心。一个简单的个人数据RAG系统可以这样搭建:
-
数据预处理与向量化 :
- 从Notion数据库(或原始数据源)读取所有书影音条目、评论、日记。
-
对每条记录,生成一个“文本块”。例如,对于一本书,文本块可以是:
标题:《人类简史》 作者:[以色列]尤瓦尔·赫拉利 我的评分:5星 我的评论:这本书从宏观视角... 标签:历史, 科普。 -
使用嵌入模型(如OpenAI的
text-embedding-3-small,或开源的BAAI/bge-small-zh-v1.5)将每个文本块转换为向量。 -
将向量和对应的元数据(如来源、类型、原始ID)存储到本地向量数据库,如
Chroma或Qdrant。
-
问答接口 :
- 接收用户自然语言问题,如“我去年给过5星评价的历史类书籍有哪些?”。
- 将问题同样转换为向量。
- 在向量数据库中执行相似性搜索,找出与问题向量最相关的N个文本块。
- 将这些文本块作为“上下文”,连同原始问题,一起构造提示词(Prompt),发送给大语言模型(如GPT-4, Claude,或本地部署的Llama 3)。
- 将LLM生成的答案返回给用户。
# 极度简化的RAG问答示例
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
# 1. 初始化向量库和LLM
vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=OpenAIEmbeddings())
llm = ChatOpenAI(model="gpt-4-turbo")
# 2. 定义检索链
retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) # 检索最相关的4条
# 3. 定义提示词模板
template = """你是一个基于我个人生活记录的AI助手。请根据以下上下文回答我的问题。
上下文:
{context}
问题:{question}
请仅根据上下文回答。如果上下文不包含相关信息,请说“根据你的记录,我没有找到相关信息”。
答案:"""
prompt = ChatPromptTemplate.from_template(template)
# 4. 构建处理链
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
)
# 5. 提问
answer = rag_chain.invoke("我去年给过5星评价的历史类书籍有哪些?")
print(answer.content)
4.3 自动报告生成
基于AI的自动报告生成,可以视为一个“文本摘要”和“数据叙事”任务。步骤包括:
- 数据聚合 :从数据库中筛选出特定时间段(如最近一周、一个月)的所有数据条目。
- 关键信息提取 :使用LLM从这些数据中提取关键事件、高频标签、情感倾向(从评论中)、评分变化等。
- 叙事生成 :根据预定义的报告模板(如“本周你主要沉浸在科幻世界中,看了《三体》并打了5星...”),让LLM将提取的信息填充进去,生成连贯、有个人风格的叙述。
-
格式化输出
:将生成的Markdown文本,通过其他库(如
weasyprint)转换为PDF,或直接发布到静态网页。
5. 部署、维护与常见问题排查
5.1 本地运行与自动化部署
对于个人使用,本地运行是最简单的。你可以设置一个定时任务(如Linux的
cron
或Windows的“任务计划程序”),定期执行同步脚本。
示例Cron Job(每天凌晨3点运行) :
0 3 * * * cd /path/to/your/lifeink && /usr/local/bin/uv run python main.py >> /path/to/log/sync.log 2>&1
对于希望24小时提供服务(如AI对话接口),可以考虑部署到云服务器或VPS。你需要:
- 安装Python、Playwright浏览器等依赖。
-
使用
screen,tmux或systemd服务来守护进程。 - 配置反向代理(如Nginx)将AI接口暴露给公网( 务必注意安全,添加认证! )。
5.2 常见问题与解决方案速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 豆瓣抓取失败,页面空白或元素找不到 |
1. 网站结构更新
2. 登录态失效 3. 反爬虫限制 |
1. 检查并更新Playwright选择器。
2. 删除旧的
cookies
文件,重新运行登录流程。
3. 增加请求间隔,添加随机延迟,考虑使用代理IP。 |
Notion API报错
400
或
object invalid
|
1. Token或Database ID错误
2. 请求体属性格式错误 3. 属性类型不匹配 |
1. 检查
.env
文件变量名和值是否正确,数据库是否已邀请集成。
2. 使用
print
或日志输出即将发送的
properties
JSON,与Notion数据库属性定义仔细比对。
3. 确保数字、日期、选择等属性的值类型完全符合API文档要求。 |
uv sync
或
pip install
失败
|
1. 网络问题
2. 依赖冲突 3. 系统依赖缺失(如Playwright) |
1. 更换PyPI源(如清华源)。
2. 检查
requirements.txt
或
pyproject.toml
,尝试逐个安装定位冲突包。
3. 对于Playwright,运行
playwright install
安装浏览器。
|
| AI对话回答与我的数据无关 |
1. 向量检索失败
2. 上下文未正确传入LLM 3. 嵌入模型不匹配 |
1. 检查向量数据库是否成功存储了数据,尝试用简单关键词搜索测试。
2. 打印调试
retriever
获取到的
context
,看是否相关。
3. 确保问答时使用的嵌入模型与建库时相同。 |
| 脚本运行一段时间后内存占用过高 |
1. Playwright浏览器实例未关闭
2. 数据未及时释放 |
1. 确保在
try...finally
块中或使用上下文管理器正确关闭
browser
和
context
。
2. 对于处理大量数据的循环,考虑分批次处理,及时清理中间变量。 |
5.3 安全与隐私考量
这是重中之重! LifeInk AI处理的是你最私人的数据。
-
环境变量
:绝对不要将
NOTION_TOKEN、豆瓣COOKIE等敏感信息硬编码在代码中或提交到Git。始终使用.env文件,并将.env添加到.gitignore。 - 数据存储 :如果你将数据同步到Notion,请理解数据存储在Notion的服务器上。虽然Notion安全性很高,但极度敏感的个人日记是否要同步,需自行权衡。
- AI服务 :如果使用OpenAI等第三方LLM API,你的个人数据(作为上下文)会被发送到其服务器。请阅读其隐私政策。对于高度敏感的分析,可以考虑使用本地部署的开源大模型(如Llama 3、Qwen),虽然效果可能稍逊,但数据完全不出私域。
- 公开部署 :如果你将AI对话接口部署到公网,必须实施严格的身份验证(如API Key、JWT令牌),防止他人访问你的私人数据。
最后,这个项目的魅力在于它的高度可定制性。你可以根据自己的需求,轻松地添加新的数据源(比如Spotify听歌记录、Steam游戏库),设计独特的报告模板,或者调整AI助手的性格。它不仅仅是一个工具,更是一个关于如何用技术赋能个人记忆与成长的实践。从简单的数据同步开始,逐步构建起属于你自己的数字生命外脑,这个过程本身,就是对生活的一次深度梳理和审视。
