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

来源:http://www.cnblogs.com/1996swg/archive/2017/08/16/7376360.html
-Advertisement-
Play Games

介於上一篇的java實現網路爬蟲基礎之上,這一篇的思想是將網路收集的數據保存到HDFS和資料庫(Mysql)中;然後用MR對HDFS的數據進行索引處理,處理成倒排索引;搜索時先用HDFS建立好的索引來搜索對應的數據ID,根據ID從資料庫中提取數據,呈現到網頁上。 這是一個完整的集合網路爬蟲、資料庫、 ...


  介於上一篇的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         // 先判斷當前url是否爬取過
 80         // 判斷深度是否符合要求
 81         if (!(allOverUrl.contains(strUrl) || depth > maxDepth)) {
 82             System.out.println("當前執行的  " + Thread.currentThread().getName()
 83                     + "  爬蟲線程處理爬取: " + strUrl);
 84 
 85             try {
 86                 // 用jsoup進行數據爬取
 87                 Document doc = Jsoup.connect(strUrl).get();
 88                 // 通過doc接受返回的結果
 89                 // 提取有效的title和description
 90                 String title = doc.title();
 91                 Element descE = doc.getElementsByAttributeValue("name",
 92                         "description").first();
 93                 String desc = descE.attr("content");
 94 
 95                 // System.out.println(title + " --> " + desc);
 96 
 97                 // 如果有效,則驚醒保存
 98                 if (title != null && desc != null && !title.trim().equals("")
 99                         && !desc.trim().equals("")) {
100                     // 需要生成一個id,以便放入資料庫中,因此id也要加入到HDFS中,便於後續索引
101                     News news = new News();
102                     news.setId(id++);
103                     news.setTitle(title);
104                     news.setDescription(desc);
105                     news.setUrl(strUrl);
106                     // 添加到資料庫語句
107                     dao.doCreate(news);
108                     // 向HDFS保存數據
109                     path = new Path("hdfs://localhost:9000/sina_news_input/"
110                             + System.currentTimeMillis() + ".txt");
111                     fs = path.getFileSystem(conf);
112                     FSDataOutputStream os = fs.create(path);
113                     // 進行內容輸出,此處需要用news.getId(),不然資料庫和HDFS的id會不相同,因為多線程的運行
114                     os.writeUTF(news.getId() + "\r\n" + title + "\r\n" + desc);
115                     os.close();
116 
117                     // 解析所有超鏈接
118                     Elements aEs = doc.getElementsByTag("a");
119                     // System.out.println(aEs);
120                     if (aEs != null && aEs.size() > 0) {
121                         for (Element aE : aEs) {
122                             String href = aE.attr("href");
123                             System.out.println(href);
124                             // 截取網址,並給出篩選條件!!!
125                             if ((href.startsWith("http:") || href
126                                     .startsWith("https:"))
127                                     && href.contains("news.sina.com.cn")) {
128                                 // 調用addUrl()方法
129                                 addUrl(href, depth + 1);
130                             }
131                         }
132                     }
133 
134                 }
135 
136             } catch (Exception e) {
137 
138             }
139             // 吧當前爬完的url放入到偶爾中
140             allOverUrl.add(strUrl);
141             System.out.println(strUrl + "爬去完成,已經爬取的內容量為:" + allOverUrl.size()
142                     + "剩餘爬取量為:" + allWaitUrl.size());
143 
144             // 判斷是否集合中海油其他的內容需要進行爬取,如果有,則進行線程的喚醒
145             if (allWaitUrl.size() > 0) {
146                 synchronized (obj) {
147                     obj.notify();
148                 }
149             } else {
150                 System.out.println("爬取結束...");
151                 System.exit(0);
152             }
153 
154         }
155     }
156 
157     /**
158      * url加入到等待隊列中 並判斷是否已經放過,若沒

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Android,Android開發,Kotlin,源碼,技術 ...
  • 前言 今天我們來討論一個經常出現的需求場景,也是一個老話題。在開發中我們往往會遇到需要進行多個網路請求,並且需要多個網路請求成功返回後再做其他事的場景。比如同一個界面顯示的內容需要用到兩個網路介面,而需求又希望成功返回兩個介面的數據再進行頁面展示;又比如喜歡挖坑的後臺同學就只提供了返回一條數據的介面 ...
  • 前提條件,存在A和B兩個頁面,A是主界面,A push 到 B 方法一:如果B頁面的返回按鈕要用自定義的按鈕(在iOS7中,如果使用了UINavigationController,那麼系統自帶的附加了一個從屏幕左邊緣開始滑動可以實現pop的手勢。但是,如果自定義了navigationItem的lef ...
  • 本文是根據文頂頂老師的博客學習而來,轉載地址:http://www.cnblogs.com/wendingding/p/3809042.html 一、NSOperation簡介 1.簡單說明 NSOperation的作⽤:配合使用NSOperation和NSOperationQueue也能實現多線程 ...
  • 最近自己在做一個小說閱讀器,看到某閱有護眼模式功能,別人都有,我怎麼能沒有? <! more 現在這功能已經不稀奇了,很多手機都帶有這個功能。 實現起來不難,用一個蒙版遮在界面上面就行。 至於蒙版,可以用Window實現,也可以只用套個FrameLayout實現。 Window實現的優點是,支持全局 ...
  • 報錯原因是 項目使用的是ARC,但是有非ARC代碼。 項目中要混合使用ARC和非ARC。 解決: 如果使用的非 ARC ,則為 ARC 的代碼加入 -fobjc-arc 如果使用的是 ARC ,則為非 ARC 代碼加入 -fno-objc-arc 判斷項目是否用的ARC: 如果使用的非 ARC ,則 ...
  • http://bbs.gfan.com/android-6740350-1-1.html 原創處女貼,呵呵。。。 研究換4.1.2也有段時間了,4.1.2各方面功能均讓我挺滿意的,用著也蠻順手的。偶爾上論壇,看到有人說,4.1.2存在媒體掃描耗電的bug,當時我還不以為然,也慶幸自己的這個版本沒有問 ...
  • 代碼: ViewController.m ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...