目 录
摘 要… 1
1 概述… 6
2 技术选型… 6
2.1 Scrapy-Redis 分布式爬虫 … 6
2.1.1 Redis… 6
2.1.2 Scrapy… 7
2.2 MySQL 数据存储 … 8
2.3 Django 搭建搜索网站 … 8
2.4 ElasticSearch 搜索引擎 … 9
2.4.1 Elasticsearch-RTF… 9
2.4.2 Elasticsearch-head… 10
2.4.3 Kibana… 10
3 实现细节… 10
3.1 处理反爬… 10
3.1.1 更换随机 User-Agent … 10
3.1.2 使用 IP 代理池… 11
3.1.3 访问频率限制… 12
3.1.4 Cookie 的禁用 … 13
3.1.5 验证码识别… 13
3.2 爬取数据… 14
3.2.1 先知社区… 14
3.2.2 安全客… 17
3.2.3 嘶吼… 19
3.3 重构分布式爬虫… 21
3.3.1 需要解决的问题… 21
3.3.2 分布式的原理… 22
3.3.3 分布式的实现… 23
3.4 搜索引擎… 24
3.4.1 倒排索引… 24
3.4.2 排序评分… 25
3.4.3 搜索提示… 26
3.4.4 模糊搜索… 27
3.5 网页搭建… 28
3.5.1 爬虫统计数据… 28
3.5.2 热门搜索… 28
3.6 其他技术… 29
3.6.1 URL 去重策略 … 29
3.6.2 Bloom Filter 使用 … 31
4 系统展示 … 34
4.1 分布式爬取… 34
4.2 搜索网站首页… 36
4.3 搜索提示展示… 36
4.4 搜索结果展示… 37
1概述
爬虫的应用领域非常广泛,目前利用爬虫技术市面上已经存在了比较成熟的搜索引擎产品,如百度、谷歌,以及其他垂直领域搜索引擎,这些都是非直接目的;还有一些推荐引擎,如今日头条,可以定向给用户推荐相关新闻;爬虫还可以用来作为机器学习的数据样本。
本项目的主要目的是为了更好的整合利用安全领域特有的社区资源优势。首先使用 Scrapy 爬虫框架结合 NoSQL 数据库 Redis 编写分布式爬虫,并对先知、安全客、嘶吼三个知名安全社区进行技术文章的爬取;然后选取 ElasticSearch 搭建搜索服务,同时提供了 RESTfulweb 接口;最后通过 Django 搭建可视化站点,供用户透明的对文章进行搜索。
最终通过本项目可以更加透彻的理解爬虫的相关知识;在熟练运用 Python 语言的基础上,更加深入的掌握开源的爬虫框架 Scrapy,为后续其他与爬虫相关的业务奠定理论基础和数据基础;进一步理解分布式的概念,为大数据的相关研究和硬件条件奠定基础;熟练掌握 Python 搭建网站的框架 Django,深入理解基于 Lucene 的搜索服务器 ElasticSearch。
2技术选型
2.1Scrapy-Redis 分布式爬虫
2.1.1Redis
Redis 是完全开源免费的,遵守 BSD 协议的,高性能的 key-value 数据库。
Redis 与其他 key-value 缓存产品有以下三个特点:
(1)Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。这样可以防止数据的丢失,在实际生产应用中数据的完整性是必须保证的。
(2)Redis 不仅仅支持简单的 key-value 类型的数据,同时还提供 list,set,
zset,hash 等数据结构的存储。这些功能更强大的数据存储方式极大地节约了存储空间,优化了查询的性能,大大提高了查询效率。存储的目的是为了后期更好的取出,Redis 很好地做到了这一点。
(3)Redis 支持数据的备份,即 master-slave 模式的数据备份。主从结构目前是大数据里面的主流结构,主从模式能保证数据的健壮性和高可用。当出现电脑宕机,硬盘损坏等重大自然原因时,本文转载自http://www.biyezuopin.vip/onews.asp?id=16995主从模式能很好的保证存储的数据不丢失, 随时恢复到可用状态。
2.1.2Scrapy
Scrapy 的原理如下所示:
<!DOCTYPE html >
<html xmlns="http://www.w3.org/1999/xhtml">
{% load staticfiles %}
<head><meta http-equiv="X-UA-Compatible" content="IE=emulateIE7"/><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><title>ISA Search 搜索引擎</title><link href="{% static 'css/style.css' %}" rel="stylesheet" type="text/css"/><link href="{% static 'css/index.css' %}" rel="stylesheet" type="text/css"/>
</head>
<body>
<div id="container"><div id="bd"><div id="main"><h1 class="title"><div class="logo large"></div></h1><div class="inputArea"><input type="text" class="searchInput"/><input type="button" class="searchButton" onclick="add_search()"/><ul class="dataList"></ul></div><div class="historyArea"><p class="history"><label>热门搜索:</label>{% for search_word in topn_search %}<a href="/search?q={{ search_word }}">{{ search_word }}</a>{% endfor %}</p><p class="history mysearch"><label>我的搜索:</label><span class="all-search"><a href="javascript:;"></a></span></p></div></div><!-- End of main --></div><!--End of bd--></div>
</body>
<script type="text/javascript" src="{% static 'js/jquery.js' %}"></script>
<script type="text/javascript" src="{% static 'js/global.js' %}"></script>
<script type="text/javascript">var suggest_url = "{% url 'suggest' %}"var search_url = "{% url 'search' %}"$('.searchList').on('click', '.searchItem', function () {$('.searchList .searchItem').removeClass('current');$(this).addClass('current');});function removeByValue(arr, val) {for (var i = 0; i < arr.length; i++) {if (arr[i] == val) {arr.splice(i, 1);break;}}}// 搜索建议$(function () {$('.searchInput').bind(' input propertychange ', function () {var searchText = $(this).val();var tmpHtml = ""$.ajax({cache: false,type: 'get',dataType: 'json',url: suggest_url + "?s=" + searchText,async: true,success: function (data) {for (var i = 0; i < data.length; i++) {tmpHtml += '<li><a href="' + search_url + '?q=' + data[i] + '">' + data[i] + '</a></li>'}$(".dataList").html("")$(".dataList").append(tmpHtml);if (data.length == 0) {$('.dataList').hide()} else {$('.dataList').show()}}});});})hideElement($('.dataList'), $('.searchInput'));</script>
<script>function htmlSpecialChars(str) {var s = "";if (str.length == 0) return "";for (var i = 0; i < str.length; i++) {switch (str.substr(i, 1)) {case "<":s += "<";break;case ">":s += ">";break;case "&":s += "&";break;case " ":if (str.substr(i + 1, 1) == " ") {s += " ";i++;} else s += " ";break;case "\"":s += """;break;case "\n":s += "<br>";break;default:s += str.substr(i, 1);break;}}return s;}var searchArr;//定义一个search的,判断浏览器有无数据存储(搜索历史)if (localStorage.search) {//如果有,转换成 数组的形式存放到searchArr的数组里(localStorage以字符串的形式存储,所以要把它转换成数组的形式)searchArr = localStorage.search.split(",")} else {//如果没有,则定义searchArr为一个空的数组searchArr = [];}//把存储的数据显示出来作为搜索历史MapSearchArr();function add_search() {var val = $(".searchInput").val();if (val.length >= 2) {//点击搜索按钮时,去重KillRepeat(val);//去重后把数组存储到浏览器localStoragelocalStorage.search = searchArr;//然后再把搜索内容显示出来MapSearchArr();}window.location.href = search_url + '?q=' + val}function MapSearchArr() {var tmpHtml = "";var arrLen = 0if (searchArr.length >= 5) {arrLen = 5} else {arrLen = searchArr.length}for (var i = 0; i < arrLen; i++) {tmpHtml += '<a href="' + search_url + '?q=' + searchArr[i] + '">' + htmlSpecialChars(searchArr[i]) + '</a>'}$(".mysearch .all-search").html(tmpHtml);}//去重function KillRepeat(val) {var kill = 0;for (var i = 0; i < searchArr.length; i++) {if (val === searchArr[i]) {kill++;}}if (kill < 1) {searchArr.unshift(val);} else {removeByValue(searchArr, val)searchArr.unshift(val)}}</script>
</html>