Python实用工具:pysolr 从入门到精通——高效操作Solr搜索引擎的指南

一、pysolr 库概述

1.1 用途

pysolr 是一个专门用于和 Apache Solr 搜索引擎进行交互的 Python 客户端库,它能够让开发者通过简洁的 Python 代码,轻松实现对 Solr 索引的创建、数据的添加、删除、更新以及复杂的查询操作。无论是构建企业级的全文检索系统,还是实现数据分析场景下的快速数据筛选,pysolr 都能提供稳定且高效的支持。

1.2 工作原理

pysolr 底层基于 HTTP/HTTPS 协议与 Solr 服务器进行通信,它将 Python 代码中的操作指令(如查询语句、数据提交指令)封装成符合 Solr API 规范的 HTTP 请求,发送到 Solr 服务器的指定接口(如 /solr/core_name/update 用于数据更新,/solr/core_name/select 用于数据查询),然后接收 Solr 服务器返回的 JSON 格式响应,并将其解析为 Python 中的字典、列表等数据结构,方便开发者直接处理。

1.3 优缺点

优点

  • 接口简洁易用,极大降低了 Python 开发者操作 Solr 的门槛,无需手动构造复杂的 HTTP 请求。
  • 支持 Solr 的大部分核心功能,包括全文检索、过滤查询、排序、分组统计、高亮显示等。
  • 兼容性良好,能够适配不同版本的 Apache Solr,且支持 Python 3.6 及以上的主流 Python 版本。

缺点

  • 功能覆盖相较于 Solr 的原生 API 存在少量缺失,部分高级特性(如自定义请求处理器的复杂配置)需要手动扩展 HTTP 请求参数。
  • 对大规模数据批量操作的性能优化需要开发者自行调整参数(如批量提交的大小),默认配置下的大批量数据插入效率有待提升。

1.4 License 类型

pysolr 采用的是 BSD 3-Clause 许可证,这是一个宽松的开源许可证,允许开发者自由地使用、修改、分发该库的代码,无论是用于商业项目还是开源项目,都几乎没有限制,只需要保留原作者的版权声明即可。

二、pysolr 安装与环境准备

2.1 安装 pysolr

安装 pysolr 非常简单,推荐使用 Python 的包管理工具 pip 进行安装,在命令行中执行以下命令即可完成安装:

pip install pysolr

该命令会自动从 PyPI 下载并安装最新版本的 pysolr 库及其依赖项(主要依赖 requests 库用于 HTTP 通信)。

