##定义与原理
搜索引擎( Search Engine)是指根据一定的策略、运用特定的计算机程序从互联网上搜集信息,在对信息进行组织和处理后,为用户提供检索服务,将用户检索相关的信息展示给用户的系统。搜索引擎包括全文索引、目录索引、元搜索引擎、垂直搜索引擎、集合式搜索引擎、门户搜索引擎与免费链接列表等。
垂直搜索引擎是应用于某一个行业、专业的搜索引擎,是搜索引擎的延伸和应用细分化。垂直搜索引擎为用户提供的并不是上百甚至上千万相关网页,而是范围极为缩小、极具针对性的具体信息。因此,特定行业的用户更加青睐垂直搜索引擎。
搜索引擎的基本工作原理包括如下三个过程:首先在互联网中发现、搜集网页信息;同时对信息进行提取和组织建立索引库;再由检索器根据用户输入的查询关键字,在索引库中快速检出文档,进行文档与查询的相关度评价,对将要输出的结果进行排序,并将查询结果返回给用户。
##功能介绍
搜索引擎对大家来说都不陌生,如百度,谷歌…… 我们今天就来构建我们自己的搜索引擎。
下面我们就来介绍一下我们要实现的功能:
- 创建ES索引,建立mapping;
- 网络数据抓取(即网络爬虫);
- 抓取数据解析;
- 解析数据保存到ES中;
- 建立搜索页面,用户通过搜索框,输入关键字进行搜索;
- 点击搜索按钮,实现对数据的检索;
- 搜索数据内容展示。
-
##技术选型
本项目主要使用到NodeJS、Python、Elasticsearch,详细介绍如下:
NodeJS:Node.js采用Google Chrome浏览器的V8引擎,一个后端的Javascript运行环境,提供很多系统级的API,如文件操作、网络编程等。
Python:Python(英语发音:/ˈpaɪθən/), 是一种面向对象、解释型计算机程序设计语言,Python语法简洁而清晰,具有丰富和强大的类库。能够把用其他语言制作的各种模块(尤其是C/C++)很轻松地联结在一起。
ElasticSearch:ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是第二流行的企业搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
有对以上技术不了解的同学,可以去看我们的课程。
-
##结构划分
项目主要分为以下几大模块:数据搜索模块,数据展示模块,网络数据抓取与存储模块。
- 网络数据抓取:对网络上的数据进行抓取解析;
- 数据存储模块:对抓取到数据按规则进行存储。
-
后面我们将会按功能来实现这些模块。
- 数据搜索展示模块:用户通在搜索框输入关键字进行搜索,搜索到的数据进行展示;
-
##开发环境
由于本项目用到技术较多,而在我们右面的编辑环境中只能用来做部分代码的测试,所以我们要在自己的电脑上进行开发环境的安装,本项目用到的操作系统是ubuntu,当然你也可以选择其它系统。
1.安装nodejs开发环境,我们的环境安装的是nodejs v0.10.38,并且安装EXPRESS框架;
2.安装Elasticsearch及中文分词ik,ES安装的是v 1.5.2;
3.安装python,并且安装pyspider,我们的环境安装的是python 2.7;
将以上开发程序安装完成以后,就可以开始我们的项目了,有不了解以上技术的同学,可以去课程中心查看对应的课程
-
##建立索引
在我们安装完Elasticsearch与中文分词以后,我们要在Elasticsearch中建立一个索引来存储数据。比如我创建的索引是article。因为ES是安装在ubuntu系统下,所以需要用到ubuntu中的curl命令,不了解的同学可以去查一下curl命令的相关资料。
下面代码是建立索引为article的索引。
curl -XPUT 'http://localhost:9200/article/'
返回结果为:
{"acknowledged":true}
索引建立完成。
-
##建立mapping
在前面一节中我们已经建立索引名为article的索引,我们在这将对article中的内容进行约束,进行验证。从而在存取数据时按照我们预定的规则进行存储。也就是我们在这里要建立article的mapping。下面代码是建立索引为article,索引类型为detail的mapping,同时指定中文分词ik。
curl -XPUT 'http://localhost:9200/article/_mapping/detail' -d ' { "detail" : { "dynamic" : true, "properties" : { "title" : { "type" : "string" }, "url" : { "type" : "string" }, "content" :{ "type" : "string", "analyzer" : "ik" } } } } '
返回结果为:
{"acknowledged":true}
索引mapping建好完成,这样我们的数据存储准备工作就已经完成。
-
##数据保存
我们想要把下面一组json数据数据存入ES的article索引中,例如:
{ "title":"hello world!", "url": "http://www.hubwiz.com", "content":"一个在线学习编程的网站。" }
我们要根据前面两节建立内容,对以上json数据用下面的语句来实现:
curl -XPOST 'http://localhost:9200/article/detail' -d '{ "title":"hello world!", "url": "http://www.hubwiz.com", "content":"一个在线学习编程的网站。" }'
返回结果如下:
{"_index":"article","_type":"detail","_id":"AU2tlMrXq0fYwjnKKzWI","_version":1,"created":true}
其中id是系统自动产生的,这样我们就将以上数据存入了ES中。
-
##ES数据检索
ES中已经存储了我们需要的数据,我们现在要通过ES的request body来查找属性为“content”,值可以匹配“编程”的记录,代码如下:
curl -XGET 'http://localhost:9200/article_one/detail/_search' -d ' { "query": { "match": {"content":"编程"} } }'
查询结果返回如下:
{ "took" : 5, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.076713204, "hits" : [ { "_index" : "article_one", "_type" : "detail", "_id" : "AU2tlMrXq0fYwjnKKzWI", "_score" : 0.076713204, "_source":{ "title":"hello world!", "url": "http://www.hubwiz.com", "content":"一个在线学习编程的网站" } } ] } }
-
##pyspider简介
pyspider的设计基础是:以python脚本驱动的抓取环模型爬虫;
- 通过python脚本进行结构化信息的提取,follow链接调度抓取控制,实现最大的灵活性;
- 通过web化的脚本编写、调试环境。web展现调度状态;
- 抓取环模型成熟稳定,模块间相互独立,通过消息队列连接,从单进程到多机分布式灵活拓展。
-
pyspider作为一个开源的数据抓取框架,它提供了数据抓取,数据解析,数据展示等功能。
pyspider 的主要特性:
- python 脚本控制,可以用任何你喜欢的html解析包(内置 pyquery)
- WEB 界面编写调试脚本,起停脚本,监控执行状态,查看活动历史,获取结果产出
- 支持 MySQL, MongoDB, SQLite
- 支持抓取 JavaScript 的页面
- 组件可替换,支持单机/分布式部署,支持 Docker 部署
- 强大的调度控制
-
##pyspider数据抓取
由于教程是基于 pyspider 的,你可以安装一个 pyspider(http://demo.pyspider.org/)。
网络数据抓取实际就是去爬网页,而爬网页实际上就是:
- 找到包含我们需要的信息的网址(URL)列表;
- 通过 HTTP 协议把页面下载回来;
- 从页面的 HTML 中解析出需要的信息;
- 找到更多这个的 URL,回到 2 继续。
-
在数据抓取的过程中,我们需要对万维网有一些简单的认识,万维网使用http协议传输,采用html描述外观和语义,使用网址(URL)定位,并链接彼此等。这样我们更容易对抓取到的数据进行解析。
from libs.base_handler import * class Handler(BaseHandler): crawl_config = { } @every(minutes=24 * 60) def on_start(self): self.crawl('http://scrapy.org/', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('a[href^="http"]').items(): self.crawl(each.attr.href, callback=self.detail_page) @config(priority=2) def detail_page(self, response): return { "url": response.url, "title": response.doc('title').text(), }
以上代码是创建任务后默认生成的一个脚本示例。
通过 _onstart 回调函数,作为爬取的入口点,当点击主面板上的 run 的时候,就是调用这个函数,启动抓取。 self.crawl 告诉调度器,默认抓取 'http://scrapy.org/' 这个页面,然后使用 _callback=self.indexpage 这个回调函数进行数据解析。所有 return 的内容默认会被捕获到 resultdb 中。
-
##pyspider数据解析
在上一节中我们介绍pyspider抓取数据,而其中有一个函数_detailpage,这个函数的功能是对数据进行解析。由于示例代码不满足我们要存取的数据格式,所以要对该函数进行修改,修改成我们想要的内容如下:
def detail_page(self, response): return { "url": response.url, "title": response.doc('title').text(), "content":response.doc('#article-content').text(), }
-
##数据存储
pyspider是个非常强大简单易用的爬虫框架,默认软件会把采集的所有字段打包保存到默认的数据库中,但是我们不希望存到默认数据库中,要存到ES中。这就要求重写pyspider的_onresult函数,我们将重写的_onresult函数放到_detailpage函数后面,当程序运行时就会执行我们的_onresult函数了。
重写的_onresult函数的功能就是将已经解析完成的数据保存到ES中。具体实现我们将在后面进行讲解。
-
##建立项目
前面讲解了pyspider的抓取示例代码,现在我们就来创建一个pyspider的项目。
我们在安装完成pyspider启动以后,就可以访问pyspider的dashboard地址为http://localhost:5000, 在pyspider 的 dashboard 的右下角,点击 "Create" 按钮,项目名为:_pytest
替换 _onstart 函数的 self.crawl 的 URL,我们要抓取的地址是:'http://blog.csdn.net/qust_waiwai/article/details/18564231'
def on_start(self): self.crawl('http://blog.csdn.net/qust_waiwai/article/details/18564231', callback=self.index_page)
这样我们的抓取路径就修改完成。
-
##解析
在前一节中已经建立了_pytest 的项目, 在 _onstart 函数后面还有 _indexpage 与_detailpage两个函数,其中_indexpage函数通过回调_detailpage函数来对数据进行解析。比如我们前一节中抓取的页面的html结构如下图,其中深蓝色的部分就是我们想要的内容,由于pyspider支持CSS选择器来获取抓取页面元素的值,如下代码取到id为article-content的值,而在_indexpage函数中的CSS表达式.search-list-con dl dt a[href^="http"] 的作用是进一步去抓取该页中满足条件的链接。
def index_page(self, response): for each in response.doc('.search-list-con dl dt a[href^="http"]').items(): self.crawl(each.attr.href, callback=self.detail_page) @config(priority=2) def detail_page(self, response): return { "url": response.url, "title": response.doc('title').text(), "content":response.doc('#article-content').text(), }
这样我们的数据解析部分就完成了。
##ES存储
前面已经提到把数据存储到ES中,我们在这里就来实现,代码如下:
def on_result(self, result): if not result or not result['title']: return es = Elasticsearch() es.index(index="article", doc_type="detail", body={"content": result['txt'],"title" :result['title'],"url":result['url']})
在代码的开头部分我们还需要引入操作ES的模块:from elasticsearch import Elasticsearch。
这样存储部分也完成了,然后点击save。回到dashboard页面,找到项目_pytest,修改status为running,点击run就可以抓取了。
-
##搜索页面
首先要建立一个类似于百度那样的搜索页面,在这里我们用nodejs来实现。具体效果如图:
首先呢,我们先新建一个项目工程目录,然后在目录下创建启动文件app.js,开始我们的第一步。这里我们会用到Express框架来实现相关功能,所以,需要先安装它,具体安装方法这里就不在做介绍了。在启动文件添加如下内容,来测试Express框架是否引用成功。
var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('Hello World!'); }); app.listen(80);
然后创建目录,使用engine函数注册模板引擎并指定处理后缀名为html的文件。
app.set( 'view engine', 'html' ); app.engine( '.html', require( 'ejs' ).__express );
最后在view文件夹中按照效果图的样子建立一个文件名为index.html的页面。这样我们的搜索页面就完成了。
<div class="input-group"> <input type="text" id="key_search" maxlength="256" size="46" style=" width:450px"/> <button class="btn btn-primary" data-toggle="button" id="btn_search" style="margin-bottom:7px !important;">search</button> </div>
##数据检索
当我们在输入框,输入要检索的关键字后,点击查询按钮,这时我们要做的就是去实现数据的检索。主要代码如下:
$("#btn_search").click(function () { var key = $("#key_search").val(); var data = '{"query":{"match":{"content":"' + key + '"}}}'; $.ajax({ url: 'http://192.168.1.200:9200/article/detail/_search?source=' + data, type: 'POST', data: data, dataType: "jsonp", async: false, jsonp: "callback", success: function (data) { //展示结果 // data中的数据即为我们要查询的数据,然后解析出我们需要的数据,展示出来。 }, error: function (data) { alert("Error!"); } }); });
我们这里主要用了jquery的ajax技术来实现数据的搜索,对搜索结果具体解析,得到我们想要的数据,在页面上展示出来,其中data就是我们要解析展示的数据。
-
##数据解析
根据上一节的内容,我们按关键字“编程”查询,得到的结果如下:
{ "took" : 2, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 1.0, "hits" : [ { "_index" : "article_one", "_type" : "detail", "_id" : "AU2tlMrXq0fYwjnKKzWI", "_score" : 1.0, "_source":{ "title":"hello world!", "url": "http://www.hubwiz.com", "content":"一个在线学习编程的网站。" } } ] } }
按照搜索的结果,我们就可以根据json格式取出想要的数据,然后对数据进行加工修饰,以达到想要的效果。
简单效果图如下:
这样我们的搜索引擎基本上已经完成了,下面就开启ES的服务,用nodejs运行我们的网站吧。