搜索功能实现:模糊查询与全文检索
📂 实战归属:道满博客·第四阶段(实战演练)
🔗 前置知识:SQLAlchemy ORM · 文章发布与 Markdown 支持
对于个人博客类产品,可检索性是仅次于「内容发布流畅」的核心需求——试想辛辛苦苦写了干货,用户却找不到,体验会打对折。今天我们从最简单的 LIKE 模糊查询讲起,逐步实现分类/时间过滤、搜索结果高亮,最后用 PostgreSQL 的内置全文检索做升级,覆盖小/中型博客的搜索场景。
1. 基础方案:SQLAlchemy LIKE/ilike 模糊查询
1.1 多字段文章搜索
个人博客通常只需要搜 已发布文章 的三个核心字段:标题、摘要、正文。原代码用了 ilike(不区分大小写的 LIKE),这个在中文场景下虽然意义不大,但兼容英文关键词的习惯,保留很合理。
我们可以在原路由的基础上,补充一下路由装饰器的导入(虽然原场景大概率是有的,但代码片段要尽量可运行),还有模板参数的说明可以用注释补上,让逻辑更清晰。
💡 小细节优化:用 filter(Post.is_published.is_(True)) 替代 == True——虽然 == True 对 SQLite/MySQL/PostgreSQL 都能用,但更符合 SQLAlchemy 的规范,能避免某些特殊布尔类型(比如 PostgreSQL 的 BOOLEAN)的隐式转换问题。
1.2 搜索结果高亮+摘要截取
直接展示完整内容太臃肿,原代码做了摘要截取,但可以优化两个点:
- 优先用文章的 已写摘要,没有的话再搜正文关键词前后的内容(比直接截取前200字更有相关性)
- 原高亮函数在
match分支才定义match.group(),但sub可以接受函数动态处理匹配项,适配大小写混合的情况(比如搜「Python」,正文里的「python」「PYTHON」也能保留原大小写高亮)
然后模板里也要调整,一定要加CSS控制highlight的样式,还有优先用已写摘要、再用高亮后的正文片段。
⚠️ 安全提醒:模板里用了 | safe 过滤器,因为 highlight_excerpt 返回的是带 <mark> 标签的 HTML——但这个函数里只处理了我们自己生成的标签,原始文本已经通过 re.escape(query) 过滤了关键词里的特殊字符,正文还先做了 striptags 去HTML,所以没问题。
2. 进阶过滤:分类+时间筛选
博客搜索一般不需要太复杂的布尔逻辑(AND/OR/NOT),但加个 分类筛选 和 近N天/指定时间开始的文章筛选 会很实用。原代码的基础上,我们把分类和时间的参数补全到模板里,让用户可以交互。
然后在搜索结果页的顶部加个筛选栏,用 GET 请求提交参数(这样筛选条件可以保存到链接里分享):
3. 优化方案:PostgreSQL 内置全文检索
LIKE/ilike 虽然简单,但有两个致命缺点:
- 无法利用索引(除非用
%xxx后缀匹配,但一般博客要的是任意位置匹配),数据量超过1万篇就会明显变慢 - 没有分词功能(比如搜「道满博客」,正文里的「道满的博客」搜不到)
如果你的博客用的是 PostgreSQL(强烈推荐!个人博客免费、功能全),可以用它的内置全文检索解决这两个问题。原代码用了 chinese 配置,但 PostgreSQL 12+ 开始虽然内置了中文分词,但分词质量一般,生产环境可以配合 zhparser 插件或者 Python 的 jieba 分词生成自定义的 tsvector。
3.1 基础版:内置 chinese 配置
先看最简单的写法,然后讲怎么优化:
3.2 生产优化建议
- 加索引:在
Post模型里加一个 GIN 索引,这是 PostgreSQL 全文检索的专用索引,查询速度会提升几十倍:⚠️computed列在 SQLite 里不支持,所以开发环境如果用 SQLite,这部分可以暂时注释掉。 - 给字段加权重:标题的相关性应该比正文高,用
setweight函数:权重从高到低是 A > B > C > D,ts_rank会自动计算带权重的分数。 - 用 jieba 分词:PostgreSQL 内置的中文分词太粗糙,比如搜「人工智能」,可能会拆成「人」「工」「智」「能」,可以用 Python 的 jieba 分词在插入/更新文章时生成 tsvector,或者配合
zhparser插件。
4. 方案总结与选型建议
我们今天讲了三种搜索方案,适合不同规模的博客:
🔗 扩展阅读

