☰
用SQLite把浏览器书签变成可搜索的个人知识库
2026/10/14 12:31:41 网站建设 项目流程

把收藏夹从“数字坟墓”变成可搜索知识库:我的 clawsqlite-knowledge 实战

不知道你有没有这种经历:浏览器里收藏了成百上千个网址,真到要用的时候,却一个也想不起来。收藏时觉得“这个以后肯定用得上”,三个月后再看,连当初为什么存它都忘了。我去年清理过一次浏览器书签,发现 2019 年收藏的页面里,超过 60% 已经 404,剩下的大多数是“标题看不出用途”的孤岛链接。那一刻我意识到,收藏夹不是知识管理工具,它只是“数字坟墓”。

后来我花了一个周末,用 SQLite 写了个本地小工具,把书签从浏览器里导出来,清洗、入库、打标签、做全文搜索,现在每天找资料基本靠它,浏览器原生的书签管理器反而再也不打开了。这篇文章就把这个工具的完整思路和相关代码拆开讲一遍,包括为什么选 SQLite、表结构怎么设计、搜索怎么做到能用、日常怎么维护不烂尾。全程不依赖任何在线服务,数据全在本地,适合所有想把收藏夹真正盘活的人。

1. 收藏夹为什么必然变成“数字坟墓”:痛点拆解

先泼一盆冷水:只要你还用浏览器书签栏收藏网页,这件事大概率会烂。不是因为你懒,而是浏览器的书签功能在设计上就没有“回访”这个目标。咱们把收藏这件事拆开看,问题出在三个环节。

1.1 收藏时:零成本导致零价值

浏览器收藏一个网页的成本太低了,Ctrl+D,一秒钟就完成。正因为太容易,大脑根本不把它当回事。这个动作本质上更接近于“缓解焦虑”——我存下来了,就等于我拥有了。但收藏时没有记录“我为什么存它”“它解决了我什么问题”“它属于哪个主题”,导致三个月后面对大量光秃秃的标题时,你根本重建不了当时的语境。我复盘过自己的收藏,能回忆起收藏理由的不到 10%,剩下的全是“可能是当时觉得有用”。

1.2 回访时:搜索能力几乎为零

浏览器自带的书签搜索,只能匹配标题和 URL。标题往往又是营销号风格或者技术博客的“斗大标题”,跟你想搜的关键词根本不是一回事。比如我存过一个“聊聊 Go 的并发模型”的链接,当时的想法是“面试前要再复习一遍 goroutine”,但我收藏时没有写这个备注。三个月后我搜“goroutine”,搜索引擎能找到这篇博客吗?搜不到,因为标题里没有这个词。浏览器搜索会找 URL 和标题,存的是 Go 并发模型,自然搜不到“面试”“复习”这层信息。

更要命的是 URL 搜索也不靠谱,现在很多网站的链接要么带 tracking 参数,要么是短链跳转,URL 里全是乱码,完全不可读。搜索体验连一个 2005 年的下载站都不如。

1.3 维护时:没有反馈回路

书签是只进不出的结构。你收藏了 500 个链接,浏览器不会告诉你其中 180 个已经 404,也不会提醒你“同一个主题你已经存了 30 篇差不多的文章”。没有统计、没有状态、没有过期时间,它当然不会自我清理。而 C 端的收藏工具虽然做了“稍后读”,核心机制还是“标记已读”,也不适合做长期的个人知识沉淀。

所以我的判断是:要盘活收藏夹,第一步就是把它从浏览器里拽出来,放进一个我可以跑 SQL 的地方。SQLite 恰好是这个场景里最合适的存储。

2. 为什么是 SQLite:一个本地文件撑起整个知识库

选 SQLite 不是因为它流行,而是它在这件事里有几个不可替代的优势。我先给结论:这个场景如果有 10 个需求,浏览器书签大概能满足 2 个,在线网盘笔记软件能解决 5 个,SQLite 可以做到 8 个,剩下的 2 个需要配合脚本补。

2.1 单文件、零依赖、可迁移