2.2 环境依赖确认

  • Python 版本:确保你的 Python 环境版本为 3.6 及以上,可以通过 python --version 命令查看当前 Python 版本。
  • Solr 服务器环境:pysolr 是操作 Solr 的客户端,因此需要先搭建好 Solr 服务器环境。你可以从 Apache Solr 官方网站(https://solr.apache.org/)下载对应版本的 Solr 安装包,按照官方文档完成安装和启动,并创建至少一个 Solr Core(Solr 的核心索引单元)用于后续操作。
  • 网络连通性:确保运行 pysolr 代码的机器能够和 Solr 服务器所在的机器互通网络,Solr 默认的 HTTP 端口为 8983,需要保证该端口未被防火墙拦截。

三、pysolr 核心使用方法与代码示例

3.1 连接 Solr 服务器

在使用 pysolr 进行任何操作之前,首先需要创建一个 Solr 客户端实例,建立与 Solr 服务器的连接。核心代码如下:

import pysolr

# 定义 Solr 服务器的基础 URL 和 Core 名称
# 格式为:http://solr_host:solr_port/solr/core_name
SOLR_URL = "http://localhost:8983/solr/gettingstarted"

# 创建 Solr 客户端实例
solr = pysolr.Solr(SOLR_URL, timeout=10)

print("成功连接到 Solr 服务器!")

代码说明

  • pysolr.Solr() 是创建客户端实例的构造函数,第一个参数是 Solr Core 的完整 URL,其中 localhost 是 Solr 服务器的主机名,8983 是默认端口,gettingstarted 是 Solr Core 的名称(需要替换为你自己创建的 Core 名称)。
  • timeout 参数设置了 HTTP 请求的超时时间(单位为秒),避免因网络问题导致程序长时间阻塞。

3.2 向 Solr 中添加数据

Solr 存储的数据是以文档(Document)为单位的,每个文档是一个键值对的集合,对应 Solr Schema 中定义的字段。我们可以通过 add() 方法向 Solr 中添加单个或多个文档。

3.2.1 添加单个文档

import pysolr

SOLR_URL = "http://localhost:8983/solr/gettingstarted"
solr = pysolr.Solr(SOLR_URL, timeout=10)

# 定义一个 Solr 文档,字段需要和 Solr Schema 中的定义一致
document = {
    "id": "book_001",  # id 字段是 Solr 的默认唯一标识字段,必填
    "title": "Python编程:从入门到实践",
    "author": "埃里克·马瑟斯",
    "publisher": "人民邮电出版社",
    "publish_date": "2020-01-01",
    "price": 59.8,
    "tags": ["Python", "编程", "入门"]
}

# 添加文档到 Solr
solr.add([document])

# 提交更改,确保数据被持久化到索引中
solr.commit()

print("单个文档添加成功!")

3.2.2 批量添加多个文档

当需要添加大量数据时,批量添加的效率远高于逐个添加,代码示例如下:

import pysolr

SOLR_URL = "http://localhost:8983/solr/gettingstarted"
solr = pysolr.Solr(SOLR_URL, timeout=10)

# 定义多个文档的列表
documents = [
    {
        "id": "book_002",
        "title": "流畅的Python",
        "author": "卢西亚诺·拉马略",
        "publisher": "人民邮电出版社",
        "publish_date": "2017-05-01",
        "price": 129.0,
        "tags": ["Python", "进阶", "编程思想"]
    },
    {
        "id": "book_003",
        "title": "Python数据分析与挖掘实战",
        "author": "张良均",
        "publisher": "机械工业出版社",
        "publish_date": "2019-03-01",
        "price": 79.0,
        "tags": ["Python", "数据分析", "挖掘"]
    },
    {
        "id": "book_004",
        "title": "深度学习入门:基于Python的理论与实现",
        "author": "斋藤康毅",
        "publisher": "人民邮电出版社",
        "publish_date": "2018-07-01",
        "price": 69.0,
        "tags": ["Python", "深度学习", "AI"]
    }
]

# 批量添加文档
solr.add(documents, batch_size=2)  # batch_size 表示每次提交的文档数量

# 提交更改
solr.commit()

print("批量文档添加成功!")

代码说明

  • add() 方法接收一个文档列表作为参数,batch_size 参数可以控制每次向 Solr 提交的文档数量,当文档数量较多时,合理设置 batch_size 可以避免单次请求数据量过大导致的失败。
  • commit() 方法用于提交更改,Solr 在接收到 add 请求后,会先将数据存入内存,只有执行 commit 操作后,数据才会被写入磁盘索引,并且才能被查询到。

3.3 从 Solr 中删除数据

pysolr 支持通过文档 ID、查询条件等方式删除 Solr 中的数据,常用的删除方法有 delete()delete_by_query()

3.3.1 通过 ID 删除单个文档

import pysolr

SOLR_URL = "http://localhost:8983/solr/gettingstarted"
solr = pysolr.Solr(SOLR_URL, timeout=10)

# 通过文档 ID 删除
solr.delete(id="book_001")

# 提交更改
solr.commit()

print("通过ID删除文档成功!")

3.3.2 通过查询条件删除多个文档

如果需要删除满足特定条件的一批文档,可以使用 delete_by_query() 方法,代码示例如下:

import pysolr

SOLR_URL = "http://localhost:8983/solr/gettingstarted"
solr = pysolr.Solr(SOLR_URL, timeout=10)

# 删除 publisher 为"机械工业出版社"的所有文档
solr.delete_by_query("publisher:机械工业出版社")

# 提交更改
solr.commit()

print("通过查询条件删除文档成功!")

3.3.3 删除所有文档

如果需要清空整个 Solr Core 的数据,可以使用通配符查询条件 *:*,代码如下:

import pysolr

SOLR_URL = "http://localhost:8983/solr/gettingstarted"
solr = pysolr.Solr(SOLR_URL, timeout=10)

# 删除所有文档
solr.delete_by_query("*:*")

# 提交更改
solr.commit()

print("所有文档删除成功!")

代码说明

  • delete(id="xxx") 方法用于删除指定 ID 的文档,ID 是 Solr 文档的唯一标识。
  • delete_by_query(query) 方法接收一个 Solr 查询语句作为参数,会删除所有满足该查询条件的文档,使用时需要格外谨慎,避免误删数据。

3.4 查询 Solr 中的数据

查询是 Solr 的核心功能,pysolr 提供了 search() 方法来执行各种查询操作,支持全文检索、过滤、排序、分页、高亮等多种功能。

3.4.1 基础全文检索

import pysolr

SOLR_URL = "http://localhost:8983/solr/gettingstarted"
solr = pysolr.Solr(SOLR_URL, timeout=10)

# 先添加一些测试数据,方便查询
test_docs = [
    {
        "id": "book_002",
        "title": "流畅的Python",
        "author": "卢西亚诺·拉马略",
        "publisher": "人民邮电出版社",
        "publish_date": "2017-05-01",
        "price": 129.0,
        "tags": ["Python", "进阶", "编程思想"]
    },
    {
        "id": "book_003",
        "title": "Python数据分析与挖掘实战",
        "author": "张良均",
        "publisher": "机械工业出版社",
        "publish_date": "2019-03-01",
        "price": 79.0,
        "tags": ["Python", "数据分析", "挖掘"]
    }
]
solr.add(test_docs)
solr.commit()

# 基础全文检索:搜索标题中包含"Python"的文档
results = solr.search("title:Python")

# 处理查询结果
print(f"查询到 {len(results)} 条结果:")
for result in results:
    print(f"ID: {result['id']}")
    print(f"标题: {result['title']}")
    print(f"作者: {result['author']}")
    print(f"价格: {result['price']}")
    print("-" * 50)

3.4.2 带过滤条件的查询

在实际应用中,我们经常需要在全文检索的基础上,添加过滤条件来缩小查询范围,例如过滤价格区间、出版社等,代码示例如下:

import pysolr

SOLR_URL = "http://localhost:8983/solr/gettingstarted"
solr = pysolr.Solr(SOLR_URL, timeout=10)

# 搜索标题包含"Python",且价格在 50-100 之间,出版社为"人民邮电出版社"的文档
# q 参数是查询语句,fq 参数是过滤条件(可以是多个)
results = solr.search(
    q="title:Python",
    fq=[
        "price:[50 TO 100]",  # 价格区间过滤,闭区间
        "publisher:人民邮电出版社"
    ]
)

print(f"过滤查询到 {len(results)} 条结果:")
for result in results:
    print(f"ID: {result['id']}")
    print(f"标题: {result['title']}")
    print(f"价格: {result['price']}")
    print(f"出版社: {result['publisher']}")
    print("-" * 50)

3.4.3 带排序和分页的查询

当查询结果较多时,分页和排序功能是必不可少的,pysolr 支持通过 sortstartrows 参数来实现,代码示例如下:

import pysolr

SOLR_URL = "http://localhost:8983/solr/gettingstarted"
solr = pysolr.Solr(SOLR_URL, timeout=10)

# 先添加更多测试数据
more_docs = [
    {
        "id": "book_005",
        "title": "Python爬虫开发与项目实战",
        "author": "范传辉",
        "publisher": "机械工业出版社",
        "publish_date": "2020-01-01",
        "price": 65.0,
        "tags": ["Python", "爬虫"]
    },
    {
        "id": "book_006",
        "title": "Python Web开发实战",
        "author": "陶俊杰",
        "publisher": "清华大学出版社",
        "publish_date": "2018-10-01",
        "price": 89.0,
        "tags": ["Python", "Web开发"]
    }
]
solr.add(more_docs)
solr.commit()

# 搜索标题包含"Python"的文档,按价格降序排序,分页获取第1页(从0开始),每页3条
results = solr.search(
    q="title:Python",
    sort="price desc",  # desc 降序,asc 升序
    start=0,  # 起始位置
    rows=3  # 每页显示的条数
)

print(f"分页查询到 {len(results)} 条结果:")
for result in results:
    print(f"ID: {result['id']}")
    print(f"标题: {result['title']}")
    print(f"价格: {result['price']}")
    print("-" * 50)

# 获取总记录数
print(f"符合条件的总记录数: {results.hits}")

3.4.4 高亮显示查询结果

高亮显示可以让查询结果中匹配的关键词以特殊样式呈现,提升用户体验,pysolr 支持通过 hl 相关参数实现高亮功能,代码示例如下:

import pysolr

SOLR_URL = "http://localhost:8983/solr/gettingstarted"
solr = pysolr.Solr(SOLR_URL, timeout=10)

# 搜索标题包含"Python"的文档,并高亮显示标题中的关键词
results = solr.search(
    q="title:Python",
    hl=True,  # 开启高亮功能
    hl_fl="title",  # 指定需要高亮的字段
    hl_simple_pre="<em>",  # 高亮前缀
    hl_simple_post="</em>"  # 高亮后缀
)

print(f"高亮查询到 {len(results)} 条结果:")
for result in results:
    print(f"ID: {result['id']}")
    # 获取高亮后的标题
    highlighted_title = result.highlighting.get(result['id'], {}).get('title', [result['title']])[0]
    print(f"高亮标题: {highlighted_title}")
    print(f"作者: {result['author']}")
    print("-" * 50)

代码说明

  • hl=True 表示开启高亮功能,hl_fl 指定需要进行高亮处理的字段。
  • hl_simple_prehl_simple_post 分别设置高亮的前缀和后缀,通常用于 HTML 页面展示,让关键词以斜体、加粗等样式显示。
  • result.highlighting 中存储了高亮后的字段内容,需要通过文档 ID 来获取对应字段的高亮结果。

3.5 更新 Solr 中的数据

Solr 的数据更新可以通过 add() 方法结合文档 ID 实现,因为 Solr 会根据 ID 进行覆盖更新,代码示例如下:

import pysolr

SOLR_URL = "http://localhost:8983/solr/gettingstarted"
solr = pysolr.Solr(SOLR_URL, timeout=10)

# 定义需要更新的文档,ID 为已存在的文档 ID
updated_document = {
    "id": "book_002",
    "title": "流畅的Python(第2版)",  # 更新标题
    "author": "卢西亚诺·拉马略",
    "publisher": "人民邮电出版社",
    "publish_date": "2022-01-01",  # 更新出版日期
    "price": 149.0,  # 更新价格
    "tags": ["Python", "进阶", "编程思想", "第2版"]  # 更新标签
}

# 通过 add 方法实现更新,Solr 会根据 ID 覆盖原有文档
solr.add([updated_document])
solr.commit()

print("文档更新成功!")

# 验证更新结果
result = solr.search(q="id:book_002")
for doc in result:
    print(f"更新后的标题: {doc['title']}")
    print(f"更新后的价格: {doc['price']}")
    print(f"更新后的标签: {doc['tags']}")

代码说明
Solr 没有专门的更新方法,而是通过“先删除后添加”的逻辑实现更新,当使用 add() 方法提交一个已存在 ID 的文档时,Solr 会自动删除原有 ID 的文档,然后添加新的文档内容,从而实现更新效果。

四、pysolr 实际应用案例:构建简单的图书检索系统

4.1 案例需求

我们需要构建一个简单的图书检索系统,实现以下功能:

  1. 批量导入图书数据到 Solr。
  2. 支持按书名、作者、出版社进行全文检索。
  3. 支持按价格区间过滤检索结果。
  4. 支持对检索结果按价格排序和分页。
  5. 支持高亮显示检索关键词。

4.2 案例代码实现

import pysolr
from typing import List, Dict, Optional

class BookSearchSystem:
    def __init__(self, solr_url: str, timeout: int = 10):
        """
        初始化图书检索系统
        :param solr_url: Solr Core 的 URL
        :param timeout: HTTP 请求超时时间
        """
        self.solr = pysolr.Solr(solr_url, timeout=timeout)

    def import_books(self, books: List[Dict]) -> None:
        """
        批量导入图书数据到 Solr
        :param books: 图书数据列表
        """
        if not books:
            print("没有需要导入的图书数据!")
            return
        try:
            self.solr.add(books, batch_size=5)
            self.solr.commit()
            print(f"成功导入 {len(books)} 本图书数据!")
        except Exception as e:
            print(f"导入图书数据失败:{e}")

    def search_books(
        self,
        keyword: str,
        field: str = "*",
        min_price: Optional[float] = None,
        max_price: Optional[float] = None,
        sort_by: str = "price asc",
        page: int = 1,
        page_size: int = 3,
        highlight: bool = True
    ) -> pysolr.Results:
        """
        检索图书数据
        :param keyword: 检索关键词
        :param field: 检索的字段,* 表示所有字段
        :param min_price: 最低价格过滤条件
        :param max_price: 最高价格过滤条件
        :param sort_by: 排序方式,如 price desc
        :param page: 页码,从 1 开始
        :param page_size: 每页显示的条数
        :param highlight: 是否开启高亮
        :return: 检索结果
        """
        # 构建查询语句
        if field == "*":
            query = f"{keyword}"
        else:
            query = f"{field}:{keyword}"

        # 构建过滤条件
        filter_queries = []
        if min_price is not None and max_price is not None:
            filter_queries.append(f"price:[{min_price} TO {max_price}]")
        elif min_price is not None:
            filter_queries.append(f"price:[{min_price} TO *]")
        elif max_price is not None:
            filter_queries.append(f"price:[* TO {max_price}]")

        # 计算分页参数
        start = (page - 1) * page_size

        # 构建高亮参数
        hl_params = {}
        if highlight:
            hl_params = {
                "hl": True,
                "hl_fl": field if field != "*" else "title,author,publisher",
                "hl_simple_pre": "<strong>",
                "hl_simple_post": "</strong>"
            }

        # 执行查询
        results = self.solr.search(
            q=query,
            fq=filter_queries,
            sort=sort_by,
            start=start,
            rows=page_size,
            **hl_params
        )

        return results

    def display_results(self, results: pysolr.Results) -> None:
        """
        展示检索结果
        :param results: 检索结果对象
        """
        if not results:
            print("没有查询到符合条件的图书!")
            return
        print(f"\n共查询到 {results.hits} 本符合条件的图书,当前显示第 {(results.start // results.rows) + 1} 页:")
        print("=" * 80)
        for idx, result in enumerate(results, start=1):
            book_id = result['id']
            # 获取高亮内容
            highlighting = result.highlighting.get(book_id, {})
            title = highlighting.get('title', [result.get('title', '未知标题')])[0]
            author = highlighting.get('author', [result.get('author', '未知作者')])[0]
            publisher = highlighting.get('publisher', [result.get('publisher', '未知出版社')])[0]
            price = result.get('price', 0.0)

            print(f"[{idx}] ID: {book_id}")
            print(f"标题: {title}")
            print(f"作者: {author}")
            print(f"出版社: {publisher}")
            print(f"价格: {price} 元")
            print("-" * 80)

# 测试图书检索系统
if __name__ == "__main__":
    # Solr Core URL
    SOLR_CORE_URL = "http://localhost:8983/solr/gettingstarted"

    # 初始化系统
    book_system = BookSearchSystem(SOLR_CORE_URL)

    # 准备测试图书数据
    test_books = [
        {"id": "b1001", "title": "Python编程:从入门到实践", "author": "埃里克·马瑟斯", "publisher": "人民邮电出版社", "price": 59.8, "tags": ["Python", "入门"]},
        {"id": "b1002", "title": "流畅的Python", "author": "卢西亚诺·拉马略", "publisher": "人民邮电出版社", "price": 129.0, "tags": ["Python", "进阶"]},
        {"id": "b1003", "title": "Python数据分析与挖掘实战", "author": "张良均", "publisher": "机械工业出版社", "price": 79.0, "tags": ["Python", "数据分析"]},
        {"id": "b1004", "title": "深度学习入门:基于Python的理论与实现", "author": "斋藤康毅", "publisher": "人民邮电出版社", "price": 69.0, "tags": ["Python", "AI"]},
        {"id": "b1005", "title": "Python爬虫开发与项目实战", "author": "范传辉", "publisher": "机械工业出版社", "price": 65.0, "tags": ["Python", "爬虫"]},
        {"id": "b1006", "title": "Java编程思想", "author": "布鲁斯·埃克尔", "publisher": "机械工业出版社", "price": 109.0, "tags": ["Java", "进阶"]},
        {"id": "b1007", "title": "Python Web开发实战", "author": "陶俊杰", "publisher": "清华大学出版社", "price": 89.0, "tags": ["Python", "Web"]},
        {"id": "b1008", "title": "数据结构与算法分析:Python语言描述", "author": "马克·艾伦·维斯", "publisher": "机械工业出版社", "price": 75.0, "tags": ["Python", "算法"]}
    ]

    # 批量导入图书数据
    book_system.import_books(test_books)

    # 测试检索功能:搜索标题包含"Python",价格在 50-100 之间的图书,按价格降序排序,第1页,每页3条
    search_results = book_system.search_books(
        keyword="Python",
        field="title",
        min_price=50.0,
        max_price=100.0,
        sort_by="price desc",
        page=1,
        page_size=3,
        highlight=True
    )

    # 展示检索结果
    book_system.display_results(search_results)

    # 测试检索功能:搜索作者包含"张良均"的图书
    print("\n\n===== 按作者检索 =====")
    author_results = book_system.search_books(keyword="张良均", field="author")
    book_system.display_results(author_results)

4.3 案例运行说明

  1. 运行该代码前,需要确保 Solr 服务器已启动,且对应的 Core 已创建。
  2. 代码中定义了 BookSearchSystem 类,封装了图书数据的导入和检索功能,便于复用和维护。
  3. 测试部分首先初始化系统,然后导入测试图书数据,接着执行两次检索操作,分别按标题和作者检索,并展示结果。
  4. 检索结果中,匹配的关键词会被 <strong> 标签包裹,在 HTML 页面中展示时会呈现为加粗样式。

五、pysolr 相关资源链接

  • PyPI 地址:https://pypi.org/project/pysolr
  • Github 地址:https://github.com/django-haystack/pysolr
  • 官方文档地址:https://pysolr.readthedocs.io/en/latest/

关注我,每天分享一个实用的Python自动化工具。