---
title: "用Python和Postgresql实现中文全文搜索"
url: "https://lerry.me/post/2021/01/python-postgresql-fulltext-search"
date: 2021-01-22T02:53:49.924Z
updated: 2026-09-01T14:26:02.672Z
tags:
  - "Python"
  - "PostgreSQL"
language: zh-CN
---

# 用Python和Postgresql实现中文全文搜索

全文检索的基本原理就是，把文本分词，提取出结构化数据（创建索引），根据用户查询请求，使用索引查找到目标（搜索索引）。这个博客程序使用了`PostgreSQL`数据库和`Peewee` ORM，为了实现这个目标，我们需要使用`PostgreSQL`的全文检索插件，这里使用的是[zhparser](https://github.com/amutu/zhparser)。

## PostgreSQL的配置

首先根据系统安装`PostgreSQL`数据库，建议根据官方文档安装，这里使用 PostgreSQL 14 版本。

因为 `zhparser` 实际使用的是 `SCWS` 分词，要安装 SCWS 分词插件。

```bash
wget -q -O - http://www.xunsearch.com/scws/down/scws-1.2.3.tar.bz2 | tar xf -
cd scws-1.2.3
./configure
make install
```

然后安装zhparser

```bash
git clone --depth=1 https://github.com/amutu/zhparser.git
make && make install
```

创建extension
打开 psql 终端，运行以下脚本。

```sql
CREATE EXTENSION zhparser;
```

这里的配置适用于普遍情况，如有特殊需求，请参考对应网站进行配置，
在 2021 年的现在，建议一切部署都通过 Docker 实现。这里有一份我的 Docker 配置，可以快速搭建起一个包含全文检索插件的 PostgreSQL 实例。

[https://github.com/lerry/postgresql-docker](https://github.com/lerry/postgresql-docker)

## 数据库配置

这里假设你已经创建好了你的数据库，和对应的数据表，以本博客程序为例，

```sql
CREATE TABLE entry (
    id SERIAL PRIMARY KEY,
    uid integer NOT NULL,
    slug character varying(50) NOT NULL,
    title character varying(50) NOT NULL,
    content text NOT NULL,
    created_at timestamp without time zone NOT NULL,
    updated_at timestamp without time zone NOT NULL,
    status smallint NOT NULL,
    password character varying(50),
    allow_comment smallint NOT NULL,
    view_times integer NOT NULL,
    cover text
);
```

创建全文检索相关的配置，注意最后两行要根据你实际的数据库字段修改，

```sql
CREATE EXTENSION zhparser;
CREATE TEXT SEARCH CONFIGURATION zh (PARSER = zhparser);
ALTER TEXT SEARCH CONFIGURATION zh ADD MAPPING FOR n,v,a,i,e,l WITH simple;
CREATE INDEX fts_idx_title ON entry USING gin(to_tsvector('zh',title));
CREATE INDEX fts_idx_content ON entry USING gin(to_tsvector('zh',content));
```

## 查询

用peewee ORM查询

```python
result = Entry.select().where(Entry.status == 1, Match(Entry.title, q, 'zh') | Match(Entry.content, q, 'zh')) \
            .order_by(Entry.created_at.desc()).limit(options.PAGE_LIMIT)
```

这里的 `zh` 对应这条配置语句：`CREATE TEXT SEARCH CONFIGURATION zh (PARSER = zhparser);`
            
对应的 SQL 语句如下：

```sql
SELECT  * FROM entry WHERE to_tsvector('zh', title) @@ to_tsquery('zh', 'python') OR to_tsvector('zh', content) @@ to_tsquery('zh', 'python')
```

这里有个小插曲，最早我用peewee的时候，它的`Match`函数，第三个参数，语言配置并没有生效，于是我提交了一个PR，修正了这个问题，很快这个PR被合并到代码库了，这也是我第一次参与到实际的开源项目。

PR 链接：[https://github.com/coleifer/peewee/pull/1379/files](https://github.com/coleifer/peewee/pull/1379/files)。