SQLite 的存储就是一个 .db 文件,没有服务进程,没有配置文件,没有端口号。你可以把整个知识库文件放在 Dropbox 同步目录里,也可以放进 U 盘,换电脑时拷过去就能用。这解决了在线工具最大的痛点——数据不在自己手里。我在本地建库的规则是:任何重要数据,先落 SQLite,再谈同步。一个只有几千行的小表,SQLite 毫无压力,数据处理时间是毫秒级的。

2.2 SQL 表达式就是你的搜索语法

浏览器书签只能做“关键词 + 标题”匹配,SQLite 里你可以写任意灵活的查询。比如:

  • “找所有 2020 年收藏的、标签包含‘python’的、还没访问过的链接”
  • “找出标题里同时出现‘分布式’和‘架构’的文章”
  • “统计哪个标签收藏量最高,哪些标签已经三个月没新增了”

这些查询在浏览器收藏夹里没有对应的 UI 操作,但 SQL 里每一行都只是一条 SELECT。SQLite 的全文检索 FTS5 扩展还能做真正意义上的全文搜索,命中内容里的词,不只是标题。

2.3 本地工具的“反脆弱”属性

在线书签服务网页离线、服务商改版、账号异常、内容被删,都会让你损失收藏。SQLite 文件是离线的,内容是你导入时的快照。你可以随时用 SELECT 导出备份,也可以直接复制 .db 文件换机器,没有任何平台绑架。它不是协作笔记,但它是你个人的“只读档案馆”。

不过也要泼一盆冷水:SQLite 不是设计来做海量高并发写入的,日常个人知识库完全没有性能问题,但不要指望把它做成多人实时编辑后端。我们的场景是单机个人使用,这一点完美契合。

3. 数据库设计:为“查询”而不是“收藏”建表

设计表结构前,要想清楚一个核心问题:这个库的主要操作不是“插入”,而是“查询”。所以我重点设计的是索引和过滤条件,以及怎么在写入时就为后续查询准备字段。

3.1 核心表结构

我最终用了三张表:

CREATE TABLE IF NOT EXISTS links ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL UNIQUE, title TEXT DEFAULT '', description TEXT DEFAULT '', status TEXT DEFAULT 'active', -- active / broken / archived / read importance INTEGER DEFAULT 0, -- 0-5,手动标记重要程度 created_at TEXT DEFAULT (datetime('now', 'localtime')), last_visited_at TEXT, last_checked_at TEXT, check_count INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL ); CREATE TABLE IF NOT EXISTS link_tags ( link_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY (link_id, tag_id), FOREIGN KEY (link_id) REFERENCES links(id) ON DELETE CASCADE, FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE ); CREATE INDEX idx_links_status ON links(status); CREATE INDEX idx_links_created_at ON links(created_at); CREATE INDEX idx_links_last_visited_at ON links(last_visited_at);

关于status这个字段,很多人建表时会漏掉,但它恰恰是“数字坟墓”和“活知识库”的分水岭。一个链接收藏后应该有生命周期:刚收藏是active,访问过打开过可以标记read,发现打不开了标记broken,确定再也不需要了可以标archived。浏览器书签没有这个概念,所以它永远只有“在”和“不在”两种状态,自然也谈不上维护。

3.2 为什么单独建 tag 表,而不是用 JSON 数组存标签

初版我也图省事,直接在links表里加一列tags TEXT,用逗号分隔。后来发现搜索时很痛苦:查“python”标签,要用LIKE '%python%',结果把所有含 python 的拼写都匹配进来,还容易误伤。而且统计标签频率、改标签名、删除标签都要写字符串处理逻辑。拆成link_tags交叉表之后,一切变成标准 SQL:

SELECT t.name, COUNT(*) AS cnt FROM link_tags lt JOIN tags t ON t.id = lt.tag_id GROUP BY t.name ORDER BY cnt DESC;

这个思路和关系数据库的基本范式是一致的:把多对多关系独立成表,后续扩展性极好。个人工具虽然规模小,但表设计值得认真对待,否则写死在代码里的坑越来越多。

3.3 一个重要的补充:description 字段

description不是给你填“这个网页讲了啥”,而是给你填“我为什么收藏它”。比如我有一条收藏是某博客的“聊聊 Go 的并发模型”,我的description写的是:“面试复习用,重点看 goroutine 调度与 channel 通信,不要看前 20 行讲故事部分。”三个月后我搜“面试复习”,这条记录就能被搜出来。这才是把收藏变成“知识库”的关键。浏览器书签没有这个字段,所以收藏之后信息量不增加;SQLite 表有,你就自然会写。

