一个搜索引擎的实现流程大概为:首先获取海量的数据,整理成统一的格式,然后交给索引程序建立索引,当索引建立好后,就可以进行搜索。简而言之就是:数据获取->数据检索->数据搜索

0x1数据获取

数据获取大概有如下两种:

  • 爬虫定期获取:根据网站特征,写爬虫规则,定期获取想要的文章数据
  • 网站主动推送:网站拥有者主动向搜索引擎提交文章数据

搜索引擎的初期数据获取一般只能采取爬虫定期获取,当搜索引擎比较普遍使用(如谷歌、百度等),才会有很多网站拥有者主动推送。

0x1.1爬取站点

信息安全文章的站点,可以分为三类

  • 安全社区:先知社区、安全客、嘶吼、freebuf、安全脉搏、91ri、看雪论坛、乌云知识库等
  • 创作社区:博客园、csdn、简书、知乎、腾讯云社区等
  • 个人博客:hexo主题博客、wordpress博客等

0x1.2爬取方式

在爬取之前,先弄清一下爬取的需求,每篇文章需要获取发布日期、作者、标题、正文内容、文章链接、网站域名。接着对文章重复的判断,这里主要是根据文章链接的唯一性来判断是否重复,当然有的文章可能会在多处发表,存在一小部分的重复文章,最后根据每个网站特点,写定制化爬虫。

定制化爬虫的爬取方式可以分为两种,一种是根据网站页面特征来爬取,一种是请求数据接口来爬取。本次爬虫使用的 python 的 Scrapy 框架来演示。

0x1.2.1 网页特征

通过观察 HTML 页面,先确定一下要爬取信息所在的位置,然后看一下该位置所处的 DOM 路径、标签元素、属性元素,找到能准确获取该信息的方式。以 hexo 的 next 主题博客为例,这个主题还是挺多师傅使用的。

爬取思路:
(1). 爬取当前页面的所有文章链接
(2). 对页面中的每个文章链接进行爬取,得到文章相关信息
(3). 当前页面爬取完后,获取“下一页”,再从步骤(1)爬取

获取链接

获取文章信息

得到下一页面

在Scrapy 中可以使用 Scrapy shell 调试

scrapy shell article_url

Scrapy中的Spider代码如下

import scrapy
from urllib import parse
from crawlersec.items import CrawlersecItem
from crawlersec.util import html_entity
class SiHouSpider(scrapy.Spider):name="hexo_next"start_urls = ["https://chybeta.github.io/"]def parse(self, response):urls=response.xpath("//a[@class='post-title-link']/@href").extract()#得到页面的所有文章链接for i in range(len(urls)):absolute_url=parse.urljoin(response.url,urls[i])yield scrapy.Request(url=absolute_url, callback=self.parse_text)next_page=response.xpath("//a[@class='extend next']/@href").extract_first()if next_page:#获取下一页页面absolute_url = parse.urljoin(response.url, next_page)yield scrapy.Request(url=absolute_url, callback=self.parse)def parse_text(self,response):#获取文章的相关信息item=CrawlersecItem()item['url']=response.urlitem['title']=html_entity(response.css(".post-title::text").extract_first().strip())item['author']=get_author_by_url(response.url)item['date']=response.xpath("//time/text()").extract_first().strip()content=""for text in response.xpath("//div[@class='post-body']//text()").extract():content +="".join(text.split())content=html_entity(content)item['content']=contentitem['domain']=list(parse.urlparse(response.url))[1]yield item
def get_author_by_url(url):authors = {"chybeta.github.io": "chybeta"}return authors[list(parse.urlparse(url))[1]]

0x1.2.2 数据接口

有些站点的文章信息是从数据接口请求而来,刚好可以直接请求数据接口获取文章的信息。例如安全客的文章:

安全客的网站结构是相对比较复杂,但点击加载更多,发现文章信息是通过请求数据接口得来的。
数据接口为

https://api.anquanke.com/data/v1/posts?size=20&page=1

