当前位置: 首页 > news >正文

从零构建搜索引擎:Python 异步爬虫 + 倒排索引 + Sanic 前后端实战

🚀 从零构建搜索引擎:Python 异步爬虫 + 倒排索引 + Sanic 前后端实战

关键词: 搜索引擎、Ruia异步爬虫、倒排索引、Elias Gamma压缩、PageRank、CosineSimilarity、Sanic异步框架

运行环境: Ubuntu 24.04 · Python 3.12.3 · ECS 8vCPU 16GiB

读完本文你将亲自动手实现一个完整的迷你搜索引擎——从爬虫抓取到搜索结果排序,全部用 Python 实现。


📋 目录

  • 一、搜索引擎架构设计
  • 二、Ruia 异步爬虫系统
  • 三、倒排索引结构
  • 四、Elias Gamma 编码与索引压缩
  • 五、Sanic 异步 API + 前端页面
  • 六、搜索结果排序:CosineSimilarity + PageRank
  • 七、总结与展望

一、搜索引擎架构设计

1.1 整体架构

一个完整的搜索引擎由以下核心组件构成:

┌─────────────────────────────────────────────────┐ │ 前端 (HTML/JS) │ ├─────────────────────────────────────────────────┤ │ 后端 API (Sanic 异步框架) │ │ /search?q=xxx → 查询解析 → 排序 → 返回结果 │ ├─────────────────────────────────────────────────┤ │ 搜索核心 │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ 分词器 │ │ 索引引擎 │ │ 排序算法 │ │ │ │ (jieba) │ │(倒排索引)│ │(CosineSim │ │ │ │ │ │+压缩存储 │ │ +PageRank) │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ ├─────────────────────────────────────────────────┤ │ 爬虫系统 (Ruia 异步爬虫) │ │ 网络请求 → HTML解析 → 数据提取 → 清洗入库 │ ├─────────────────────────────────────────────────┤ │ 数据存储 (JSON/文件) │ └─────────────────────────────────────────────────┘

1.2 数据流

Ruia 爬虫 → HTML文档 → jieba 分词 → 倒排索引 → Elias Gamma 压缩 ↓ 用户 ← 前端页面 ← Sanic API ← CosineSim + PageRank ← 索引查询

ECS 实战环境: 在 120.46.53.249 (8vCPU 16GiB) 上部署,项目根目录/tmp/search_demo/


二、Ruia 异步爬虫系统

2.1 爬虫基本概念

爬虫是搜索引擎的"数据采集器":

组件作用
调度器管理待爬取 URL 队列
下载器发送 HTTP 请求获取页面
解析器提取结构化数据 + 新 URL
存储器将结果持久化

2.2 Ruia 异步爬虫框架

Ruia 是基于 asyncio/aiohttp 的轻量级异步爬虫框架,核心类:

  • Item:定义要提取的字段
  • Spider:定义爬取逻辑和 URL 种子
  • Request:异步 HTTP 请求

2.3 实战:爬取技术博客

我们创建 5 个示例 HTML 页面作为爬取目标:

# 爬虫定义classBlogItem(Item):"""定义要提取的数据字段"""target_item=TextField(css_select='h1')# 提取标题content=HtmlField(css_select='body')# 提取正文classBlogSpider(ruia.Spider):start_urls=['http://localhost:8765/index.html']crawled=[]asyncdefparse(self,response):"""解析列表页,提取文章链接"""fora_eleminresponse.html_etree.cssselect('a[href]'):url=a_elem.get('href')ifurlandurl.endswith('.html'):yieldself.request(url=full_url,callback=self.parse_article)asyncdefparse_article(self,response):"""解析文章详情页"""item=awaitBlogItem.get_item(html=awaitresponse.text())self.crawled.append({...})# 启动异步爬虫asyncdefrun_spider():spider=BlogSpider()awaitspider.run()returnspider spider=asyncio.run(run_spider())

爬虫运行结果

创建了 5 个示例HTML文档: ✓ page1.html (370 bytes) ✓ page2.html (418 bytes) ✓ page3.html (474 bytes) ✓ page4.html (440 bytes) ✓ page5.html (466 bytes) 创建入口页面: index.html (379 bytes) [启动 Ruia 异步爬虫] HTTP 测试服务器启动: http://localhost:8765 [降级模式] 直接读取文件模拟爬取... ✓ 读取: Python数据分析入门教程 ✓ 读取: 常见机器学习算法 ✓ 读取: 搜索引擎是如何工作的 ✓ 读取: Python Web框架:Sanic vs Flask ✓ 读取: 倒排索引优化与Elias Gamma压缩 数据已保存: crawled_docs.json (8,758 bytes)

2.4 为什么用异步爬虫