4. 从浏览器书签文件导入数据:解析和清洗

迁移数据是第一步。浏览器都支持导出书签为 HTML 文件,格式大概是标准的 Netscape Bookmark File。写一个 Python 脚本解析它,不复杂,但有一个坑要提前预警:嵌套目录。浏览器书签是树形结构,解析时要维护一个栈,目录节点入栈,链接节点归到当前栈顶目录下。

4.1 导入脚本的核心代码

import html.parser import sqlite3 from urllib.parse import urlparse, unquote from datetime import datetime class BookmarkParser(html.parser.HTMLParser): def __init__(self): super().__init__() self.links = [] # [(title, url, add_date, folder)] self.folder_stack = ["未分类"] self.current_tag = None def handle_starttag(self, tag, attrs): attrs = dict(attrs) if tag == "h3": self.current_tag = "folder" elif tag == "a": self.current_tag = "link" self.current_link = { "title": "", "url": attrs.get("href", ""), "add_date": attrs.get("add_date", ""), "folder": self.folder_stack[-1], # 当前所在文件夹 } def handle_data(self, data): if self.current_tag == "folder": folder_name = data.strip() if folder_name: self.folder_stack.append(folder_name) elif self.current_tag == "link": self.current_link["title"] = data.strip() self.links.append(self.current_link) def handle_endtag(self, tag): if tag == "h3" and self.folder_stack: self.folder_stack.pop() # 退出文件夹 self.current_tag = None parser = BookmarkParser() with open("bookmarks.html", encoding="utf-8") as f: parser.feed(f.read())

这段逻辑里最需要注意的是folder_stack。<h3>是文件夹标题,<a>是链接。HTMLParser 的事件模型是按顺序触发的,所以我用栈来记录当前“深度”,链接归入栈顶文件夹。实际操作中,书签文件可能有几十个嵌套层级,一定要测试下解析几个包含“文件夹套子文件夹”的样例。

导入后的清洗阶段,有几件必须做的事:

  1. URL 规范化:统一去掉#片段、统一小写域名、去掉常见的跟踪参数utm_source、utm_medium等。否则同一条链接会因为参数不同入库两次。
import re from urllib.parse import urlparse, urlunparse, parse_qsl, urlencode def normalize_url(url): parsed = urlparse(url) # 去掉视觉上不可读的 fragment parsed = parsed._replace(fragment="") # 去掉常见的跟踪字段 query_pairs = [ (k, v) for k, v in parse_qsl(parsed.query) if k.lower() not in {"utm_source", "utm_medium", "utm_campaign", "from"} ] parsed = parsed._replace(query=urlencode(query_pairs)) return urlunparse(parsed)
  1. 标题清洗:很多书签导出的标题前后带空格、换行,要strip()。还遇到过标题全是空格的,直接标为《无标题》。

  2. 去重:在links表上建了UNIQUE约束,导入时用INSERT OR IGNORE跳过重复 URL。如果你不想丢重复记录,也可以先SELECT查一遍,然后打日志。

conn = sqlite3.connect("claw.db") cur = conn.cursor() for title, url, add_date, folder in parser.links: url = normalize_url(url) cur.execute( "INSERT OR IGNORE INTO links (url, title, created_at) VALUES (?, ?, ?)", (url, title or "《无标题》", add_date or datetime.now().isoformat()) ) conn.commit()

5. 搜索不将就:SQL 查询与全文检索实战

库建好之后,重头戏就是搜索。这部分的思路直接决定工具好不好用。我试过三种方案,挨个说下取舍。

5.1 第一版:LIKE '%关键词%'

最简单,但问题明显。中文分词不友好,比如你搜“分布式”,LIKE '%分布式%'只能精确匹配整词,搜不出来“分布”“分布式的”。英文也有大小写问题,LIKE默认区分大小写(除非你用 unicode 规则)。而且%和_是通配符,用户在搜索框里输入%,会被当成通配符,出现神秘的匹配行为。这个版本大概用了半小时就放弃了。

5.2 第二版:SQLite FTS5 全文检索