爬取思路为:
(1).首先获取当前数据分页中每条数据文章标题、文章id、发布日期、作者
(2).将获取的文章id,都加上https://www.anquanke.com/post/id/,得到文章链接,请求文章链接获取正文内容
(3).获取下一数据分页,重复步骤(1)

Scrapy中的Spider代码如下

import scrapy
from urllib import parse
import simplejson
from crawlersec.items import CrawlersecItem
from crawlersec.util import html_entity
class AnquankeSpider(scrapy.Spider):name="anquanke"allowed_domains=["anquanke.com"]base_url="https://www.anquanke.com/"start_urls=["https://api.anquanke.com/data/v1/posts?size=20"]def parse(self,response):prefix_url="https://www.anquanke.com/post/id/"res=simplejson.loads(response.text)posts=res['data']for post in posts:item=CrawlersecItem()item['author']=post['author']['nickname']item['date']=post['date']item['title'] = post['title']url=prefix_url+str(post['id'])item['url']=urlitem['domain']=list(parse.urlparse(self.base_url))[1]yield scrapy.Request(url=url,meta={'article_item':item},callback=self.parse_text)next_url=res['next']if next_url:yield scrapy.Request(url=next_url,callback=self.parse)def parse_text(self,response):item=response.meta.get('article_item','')content=""for text in response.xpath("//text()").extract():content +="".join(text.split())content=html_entity(content)#HTML entity encodingitem['content']=contentprint(item['content'])yield item

0x1.3 反爬策略绕过

0x1.3.1 User-Agent

反爬策略:网站在处理反爬的过程中,很常见的一种方式就是通过检测 User-agent 来拒绝非浏览器的访问。

绕过方式:可以维护一个 User-agent 组合列表,在发送请求时随机从列表中抽取一个,放入 Headers 请求头部里。

可以在 Scrapy 的 middlewares.py 中自定义 RandomUserAgentMiddleware 类,并作为Download Middleware 启用。Download Middware 是引擎和下载器的中间件,每个 Request 在爬取之前都会调用其中开启的类,从而对 Request 进行一定的处理,在这里对每个请求加上随机的User-Agent

class RandomUserAgentMiddleware(object):def process_request(self, request, spider):rand_use  = random.choice(USER_AGENT_LIST)if rand_use:request.headers.setdefault('User-Agent', rand_use)

在 Scrapy 的 setting.py 中定义 USER_AGENT_LIST

