从0到1:手把手教你开发一个简单的音乐歌词搜索引擎
更新时间:2025-07-28 16:00 浏览量:1
Elastic Stack 是由 Elastic 公司推出的一整套开源产品,目的是用于帮助用户从各种来源采集不同格式的数据,并实现实时的数据搜索、分析和可视化。
其中,Elasticsearch 是一个基于 Java 的 RESTful 分布式搜索引擎,支持对多种格式的文档进行高效索引与搜索。
Kibana 则是一款功能强大的可视化与分析工具,能够以图形化的方式直观展示复杂数据,特别适用于处理大规模实时数据流。
除了上述两大核心组件,Elastic Stack 还包括 Logstash 和 Beats,用于实现数据的采集、过滤与传输。
在本文中,我们将基于 Elasticsearch 一步步搭建一个非常简单的歌曲歌词搜索引擎,主要针对泰米尔语(Tamil/Thamizh)歌词的搜索场景。
在正式构建搜索引擎之前,第一步是收集足够的歌词数据。网站 www.tamilpaa.com 提供了超过 3200 首泰米尔语歌词,并按电影名称、演唱者、音乐导演等多个维度进行了系统整理,因此本文选择这个网站作为爬虫的数据来源。
为便于自动化抓取,我们使用 Scrapy 框架编写了一个网页爬虫程序,用于从该网站抓取原始歌词数据。该爬虫可以在下面的仓库中找到。
Raw_Data : The data scraped directly from the website. Stored in different folders, categorized based on year. ├── Tamil_movie_song_lyrics_2017 ├── Tamil_movie_song_lyrics_2018 ├── Tamil_movie_song_lyrics_2019 ├── Tamil_movie_song_lyrics_2020 ├── Tamil_movie_song_lyrics_random_2300+ PreProcessedData : The data has to be pre-processed(convert to Tamil) for values of some fields were in English(E.g. A.R.Rahman => A. R. ரஹ்மான்) ModifiedData : The data was modified by adding more fields to spice up search ("வகை","நுகர்ச்சி","மதிப்பீடு") -('genre','views','ratings')首先,从 Elasticsearch 的官网下载对应平台的安装包(如 ZIP 或 TAR.GZ 格式)。下载完成后,解压到合适的目录。
以 Windows 系统为例,打开命令行工具,进入 Elasticsearch 的 bin 目录,并运行如下命令以启动服务:
C:\Users\cvvin>cd C:\Users\cvvin\Desktop\Notes\elasticsearch-7.7.1\bin C:\Users\cvvin\Desktop\elasticsearch-7.7.1\bin>elasticsearch运行成功后,Elasticsearch 服务会默认在本地(localhost)启动,并监听 9200 端口。
如果一切正常,页面返回的内容应类似于下图所示的 JSON 数据。
需要注意的是,Elasticsearch 默认监听的是 9200 端口,但这个端口是可以修改的。如果需要更换端口号,可进入 Elasticsearch 的 config 目录,打开 config.yml 文件,根据需要修改端口号的配置。
接下来,我们需要一个工具来与 Elasticsearch 的 RESTful API 进行交互。推荐使用 Postman,它是一款流行的 API 调试工具,适用于快速测试和发送 HTTP 请求,十分适合用于与 Elasticsearch 的交互测试。
在开始导入歌词数据之前,需要先为其建立索引结构。Elasticsearch 的一些核心概念与传统的关系型数据库非常类似,下面是它们之间的一些对应关系:
// Elasticsearch 对应 MySQLElastic Search => Mysql// Index 对应 DatabaseIndex => Database// Types 对应 Tables(已废弃)Types => Tables// Properties 对应 ColumnsProperties => Columns// Documents 对应 RowsDocuments => Rows简单来说,一个索引就是一个逻辑上的数据集合,类似于数据库中的“库”。在 Elasticsearch 中,数据是以文档(Document)的形式存储的,每个文档由若干字段(Properties)构成。
为了支持后续的歌词搜索,我们需要先创建一个索引。通过向 Elasticsearch 发送一个 PUT 请求即可完成。例如,以下请求将在 Elasticsearch 中创建一个名为 tamilsonglyricsdatabase 的索引:
创建成功后,会返回类似下图所示的响应结果。
如果要创建的索引名已经存在于 Elasticsearch 中,服务器将返回错误信息,提示该索引已存在。
完成索引的创建之后,我们便可以开始向其中添加实际的歌词数据了。在 Elasticsearch 中,文档(Document)是数据的基本单元,每个文档由一个或多个字段组成,结构与 JSON 格式完全兼容。
在本项目中,我们将歌词作为文档存储,每首歌词对应一个文档。文档将被存储在名为 tamilsonglyrics 的索引下,并归类于 lyrics 类型中(虽然从 7.x 版本开始,Elasticsearch 已逐步废弃 type,但这里我们仍保留用于说明)。
下面是一个添加单条文档的示例,在这个示例中,类型命名为 lyrics,文档 ID 设置为 2。然后,将歌词数据以 JSON 格式添加到 POST 请求的请求体中,发送到以下地址:
请求体中的 JSON 内容即为一个文档,如下图(左)所示,蓝色部分代表字段名,粉色部分为对应的字段值。:
可以通过 Postman 或 curl 工具发送上述请求。如果服务器返回状态码为 201 Created,并包含 _id 和 result: created 字段,说明文档已成功添加,如上图 (右)所示。
不过,在实际项目中,歌词数量往往是成百万上千万,手动一条条添加显然既低效又容易出错。为了解决这一问题,Elasticsearch 提供了 Bulk API,允许一次性批量写入大量文档,大大提高了数据导入效率。
为了使用 Bulk API,我们需要将之前通过爬虫抓取到的多个歌词 JSON 文档整理为符合 Bulk 格式的文本文件。Bulk 格式的特点是每条文档前必须配有一行 metadata 行。例如:
{ "index": { "_index": "tamilsonglyrics", "_id": 1 } }{ "title": "Song A", "lyricist": "Lyricist A", "lyrics": "..." }{ "index": { "_index": "tamilsonglyrics", "_id": 2 } }{ "title": "Song B", "lyricist": "Lyricist B", "lyrics": "..." }本文提供了三个示例 Bulk JSON 文件,分别对应 2017、2018 和 2019 年的数据。
为了进一步简化操作,我们编写了一个 Python 脚本 bulkdata.py,可自动读取 Bulk 文件,并通过 Elasticsearch 客户端完成数据写入。在运行该脚本前,请先确保相关依赖已安装:
pip install elasticsearchpip install elasticsearch-dsl如果你已经克隆了本项目的 GitHub 仓库,只需在根目录下运行:
python bulkdata.py该脚本默认会加载示例 Bulk 文件,并将内容写入 Elasticsearch。如果你打算导入自己整理的数据,也可以对脚本中的文件读取、字段格式、字符替换等逻辑进行适当修改。
通过 Python 脚本搭配 elasticsearch 库,我们不仅能批量导入文档,还可以轻松扩展,实现索引的自动创建、字段映射(Mapping)、搜索请求等功能,为后续构建完整的搜索引擎奠定基础。
from elasticsearch import Elasticsearch, helpersfrom elasticsearch_dsl import Indeximport json, reimport codecsimport unicodedata# import queries# 建立客户端连接client = Elasticsearch(HOST="http://localhost", PORT=9200)INDEX = 'lyrics'# 如需创建索引,请取消以下函数的注释# def createIndex:# index = Index(INDEX, using=client)# res = index.create# print(res)# 读取 JSON 文件中的全部歌曲def read_all_songs: with open('corpus/lyrics_2019.json', 'r', encoding='utf-8-sig') as f: all_songs = json.loads("[" + f.read.replace("}\n{", "},\n{") +"]") # all_songs = json.loads(f.read) res_list = [i for n, i in enumerate(all_songs) if i notin all_songs[n + 1:]] return res_listdef genData(song_array): for song in song_array: # Fields-capturing # print(song) title = song.get("பாடல்", None) movie = song.get("திரைப்படம்",None) lyricist = song.get("பாடலாசிரியர்", None) composer = song.get("இசையமைப்பாளர்", None) singers = song.get("பாடியவர்கள்", None) year = song.get("வருடம்", None) genre = song.get("வகை", None) lyrics = song.get("பாடல்வரிகள்", None) rating = song.get("மதிப்பீடு",None) views = song.get('நுகர்ச்சி', None) yield { "_index": "tamilsonglyrics", "_source": { "பாடல்": title, "திரைப்படம்": movie, "பாடலாசிரியர்": lyricist, "இசையமைப்பாளர்": composer, "பாடியவர்கள்": singers, "வருடம்": year, "வகை": genre, "பாடல்வரிகள்": lyrics, "மதிப்பீடு": rating, "நுகர்ச்சி": views }, }# 如果尚未创建索引,请取消以下行的注释# createIndexall_songs = read_all_songshelpers.bulk(client,genData(all_songs))运行 bulkdata.py 脚本后,程序将通过 Elasticsearch 的 Bulk API 一次性将所有歌词文档导入到指定索引中。
默认情况下,脚本会跳过索引创建操作,以避免重复覆盖已有数据。如果你是首次运行,或者尚未在 Elasticsearch 中手动创建索引,可以将脚本中 createIndex 函数的相关注释取消,这样可以自动创建所需的索引。
在执行脚本前,请根据你的实际情况,修改脚本中使用的索引名称,确保与之前创建的一致。与此同时,你需要将整理好的数据文件统一放入一个文件夹中,例如命名为 corpus,并在 read_all_songs 函数中更新文件读取路径,使其正确指向该目录。
后续如果有新的歌词数据需要添加,只需将新的 JSON 文件放入同一文件夹,并重新运行脚本即可。
需要注意的是,bulkdata.py 脚本中已根据最初爬取的数据结构预设了字段信息。如果你对原始数据的字段结构进行了修改(例如增加了专辑信息、语言分类等),请务必在 genData 函数中同步调整对应字段的处理逻辑,以确保生成的 JSON 格式与 Elasticsearch 中的映射一致。
完成上面的步骤后,歌词数据便会成功上传至 Elasticsearch 中,此时我们就可以基于这些数据实现关键词搜索、字段过滤、全文检索等功能,为构建一个完整的泰米尔语歌词搜索引擎打下基础。
为了进一步提升搜索的精准度和用户体验,我们可以在 Elasticsearch 中使用 自定义分析器(Custom Analyzer)对歌词数据进行更细致的文本处理。
默认的分析器虽然已经支持基础的分词和索引操作,但对于像泰米尔语这样结构复杂、词性变化丰富的语言来说,默认设置往往难以满足精准匹配的需求。因此,合理配置自定义分析器,能够更有效地优化倒排索引结构,提升搜索质量。
一个完整的分析器由以下三部分组成:
字符过滤器(Character Filter):在分词前对原始文本进行预处理,例如移除 HTML 标签、替换符号等。分词器(Tokenizer):将处理后的文本拆分为一个个词元(token)。这是分析过程中最核心的步骤。词元过滤器(Token Filters):对分词结果进行进一步加工,例如转为小写、去除停用词、词干还原或同义词替换等。为了适应泰米尔语歌词的特点,我们在项目中设计了一个自定义分析器,这个自定义分析器包含以下两个部分:
分词器:使用标准分词器(standard tokenizer),该分词器会在词语边界处分词,自动剔除大多数标点符号。它支持多语言处理,因此作为默认方案是较为稳妥的选择。自定义词元过滤器:停用词过滤器(Custom Stopper):过滤掉泰米尔语中频繁出现但搜索意义较弱的功能性词汇,如:ஒரு、என்று、மற்றும் 等。词干还原过滤器(Custom Stems):词干提取用于将不同形式的词语还原为词根。例如,英语中的 "playing"、"played"、"player" 都可以还原为 "play"。在泰米尔语中也有类似场景,例如:உண்ண、உண்டு、உண்டேன்、உண்கிறேன்、உண்பேன்、உண்ணும் 可统一还原为词根 உண்。同义词过滤器(Custom Anonymous):将意义相近的词语统一归类,以减少索引体积并提高召回率。例如:மிகச்சிறந்த、சிறப்பான、உயர்ந்த 等统一归为 சிறந்த。你可以在项目仓库中获取已配置好的分析器文件。配置流程如下
打开本地 Elasticsearch 安装目录,进入 config 文件夹:elasticsearch-7.7.1\config在该目录下新建一个名为 analyze 的文件夹:elasticsearch-7.7.1\config\analyze\将项目仓库中提供的分析器配置文件(包含 stem.txt、stopwords.txt 和 synonym.txt)复制到该目录下。完成文件添加后,我们需要在新建索引时指定自定义分析器。可以使用以下 JSON 示例来定义分析器结构,并在索引创建请求体中引用它:
前面我们已经完成文档添加、批量导入数据以及自定义分析器的配置。现在,可以进行搜索操作了。以下搜索请求示例可通过 Postman 工具进行测试。
当你仅知道某个关键词(例如电影名、年份或作词人),但不清楚它具体位于哪个字段时,则可以使用 query_string 查询:
{ "query": { "query_string": { "query": "搜索内容" } }}例如,在下面的示例中,“யுகபாரதி” 是一位作词人,我们希望检索由他创作的歌词。
{ "query": { "query_string": { "query":"யுகபாரதி" } }}一旦你确定了要搜索的字段(如电影名、作词人等),可以使用更高效的 match 查询方式。与在所有字段中查找关键词的方式相比,这种指定字段的搜索可以显著减少系统开销,从而提升搜索速度与效率。
例如,在下面的示例中,“பாடலாசிரியர்”(即作词人)是字段名,而 “யுகபாரதி” 是用户想要搜索的关键词。
{ "query" : { "match" : { "பாடலாசிரியர்" : "யுகபாரதி" } } }在实际搜索中,关键词往往可能出现在多个字段中,例如某个名字既可能是“பாடியவர்கள்”演唱者,也可能是“இசையமைப்பாளர்”音乐导演,甚至可能同时出现在这两个字段中。
此时,我们可以使用 multi_match 查询,在多个字段中同时搜索关键词,以提升匹配的全面性。
下面的示例演示了如何使用 multi_match 在 “பாடியவர்கள்”(演唱者) 和 “இசையமைப்பாளர்”(音乐导演) 两个字段中查找关键词 “அனிருத்”:
{ "query" : { "multi_match" : { "query" : "அனிருத்", "fields": ["பாடியவர்கள்","இசையமைப்பாளர்"] } }}在这个例子中,只要“பாடியவர்கள்”或“இசையமைப்பாளர்”是“அனிருத்”的歌词都会被搜索出来。
multi_match 查询也可以与 sort 和 size 参数结合,实现在指定字段搜索的同时,按评分或浏览量排序,获取 Top N 热门歌曲。
以下示例用于搜索类别为 “குத்துபாடல்”(摇滚风格)的前 20 首评分最高歌曲。我们将结果数量限制为 20 条,并按 மதிப்பீடு(评分)字段降序排序:
"size":20, "sort" : [ { "மதிப்பீடு" : {"order" : "desc"}} ], "query": { "multi_match": { "fields":["வகை"], "query" : "குத்துபாடல்", "fuzziness": "AUTO" } }}如果你希望按用户关注度排序,比如获取某位音乐导演最受欢迎的作品,可以使用相同结构,换用 நுகர்ச்சி(浏览量)字段进行排序。
下面的示例用于检索音乐导演 ஹரிஷ் ஜெயராஜ்(Harish Jeyaraj) 的 15 首最热门歌曲:
"size":15, "sort" : [ { "நுகர்ச்சி" : {"order" : "desc"}} ], "query": { "multi_match": { "fields":["இசையமைப்பாளர்"], "query" : "ஹரிஷ் ஜெயராஜ்", "fuzziness": "AUTO" } }}模糊查询可以帮助用户在拼写不确定或存在错误的情况下进行搜索,有助于提升搜索的灵活性和用户体验。
例如,当用户不确定作词人“யுகபாரதி”的完整拼写时,可以使用 யுக* 作为通配符进行查询。
"query" : { "match" : { "பாடலாசிரியர்" : "யுக*" } }}Elasticsearch 会根据通配符匹配结果,返回相关性较高的结果,并按匹配度进行排序。
注意:
Bool 查询用于在搜索中添加更精确的条件限制。它通过 must、should 和 must_not 等语句,分别对应布尔逻辑中的 AND、OR 和 NOT,从而有效缩小搜索范围。
must 表示所有子条件都必须满足,相当于逻辑 AND。should 可用于“至少满足一个”的情况must_not 表示排除条件下面是一个基本的示例:我们希望搜索所有由 இமான் 作曲,且发行于 2019 年 的歌曲:
{ "query": { "bool": { "must": [ { "match": { "இசையமைப்பாளர்": "இமான்" }}, { "match": { "வருடம்": "2019" }} ] } } }Range 查询适用于需要在某个范围内筛选结果的场景。
有时,我们不希望精确匹配某一年,而是查找某个时间段内的歌曲,比如“2019 年及以后”的作品。这时可以在 bool 查询中嵌入一个 range(范围)子句:
"query": { "bool": { "must": [{ "match": { "இசையமைப்பாளர்": "இமான்" } }, { "range": { "வருடம்" : { "gte" : "2019" } } } ] } }}常见范围关键字有:
在搜索歌词时,用户也可以选择只返回感兴趣的特定字段。
例如,在查询 15 首著名的 குத்துபாடல்(摇滚风格歌曲) 时,如果用户只希望获取 திரைப்படம்(电影名)和 இசையமைப்பாளர்(音乐导演)两个字段的信息,可以通过设置 _source 来实现字段过滤,避免返回不必要的数据。
{ "_source":{ "includes":["திரைப்படம்","இசையமைப்பாளர்"] }, "size":10, "sort" : [ { "மதிப்பீடு" : {"order" : "desc"}} ], "query": { "multi_match": { "fields":["வகை"], "query" : "குத்துபாடல்", "fuzziness": "AUTO" } }}用户还可以根据歌词内容本身,进行更深入的关联搜索。
MLT(More Like This)查询 在文本挖掘中非常有用,它支持较长的输入内容,并能够从中自动提取具有代表性的关键词,用于匹配语义或内容上相似的文档。
通过 MLT 查询,可以实现更智能、更语义化的歌词搜索体验。
{ "query":{ "more_like_this":{ "fields":[ "பாடல்வரிகள்" ], "like":"நெஞ்சில் மாமழை நெஞ்சில் மாமழை தந்து வானம் கூத்தாட கொஞ்சும் தாமரை கொஞ்சும் தாமரை வந்து எங்கும் பூத்தாட எத்தனை நாள் எத்தனை நாள் பார்ப்பது எட்டி நின்று எட்டி நின்று காய்வது கள்ள குரல் பாடல் உள்ளே ஓடுது கண்மூடி கண்மூடி காதோரம் பாடுது நெஞ்சில் மாமழை நெஞ்சில் மாமழை தந்து வானம் கூத்தாட கொஞ்சும் தாமரை கொஞ்சும் தாமரை வந்து எங்கும் பூத்தாட வாரத்தில் எத்தனை நாள் பார்ப்பது அன்றாடம் வந்து பார்க்க ஏங்குது வராமல் போகும் நாட்கள் வீண் என வம்பாக சண்டை போட வைக்குது சொல்ல போனால் என் நாட்களை வண்ணம் பூசி .", "min_term_freq":1, "max_query_terms":20 } } }除了前面介绍的多种查询方式外,Elasticsearch 还支持对歌词文档进行结构化的统计分析。例如,我们可以统计不同歌曲风格(类型)下的歌词数量,或查看每位作词人的作品数量分布等,这类任务可以通过 Aggregations(聚合) 框架完成。
搜索操作本质上是在回答:“哪些文档包含这个词?”
而排序与聚合则是回答:“这个字段在文档中具体是什么值?”
Elasticsearch 的 Aggregations 框架支持对文档集合执行复杂的数据汇总与分析操作,常见的聚合类型包括:Bucket(分桶)、Metric(度量)、Matrix(矩阵)和 Pipeline(管道)。其中,Bucket 用于根据指定条件将文档划分到不同的“桶”中,每个桶都有一个对应的键(key)和匹配的文档集合。执行后,系统会返回各个桶及其对应的文档摘要。
需要注意的是在进行分桶之前,必须先为参与聚合的字段启用 fielddata。因为聚合使用的是字段值而非倒排索引。
在将 வகை(风格/类型) 字段设置为 field data 之后,我们就可以基于该字段进行分桶操作了。
例如,在将 வகை(风格/类型)字段启用 fielddata 后,我们就可以基于该字段执行分桶操作。最终,Elasticsearch 会根据不同的歌曲风格创建多个桶,并统计每种风格下的文档数量,形成对应的聚合结果。
"aggs": { "ratings": { "terms": { "field": "வகை" } } }}为了更直观地体验歌词搜索引擎的效果,我们可以使用 GitHub 项目提供的简单前端界面(UI)进行试用。操作步骤如下:
首先,fork 并 clone 项目仓库。接着,将仓库中的 Analyzer 文件夹复制到 Elasticsearch 的 config 目录中启动 ElasticSearch运行 bulkdata.py 脚本,添加索引(如果尚未手动添加索引,请取消脚本中的相关注释)并导入数据将项目中的 LyricSearch 文件夹放入本地服务器的 htdocs 目录下,以便提供网页界面在浏览器中访问:http://localhost/LyricsSearch/,即可尝试进行歌词搜索(目前该界面仅支持基本查询功能)。# query.pyimport jsondef standard_analyzer(query): q = { "analyzer": "standard", "text": query } return qdef basic_search(query): q = { "query": { "query_string": { "query": query } } } return qdef search_with_field(query, field): q = { "query": { "match": { field: query } } } return qdef multi_match(query, fields=['title', 'song_lyrics'], operator='or'): q = { "query": { "multi_match": { "query": query, "fields": fields, "operator": operator, "type": "best_fields" } } } return qdef agg_q: q = { "size": 0, "aggs": { "Category Filter": { "terms": { "field": "genre", "size": 10 } } } } return qGitHub 中的 query.py 脚本目前仅支持部分查询类型,如基本查询、模糊查询和聚合查询等。
如果想尝试前面介绍的其他类型的查询,建议使用 Postman 工具,通过提供的 Postman Collection 来进行测试和操作。
# searchquery.pyfrom elasticsearch import Elasticsearchfrom query import basic_search, standard_analyzer# from rules import processINDEX = 'tamilsonglyrics'client = Elasticsearch(HOST="http://localhost",PORT=9200)def search(query): query_body = basic_search(query) print('Making Basic Search ') res = client.search(index=INDEX, body=query_body) return ressearchquery.py 脚本展示了如何通过 Python 实现基本的搜索功能。
对于更复杂的高级查询,建议使用 Postman 工具,并通过仓库中提供的查询集合(Postman Collection)进行测试与操作。
至此,本文关于构建一个简单的泰米尔语歌词搜索引擎的内容就告一段落了。