资讯动态

Scrapy+ES+Django搭建小型全文搜索引擎:毕设避坑指南

发布时间:2026/10/8 21:12:00 来源:尧图企业网站定制
简介一套基于Scrapy、ElasticSearch和Django的小型全文搜索引擎毕业设计项目方案适合计算机专业毕设、课设以及想掌握爬虫与搜索联动开发的初学者。内容覆盖需求分析、技术选型、爬虫抓取策略、索引映射设计、Django后端路由与视图等关键环节可辅助快速搭建可运行的搜索原型理解从网页抓取到索引检索再到前端展示的完整链路。包内共2000个文件以Python脚本1605个py为主配合HTML页面、JavaScript/CSS前端样式、C头文件及少量配置文档压缩包约88.15MB结构清晰便于按模块查阅目前已有136人学习下载。对于需要系统梳理搜索引擎类毕设方案或动手复现全流程的学生是一份实用且完整的参考资料。1. 基于ScrapyElasticSearchDjango的小型全文搜索引擎毕设这么做才不翻车毕业设计选“基于ScrapyElasticSearchDjango的小型全文搜索引擎”这个题通常意味着你要用Scrapy把某个网站的数据抓下来清洗后送进ElasticSearch再通过Django把搜索结果渲染成网页。方向本身不冷门但很多人做到一半才发现难点不在爬虫而在三个组件怎么衔接顺畅、如何让中文搜索出得了结果。这篇文章按我自己的落地顺序讲先给爬虫定数据格式再定ES索引结构最后把Django的搜索页接上并给出完整避坑清单。适合有Python基础、但没完整跑过搜索型项目的同学不是教你怎么写论文而是照着做能通、能演示、能答辩的一套最小可行方案。2. 先把数据喂进来Scrapy爬虫的选型与最小可运行项目2.1 为什么是Scrapy而不是requestsBeautifulSoup三个理由做小型全文搜索引擎很多人第一反应是requests BeautifulSoup写起来轻快但等你要爬几百上千个页面时就难受了。Scrapy的优势在于调度、去重、并发、重试、日志这些能力是自带的天然就是一个“能上量的骨架”。换requests方案每一个并发、去重、失败重试的细节都要自己实现代码量至少翻一倍。另一个更关键的点Scrapy的Item是结构化的写好的item怎么清洗、怎么落库、怎么推给ElasticSearch都能在pipeline里按顺序处理。后面接Django或ES时你手里是一份确定字段的字典而不是一串散落的选择器代码。第三个理由和毕业设计答辩相关。Scrapy项目的目录结构天然就是“扩展点清晰”的spiders放采集逻辑pipelines放清洗与存储middlewares放下载中间件settings做全局配置。评委问“你的系统架构是什么”你直接按这个目录讲就行。requests方案往往是脚本式组织边界模糊讲不清楚。2.2 一个能跑的爬虫骨架settings、items、pipeline怎么写先用scrapy startproject建立一个项目目录结构大致是这样search_engine/ scrapy.cfg search_engine/ settings.py items.py pipelines.py spiders/ news_spider.pyitems.py里先定义你的数据模型。这里建议为搜索场景直接设计字段而不是什么都抓。我一般会先限定四五个核心字段后续加字段是便宜的事。# items.py import scrapy class NewsItem(scrapy.Item): url scrapy.Field() # 唯一标识也是ES里的_id title scrapy.Field() # 标题字段需要分词 content scrapy.Field() # 正文字段需要分词 author scrapy.Field() # 作者keyword类型 publish_time scrapy.Field() # 发布时间date类型 source scrapy.Field() # 来源站点逻辑说明url做唯一标识是为了防止重复数据进入EStitle和content是主要的全文检索字段中文分词会作用在它们上面author、publish_time、source这类字段在搜索里更多用于过滤分词意义不大。写Spider时把网页解析结果填充到这个Item里。settings.py里要注意几个配置项直接影响爬取量和稳定性# settings.py 关键配置 BOT_NAME search_engine ROBOTSTXT_OBEY True # 毕设建议遵守robots别给答辩惹麻烦 CONCURRENT_REQUESTS 8 DOWNLOAD_DELAY 1.0 # 单位秒保持礼貌爬取 ITEM_PIPELINES { search_engine.pipelines.ElasticsearchPipeline: 300, }参数说明CONCURRENT_REQUESTS控制在8DOWNLOAD_DELAY设为1秒对一个小型站点每秒最多8个请求已经足够支撑毕设的数据量同时降低被封IP的概率。ITEM_PIPELINES里的数字300表示执行优先级数字越小越先执行。你可以在同一条pipeline里先做清洗再入库也可以拆成多个pipeline按顺序处理。pipeline是数据进ES的关键入口。下面是常见的写法# pipelines.py from elasticsearch import Elasticsearch class ElasticsearchPipeline: def open_spider(self, spider): # 连接ES注意设置超时避免爬虫卡在连接上 self.es Elasticsearch([http://localhost:9200], timeout30) def process_item(self, item, spider): # 这里可以做字段清洗 if not item.get(title): raise DropItem(f缺少标题丢弃: {item.get(url)}) # 使用url做文档ID天然去重 self.es.index(indexnews, iditem[url], bodydict(item)) return item逻辑说明open_spider在爬虫启动时初始化ES连接process_item对每一条item做清洗并写入ES。用url作为文档ID同一个页面重复被抓取时ES会直接覆盖不会产生重复文档。字段清洗放在pipeline里而不是spider里是为了让spider只关注网页解析职责更干净。2.3 动态渲染页面怎么办Scrapy集成Playwright处理iframe的基本姿势毕设选题常见的是新闻、招聘、商品这类站点很多都用了动态渲染页面里还有iframe嵌入内容。Scrapy默认的下载器拿不到JavaScript渲染后的DOM自然解析不到数据。常见做法是给Scrapy接入Playwright中间件让Chrome内核去执行页面脚本再把最终的HTML交给spider解析。安装playwright并下载浏览器后settings.py里这样开# settings.py 启用Playwright DOWNLOAD_HANDLERS { http: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, https: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, } PLAYWRIGHT_LAUNCH_OPTIONS { headless: True, }spider里声明某个请求需要动态渲染# news_spider.py 片段 def parse_list(self, response): for url in response.css(a::attr(href)).getall(): yield scrapy.Request( url, meta{playwright: True}, # 开启动态渲染 callbackself.parse_detail ) def parse_detail(self, response): # 此时response.body已经是渲染后的HTML # 如果是iframe需要先切换到对应frame frame response.css(iframe::attr(src)).get() if frame: # 用一个无头请求去抓iframe真实地址 yield scrapy.Request(frame, callbackself.parse_frame)逻辑说明meta里的playwright标记只对单个请求生效不会影响其他普通请求的抓取速度。iframe场景的常见处理是先拿到iframe的src再单独发一个请求因为iframe内容是独立文档。如果你发现iframe里的数据还是空的大概率是iframe本身又依赖了二次请求这种情况属于少数毕设里用上述方式一般就够用了。3. 数据落库与中文分词ElasticSearch的索引设计与Query DSL3.1 Windows上启动ElasticSearchJDK、内存和启动报错排查毕设环境大多是WindowsES启动比Linux多几个经典的坑。先确认版本匹配ES 7.x自带JDK不需要额外装ES 8.x也是。但你的电脑上如果有其他Java应用依赖JDK 8要注意ES 7.10以上不支持Java 8最好直接用ES 7.17.x或8.x自带的JDK。下载zip解压后进入bin目录双击elasticsearch.bat。如果你看到一闪而过的窗口去命令行手动跑cd elasticsearch-7.17.10/bin elasticsearch.bat常见启动失败像这样报“future versions of Elasticsearch will require Java 11”。ES 7.17默认用自带JDK但如果你设置了JAVA_HOME指向旧版本它会优先用你指定的JDK。解决方式是临时清空JAVA_HOME再启动set JAVA_HOME elasticsearch.bat还有内存不足的报错。ES启动默认JVM堆是1GB如果你的机器内存紧张在config/jvm.options里改小-Xms512m -Xmx512m参数说明Xms和Xmx相等是ES官方推荐的做法避免运行中动态扩容引发GC停顿。毕设级数据量512MB足够如果你的文档数到几十万再升到1GB。启动后访问http://localhost:9200能看到包含cluster_name和tagline的JSON就说明成了。windows上另一个常见坑是路径里有中文或空格ES会启动失败或数据目录异常务必把zip解压到纯英文路径。3.2 索引mapping怎么定ik分词、字段类型和中文搜索效果索引设计是搜索引擎的核心。ES默认的标准分词器对中文是按字切的搜索“苹果”时会把“苹”和“果”拆开结果不理想。中文搜索的常见做法是装ElasticSearch的ik分词插件支持最细粒度和智能切词两种分词模式。安装方式是把ik插件zip解压到ES的plugins目录下然后重启ES。版本必须和ES主版本一致用错版本会报illegalArgumentException。创建一个带ik分词的索引同时指定mappingcurl -X PUT http://localhost:9200/news -H Content-Type: application/json -d { settings: { analysis: { analyzer: { ik_analyzer: { type: custom, tokenizer: ik_max_word } } } }, mappings: { properties: { title: { type: text, analyzer: ik_max_word }, content: { type: text, analyzer: ik_max_word }, author: { type: keyword }, publish_time: { type: date, format: yyyy-MM-dd HH:mm:ss }, url: { type: keyword }, source: { type: keyword } } } }逻辑说明title和content是全文检索的核心用ik_max_word做最大切分能切出尽可能多的词元召回率高author和url这些字段用keyword因为你要的是精确匹配publish_time设为date类型是为了后续能按时间排序。如果你发现某些行业词切得不对比如“Transformer”被拆成“Transformer”一个词还行但“BERT”经常被ik拆成“B”“ERT”这时要自定义词典后面会在避坑部分专门讲。3.3 Query DSL详解match、multi_match与bool组合查询数据进去了查询怎么写是另一个容易卡住的点。DSL是ES的查询语法用JSON表示。最简单的全文检索用match{ query: { match: { content: 人工智能 } } }match会对查询词做同样的分词处理然后去倒排索引里匹配。毕设里我更推荐用multi_match同时搜索标题和正文相关性更高{ query: { multi_match: { query: 人工智能, fields: [title^3, content] } } }逻辑说明title^3表示标题字段的权重乘以3匹配到标题的文档会排到前面。如果你想加上过滤条件比如只看某一来源就要用bool查询把过滤和全文检索组合起来。{ query: { bool: { must: [ { multi_match: { query: 人工智能, fields: [title^3, content] } } ], filter: [ { term: { source: 新华网 } } ] } }, from: 0, size: 10, sort: [ { publish_time: { order: desc } } ], highlight: { fields: { title: {}, content: {} } } }参数说明filter里的term是精确匹配不参与评分速度快from和size做分页sort按发布时间倒序highlight指定要高亮的字段ES会返回带em标签的片段前端直接渲染就能看到红字效果。这个DSL基本覆盖了毕设演示时90%的查询场景。调试DSL可以直接在Kibana的Dev Tools里或者用curl跑不要写一行代码调一次。4. 让搜索在网页上出来Django的搜索视图与数据同步策略4.1 Django创建app与ORM连接搜索项目里Django到底负责什么在这个三国杀架构里Django的主要职责是接收用户搜索词构造ES查询把结果渲染成网页。它不需要存数据因为ES本身就是数据源也不需要直接操作数据库。很多同学一上来就在Django里建models、跑migrate然后发现数据进不去这是没想清楚架构。Django负责的是Web层ES负责存储与检索引擎Scrapy负责数据采集三者通过HTTP协议沟通。创建app的命令如下django-admin startproject mysite cd mysite python manage.py startapp search然后在mysite/settings.py里把search加进INSTALLED_APPS。写一个简单的搜索页面只需要一个视图、一个模板、一个路由。视图里通过ES客户端发起查询把结果传给模板渲染。4.2 搜索视图怎么写分页、高亮、排序一次到位直接看代码。用elasticsearch-py库来连ES视图函数里执行查询# search/views.py from django.shortcuts import render from elasticsearch import Elasticsearch es Elasticsearch([http://localhost:9200], timeout30) def search_view(request): q request.GET.get(q, ).strip() page int(request.GET.get(page, 1)) page_size 10 if not q: return render(request, search/index.html) body { query: { multi_match: { query: q, fields: [title^3, content] } }, from: (page - 1) * page_size, size: page_size, highlight: { fields: {title: {}, content: {}} } } # 只在有排序需求时加sort默认按评分排序 resp es.search(indexnews, bodybody) return render(request, search/results.html, { results: resp[hits][hits], q: q, total: resp[hits][total][value], page: page, })逻辑说明from和size组合实现分页highlight的字段声明和DSL里保持一致这样返回的_hit里会多出一个highlight字段total用于前端显示总共找到多少条。一个常见问题是默认排序是按相关性得分如果你想要最新优先就在body里加上sort。模板里取出高亮片段{% for hit in results %} h3 {% if hit.highlight.title %} {{ hit.highlight.title.0|safe }} {% else %} {{ hit._source.title }} {% endif %} /h3 p {% if hit.highlight.content %} {{ hit.highlight.content.0|safe }} {% else %} {{ hit._source.content|truncatechars:150 }} {% endif %} /p a href{{ hit._source.url }}原文链接/a {% endfor %}说明highlight返回的片段里包含标签模板里用safe过滤器放行如果不加safe页面上会直接显示em标签源码。content如果没有匹配到高亮片段就用truncatechars截断前150字作为摘要。这套写法做出来的搜索页观感上已经有搜索引擎的样子了。4.3 数据同步与删除对象Django和ES的一致性怎么维护小型系统最常见的数据同步方式是让Scrapy的pipeline同时写数据库和ES或只写ES。毕设里如果只用ES就不需要同步。但有一个场景必须处理当你重跑爬虫时旧数据可能已经在ES里了不清理就会重复或残留。常见做法是在爬虫启动前先删掉对应索引再重建索引# pipelines.py 中 open_spider 里做重建 def open_spider(self, spider): if self.es.indices.exists(indexnews): self.es.indices.delete(indexnews) # 这里调用上一章创建mapping的代码建立新索引逻辑说明删除再重建是最粗暴但最可靠的方式数据量在万级时重建只需几秒。如果你不想全量重建可以在Django里写一个删除视图比如管理员手动删除某条过期数据时同时从ES删除。Django删除对象本身用ORM很简单问题是ES里对应文档也要删# 删除ES文档示例 def delete_news(request, doc_id): es.delete(indexnews, iddoc_id) return redirect(/search/)注意这个doc_id就是之前pipeline写入时用的url字段。如果你在Scrapy里没用url做id这里就要维护一个外部队列映射徒增复杂度。所以设计阶段给每条数据一个稳定ID是后续所有操作的地基。5. 避坑指南这几个坑我当年全踩过你在答辩前绕开5.1 现象数据明明写进去了搜索却查不到原因分析ES写入是近实时的默认refresh间隔是1秒。你写入文档后立刻查可能查不到。另一个更隐蔽的问题是分页太深如果你用from和size翻到10000条以后ES会报错因为默认的max_result_window是10000。很多毕设数据量不会超过这个数但是批量导入时可能误触发。解决方式测试时手动触发刷新curl -X POST http://localhost:9200/news/_refresh分页超过10000条时改用search_after但毕设里建议直接把size限制在10000内或者加大max_result_window。我一般只用前10页结果做演示够用了。5.2 现象爬虫跑完了ES里还是空的原因分析最常见是pipeline没启用。Scrapy的pipeline写在代码里还不够必须在settings.py的ITEM_PIPELINES里注册否则process_item永远不会被调用。另一个可能是item出现异常被丢弃了而你又没看日志。解决方式先看Scrapy日志里是否有“Item dropped”或异常堆栈把pipeline里对外的网络请求用try/except包起来并打日志。记住一个原则先跑进ES一条再去跑完整爬虫黑盒出问题就先缩小范围到单条数据。5.3 现象Django页面响应慢搜索请求要好几秒原因分析首当其冲的是ES连接初始化问题。每次请求都新建Elasticsearch客户端握手开销几毫秒到几十毫秒不等但高并发下特别明显。更常见的是你在视图里做了多余工作比如把全部结果都取回再在Python里过滤而不是把过滤条件放进DSL。解决方式把ES客户端做成模块级单例Django进程内复用查询参数尽量在DSL里完成别用Python做后置过滤。如果页面还要显示摘要考虑在pipeline阶段把摘要截好存进ES省掉每次查询时的截断动作。尺寸不大的项目做到这两点接口响应能回到百毫秒级。5.4 现象中文搜索“Transformer”查不到东西原因分析ik分词器默认词典是通用中文词库对专业术语和英文缩写支持不好。“Transformer”会被拆成“Transform”“er”或者干脆分不出来。自定义词典是常见做法在ik/config目录下新建一个词典文件比如main.dic里加一行“transformer”然后在IKAnalyzer.cfg.xml里引入这个词典重启ES才生效。解决方式把行业术语、人名、专有名词慢慢累积进自定义词典爬虫数据量不大时也可以把分词器改成ik_smart做粗粒度切分效果不一定差。词典生效后用curl -X POST http://localhost:9200/news/_analyze传一段文本就能看到切词结果这招我每次都用来验证分词状态。5.5 现象重启ES后索引丢了原因分析ES的数据目录默认在解压目录的data文件夹里。如果你为了“干净”把解压目录删了或者在不同版本ES之间拷贝data目录索引基本就废了。还有一种情况是ES没有正常退出数据目录里的translog损坏启动时直接报index.must_be_specified这类错误。解决方式毕设阶段最简单的策略是保留重建脚本索引丢了就重跑一次。把创建mapping的curl脚本存下来把Scrapy爬虫的启动命令存下来整个数据流程能在十几分钟内重建完毕。这不丢人反而显得你的方案可工程化、可恢复性强。生产级的调备份策略不在毕设范围内。6. 让毕设真正能演示验证方法、数据规模与一个加分技巧毕设演示最怕的是临场翻车。我建议你在答辩前定量验证三个东西数据量是否覆盖了搜索意图、搜索响应时间是否稳定、高频词搜索结果是否合理。数据量在1万条以上、搜索响应低于500毫秒、搜索结果第一页包含你预先准备的关键词这三个指标达标演示就很稳。验证响应时间可以用Django的调试工具栏也可以直接在浏览器F12里看网络耗时。一个加分技巧是加入搜索建议词或热门搜索排行。在Django里写一个中间件把每次搜索词记到ES里单独一个索引或者存到SQLite表里然后在前端页面显示“大家都在搜”。这个功能不需要改动搜索引擎本身但展示出来会让评委觉得你考虑了产品完整性。我当年就是把搜索日志做了个词频统计然后在前端渲染成热词标签点击标签直接触发搜索效果比干巴巴的一个搜索框好很多。另一个我自己习惯用的验证思路是故意输入一个错别字或者不完整的短语看搜索能不能兜底。小型引擎的容错能力普遍弱遇到查不到的结果页要友好提示而不只是空白页。你可以在视图里加一个判断命中数为0时给出相近的搜索建议。这个细节在答辩时非常加分因为它说明了你知道搜索引擎不是万能的而且你做的是产品而不是demo。总的来说这个毕设项目的核心不是单点技术多深而是你能否三言两语讲清楚“数据从哪儿来、怎么变成可检索的结构、用户怎么查”。把三条链路打通并且稳定复现比追求花哨的算法更实用。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