SQLite 从 3.9 开始支持 FTS5 扩展,可以建全文索引,使用MATCH语法。支持中文需要往 FTS5 里塞分词器,默认的unicode61对中文不友好——中文基本是按“字”切的,搜“分布式”时命中效果不差,但精确度不足。而且 FTS5 无法直接对links.title建立与 SQL 查询完全一致的语法,会有表和索引同步的复杂度。如果只是搜英文内容,FTS5 很猛;中文内容,我只能说“能用,但不是最优”。

我当时的做法是:用 FTS5 建了一个独立的 search 表,触发器把links表的 title、description、tags 拼接起来同步进去。

CREATE VIRTUAL TABLE search_index USING fts5(title, description, tags); CREATE TRIGGER links_ai AFTER INSERT ON links BEGIN INSERT INTO search_index(rowid, title, description, tags) VALUES (new.id, new.title, new.description, ''); END;

真实查询:

SELECT id, title, snippet(search_index, 0, '[', ']', '...', 12) FROM search_index WHERE search_index MATCH '分布式 AND python' ORDER BY rank;

rank是 FTS5 内置的搜索排序权重,找出的结果相对准确。但实际用下来,个人知识库一天新增几十条记录,全表 LIKE 其实也能接受。于是我又冷静下来,测试了 3000 条记录的 LIKE 查询延迟,稳定在 40ms 以内。那我干脆在查询时,直接把 LIKE 和 FTS5 都保留,LIKE用于快速模糊查询,MATCH用于精确检索,两条路并行。

5.3 最终做法:轻量倒排索引 + 关键词扩展

我用了一个比较“土”但很实用的方法:在导入时对每条链接的 title 和 description 做分词,存一个keywords字段,用空格隔开。搜索时对输入的关键词也做同样的分词处理,然后叠加多个 LIKE 条件。

import re import jieba # 中文分词的常用库 def tokenize(text): if not text: return [] text = re.sub(r'[\W_]+', ' ', text.lower()) words = [w.strip() for w in jieba.cut(text) if w.strip()] return words def build_keywords(link): words = set() words.update(tokenize(link.get("title", ""))) words.update(tokenize(link.get("description", ""))) words.update(tokenize(" ".join(link.get("tags", [])))) return " ".join(sorted(words))

查询时:

SELECT * FROM links WHERE (title LIKE ? OR description LIKE ? OR keywords LIKE ?) AND status != 'archived' ORDER BY importance DESC, created_at DESC;

参数传%关键词%,同样的关键词拼三个条件。这样做的优势是代码简单直接,且可以自由组合多关键词。配合jieba分词,中文搜索的召回率大幅提升。比如标题是“Go 并发模型”,分词后 keywords 里有“Go”“并发”“模型”,你搜“并发”能命中,搜“Go 模型”也能命中一个词。

5.4 组合查询:搜索条件自由拼装

搜索框不能只支持一个关键词。我封装了一个查询构造器,把“关键词、标签、状态、时间范围、重要度”全部变成可选条件,然后动态拼接 WHERE 子句:

import sqlite3 def search(db_path, keyword="", tag="", status=None, since=None, before=None): conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row cur = conn.cursor() conditions = [] params = [] if keyword: conditions.append("(links.title LIKE ? OR links.description LIKE ? OR links.keywords LIKE ?)") like = f"%{keyword}%" params.extend([like, like, like]) if tag: conditions.append("EXISTS (SELECT 1 FROM link_tags lt JOIN tags t ON t.id=lt.tag_id WHERE lt.link_id=links.id AND t.name=?)") params.append(tag) if status: conditions.append("links.status = ?") params.append(status) if since: conditions.append("links.created_at >= ?") params.append(since) if before: conditions.append("links.created_at <= ?") params.append(before) sql = "SELECT links.* FROM links" if conditions: sql += " WHERE " + " AND ".join(conditions) sql += " ORDER BY links.importance DESC, links.created_at DESC LIMIT 50" rows = cur.execute(sql, params).fetchall() conn.close() return rows

这里EXISTS子查询比 JOIN + GROUP BY 更优雅,因为它不会因为多标签产生重复行。实际验证下来,3000 条记录加多条件组合查询,耗时仍在 20ms 上下,体感“秒开”。