USER_AGENT_LIST=["Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1","Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5","Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3","Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]

在 setting.py 的 Download Middware 参数配置中添加定义的类

DOWNLOADER_MIDDLEWARES = {'crawlersec.middlewares.CrawlersecDownloaderMiddleware': 543,'crawlersec.middlewares.RandomUserAgentMiddleware': 400,}

0x1.3.2 IP限制

反爬策略:同一 IP 访问网站过于频繁,就会对该 IP 进行限制,短时间内无法访问。
绕过方式:
1.维护一个 IP 代理池,每次请求随机使用 一个 IP 代理
2.调小爬虫的线程并发数,或每次请求后,设置一个短时间暂停

这里就从一些免费的站点中获取一些 IP,并保存在 proxies.py 文件中
如 www.xiladaili.com 站点

import scrapy
import requestsclass SeeBugSpider(scrapy.Spider):name="proxy"allowed_domains=["www.xiladaili.com"]base_url="http://www.xiladaili.com/"def start_requests(self):tmp_url="http://www.xiladaili.com/gaoni/{}/"f = open('proxies.txt', "r+")f.truncate()for i in range(2,200):#数字可查看官网链接yield scrapy.Request(url=tmp_url.format(i),callback=self.parse)def parse(self,response):proxy_list=response.xpath("//tbody//tr/td/text()").extract()for i in range(0,len(proxy_list),8):self.verify_one_proxy(proxy_list[i+1],proxy_list[i])def verify_one_proxy(self,protocol,url):schema = 'https' if 'https' in protocol else 'http'proxies = {schema: url}print(proxies)try:if requests.get('https://www.baidu.com/', proxies=proxies, timeout=2).status_code == 200:with open('./proxies.txt', 'a+',encoding='utf-8') as f:f.write(schema+"://"+url+"\n")except:pass

在 Scrapy 的 middlewares.py 中定义一个 ProxyMiddleWare 类

class ProxyMiddleWare(object):"""docstring for ProxyMiddleWare"""def process_request(self, request, spider):'''对request对象加上proxy'''proxy = self.get_random_proxy()print("this is request ip:" + proxy)request.meta['proxy'] = proxydef process_response(self, request, response, spider):'''对返回的response处理'''# 如果返回的response状态不是200,重新生成当前request对象if response.status != 200:proxy = self.get_random_proxy()print("this is response ip:" + proxy)# 对当前request加上代理request.meta['proxy'] = proxyreturn requestreturn responsedef get_random_proxy(self):'''随机从文件中读取proxy'''while 1:with open('./proxies.txt', 'r') as f:proxies = f.readlines()if proxies:breakelse:pass#time.sleep(1)proxy = random.choice(proxies).strip()return proxy

在 setting.py 的 Download Middware 参数中添加配置的类

DOWNLOADER_MIDDLEWARES = {'crawlersec.middlewares.CrawlersecDownloaderMiddleware': 543,'crawlersec.middlewares.ProxyMiddleWare': 540,}

0x1.3.3 Cookie

反爬策略:文章需要登录后才能访问
绕过方法:
1.手动登录获取 Cookie,将 Cookie 添加到爬虫脚本中
2.模拟登录

0x1.3.4 Header

反爬策略:网站的文章需要带特定的头部请求才能允许访问,如Referer等
绕过方法: 每次请求中添加需要的头部
例如:

headers={"X-Requested-With":"XMLHttpRequest","Referer": "https://www.kanxue.com/"}yield scrapy.FormRequest(url=url,formdata=data,headers=headers,callback=self.parse)

0x2.数据检索

当数据爬取后,对数据建立倒排索引,方便我们快速搜索

0x2.1 倒排索引

倒排索引也称全文索引,检索程序对文章的每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

例如有两篇文章:
文章1内容:it is sunny today
文章2内容:today is rainy

1.首先取得关键字
文章1关键字:[it] [is] [sunny] [today]
文章2关键字:[today] [is] [rainy]
2.建立倒排索引
有了关键词后,就可以建立倒排索引了。上面的对应关系是:“文章号”对“文章中所有关键词”。倒排索引把这个关系倒过来,变成: “关键词”对“拥有该关键词的所有文章号”。

关键词 文章号
it 1
is 1,2
sunny 1
today 1,2
rainy 2

通常仅知道关键词在哪些文章中出现还不够,我们还需要知道关键词在文章中出现次数和出现的位置

关键词 文章号[出现频率] 出现位置
it 1[1] 1
is 1[1] 2
is 2[1] 2
sunny 1[1] 3
today 1[1] 4
today 2[1] 1
rainy 2[1] 3

实现时,将上面三列分别作为词典文件(Term Dictionary)、频率文件(frequencies)、位置文件 (positions)保存。其中词典文件不仅保存有每个关键词,还保留了指向频率文件和位置文件的指针,通过指针可以找到该关键字的频率信息和位置信息。

0x2.2 elasticsearch

数据检索这里借助了 Elasticsearch。Elasticsearch的 Mapping 提供了对 Elasticsearch 中索引字段名及其数据类型的定义,还可以对某些字段添加特殊属性:该字段是否分词,是否存储,使用什么样的分词器等。

常用的数据类型(type)有:string、text、date等
Elaticsearch 的 mapping 样例如下,对文章链接、标题、作者、发布日期、正文内容、网站域名这六个字段指定检索方式

mappings = {"mappings": {"properties": {"url": {"type": "keyword"},"title": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_max_word","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"author": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_max_word","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"date": {"type": "date","format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"},"content": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_max_word"},"domain": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_max_word","fields": {"keyword": {"type": "keyword","ignore_above": 256}}}}}}

0x3.数据搜索

对数据检索完后,就可以搜索,然而如果搜索结果有一千个,甚至成千上万个呢?哪个又是您最想要的文章?

打开 google ,搜索 “web安全”,返回 518000000 条结果,好大的一个数字,在众多的搜索结果中,如何将最相关的放在最前面?

