Lucene搜索引擎+HDFS+MR完成垂直搜索

Posted Android Graphics

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lucene搜索引擎+HDFS+MR完成垂直搜索相关的知识,希望对你有一定的参考价值。

  介于上一篇的java实现网络爬虫基础之上,这一篇的思想是将网络收集的数据保存到HDFS和数据库(Mysql)中;然后用MR对HDFS的数据进行索引处理,处理成倒排索引;搜索时先用HDFS建立好的索引来搜索对应的数据ID,根据ID从数据库中提取数据,呈现到网页上。

   这是一个完整的集合网络爬虫、数据库、HDFS、MapReduce、DAO设计模式、JSP/Servlet的项目,完成了数据收集、数据分析、数据索引并分页呈现。

  完整的代码呈现,希望认真仔细阅读。

------>

目录:

1、搜索引擎阐述

2、数据库表建立

3、使用DAO设计模式进行数据库操作

【Ⅰ】数据库连接类DataBaseConnection

【Ⅱ】表单元素的封装类News

【Ⅲ】编写DAO接口INewsDAO

【Ⅳ】DAO接口的实现类NewsDAOImp类

【Ⅴ】工厂类DAOFactory类

4、网络爬虫实现★★ 【参考博客《java实现网络爬虫》《Heritrix实现网络爬虫》

5、MR(MapReduce)对HDFS数据进行索引处理★★

6、实现搜索引擎

【Ⅰ】创建web项目,编写测试用例,测试是否可以读取HDFS的数据内容

【Ⅱ】 编写index首页

【Ⅲ】处理HDFS查询的操作

【Ⅳ】servlet类搜索结果向页面传递

【Ⅴ】结果呈现,实现分页

7、总结

------>

 

1、搜索引擎阐述

搜索引擎的执行流程:

1) 通过爬虫来将数据下载到本地

2) 将数据提取出来保存到HDFS和数据库中(mysql

3) 通过MR来对HDFS的数据进行索引处理,处理成为倒排索引

4) 搜索时先使用HDFS建立好的索引来搜索对应的数据ID,再根据ID来从MySQL数据库中提取数据的具体信息。

5) 可以完成分页等操作。

 

倒排索引对应的就是正排索引,正排索引指的就是MySQL数据库中id的索引。

而倒排索引的目的是可以根据关键字查询出该关键字对应的数据id

 

这里就需要用到MySQL数据库,以及通过Java EE版的Eclipse来完成网站的开发。

为了开发起来更方便,我们这里使用MyEclipse来完成。

2、数据库表建立

先安装好MySQL数据库。

 

安装时,注意编码选择gbk

 

通过控制台的mysql -u用户名 –p密码 即可登录mysql数据库。

之后使用show databases可以看到所有的数据库。

使用create database 可以建立一个新的库。

使用 use 库名 ,可以切换到另一个库。

使用show tables可以看到一个库下的所有表。

之后就可以通过普通的sql语句来建立表和进行数据的操作了。

 

在进行数据库操作时,企业开发中必定要使用DAOData Access Object)设计模式

组成如下:

1) DataBaseConnection:建立数据库连接

2) VO:与表对应的数据对象

3) DAO接口:规范操作方法

4) DAOImpl:针对DAO接口进行方法实现。

5) Factory:用来建立DAO接口对象。

 

首先根据需求,将数据库表建立出来,这里只需建立一个简单的news新闻表,用于存储网络上爬取得数据。

1 CREATE TABLE news (
2     id                int                primary key ,
3     title            varchar(200)    not null,
4     description        text            ,
5     url                varchar(200)
6 );

 

3、使用DAO设计模式进行数据库操作

根据上述的DAO设计模式,我们需要编写相关操作类,来完成数据库的操作。