6. 日常使用与维护:工具不维护,三天必烂

把书签导入 SQLite 只完成了 20%,剩下 80% 是让这套东西真的进入工作流。下面是几个我坚持了三个月的使用方式。

6.1 CLI 快速添加,不打断思路

我写了一个 60 行的 Python 命令行工具,常驻在 PATH 里,主要命令只有三个:

  • add <url> --title "..." --desc "..." --tags python,wp
  • search <关键词>直接输出标题、链接、描述、标签
  • open <关键词>搜索并直接调起浏览器打开
alias bm="python3 ~/tools/claw.py" bm add "https://example.com/article" --title "测试文章" --tag "看书" bm search "分布式" bm open "goroutine 调度"

遇到好文章,直接命令行补一句描述存起来,比打开浏览器再拖进书签栏快得多。这条命令相当于一个 API,任何能调 shell 的场景都能组合使用,比如你写日报时想查“上周收藏的架构文章”,一条命令就出来。

6.2 自动检查死链:SQLite 当待办清单

书签成为“坟墓”的另一个原因是死链。我写了一个巡检脚本,每两周跑一次,把links.status='active'的链接挨个发HEAD请求,状态码 >= 400 就标记broken,并把last_checked_at更新为当前时间。

import requests def check_link(conn, url, link_id): try: r = requests.head(url, allow_redirects=True, timeout=5) if r.status_code >= 400: conn.execute("UPDATE links SET status='broken', last_checked_at=datetime('now','localtime') WHERE id=?", (link_id,)) else: conn.execute("UPDATE links SET last_checked_at=datetime('now','localtime'), check_count=check_count+1 WHERE id=?", (link_id,)) except Exception: conn.execute("UPDATE links SET status='broken', last_checked_at=datetime('now','localtime') WHERE id=?", (link_id,)) conn.commit()

注意别把check_count忘了,这个字段记录了“这个链接存活检查了几次”。如果一个链接连续三次broken,我大概率会直接DELETE或者标archived,不再留它在堆里占位。死链清理是知识库“保鲜”的关键动作。

6.3 输出和复盘:让收藏变成资料库

搜索是读取,但真正的复盘是“输出”。我会每月跑一条 SQL 统计本月新增、被访问、被标记 broken 的链接数,顺便生成一份 top 10 标签报告。这不只是一个统计,它会反向提醒你:月初那个“分布式系统”专题你是不是已经收集了 20 篇文章却一篇都没打开。看到这个数字,你才会去真的打开。

SELECT status, COUNT(*) FROM links WHERE created_at >= datetime('now', '-30 days') GROUP BY status; SELECT t.name, COUNT(*) FROM link_tags lt JOIN tags t ON t.id = lt.tag_id GROUP BY t.name ORDER BY 2 DESC LIMIT 10;

7. 实测放大镜:踩坑、优化和性能边界

任何小工具,写完到稳定使用之间,都会暴露一堆问题。这一节就是我在实际运行中遇到的最有价值的几个坑。

7.1 中文搜索的“无故失配”:分词导致的分裂

排查过一次很莫名其妙的搜索:搜“树莓派”搜不到任何结果,但库里明明有一条标题是“树莓派 4B 上手体验”。后来把 keywords 字段打出来才发现,分词结果是“树莓”、“派”、“4”、“b”、“上手”、“体验”。因为 jieba 默认词典把“树莓派”切成了“树莓”和“派”,搜“树莓派”时 LIKE 无法匹配“树莓派”整体。这个问题的解决办法不是修分词,而是搜索时也做同样的分词:

search_term_tokens = tokenize("树莓派") # 得到 ["树莓", "派"] # 然后每个 token 都对 keywords LIKE 一遍,并用 AND 连接

这样“树莓”命中,“派”也命中,两条 LIKE 条件用 AND,结果就能匹配到。个人用的搜索工具,没有必要追求搜索引擎级的那种“一个词都不差”,只要搜索时和索引时用了同一套分词逻辑,大概率就能命中。这是关键词检索的基本原则:not 全部匹配,而是合并匹配。

7.2 外键约束建议显式开启

SQLite 默认不检测外键约束,如果你手动 DELETE 一条links记录,没有写级联删除的代码,link_tags里会留下孤儿。想在启动时开启:

conn.execute("PRAGMA foreign_keys = ON;")

或者在link_tags定义里已经写上ON DELETE CASCADE,也要确保每个连接执行一次 PRAGMA。另一个实用的 PRAGMA:

conn.execute("PRAGMA journal_mode = WAL;")

WAL 模式可以让读写并发更平滑,配合单用户场景问题不大,但如果你是纯本地脚本,journal_mode默认的 delete 其实也够用。我自己开了 WAL,因为偶尔会同时开着 CLI 和 GUI 管理器看数据,WAL 能规避不必要的锁冲突。

7.3 大文本别直接塞 FTS5 表:热度失控

第一版尝试过把所有正文快照塞进去,比如用脚本抓取网页正文纯文本存入库。结果库膨胀得很快,一个 3000 条记录的库,正文全文复制之后,体积直接上 500MB,检索性能也开始劣化。最后我优化规则:只存title、description和用户自写摘要,不存正文全文。如果真需要正文检索,那应该用专门的搜索索引,而不是个人知识库。保持小而美是这类工具的第一法则。

7.4 性能边界:什么时候需要换数据库?

我实测的是 5000 条记录,配合 LIKE 和 FTS5 的查询都稳在几十毫秒内。如果收藏量到 5 万条以上,LIKE 匹配变慢到半秒以上,还可以继续优化:把 keywords 字段做倒排表存到内存,或者直接迁移到 FTS5。我的建议是,只要维持在单机个人使用,SQLite 的性能上限大概率不会触碰,别给自己加太多未来包袱。

8. 进一步扩展:把知识库变成个人搜索引擎

最后一层,是把这个库用起来,而不只是“查得到”。我加了两个扩展,都很简单。

8.1 浏览器扩展:快速存链

我用浏览器的书签树做入口,每次手动导 HTML 很麻烦。更顺滑的方案是写一个浏览器扩展,在地址栏旁边点一下,自动抓取当前页面的标题、URL、正文摘要,POST 到本地一个 Flask 小服务,这个端口再写成 SQLite 插入脚本。由于不是本篇重点,方案思路说一下就够了:本地起一个flask服务,监听 127.0.0.1:8777,扩展发 JSON 请求,服务端调用与 CLI 相同的add函数。这样“收藏”从浏览器点击变成跨进程 API 调用,体验接近原生。

8.2 自动化标签建议

另外一个有意思的想法是:根据新收藏链接的标题,以及已有链接的标签分布,用简单的规则给出标签候选。比如标题里含“tutorial”就默认打“教程”标签,含“docker”就打“容器”。不必上模型,个人知识库用规则就行。规则的好处是可解释、易调整,代码可能只有二十行。

def auto_tag(link): text = (link.get("title", "") + " " + link.get("description", "")).lower() suggestions = [] if "docker" in text or "container" in text: suggestions.append("容器") if "教程" in text or "tutorial" in text or "guide" in text: suggestions.append("教程") if "面试" in text or "brush up" in text: suggestions.append("面试") return suggestions

这类规则可以慢慢积累,我在命令行工具里用--tags做了组合,支持“规则推荐 + 手动确认”两种模式。

8.3 与笔记工具的联动

现在很多笔记软件支持本地 Markdown 目录。我会定期把links表里最新 7 天的链接导出成 Markdown,放到我的周报目录里。说白了就是 SQL 查询结果渲染成格式化的 Markdown 列表,这个动作让知识库不再封闭在数据库里,而是能流动到日常文档流程中。


写到这里,我想说说个人最大的体会。收藏夹之所以变成“数字坟墓”,不是因为你没时间,而是因为收藏这个动作没有带着“我想用它做什么”的问题。而当我把它搬进 SQLite,用一个能写 SQL 的本地数据库管理之后,每次添加链接我都得顺手填一句“我为什么存它”,每次搜索我都得顺手给它打标签。这个过程强迫我思考链接的价值,而不是收藏的焦虑。工具不神奇,神奇的是它对使用者的反馈:你的收藏不再是终点,而是下一次查阅的起点。

如果你也有一堆吃灰的书签,我建议不要直接删,先按这篇文章的步骤导到 SQLite 里试试。搜索到半年前存过的文章那一刻,你会回来感谢自己的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询