先简单了解一下数据搜索的过程:
1.搜索字符串分词
对输入的 “web安全”进行分词:web 、安全、安、全
2.搜索字符串和文档的相关性计算
首先,一个文档有很多词(Term)组成,如web、安全、安、全、等。
其次对于文档之间的关系,不同的 Term 重要性不同,比如对于本篇文档,“web、安全”就相对重要一些,“的、地、可”可能相对不重要一些。所以如果两篇文档都包含“web、安全”,这两篇文档的相关性好一些,然而就算一篇文档包含“的、地、可”,另一篇文档不包含“的、地、可”,也不能影响两篇文档的相关性。

因而判断文档之间的关系,首先找出哪些词(Term)对文档之间的关系最重要,如“web、安全”,然后判断这些词(Term)之间的关系。

找出词(Term)对文档的重要性的过程称为计算词的权重(Term weight)的过程。
计算词的权重(Term weight)有两个参数,第一个是词(Term),第二个是文档(Document)
判断词(Term)之间的关系从而得到文档相关性的过程应用一种叫做向量空间模型的算法(Vector Space Model)。

(1)计算权重
影响一个词(Term)在一篇文档中的重要性主要有两个因素:

  • Term Frequency (tf):即此Term在此文档中出现了多少次。tf 越大说明越重要。
  • Document Frequency (df):即有多少文档包含次Term。df 越大说明越不重要。

词(Term)在文档中出现的次数越多,说明此词(Term)对该文档越重要,如“web”这个词,在本文档中出现的次数很多,说明本文档主要就是讲这方面的事的。然而在一篇文档中,“的”出现的次数更多,就说明越重要吗?不是的,这是由第二个因素进行调整,第二个因素说明,有越多的文档包含此词(Term), 说明此词(Term)太普通,不足以区分这些文档,因而重要性越低。
权重计算公式如下:

(2)向量空间模型的算法(VSM)
我们把文档看作一系列词(Term),每一个词(Term)都有一个权重(Term weight),不同的词(Term)根据自己在文档中的权重来影响文档相关性的打分计算。
于是我们把所有此文档中词(term)的权重(term weight) 看作一个向量。
Document = {term1, term2, …… ,term N}
Document Vector = {weight1, weight2, …… ,weight N}
同样我们把查询语句看作一个简单的文档,也用向量来表示。
Query = {term1, term 2, …… , term N}
Query Vector = {weight1, weight2, …… , weight N}
我们把所有搜索出的文档向量及查询向量放到一个N维空间中,每个词(term)是一维。

如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们认为两个向量之间的夹角越小,相关性越大。

所以我们计算夹角的余弦值作为相关性的打分,夹角越小,余弦值越大,打分越高,相关性越大。
相关性打分公式如下:

0x4 系统展示

最终项目地址:http://secsea.cfyqy.com/ 。写得有点简洁,莫喷。
1.web界面
显示最近一周的实时文章和相关资讯

显示收录文章数量比较多的站点

2.搜索功能
提供了正文内容搜索、标题搜索、作者搜索、时间搜索、站点数据搜索功能。

默认使用的是正文内容搜索

只想要某个站点的数据,并显示最近一年的

参考文章:
倒排索引原理和实现: https://www.cnblogs.com/binyue/p/3380750.html
全文检索的基本原理:https://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623594.html

