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语句来建立表和进行数据的操作了。
在进行数据库操作时,企业开发中必定要使用DAO(Data 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完成垂直搜索的主要内容,如果未能解决你的问题,请参考以下文章 原创:史上对BM25模型最全面最深刻的解读以及lucene排序深入讲解(佟学强)
搜索引擎系列五:Lucene索引详解(IndexWriter详解Document详解索引更新)
Poseidon 系统是一个日志搜索平台——认证看链接ppt,本质是索引的倒排列表和原始日志数据都存在HDFS,而文档和倒排的元数据都在NOSQL里,同时针对单个filed都使用了独立索引,使用MR来