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

关键点解析与避坑指南 :

  1. 选择器稳定性 :豆瓣的HTML结构可能变动。 div.subject-item 这类选择器是脆弱的。更稳健的做法是同时使用多个特征定位,或定期检查更新。
  2. 等待策略 : time.sleep 是脆弱的。 Playwright 提供了 page.wait_for_selector , page.wait_for_load_state 等方法,应优先使用这些方法等待特定元素或状态,这样更高效且抗干扰。
  3. 错误处理与日志 :循环内每个条目的解析必须用 try...except 包裹,防止因单个条目解析失败导致整个任务崩溃。详细的日志( logging )对于调试爬虫至关重要。
  4. 反爬虫应对 :过于频繁的请求会导致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数据库。

步骤拆解 :

  1. 获取凭证 :在Notion官网创建集成(Integration),获取 INTERNAL_TOKEN 。邀请该集成到你的目标数据库,获取数据库的 DATABASE_ID 。
  2. 配置环境变量 :在项目根目录创建 .env 文件,安全地存储 NOTION_TOKEN 和 DATABASE_ID 。
  3. 数据模型映射 :定义你的Notion数据库属性(Properties),如“书名”(Title)、“评分”(Number)、“状态”(Select)、“链接”(URL)等。在代码中,需要将 BookItem 等数据模型映射到Notion API要求的JSON格式。
  4. 增量同步 :这是生产级应用必须考虑的。简单的实现是,在插入新数据前,根据唯一标识(如豆瓣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内。思路有以下几种:

  1. 模拟移动端请求(推荐) :使用 mitmproxy 等抓包工具,分析微信读书App的API请求。通常可以找到获取书架、阅读进度、笔记等数据的接口。然后使用 requests 库模拟这些请求,需要携带有效的 Cookie 或 Token 。这需要一定的逆向工程能力。
  2. 浏览器自动化 :在桌面端登录微信读书网页版,使用 Playwright 抓取。但网页版功能可能有限,且登录状态可能不稳定。
  3. 官方API(不存在) :微信读书没有开放个人数据的官方API。

实现要点 :

  • 登录态维持 :微信读书的登录态(通常是一个 wr_skey 或类似的 cookie )有效期较长,但需要妥善保管。可以设计一个类似的 session.py 来管理。
  • 数据模型设计 :定义 WereadBook 模型,包含书名、作者、阅读进度、划线笔记、想法等字段。需要与豆瓣的 BookItem 模型协调,考虑未来统一数据模型的可能性。
  • 增量同步 :重点关注“笔记”和“想法”的同步,这些是动态产生的。可以根据笔记的更新时间戳进行增量抓取。

4.2 构建AI对话能力(RAG架构雏形)

这是Phase 2的核心。一个简单的个人数据RAG系统可以这样搭建:

  1. 数据预处理与向量化 :

    • 从Notion数据库(或原始数据源)读取所有书影音条目、评论、日记。
    • 对每条记录,生成一个“文本块”。例如,对于一本书,文本块可以是: 标题:《人类简史》 作者:[以色列]尤瓦尔·赫拉利 我的评分:5星 我的评论:这本书从宏观视角... 标签:历史, 科普 。
    • 使用嵌入模型(如OpenAI的 text-embedding-3-small ,或开源的 BAAI/bge-small-zh-v1.5 )将每个文本块转换为向量。
    • 将向量和对应的元数据(如来源、类型、原始ID)存储到本地向量数据库,如 Chroma 或 Qdrant 。
  2. 问答接口 :

    • 接收用户自然语言问题,如“我去年给过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的自动报告生成,可以视为一个“文本摘要”和“数据叙事”任务。步骤包括:

  1. 数据聚合 :从数据库中筛选出特定时间段(如最近一周、一个月)的所有数据条目。
  2. 关键信息提取 :使用LLM从这些数据中提取关键事件、高频标签、情感倾向(从评论中)、评分变化等。
  3. 叙事生成 :根据预定义的报告模板(如“本周你主要沉浸在科幻世界中,看了《三体》并打了5星...”),让LLM将提取的信息填充进去,生成连贯、有个人风格的叙述。
  4. 格式化输出 :将生成的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。你需要:

  1. 安装Python、Playwright浏览器等依赖。
  2. 使用 screen , tmux 或 systemd 服务来守护进程。
  3. 配置反向代理(如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数据库属性定义仔细比对。

构建个人AI智能体:聚合多平台数据与RAG技术实践


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助手的性格。它不仅仅是一个工具,更是一个关于如何用技术赋能个人记忆与成长的实践。从简单的数据同步开始,逐步构建起属于你自己的数字生命外脑,这个过程本身,就是对生活的一次深度梳理和审视。