【Python】Whoosh:全流程自建搜索引擎

Whoosh是一个纯Python编写的全文搜索库,适用于快速构建搜索引擎。

环境部署

在开始使用Whoosh之前,你需要确保你的开发环境已经正确设置。以下是详细的环境部署步骤。

安装Python

首先,确保你的系统上安装了Python。Whoosh支持Python 2.7和Python 3.x。建议使用Python 3.x版本。

  • 你可以从Python官方网站下载并安装适合你操作系统的版本。
  • 创建虚拟环境(可选)

    使用虚拟环境可以帮助你隔离不同项目的依赖关系。你可以使用venv模块创建虚拟环境。

    # 创建虚拟环境
    python -m venv whoosh_env
    
    # 激活虚拟环境
    # Windows
    whoosh_env\Scripts\activate
    # macOS/Linux
    source whoosh_env/bin/activate
    

    安装Whoosh

    在激活的虚拟环境中,使用pip安装Whoosh:

    pip install Whoosh
    

    你可以在终端中输入以下命令确认Whoosh已成功安装:

    pip show Whoosh
    

    验证安装

    你可以通过创建一个简单的Python脚本来验证Whoosh是否已正确安装。创建一个名为test_whoosh.py的文件,内容如下:

    from whoosh.index import create_in
    from whoosh.fields import Schema, TEXT, ID
    import os
    
    # 定义一个架构
    schema = Schema(title=TEXT(stored=True), content=TEXT(stored=True), path=ID(stored=True))
    
    # 创建一个目录来存放索引
    if not os.path.exists("indexdir"):
        os.mkdir("indexdir")
    
    # 创建索引
    ix = create_in("indexdir", schema)
    
    print("Whoosh环境部署成功!")
    

    运行脚本:

    python test_whoosh.py
    

    如果输出“Whoosh环境部署成功!”,则说明Whoosh已成功部署。

    创建索引

    创建索引是使用Whoosh进行全文搜索的核心步骤。索引允许快速检索存储的数据。下面是有关创建索引的详细介绍,包括相关概念和使用细节。

    索引的概念

  • 索引:索引是一个数据结构,用于加速搜索操作。Whoosh通过将文档和相关信息存储在索引中来实现快速查找。
  • 架构(Schema):索引的结构定义,包括存储哪些字段及其数据类型。例如,你可以定义文档的标题、内容和路径等字段。
  • 文档:文档是被索引的单位。每个文档包含一个或多个字段。Whoosh将文档存储在索引中以便快速检索。
  • 定义索引架构

    在创建索引之前,首先需要定义架构。这可以通过whoosh.fields模块来实现。常用的数据类型包括:

    FieldType 描述 使用场景
    TEXT 存储文本内容,支持分词和搜索 文章内容、评论、描述等文本字段
    ID 存储唯一标识符,通常用于标识文档 文档ID、路径、用户ID等
    NUMERIC 存储数字值,可以用于范围查询 年龄、价格、评分等数值字段
    BOOLEAN 存储布尔值(True/False) 标记状态、启用/禁用等
    DATETIME 存储日期和时间,支持日期范围查询 创建时间、更新时间等
    KEYWORD 存储单一值的关键字,适合用于精确匹配 标签、类别、关键字等
    TEXT(stored=True) 存储文本内容,并支持分词和搜索,存储查询结果 需要返回查询结果的文本字段
    STORED 任意类型的字段,存储文档数据 需要存储但不需要索引的字段
  • TEXTID 是使用频率最高的类型,适合绝大多数文本和文档标识需求。
  • NUMERICBOOLEAN 常用于需要数值或状态的字段。
  • DATETIME 适用于时间相关的数据。
  • KEYWORD 则用于精确匹配,适合固定值的场景。
  • from whoosh.fields import Schema, TEXT, ID
    
    # 定义一个架构
    schema = Schema(
        title=TEXT(stored=True),    # 存储文档标题
        content=TEXT(stored=True),  # 存储文档内容
        path=ID(stored=True)        # 存储文档路径
    )
    

    创建索引目录

    创建索引时,你需要指定一个存放索引文件的目录。如果目录不存在,Whoosh会自动创建。

    import os
    from whoosh.index import create_in
    
    # 创建一个目录来存放索引
    if not os.path.exists("indexdir"):
        os.mkdir("indexdir")
    
    # 创建索引
    ix = create_in("indexdir", schema)
    

    添加文档到索引

    创建完索引后,可以向其中添加文档。Whoosh提供了IndexWriter类用于写入操作。

    from whoosh.writing import IndexWriter
    
    writer = ix.writer()  # 创建写入器
    writer.add_document(title="First document", content="This is the content of the first document.", path="/a")
    writer.add_document(title="Second document", content="This is the content of the second document.", path="/b")
    writer.commit()  # 提交更改
    
  • add_document():向索引添加文档。每个文档使用定义的架构中的字段名称。
  • commit():提交所有更改并关闭写入器。
  • 更新和删除文档

    在使用过程中,可能需要更新或删除文档。Whoosh提供了相应的方法来处理这些操作。

    writer.update_document(path="/a", title="Updated document title", content="Updated content.")
    writer.commit()
    
    writer.delete_by_term('path', '/b')
    writer.commit()
    

    存储索引

    Whoosh支持多种索引存储方式,以满足不同应用场景和性能需求。以下是对Whoosh支持的索引存储方式的详细介绍:

    内存存储

  • 概述:索引存储在内存中,适合短期任务、测试或开发阶段。
  • 优点
  • 速度快,检索性能高。
  • 不需要管理文件或数据库,简单易用。
  • 缺点
  • 数据在程序结束时会丢失,无法持久化。
  • 不适合大规模数据或需要长期存储的应用。
  • from whoosh.index import create_in
    from whoosh.fields import Schema, TEXT, ID
    
    # 定义Schema
    schema = Schema(
        title=TEXT(stored=True),
        content=TEXT(stored=True),
        path=ID(stored=True)
    )
    
    # 创建内存索引
    ix = create_in("", schema)  # 使用空字符串表示内存存储
    

    文件存储

  • 概述:索引存储在文件系统中,可以持久化存储索引数据。
  • 优点
  • 数据持久化,不会因为程序结束而丢失。
  • 支持较大的索引数据,适合大规模应用。
  • 缺点
  • 相对于内存存储,性能较低。
  • 需要管理索引文件的存储位置和清理。
  • import os
    from whoosh.index import create_in
    from whoosh.fields import Schema, TEXT, ID
    
    # 定义Schema
    schema = Schema(
        title=TEXT(stored=True),
        content=TEXT(stored=True),
        path=ID(stored=True)
    )
    
    # 创建索引目录
    if not os.path.exists("indexdir"):
        os.mkdir("indexdir")
    
    # 创建Whoosh索引
    ix = create_in("indexdir", schema)
    

    数据库存储

  • 概述:使用SQLite等关系数据库将索引存储在数据库中,适合需要数据完整性和事务管理的场景。
  • 优点
  • 数据持久化,不会丢失,支持事务管理。
  • 可以支持多用户访问,适合协作环境。
  • 缺点
  • 需要额外的数据库知识和配置。
  • 性能可能受限于数据库的访问速度。
  • import sqlite3
    from whoosh.index import create_in
    from whoosh.fields import Schema, TEXT, ID
    
    # 创建SQLite数据库连接
    conn = sqlite3.connect('whoosh_index.db')
    
    # 定义Schema
    schema = Schema(
        title=TEXT(stored=True),
        content=TEXT(stored=True),
        path=ID(stored=True)
    )
    
    # 创建Whoosh索引并存储在SQLite数据库中
    ix = create_in(conn, schema)
    
    # 添加文档
    with ix.writer() as writer:
        writer.add_document(title="文档1", content="这是第一篇文档。", path="/a")
        writer.add_document(title="文档2", content="这是第二篇文档。", path="/b")
        writer.commit()  # 提交更改
    
    # 添加文档等操作...
    

    查询索引

    查询索引是使用Whoosh进行全文搜索的关键步骤。通过查询,可以检索存储在索引中的文档。以下是关于Whoosh查询索引的详细介绍,包括相关概念和使用细节。

    查询的概念

  • 查询:查询是指对索引进行搜索的操作,返回符合条件的文档。Whoosh支持多种查询类型,包括基本查询、布尔查询和短语查询等。
  • 搜索器(Searcher):用于执行查询的对象。通过搜索器,可以在索引中搜索文档。
  • 创建搜索器

    在执行查询之前,需要创建一个搜索器。Whoosh允许在索引中创建多个搜索器,但每个搜索器只能针对一个索引。

    from whoosh.index import open_dir
    
    # 打开已有索引
    ix = open_dir("indexdir")
    
    # 创建搜索器
    with ix.searcher() as searcher:
        # 执行查询
        pass  # 查询代码在此处
    

    基本查询

    Whoosh的基本查询可以通过解析器(QueryParser)创建。解析器将用户输入的查询字符串解析为Whoosh可以理解的查询对象。

    from whoosh.qparser import QueryParser
    
    with ix.searcher() as searcher:
        query = QueryParser("content", ix.schema).parse("first")
        results = searcher.search(query)
        for result in results:
            print(result['title'], result['path'])
    
  • QueryParser("field_name", schema):指定要搜索的字段和索引架构。
  • parse("query_string"):将查询字符串解析为查询对象。
  • 布尔查询

    Whoosh支持布尔操作符(如AND、OR、NOT)来构建复杂查询。布尔查询可以组合多个条件。

    from whoosh.query import Or, And
    
    query = Or([
        QueryParser("content", ix.schema).parse("first"),
        QueryParser("content", ix.schema).parse("second")
    ])
    
    with ix.searcher() as searcher:
        results = searcher.search(query)
        for result in results:
            print(result['title'], result['path'])
    

    短语查询

    如果需要查找包含特定短语的文档,可以使用短语查询。短语查询将多个词作为一个整体进行匹配。

    query = QueryParser("content", ix.schema).parse('"first document"')
    
    with ix.searcher() as searcher:
        results = searcher.search(query)
        for result in results:
            print(result['title'], result['path'])
    

    排序和评分

    Whoosh允许根据特定字段对查询结果进行排序。你可以指定排序字段,并控制返回结果的顺序。

    with ix.searcher() as searcher:
        results = searcher.search(query, sortedby="title")  # 按照标题排序
        for result in results:
            print(result['title'], result['path'])
    

    每个结果还会包含一个评分(score),表示文档与查询的匹配程度。可以在输出结果中查看这些评分。

    高亮显示查询结果

    Whoosh支持高亮显示查询结果中的匹配部分,方便用户查看搜索结果的相关性。

    from whoosh.highlight import UppercaseFormatter
    
    with ix.searcher() as searcher:
        results = searcher.search(query)
        for result in results:
            title = result.highlights("title", formatter=UppercaseFormatter())
            content = result.highlights("content", formatter=UppercaseFormatter())
            print(f"Title: {title}\nContent: {content}\n")
    

    查询选项

    Whoosh的查询支持一些可选参数,例如:

  • limit:限制返回结果的数量。
  • offset:控制结果集的起始位置,适用于分页查询。
  • with ix.searcher() as searcher:
        results = searcher.search(query, limit=10, offset=20)  # 第21到第30个结果
        for result in results:
            print(result['title'], result['path'])
    

    范围查询

    范围查询允许查找某个字段值在指定范围内的文档。常用于数值型字段和日期型字段。

    假设我们有一个日期字段,我们可以查找某个日期范围内的文档:

    from whoosh.fields import DATETIME
    from whoosh.query import DateRange
    
    # 在Schema中添加DATETIME字段
    schema = Schema(
        title=TEXT(stored=True),
        content=TEXT(stored=True),
        path=ID(stored=True),
        date=DATETIME(stored=True)  # 日期字段
    )
    
    # 范围查询示例
    date_query = DateRange("date", start_date, end_date)
    
    with ix.searcher() as searcher:
        results = searcher.search(date_query)
        print("日期范围查询结果:")
        for result in results:
            print(result['title'], result['path'])
    

    组合查询

    可以将不同类型的查询组合在一起,以构建更复杂的检索条件。例如,可以将布尔查询与短语查询结合使用。

    combined_query = And([
        Or([query1, query2]),
        phrase_query
    ])
    
    with ix.searcher() as searcher:
        results = searcher.search(combined_query)
        print("组合查询结果:")
        for result in results:
            print(result['title'], result['path'])
    

    使用查询解析器

    Whoosh的查询解析器可以帮助用户通过自然语言输入构建复杂查询。解析器会自动将用户输入转换为相应的查询对象。

    from whoosh.qparser import QueryParser
    
    query_string = "first AND document"
    query = QueryParser("content", ix.schema).parse(query_string)
    
    with ix.searcher() as searcher:
        results = searcher.search(query)
        print("查询解析器结果:")
        for result in results:
            print(result['title'], result['path'])
    

    排序和评分

    在Whoosh中,排序和评分是影响搜索结果质量的重要因素。通过适当的排序和评分机制,可以帮助用户更快速地找到相关的文档。下面是对Whoosh中排序和评分的详细介绍,包括相关概念和使用细节。

    评分的概念

  • 评分(Score):评分是Whoosh为每个文档与查询的相关性计算的一个数值。评分越高,表示该文档与查询条件的匹配度越高。
  • 评分算法:Whoosh使用一种基于TF-IDF(词频-逆文档频率)的评分机制来评估文档的相关性。TF表示一个词在文档中出现的频率,IDF则衡量该词在整个文档集中出现的稀有性。
  • 查询结果中的评分

    在进行查询时,Whoosh会为每个匹配的文档计算并返回一个评分。可以通过查询结果访问每个文档的评分。

    from whoosh.qparser import QueryParser
    
    query = QueryParser("content", ix.schema).parse("first")
    
    with ix.searcher() as searcher:
        results = searcher.search(query)
        for result in results:
            print(f"Title: {result['title']}, Score: {result.score}")
    

    排序的概念

  • 排序:排序是根据特定字段对查询结果进行排序的操作。Whoosh允许根据一个或多个字段进行升序或降序排序,以便用户可以按最相关的文档查看搜索结果。
  • 排序字段:可以根据文档中的任意字段进行排序,例如标题、创建日期或自定义评分等。
  • 排序查询结果

    在Whoosh中,可以使用sortedby参数指定要根据哪个字段进行排序。可以设置多个字段,优先级顺序从左到右。

    with ix.searcher() as searcher:
        results = searcher.search(query, sortedby="title")  # 按照标题排序
        for result in results:
            print(result['title'], result['path'])
    
    with ix.searcher() as searcher:
        results = searcher.search(query, sortedby=None)  # 默认按评分排序
        for result in results:
            print(f"Title: {result['title']}, Score: {result.score}")
    

    自定义评分

    如果你想基于特定逻辑或额外因素来改变文档的评分,可以自定义评分函数。在Whoosh中,你可以通过创建自定义查询和评分类来实现。

    from whoosh.scoring import WeightingModel
    
    class CustomScoringModel(WeightingModel):
        def score(self, fieldname, text, docnum, weighting):
            # 自定义评分逻辑
            return super().score(fieldname, text, docnum, weighting) * 2  # 举例,简单乘以2
    
    with ix.searcher(weighting=CustomScoringModel()) as searcher:
        results = searcher.search(query)
        for result in results:
            print(f"Title: {result['title']}, Custom Score: {result.score}")
    

    处理更新和删除

    在Whoosh中,处理更新和删除操作是维持索引数据最新的关键。Whoosh提供了简单的方法来更新和删除索引中的文档。下面是对更新和删除的详细介绍,包括相关概念和使用细节。

    更新的概念

  • 更新(Update):更新指的是对已存在的文档进行修改,通常包括字段值的改变。更新操作会替换原有的文档数据。
  • 唯一标识符:更新操作通常依赖于文档的唯一标识符(例如ID字段),以确保准确找到要更新的文档。
  • 更新文档

    使用IndexWriterupdate_document方法可以方便地更新文档。需要传入唯一标识符和需要更新的字段。

    with ix.writer() as writer:
        # 更新路径为"/a"的文档
        writer.update_document(path="/a", title="Updated Title", content="Updated content.")
    
  • update_document(path="/a", ...):通过指定唯一标识符path来更新文档。
  • 需要提供新的字段值,原有的字段会被替换。
  • 删除的概念

  • 删除(Delete):删除操作指的是从索引中移除特定的文档。这通常基于文档的唯一标识符进行。
  • 删除操作不会立即从磁盘上删除文件,但会标记文档为“已删除”,以便在后续的优化过程中清理。
  • 删除文档

    使用IndexWriterdelete_by_term方法可以方便地删除文档。需要指定要删除文档的字段和值。

    with ix.writer() as writer:
        # 删除路径为"/b"的文档
        writer.delete_by_term('path', '/b')
    
  • delete_by_term('path', '/b'):通过指定字段和字段值来删除文档。
  • 文档一旦被删除,在后续查询中将不再返回。
  • 提交更改

    无论是更新还是删除,都需要在操作完成后调用commit()方法来提交更改。这会将所有更改保存到索引。

    with ix.writer() as writer:
        writer.update_document(path="/a", title="Updated Title")
        writer.delete_by_term('path', '/b')
        writer.commit()  # 提交所有更改
    

    批量更新和删除

    如果需要批量处理多个文档的更新或删除,可以在一个IndexWriter上下文中执行多次操作,然后一次性提交。

    with ix.writer() as writer:
        # 批量更新
        writer.update_document(path="/a", title="First Updated Title")
        writer.update_document(path="/c", title="Second Updated Title")
        
        # 批量删除
        writer.delete_by_term('path', '/b')
        writer.delete_by_term('path', '/d')
    
        writer.commit()  # 提交所有更改
    

    优化索引

    删除操作会标记文档为“已删除”,实际的磁盘空间不会立即释放。可以通过optimize()方法来优化索引,移除已删除的文档。

    ix.optimize()  # 优化索引,清理已删除的文档
    

    权重调整

    在Whoosh中,权重调整是影响搜索结果相关性的重要手段。通过调整不同字段的权重,开发者可以控制搜索结果的排序,从而优化用户的搜索体验。以下是关于权重调整的详细介绍。

    权重的概念

  • 权重:在搜索引擎中,权重是指不同字段对最终评分的影响程度。字段的权重越高,其在结果中的重要性也越大。
  • 调整权重:可以根据应用场景和业务需求,灵活调整不同字段的权重,以影响搜索结果的排序。
  • 字段权重设置

    在定义Schema时,可以为每个字段设置不同的权重。权重的值通常是浮动数,值越大表示权重越高。

    from whoosh.fields import Schema, TEXT, ID
    
    # 定义Schema并设置字段权重
    schema = Schema(
        title=TEXT(stored=True, weight=3.0),  # 标题字段权重较高
        content=TEXT(stored=True, weight=1.0),  # 内容字段权重较低
        path=ID(stored=True)
    )
    

    查询中的权重调整

    在执行查询时,可以动态地调整字段的权重。这通常通过自定义评分类来实现。

    from whoosh.scoring import Weighting
    
    class CustomWeighting(Weighting):
        def score(self, searcher, fieldname, text, docnum, weight):
            base_score = super().score(searcher, fieldname, text, docnum, weight)
            
            # 根据字段动态调整权重
            if fieldname == "title":
                return base_score * 2  # 对标题字段给予额外权重
            return base_score
    

    使用权重调整进行查询

    当执行查询时,可以使用自定义的权重类来影响评分。

    from whoosh.qparser import QueryParser
    
    query = QueryParser("content", ix.schema).parse("文档")
    with ix.searcher(weighting=CustomWeighting()) as searcher:
        results = searcher.search(query)
        for result in results:
            print(f"标题: {result['title']}, 评分: {result.score}")
    

    组合权重

    你可以通过多种方式组合权重,例如:

  • 字段组合:对多个字段同时进行评分和权重调整。
  • 动态权重:根据查询条件动态修改权重,例如如果查询中包含特定关键词,可以增加相关字段的权重。
  • 分词

    分词是文本搜索引擎中的重要步骤,负责将文本内容切分为可以索引和检索的基本单元(词)。Whoosh在处理文本数据时提供了灵活的分词机制,以支持多种语言和应用场景。以下是对Whoosh中分词的详细介绍,包括相关概念和使用细节。

    分词的概念

  • 分词(Tokenization):分词是将文本分解成单独的词或符号的过程。在搜索引擎中,分词用于生成索引,使搜索更高效。
  • 词元(Token):分词后的单个单位称为词元。词元是构建索引的基础。
  • Whoosh的分词机制

    Whoosh提供了多种内置的分词器,允许用户根据需要选择合适的分词策略。默认情况下,Whoosh使用的是StandardAnalyzer,该分词器会对文本进行常见的处理,如小写化和去除停用词。

    常见的分词器

  • StandardAnalyzer:默认分词器,支持小写转换、去停用词、标点符号处理等。
  • SimpleAnalyzer:简单分词器,仅进行小写转换,适用于不需要复杂处理的情况。
  • WhitespaceAnalyzer:基于空格的分词器,仅根据空格切分文本,适合处理特殊格式的文本。
  • StemmingAnalyzer:在分词的同时应用词干提取,适用于需要处理不同词形的情况。
  • 使用自定义分词器

    用户可以根据特定需求创建自定义分词器,定义分词规则和处理逻辑。

    from whoosh.analysis import RegexTokenizer, Tokenizer, LowercaseFilter, StopFilter
    
    class CustomAnalyzer(Tokenizer):
        def __init__(self):
            self.tokenizer = RegexTokenizer()  # 使用正则分词
            self.lowercase = LowercaseFilter()  # 转为小写
            self.stop_filter = StopFilter()  # 去停用词
    
        def __call__(self, text):
            for token in self.tokenizer(text):
                token = self.lowercase(token)
                token = self.stop_filter(token)
                yield token
    
    # 在Schema中使用自定义分词器
    schema = Schema(
        title=TEXT(analyzer=CustomAnalyzer(), stored=True),
        content=TEXT(analyzer=CustomAnalyzer(), stored=True),
        path=ID(stored=True)
    )
    

    分词示例

    创建索引时,分词器会自动应用于字段内容。在索引创建时,Whoosh会根据定义的分词器处理文本。

    from whoosh.index import create_in
    from whoosh.fields import Schema, TEXT, ID
    import os
    
    # 创建索引目录
    if not os.path.exists("indexdir"):
        os.mkdir("indexdir")
    
    # 定义Schema
    schema = Schema(
        title=TEXT(stored=True),
        content=TEXT(stored=True),
        path=ID(stored=True)
    )
    
    # 创建索引
    ix = create_in("indexdir", schema)
    
    # 添加文档
    writer = ix.writer()
    writer.add_document(title="First Document", content="This is the first example.", path="/a")
    writer.add_document(title="Second Document", content="Another example is here.", path="/b")
    writer.commit()
    

    在这个过程中,content字段的内容会被分词处理。

    查询时的分词

    在查询时,Whoosh会使用相同的分词器处理用户的查询字符串,以确保查询和索引中的词元一致。

    from whoosh.qparser import QueryParser
    
    with ix.searcher() as searcher:
        query = QueryParser("content", ix.schema).parse("first example")
        results = searcher.search(query)
        for result in results:
            print(result['title'], result['path'])
    

    在执行查询时,"first example"也会经过分词处理。

    中文搜索

    中文搜索的挑战主要在于中文文本的分词,因为中文没有明显的单词边界。这使得正确分词成为实现有效搜索的关键。Whoosh为中文搜索提供了相应的支持,以下是对Whoosh中中文搜索的详细介绍,包括相关概念和使用细节。

    中文搜索的挑战

  • 分词:中文句子中的词与词之间没有空格,分词器需要能够有效识别词组。
  • 停用词:一些常见的词(如“的”、“是”、“在”等)在搜索中往往不具有实际意义,需要去除。
  • 词形变化:中文中的同义词和近义词处理也是提高搜索相关性的重要因素。
  • Whoosh中的中文分词器

    Whoosh并不自带中文分词器,但可以使用第三方分词库,如jieba,结合Whoosh实现中文搜索。

    使用jieba实现中文分词

    首先,需要安装jieba库:

    pip install jieba
    

    然后,可以定义自定义的分词器,以便在Whoosh中使用。

    import jieba
    from whoosh.analysis import Tokenizer, Token
    
    class JiebaTokenizer(Tokenizer):
        def __call__(self, text):
            # 使用jieba分词
            for word in jieba.cut(text):
                yield Token(word)
    
    # 定义Schema
    from whoosh.fields import Schema, TEXT, ID
    
    schema = Schema(
        title=TEXT(analyzer=JiebaTokenizer(), stored=True),
        content=TEXT(analyzer=JiebaTokenizer(), stored=True),
        path=ID(stored=True)
    )
    

    创建索引

    在创建索引时,使用上面定义的分词器来处理中文内容。

    from whoosh.index import create_in
    import os
    
    # 创建索引目录
    if not os.path.exists("indexdir"):
        os.mkdir("indexdir")
    
    # 创建索引
    ix = create_in("indexdir", schema)
    
    # 添加文档
    writer = ix.writer()
    writer.add_document(title="第一篇文档", content="这是一个中文搜索的示例。", path="/a")
    writer.add_document(title="第二篇文档", content="另一篇文档也在这里。", path="/b")
    writer.commit()
    

    查询索引

    在查询时,Whoosh会使用相同的分词器处理用户的查询字符串。

    from whoosh.qparser import QueryParser
    
    with ix.searcher() as searcher:
        query = QueryParser("content", ix.schema).parse("中文 搜索")
        results = searcher.search(query)
        for result in results:
            print(f"标题: {result['title']}, 路径: {result['path']}")
    

    停用词处理

    对于中文搜索,可以定义停用词列表,并在分词过程中进行过滤。可以在jieba分词后对分词结果进行处理。

    stop_words = {"的", "是", "在", "和"}
    
    class JiebaTokenizerWithStopwords(Tokenizer):
        def __call__(self, text):
            for word in jieba.cut(text):
                if word not in stop_words:
                    yield Token(word)
    
    # 使用新分词器定义Schema
    schema = Schema(
        title=TEXT(analyzer=JiebaTokenizerWithStopwords(), stored=True),
        content=TEXT(analyzer=JiebaTokenizerWithStopwords(), stored=True),
        path=ID(stored=True)
    )
    

    同义词处理

    可以使用同义词库来增强搜索的相关性。在分词后,查找同义词并将其加入搜索词。

    synonyms = {
        "搜索": ["查找", "检索"],
    }
    
    def expand_synonyms(query_terms):
        expanded = set(query_terms)
        for term in query_terms:
            expanded.update(synonyms.get(term, []))
        return list(expanded)
    
    # 扩展查询词
    query_terms = expand_synonyms(["中文", "搜索"])
    

    统计和分析

    Whoosh提供了一些基本的统计和分析功能,可以帮助你更好地理解索引数据的特性和搜索结果。以下是对Whoosh中统计和分析功能的详细介绍。

    文档数量统计

  • 概念:可以通过Whoosh获取索引中的文档总数,这对于分析索引的规模和容量很有帮助。
  • 实现
    使用index.num_docs()方法可以获取当前索引中存储的文档数量。
  • with ix.searcher() as searcher:
        total_docs = ix.num_docs()
        print(f"索引中的文档总数: {total_docs}")
    

    字段统计

  • 概念:可以统计特定字段的不同值或频率,这对于了解文档特性或内容分布很重要。
  • 实现
    可以使用查询来统计某个字段的唯一值,结合searcher.all_docs()和Python的集合操作实现。
  • from collections import Counter
    
    field_values = Counter()
    with ix.searcher() as searcher:
        for docnum in range(ix.num_docs()):
            fields = searcher.stored_fields(docnum)
            field_values[fields['title']] += 1
    
    print("标题字段的统计:", field_values)
    

    查询结果分析

  • 概念:可以分析查询结果的相关性和分布,例如不同文档的评分和字段值。
  • 实现
    可以在执行查询后,遍历结果集并分析各文档的评分。
  • query = QueryParser("content", ix.schema).parse("文档")
    with ix.searcher() as searcher:
        results = searcher.search(query)
        for result in results:
            print(f"文档ID: {result.docnum}, 评分: {result.score}, 标题: {result['title']}")
    

    聚合统计

  • 概念:支持基于某个字段的聚合分析,例如计算字段的平均值、最大值、最小值等。
  • 实现
    Whoosh不直接提供聚合功能,但可以通过遍历查询结果手动实现。
  • scores = []
    with ix.searcher() as searcher:
        for result in searcher.all():
            scores.append(result.score)
    
    average_score = sum(scores) / len(scores) if scores else 0
    print(f"平均评分: {average_score}")
    

    数据可视化

  • 概念:虽然Whoosh本身不支持数据可视化,但可以将统计结果导出到其他工具(如Matplotlib、Seaborn等)进行可视化。
  • 实现
    可以将统计结果整理为合适的格式,并使用可视化库展示数据分布。
  • import matplotlib.pyplot as plt
    
    # 假设我们已经有了字段值的计数
    titles = list(field_values.keys())
    counts = list(field_values.values())
    
    plt.bar(titles, counts)
    plt.xlabel('标题')
    plt.ylabel('文档数量')
    plt.title('各标题文档数量统计')
    plt.xticks(rotation=45)
    plt.show()
    

    作者:T0uken

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Python】Whoosh:全流程自建搜索引擎

    发表回复