信息安全文章搜索引擎技术原理相关推荐

  1. 搜索引擎技术原理及其应用

    搜索引擎技术原理及其应用 WEB --浅谈GOOGLE和BAIDU搜索技术 2004级计算机科学与技术三班 刘xx 本文将分为以下几部分阐述 一.Web搜索引擎技术综述 二.Google技术 三.百度 ...

  2. 浅谈搜索引擎技术原理与架构

    转载自:https://www.cnblogs.com/faruxue/p/4932009.html 搜索引擎是我们非常熟悉的互联网产品,上网都离不开搜索,毫无疑问,在pc端,是多数流量的入口.大家都 ...

  3. 信息安全-入侵检测技术原理与应用

    一.入侵检测概述 1.1 入侵检测概念 入侵应与受害目标相关联,该受害目标可以是一个大的系统或单个对象 判断与目标相关的操作是否为入侵的依据:对目标的操作是否超出了目标的安全策略范围 入侵:指违背访问 ...

  4. 全文搜索引擎Elasticsearch,这篇文章给讲透了!(Elasticsearch技术原理及实现方式)

    关于Elasticsearch的技术原理及实现方式看了两篇讲的非常好的文章,在这里分享给大家. 其中一篇是: Elasticsearch 技术分析(九):全文搜索引擎Elasticsearch,这篇文 ...

  5. 秋色园QBlog技术原理解析:性能优化篇:用户和文章计数器方案(十七)

    2019独角兽企业重金招聘Python工程师标准>>> 上节概要: 上节 秋色园QBlog技术原理解析:性能优化篇:access的并发极限及分库分散并发方案(十六)  中, 介绍了 ...

  6. 秋色园QBlog技术原理解析:性能优化篇:数据库文章表分表及分库减压方案(十五)...

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...

  7. 【第12章】网络安全审计技术原理与应用 (信息安全工程师)

    第12章 网络安全审计技术原理与应用 12.1 网络安全审计概述 12.1.1 网络安全审计概念 网络安全审计是指对网络信息系统的安全相关活动信息进行获取.记录.存储.分析和利用的工作.网络安全审计的 ...

  8. 【软考】 信息安全工程师教程 第六章 认证技术原理与应用

    目录 6.1 认证概述 6.1.1 认证概念 6.1.2 认证依据 6.1.3 认证原理 6.1.4 认证发展 6.2 认证类型与认证过程 6.2.1 单向认证 6.2.2 双向认证 6.2.3 第三 ...

  9. 【第13章】网络安全漏洞防护技术原理与应用(信息安全工程师 )-- 软考笔记

    第13章 网络安全漏洞防护技术原理与应用 13.1 网络安全漏洞概述 13.1.1 网络安全漏洞概念 网络安全漏洞又称为脆弱性,简称漏洞.漏洞一般是致使网络信息系统安全策略相冲突的缺陷,这种缺陷通常称 ...

最新文章

  1. 事务RFC(TRFC)原理和实战解析
  2. OpenCASCADE:教程概述
  3. Android开发的之基本控件和详解四种布局方式
  4. python高效办公_Python高效办公|自动分发任务
  5. 2018-2019-2 20165221 【网络对抗技术】-- Exp6 信息搜集与漏洞扫描
  6. Impala 的特点
  7. 水星usb无线网卡linux驱动下载,水星USB无线网卡mw150us苹果macOS系统驱动成功
  8. SQL Server数据库第二课:创建数据库表、完善数据库表的设计、建立数据库表之间的关系
  9. 手撸Mybatis源码-基础版
  10. 移动端应用视频小程序加密播放(存档)
  11. 半双工通信是指c语言,Linux下C语言实现半双工的UDP通信
  12. Bootstrap 字体图标和自定义矢量图标
  13. 使用mysql Installer安装失败处理办法
  14. flex 垂直方向 两端对齐
  15. 认是计算机第2节知识点,2.2《优化计算机》教案
  16. 解决plt.title()中文显示问题
  17. 杭电1042c语言循环,HDU杭电1052 Tian Ji - The Horse Racing答题报告
  18. 集中控制集中电源型消防应急照明和疏散指示系统 验收方法与标准
  19. 云之幻哔哩哔哩uwp_云之幻哔哩哔哩uwp
  20. ++在前和在后的区别

热门文章

  1. [NOI2016] 优秀的拆分 题解
  2. Oracle--使用同义词
  3. jquery获取单选框复选框下拉框值
  4. 百度AI开发者语音转文字python实现
  5. 国内家具行业数据浅析
  6. 人工智能也存在偏见?探究人工智能偏见的识别和管理
  7. JDK Stream
  8. 【实战】电商后台管理系统:路由封装基础布局
  9. python实现根据文件名自动分类转移至不同的文件夹
  10. lattice fpga ddr3 读写控制