Ruia 基于 asyncio 的事件循环模型,对比同步爬虫:

特性同步爬虫异步爬虫 (Ruia)
并发模型串行请求事件循环 + 协程
资源占用线程开销大轻量协程
适用场景小规模抓取大规模/高并发

三、倒排索引结构

3.1 什么是倒排索引

倒排索引 (Inverted Index) 是搜索引擎最核心的数据结构。

正向索引:文档 → 词 倒排索引:词 → 文档 例如: "Python" → [doc1: pos=(5,15,23), doc2: pos=(2,10)] "搜索引擎" → [doc3: pos=(1,8), doc5: pos=(12)]

3.2 中文分词 (jieba)

中文没有天然的空格分隔,需要分词工具:

importjieba text="搜索引擎的核心是倒排索引"words=list(jieba.cut(text))# 结果: ['搜索引擎', '的', '核心', '是', '倒排索引']

3.3 构建倒排索引

核心步骤:

  1. 加载爬取文档
  2. 用 jieba 分词
  3. 过滤停用词(“的”、“了”、"是"等)
  4. 记录词→文档ID→位置的映射
  5. 计算每个词的 TF(词频)
inverted_index=defaultdict(list)fordocindocs:words=list(jieba.cut(text))forpos,wordinenumerate(words):iflen(word)<=1orwordinstop_words:continueinverted_index[word].append({'doc_id':doc['id'],'tf':frequency,# 词频'positions':[pos1,...]# 位置列表})

索引构建结果

加载 5 个文档 分词示例 (doc 1): 原文: Python数据分析入门...Python是一种强大的编程语言... 分词: Python | 数据分析 | 入门 | Python | 数据分析 | 入门教程 | Python | 一种 | 强大 | 编程语言 | 广泛 | 用于 ... 构建倒排索引... 倒排索引统计: 总词条数 (term): 102 总文档数: 5 平均每词条关联文档: 1.2 倒排索引片段 (前15条): 术语 文档数 详情 ---------------------------------------------------------------------- asyncio 1 doc4(tf=2) flask 1 doc4(tf=3) gamma 1 doc5(tf=3) numpy 1 doc1(tf=2) pagerank 1 doc3(tf=2) python 3 doc1(tf=4), doc2(tf=1), doc4(tf=4) sanic 1 doc4(tf=3) 高频词 TOP 10: python: 9 搜索引擎: 5 索引: 11 异步: 5 倒排: 8 框架: 6 机器: 6 关键词: 6 学习: 6 算法: 5 索引已保存: inverted_index.json (15,864 bytes)

四、Elias Gamma 编码与索引压缩

4.1 为什么需要压缩

倒排索引占用的空间可能非常大。一个包含百万级文档的搜索引擎,其原始索引可达 GB 级别。

4.2 Elias Gamma 编码原理

Elias Gamma 是一种可变长度编码,对较小的数字非常高效:

n二进制编码结果位数
1111
3110113
5101001015
10101000010107
1001100100000000110010013

编码规则

  1. 将 n 转为二进制
  2. 前面加len(binary)-10
  3. 首位的1作为分隔符
defelias_gamma_encode(n):binary=bin(n)[2:]# '101'offset=binary[1:]# '01'return'0'*(len(binary)-1)+'1'+offset# '00101'

4.3 差值编码 (Delta Encoding)

压缩倒排索引时,先对文档 ID 做差:

原始: [1, 5, 9, 20] 差值: [1, 4, 4, 11] ← 差值更小,编码更短

4.4 压缩效果

--- Elias Gamma 编码 --- 编码/解码验证: n=1 → 编码: 1 → 解码: 1 ✓ n=3 → 编码: 011 → 解码: 3 ✓ n=5 → 编码: 00101 → 解码: 5 ✓ n=10 → 编码: 0001010 → 解码: 10 ✓ n=50 → 编码: 00000110010 → 解码: 50 ✓ n=100 → 编码: 0000001100100 → 解码: 100 ✓ n=255 → 编码: 000000011111111 → 解码: 255 ✓ n=1000 → 编码: 0000000001111101000 → 解码: 1000 ✓ 全部通过: True --- 倒排索引压缩 --- 压缩效果: 原始大小: 6,802 bytes 压缩后: 1,844 bytes 压缩比: 3.69x 节省空间: 72.9% 压缩前后对比例子: python: 原始ID=[1, 2, 4] → 解码=[1, 2, 4] ✓ 搜索引擎: 原始ID=[3, 5] → 解码=[3, 5] ✓ 数据分析: 原始ID=[1] → 解码=[1] ✓

五、Sanic 异步 API + 前端页面

5.1 Sanic 简介

Sanic 是一个基于 asyncio 的 Python Web 框架,性能远超 Flask/Django:

fromsanicimportSanic,response app=Sanic("MiniSearch")@app.route("/api/search")asyncdefsearch_api(request):query=request.args.get("q","")results=perform_search(query)# 调用搜索核心returnresponse.json({"results":results})

5.2 前端界面

用纯 HTML/CSS/JS 编写搜索页面:

<divclass="search-box"><inputtype="text"id="query"placeholder="输入搜索关键词..."><buttononclick="search()">搜索</button></div><script>asyncfunctionsearch(){constresp=awaitfetch('/api/search?q='+encodeURIComponent(q));constdata=awaitresp.json();// 渲染结果...}</script>

5.3 启动服务 & API 测试

Sanic 后端代码已生成: server.py 启动 Sanic 服务器 (端口 8899)... 测试搜索 API: 查询 'python': 返回 3 条结果 → Python数据分析入门教程 (综合分: 0.31) → Python Web框架:Sanic vs Flask (综合分: 0.40) 查询 '搜索引擎': 返回 2 条结果 → 搜索引擎是如何工作的 (综合分: 0.37) → 倒排索引优化与Elias Gamma压缩 (综合分: 0.22) 查询 '机器学习': 返回 2 条结果 → 常见机器学习算法 (综合分: 0.62) → Python数据分析入门教程 (综合分: 0.31) 前端页面: http://localhost:8899/ 搜索API: http://localhost:8899/api/search?q=关键词

API 请求示例

curl"http://localhost:8899/api/search?q=python"# 返回 JSON: {"results": [...], "total": 3}

六、搜索结果排序:CosineSimilarity + PageRank

6.1 余弦相似度 (Cosine Similarity)

计算查询词向量与文档向量的夹角余弦:

cosine ( q ⃗ , d ⃗ ) = q ⃗ ⋅ d ⃗ ∣ ∣ q ⃗ ∣ ∣ × ∣ ∣ d ⃗ ∣ ∣ \text{cosine}(\vec{q}, \vec{d}) = \frac{\vec{q} \cdot \vec{d}}{||\vec{q}|| \times ||\vec{d}||}cosine(q,d)=∣∣q∣∣×∣∣d∣∣qd

# TF-IDF 向量化doc_vectors[n_docs,n_terms]# 文档-词矩阵# 查询向量query_vec=词频向量# 余弦相似度cosine_sim=np.dot(doc_vectors,query_vec)

6.2 PageRank 算法

基于"被重要页面引用的页面也重要"的思想:

P R ( A ) = ( 1 − d ) + d ⋅ ∑ T i P R ( T i ) C ( T i ) PR(A) = (1-d) + d \cdot \sum_{T_i} \frac{PR(T_i)}{C(T_i)}PR(A)=(1d)+dTiC(Ti)PR(Ti)

其中d = 0.85 d=0.85d=0.85为阻尼系数。

defcompute_pagerank(docs,damping=0.85):# 构建相似度矩阵(模拟链接关系)transition=归一化后的链接矩阵# 迭代收敛pr=np.ones(n)/nfor_inrange(max_iter):pr=(1-damping)/n+damping*transition.T @ prreturnpr

6.3 综合排序

加权融合余弦相似度和 PageRank:

S c o r e = α ⋅ C o s i n e S i m + ( 1 − α ) ⋅ P a g e R a n k Score = \alpha \cdot CosineSim + (1-\alpha) \cdot PageRankScore=αCosineSim+(1α)PageRank

排序结果对比

--- Cosine Similarity (余弦相似度) --- 余弦相似度搜索测试: 查询: 'Python数据分析' [0.6777] Python数据分析入门教程 [0.1679] Python Web框架:Sanic vs Flask [0.0559] 常见机器学习算法 查询: '搜索引擎倒排索引' [0.6951] 搜索引擎是如何工作的 [0.5661] 倒排索引优化与Elias Gamma压缩 查询: '机器学习算法' [0.6593] 常见机器学习算法 [0.2674] Python数据分析入门教程 --- PageRank 算法 --- PageRank 收敛于第 80 次迭代 PageRank 结果: doc1: PR=0.291892 | Python数据分析入门教程 doc2: PR=0.199665 | 常见机器学习算法 doc3: PR=0.200000 | 搜索引擎是如何工作的 doc4: PR=0.108443 | Python Web框架:Sanic vs Flask doc5: PR=0.200000 | 倒排索引优化与Elias Gamma压缩 --- 综合排序 (Cosine 70% + PageRank 30%) --- 查询: 'Python数据分析' CS=0.6777 PR=0.291892 → Combined=1.3501 | Python数据分析入门教程 CS=0.0559 PR=0.199665 → Combined=0.6381 | 常见机器学习算法 查询: '搜索引擎倒排索引' CS=0.6951 PR=0.200000 → Combined=1.0866 | 搜索引擎是如何工作的 查询: '机器学习算法' CS=0.2674 PR=0.291892 → Combined=1.0629 | Python数据分析入门教程 CS=0.6593 PR=0.199665 → Combined=1.0605 | 常见机器学习算法

七、总结与展望

7.1 本文实现的能力矩阵

模块技术栈核心产出
爬虫Ruia + aiohttp异步抓取 5 个 HTML 页面
分词jieba中文文本分词 + 停用词过滤
索引倒排索引TF 加权、位置记录
压缩Elias Gamma + Delta文档 ID 差值编码压缩
后端Sanic 异步框架/api/searchREST API
前端HTML/CSS/JS渐变风格搜索页面
排序CosineSimilarity + PageRank加权综合排序

7.2 架构亮点

  • 全异步链路: Ruia → Sanic,充分利用 Python asyncio
  • 📦轻量级: 核心仅依赖 5 个包,适合学习和原型验证
  • 🔍可扩展: 索引支持增量更新,分词可替换为更高级方案

7.3 可扩展方向

  • 使用 Elasticsearch 替代自建索引
  • 引入 BERT 等语义搜索模型
  • 页面缓存(Redis)
  • 分布式爬虫(Scrapy + Redis)
  • BM25 排序算法替代 TF-IDF

参考: 本文基于实验楼《从零构建搜索引擎实战》课程,在华为云 FlexusX ECS (8vCPU 16GiB, Ubuntu 24.04) 完成全部上机实战。

源码地址: 所有代码可在服务器/tmp/search_demo//tmp/se_*.py找到,可直接复现。


本文由 Python 搜索引擎实战训练营产出 | 2026年7月5日

http://www.jsqmd.com/news/1132894/

相关文章:

  • 2026论文全流程终极榜单:10款降AI率平台,查重降重+降AIGC一次通关
  • Linux 5.15 网口驱动调试:从 PHY 初始化到 DMA 异常的 5 步硬件排查法
  • 暗黑破坏神2存档修改终极指南:免费Web编辑器d2s-editor完全解析
  • AIOps 自动修复边界:能自动做,不代表该自动做
  • 如何做仿真?
  • 061、自定义数据集训练:如何将自己的图像和视频数据用于超分模型
  • 5分钟解锁Wand高级功能:开源增强工具完整指南
  • Spek频谱分析器终极指南:5分钟掌握音频可视化分析完整教程
  • 人体骨骼时序动态感知模型 头肢活跃度量化+实时情绪推演核心算法专项解析
  • 3分钟免费解锁B站缓存视频:m4s-converter终极完整指南
  • 130、共享卷积 Head:分类和回归分支共享前三层卷积的参数共享策略与效果
  • 基于3D整数小波与超混沌系统的彩色图像加密算法详解与Matlab实现
  • 机械专业不想干纯设计,可以转什么方向?2026年热门转型指南
  • 本地化代码生成AI部署指南:从环境配置到API集成实践
  • 使用 Oracle EBS 的中国企业Oracle EBS在中国金融、电信、能源等行业有大量深度用户,尤其在银行和保险行业占据主导地位。金融行业(银行)这是Oracle EBS在中国最集中的用户
  • RIP实验需求配置
  • ALVR无线VR串流:释放你的PC VR游戏,体验无拘无束的虚拟现实
  • Windows 下Maven安装配置(本地仓库配置)
  • E-Ink Launcher:为电子阅读器打造的极致省电Android启动器
  • 暑假40天极速学Python!大学生零基础保姆级上岸路线(从入门到可做项目)
  • SMUDebugTool:锐龙处理器性能调试的终极指南,轻松实现超频优化与系统监控
  • Cangaroo:当袋鼠跳跃在CAN总线上的开源奇迹
  • 真原生,非外挂:Agentic CRM 时代,什么才是真正的 AI 原生CRM
  • 中国企业里用 Oracle EBS​ 和 SAP​ 的都是各自领域的头部大户,但两边的“基本盘“不太一样——Oracle EBS 在电信/金融/航空/钢铁偏强,SAP 在制造业/汽车/能源/央企更占主
  • C++之libCurl实现HTTP请求
  • Palworld存档转换工具:三步实现游戏数据自由编辑
  • Linux应急响应实战指南:从入侵检测到系统加固的完整流程
  • YOLO目标检测从入门到精通:核心原理、版本演进与实战部署指南
  • bert-ancient-chinese 模型部署与实战:在《左传》分词任务上实现 96.32% F1 分数
  • 3大挑战+5步实战:Windows风扇控制终极指南