【Ⅰ】  数据库连接类DataBaseConnection,需要导入jar包:

 1 package org.liky.sina.dbc;
 2 
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.SQLException;
 6 
 7 /**
 8  * 连接数据库mysql的sina_news
 9  * @author k04
10  *
11  */
12 public class DataBaseConnection {
  //此处可以试着两种表达加载类的方法
13 // private static final String DBORIVER="org.git.mm.mysql.Driver"; 14 private static final String DBORIVER="com.mysql.jdbc.Driver"; 15 private static final String DBURL="jdbc:mysql://localhost:3306/sina_news"; 16 private static final String DBUSER="root"; 17 private static final String DBPASSWORD="admin"; 18 19 private Connection conn; 20 /** 21 * 创建数据库连接 22 * @return 23 */ 24 public Connection getConnection(){ 25 try { 26 if(conn==null||conn.isClosed()){ 27 //建立一个新的连接 28 Class.forName(DBORIVER); 29 conn=DriverManager.getConnection(DBURL, DBUSER, DBPASSWORD); 30 //System.out.println("success to connect!"); 31 } 32 }catch (ClassNotFoundException e) { 33 // TODO Auto-generated catch block 34 e.printStackTrace(); 35 } catch (SQLException e) { 36 // TODO Auto-generated catch block 37 e.printStackTrace(); 38 } 39 return conn; 40 } 41 /* 42 * 关闭连接 43 */ 44 public void close(){ 45 if(conn!=null){ 46 try{ 47 conn.close(); 48 }catch(SQLException e){ 49 e.printStackTrace(); 50 } 51 } 52 } 53 54 }

 【Ⅱ】表单元素的封装类News,根据建表时设定的四种元素id,title,description,url,将网络爬取得内容完整的导入数据库中,此处可以用shift+Alt+S在Eclipse快捷创建封装类:

 1 package org.liky.sina.vo;
 2 /**
 3  * news封装类
 4  * @author k04
 5  *
 6  */
 7 public class News {
 8     private Integer id;
 9     private String title;
10     private String description;
11     private String url;
12     
13     
14     public News() {
15     }
16     public News(Integer id,String title,String description,String url) {
17         this.id=id;
18         this.title=title;
19         this.description=description;
20         this.url=url;
21     }
22     
23     
24     public Integer getId() {
25         return id;
26     }
27     public void setId(Integer id) {
28         this.id = id;
29     }
30     public String getTitle() {
31         return title;
32     }
33     public void setTitle(String title) {
34         this.title = title;
35     }
36     public String getDescription() {
37         return description;
38     }
39     public void setDescription(String description) {
40         this.description = description;
41     }
42     public String getUrl() {
43         return url;
44     }
45     public void setUrl(String url) {
46         this.url = url;
47     }
48 }

 

 【Ⅲ】编写DAO接口INewsDAO,存放数据库操作类的方法名:

 1 package org.liky.sina.dao;
 2 /**
 3  * 接口,呈现三个方法
 4  */
 5 import java.util.List;
 6 import org.liky.sina.vo.News;
 7 
 8 public interface INewsDAO {
 9     /**
10      * 添加数据
11      * @param news    要添加的对象
12      * @throws Exception
13      */
14     public void doCreate(News news)throws Exception;
15     /**
16      * 根据主键id查询数据
17      * 
18      */
19     public News findById(int id)throws Exception;
20     /**
21      * 根据一组id查询所有结果
22      * @param ids    所有要查询的id
23      * @return    查询到的数据
24      * 因为索引是根据热词查到一堆的id
25      */
26     public List<News> findByIds(int[] ids)throws Exception;
27     
28 }

 

 【Ⅳ】DAO接口的实现类NewsDAOImp类:

 1 package org.liky.sina.dao.impl;
 2 /**
 3  * 继承INewsDAO接口
 4  * 实现三个方法,插入数据,查找指定id数据,查找一组id数据
 5  */
 6 import java.sql.PreparedStatement;
 7 import java.sql.ResultSet;
 8 import java.util.ArrayList;
 9 import java.util.List;
10 
11 import org.liky.sina.dao.INewsDAO;
12 import org.liky.sina.dbc.DataBaseConnection;
13 import org.liky.sina.vo.News;
14 
15 public class NewsDAOImpl implements INewsDAO {
16     //声明一个数据库连接类对象
17     private DataBaseConnection dbc;
18     
19     
20     //构造器,参数为数据库连接类对象
21     public NewsDAOImpl(DataBaseConnection dbc) {
22         this.dbc=dbc;
23         
24     }
25 
26     @Override
27     public void doCreate(News news) throws Exception {
28         // TODO Auto-generated method stub
29         String sql="INSERT INTO news (id,title,description,url) VALUES (?,?,?,?)";
30         PreparedStatement pst=dbc.getConnection().prepareStatement(sql);
31         //设置参数
32         pst.setInt(1, news.getId());
33         pst.setString(2, news.getTitle());
34         pst.setString(3, news.getDescription());
35         pst.setString(4, news.getUrl());
36         
37         pst.executeUpdate();
38         System.out.println("create success.");
39     }
40 
41     @Override
42     public News findById(int id) throws Exception {
43         // TODO Auto-generated method stub
44         String sql="SELECT id,title,description,url FROM news WHERE id = ?";
45         PreparedStatement pst=dbc.getConnection().prepareStatement(sql);
46         pst.setInt(1, id);
47         ResultSet rs=pst.executeQuery();
48         News news=null;
49         //将符合id的数据遍历写入news并返回
50         if(rs.next()){
51             news=new News();
52             news.setId(rs.getInt(1));
53             news.setTitle(rs.getString(2));
54             news.setDescription(rs.getString(3));
55             news.setUrl(rs.getString(4));
56         }
57         //System.out.println("find success.");
58         return news;
59     }
60 
61     @Override
62     public List<News> findByIds(int[] ids) throws Exception {
63         // TODO Auto-generated method stub
64         StringBuilder sql=new StringBuilder("SELECT id,title,description,url FROM news WHERE id IN (");
65         //将id写入ids,并用逗号隔开
66         if(ids!=null&&ids.length>0){
67             for(int id:ids){
68                 sql.append(id);
69                 sql.append(",");
70             }
71             //截取最后一个逗号,并补上括号
72             String resultSQL=sql.substring(0, sql.length()-1)+")";
73             
74             PreparedStatement pst=dbc.getConnection().prepareStatement(resultSQL);
75             ResultSet rs=pst.executeQuery();
76             //存取一组id到链表中
77             List<News> list=new ArrayList<>();
78             while(rs.next()){
79                 News news=new News();
80                 news.setId(rs.getInt(1));
81                 news.setTitle(rs.getString(2));
82                 news.setDescription(rs.getString(3));
83                 news.setUrl(rs.getString(4));
84                 list.add(news);
85             }
86         }
87         //System.out.println("find success.");
88         return null;
89     }
90 
91 }

 

【Ⅴ】工厂类DAOFactory类,此类写入了数据库连接类参数,返回DAO实现类对象:

   java中,我们通常有以下几种创建对象的方式:

       (1) 使用new关键字直接创建对象;

       (2) 通过反射机制创建对象;

       (3) 通过clone()方法创建对象;

       (4) 通过工厂类创建对象。

 1 package org.liky.sina.factory;
 2 /**
 3  * 工厂类
 4  * 输入一个连接数据库对象的参数,返回数据库表操作的类
 5  */
 6 import org.liky.sina.dao.INewsDAO;
 7 import org.liky.sina.dao.impl.NewsDAOImpl;
 8 import org.liky.sina.dbc.DataBaseConnection;
 9 
10 public class DAOFactory {
11     public static INewsDAO getINewsDAOInstance(DataBaseConnection dbc){
12         return new NewsDAOImpl(dbc);
13     }
14 }

 

4、网络爬虫实现

  现在编写整个项目的重点,编写URLDemo类,在爬虫中进行数据库的操作以及HDFS的写入:

  a\'  关于此类,在网页解析时用了简单的Jsoup,并没有如《java网络爬虫》用正则表达式,所以需要导入jsoup的jar包  

  b\'  关于HDFS在eclipse的配置以及本机的连接,我后续博客会阐述,也可以网络查询方法;

  c\'  这个类也是执行类,我收集的是新浪新闻网的数据,爬取深度为5,设置线程数5,并且筛选了只有链接含有“sian.news.com.cn”的。

  d\'  网络爬虫我讲了两种方法:(1)java代码实现网络爬虫

                    (2)Heritrix工具实现网络爬虫

  此处我还是选择了直接写代码实现,自由度高也方便读写存取。

  1 package org.liky.sina.craw;
  2 
  3 import java.util.ArrayList;
  4 import java.util.HashMap;
  5 import java.util.HashSet;
  6 import java.util.List;
  7 import java.util.Map;
  8 import java.util.Set;
  9 
 10 import org.apache.hadoop.conf.Configuration;
 11 import org.apache.hadoop.fs.FSDataOutputStream;
 12 import org.apache.hadoop.fs.FileSystem;
 13 import org.apache.hadoop.fs.Path;
 14 import org.jsoup.Jsoup;
 15 import org.jsoup.nodes.Document;
 16 import org.jsoup.nodes.Element;
 17 import org.jsoup.select.Elements;
 18 import org.liky.sina.dao.INewsDAO;
 19 import org.liky.sina.dbc.DataBaseConnection;
 20 import org.liky.sina.factory.DAOFactory;
 21 import org.liky.sina.vo.News;
 22 
 23 /**
 24  * 爬虫开始进行数据库操作以及HDFS写入
 25  * 
 26  * @author k04
 27  * 
 28  */
 29 public class URLDemo {
 30     // 该对象的构造方法会默认加载hadoop中的两个配置文件,hdfs-site.xml和core-site.xml
 31     // 这两个文件包含访问hdfs所需的参数值
 32     private static Configuration conf = new Configuration();
 33 
 34     private static int id = 1;
 35 
 36     private static FileSystem fs;
 37 
 38     private static Path path;
 39 
 40     // 等待爬取的url
 41     private static List<String> allWaitUrl = new ArrayList<>();
 42     // 已经爬取的url
 43     private static Set<String> allOverUrl = new HashSet<>();
 44     // 记录所有url的深度,以便在addUrl方法内判断
 45     private static Map<String, Integer> allUrlDepth = new HashMap<>();
 46     // 爬取网页的深度
 47     private static int maxDepth = 5;
 48     // 声明object独享帮助进行线程的等待操作
 49     private static Object obj = new Object();
 50     // 设置总线程数
 51     private static final int MAX_THREAD = 20;
 52     // 记录空闲的线程数
 53     private static int count = 0;
 54 
 55     // 声明INewsDAO对象,
 56     private static INewsDAO dao;
 57 
 58     static {
 59         dao = DAOFactory.getINewsDAOInstance(new DataBaseConnection());
 60     }
 61 
 62     public static void main(String args[]) {
 63         // 爬取的目标网址
 64         String strUrl = "http://news.sina.com.cn/";
 65 
 66         // 爬取第一个输入的url
 67         addUrl(strUrl, 0);
 68         // 建立多个线程
 69         for (int i = 0; i < MAX_THREAD; i++) {
 70             new URLDemo().new MyThread().start();
 71         }
 72 
 73         // DataBaseConnection dc=new DataBaseConnection();
 74         // dc.getConnection();
 75 
 76     }
 77 
 78     public static void parseUrl(String strUrl, int depth) {
 79         以上是关于Lucene搜索引擎+HDFS+MR完成垂直搜索的主要内容,如果未能解决你的问题,请参考以下文章

8个基于Lucene的开源搜索引擎(推荐)

原创:史上对BM25模型最全面最深刻的解读以及lucene排序深入讲解(佟学强)

使用 couchdb 和 lucene 自动完成

搜索引擎系列五:Lucene索引详解(IndexWriter详解Document详解索引更新)

Poseidon 系统是一个日志搜索平台——认证看链接ppt,本质是索引的倒排列表和原始日志数据都存在HDFS,而文档和倒排的元数据都在NOSQL里,同时针对单个filed都使用了独立索引,使用MR来

lucene思维导图,让搜索引擎不再难懂