此次開發的博客主要功能或特色:
第一:能夠兼容各終端,特別是手機端。
第二:到時會用到大量html5,炫啊。
第三:導入博客園的精華文章,並作分類。(不要封我)
第四:作個插件,任何網站上的技術文章均可以轉發收藏 到本博客。html
因此打算寫個系類:《一步步搭建本身的博客》html5
演示地址:http://haojima.net/ 羣內共享源碼:469075305 jquery
今天來分析下 嗨-博客 中的搜索功能。搜索功能在我的網站裏面要有這麼個東西,但又不是特別重要。因此咱們須要有,能夠不用太深刻了解,畢竟咱們不是專門作搜索這塊的。程序員
因此,我打算把搜索分兩塊。一塊是,用Lucene.Net實現站內搜索。一塊是利用第三方搜索引擎來 實現站內搜索。架構
Lucene.net是Lucene的.net移植版本,是一個開源的全文檢索引擎開發包,即它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎。開發人員能夠基於Lucene.net實現全文檢索的功能。Lucene.net是Apache軟件基金會贊助的開源項目,基於Apache License協議。Lucene.net並非一個爬行搜索引擎,也不會自動地索引內容。咱們得先將要索引的文檔中的文本抽取出來,而後再將其加到Lucene.net索引中。標準的步驟是先初始化一個Analyzer、打開一個IndexWriter、而後再將文檔一個接一個地加進去。一旦完成這些步驟,索引就能夠在關閉前獲得優化,同時所作的改變也會生效。這個過程可能比開發者習慣的方式更加手工化一些,但卻在數據的索引上給予你更多的靈活性,並且其效率也很高。(來源百度百科)
其實 在以前 我也是接觸到過Lucene.net,那也是本身 作的個小玩意(博客備份小工具3) 瞎折騰的。可是 此次打算遷移到這個系統中,不知怎麼的 報錯了。多是此次用的是 .net 4.5。Lucene這東西過高深,我也沒打算深究。因而 在網上收索了一把,資料還挺多的。《lucene.net 3.0.三、結合盤古分詞進行搜索的小例子(分頁功能)》 我隨意看了下,這裏有個 幫助類 挺不錯的,也還符合 我這樣想要的效果。這裏來分析下這個幫助類。 ide
1.首先建立索引。 工具
IndexWriter writer = new IndexWriter(directory_luce, analyzer, false, IndexWriter.MaxFieldLength.LIMITED); Document doc = new Document(); doc.Add(new Field(name, value, Field.Store.YES, Field.Index.NOT_ANALYZED)); writer.AddDocument(doc);
這裏的 佈局
directory_luce 是索引建立路徑post
analyzer 分析器學習
value 是對應 存入索引額名字和值
2.從索引裏面搜索
string[] fileds = { "title", "content" };//查詢字段 QueryParser parser = null; parser = new MultiFieldQueryParser(version, fileds, analyzer);//多個字段查詢 Query query = parser.Parse(keyword); int n = 1000; IndexSearcher searcher = new IndexSearcher(directory_luce, true);//true-表示只讀 TopDocs docs = searcher.Search(query, (Filter)null, n); if (docs == null || docs.TotalHits == 0) { return null; } else { List<SearchResult> list = new List<SearchResult>(); int counter = 1; foreach (ScoreDoc sd in docs.ScoreDocs)//遍歷搜索到的結果 { try { Document doc = searcher.Doc(sd.Doc); int id = int.Parse(doc.Get("id")); string title = doc.Get("title"); string content = doc.Get("content"); string blogTag = doc.Get("blogTag"); string url = doc.Get("url"); int flag = int.Parse(doc.Get("flag")); int clickQuantity = int.Parse(doc.Get("clickQuantity")); string createdate = doc.Get("createdate"); PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>"); PanGu.HighLight.Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new PanGu.Segment()); highlighter.FragmentSize = 50; content = highlighter.GetBestFragment(keyword, content); string titlehighlight = highlighter.GetBestFragment(keyword, title); if (titlehighlight != "") title = titlehighlight; list.Add(new SearchResult(title, content, url, blogTag, id, clickQuantity, flag)); } catch (Exception ex) { Console.WriteLine(ex.Message); } counter++; } return list;
3.完整代碼
public class PanGuLuceneHelper { private PanGuLuceneHelper() { } #region 單一實例 private static PanGuLuceneHelper _instance = null; /// <summary> /// 單一實例 /// </summary> public static PanGuLuceneHelper instance { get { if (_instance == null) _instance = new PanGuLuceneHelper(); return _instance; } } #endregion #region 00一些屬性和參數 #region Lucene.Net的目錄-參數 private Lucene.Net.Store.Directory _directory_luce = null; /// <summary> /// Lucene.Net的目錄-參數 /// </summary> public Lucene.Net.Store.Directory directory_luce { get { if (_directory_luce == null) _directory_luce = Lucene.Net.Store.FSDirectory.Open(directory); return _directory_luce; } } #endregion #region 索引在硬盤上的目錄 private System.IO.DirectoryInfo _directory = null; /// <summary> /// 索引在硬盤上的目錄 /// </summary> public System.IO.DirectoryInfo directory { get { if (_directory == null) { string dirPath = AppDomain.CurrentDomain.BaseDirectory + "SearchIndex"; if (System.IO.Directory.Exists(dirPath) == false) _directory = System.IO.Directory.CreateDirectory(dirPath); else _directory = new System.IO.DirectoryInfo(dirPath); } return _directory; } } #endregion #region 分析器 private Analyzer _analyzer = null; /// <summary> /// 分析器 /// </summary> public Analyzer analyzer { get { { _analyzer = new Lucene.Net.Analysis.PanGu.PanGuAnalyzer();// } return _analyzer; } } #endregion #region 版本號枚舉類 private static Lucene.Net.Util.Version _version = Lucene.Net.Util.Version.LUCENE_30; /// <summary> /// 版本號枚舉類 /// </summary> public Lucene.Net.Util.Version version { get { return _version; } } #endregion #endregion #region 01建立索引 /// <summary> /// 建立索引(先刪 後更新) /// </summary> /// <param name="datalist"></param> /// <returns></returns> public bool CreateIndex(List<SearchResult> datalist) { IndexWriter writer = null; try { writer = new IndexWriter(directory_luce, analyzer, false, IndexWriter.MaxFieldLength.LIMITED);//false表示追加(true表示刪除以前的從新寫入) } catch { writer = new IndexWriter(directory_luce, analyzer, true, IndexWriter.MaxFieldLength.LIMITED);//false表示追加(true表示刪除以前的從新寫入) } foreach (SearchResult data in datalist) { writer.DeleteDocuments(new Term("id", data.id.ToString()));//新增前 刪除 否則會有重複數據 CreateIndex(writer, data); } writer.Optimize(); writer.Dispose(); return true; } public bool CreateIndex(SearchResult data) { List<SearchResult> datalist = new List<SearchResult>(); datalist.Add(data); return CreateIndex(datalist); } public bool CreateIndex(IndexWriter writer, SearchResult data) { try { if (data == null) return false; Document doc = new Document(); Type type = data.GetType();//assembly.GetType("Reflect_test.PurchaseOrderHeadManageModel", true, true); //命名空間名稱 + 類名 //建立類的實例 //object obj = Activator.CreateInstance(type, true); //獲取公共屬性 PropertyInfo[] Propertys = type.GetProperties(); for (int i = 0; i < Propertys.Length; i++) { //Propertys[i].SetValue(Propertys[i], i, null); //設置值 PropertyInfo pi = Propertys[i]; string name = pi.Name; object objval = pi.GetValue(data, null); string value = objval == null ? "" : objval.ToString(); //值 if (name == "id" || name == "flag")//id在寫入索引時必是不分詞,不然是模糊搜索和刪除,會出現混亂 { doc.Add(new Field(name, value, Field.Store.YES, Field.Index.NOT_ANALYZED));//id不分詞 } else { doc.Add(new Field(name, value, Field.Store.YES, Field.Index.ANALYZED)); } } writer.AddDocument(doc); } catch (System.IO.FileNotFoundException fnfe) { throw fnfe; } return true; } #endregion #region 02在title和content字段中查詢數據 /// <summary> /// 在title和content字段中查詢數據 /// </summary> /// <param name="keyword"></param> /// <returns></returns> public List<SearchResult> Search(string keyword) { string[] fileds = { "title", "content" };//查詢字段 QueryParser parser = null; parser = new MultiFieldQueryParser(version, fileds, analyzer);//多個字段查詢 Query query = parser.Parse(keyword); int n = 1000; IndexSearcher searcher = new IndexSearcher(directory_luce, true);//true-表示只讀 TopDocs docs = searcher.Search(query, (Filter)null, n); if (docs == null || docs.TotalHits == 0) { return null; } else { List<SearchResult> list = new List<SearchResult>(); int counter = 1; foreach (ScoreDoc sd in docs.ScoreDocs)//遍歷搜索到的結果 { try { Document doc = searcher.Doc(sd.Doc); int id = int.Parse(doc.Get("id")); string title = doc.Get("title"); string content = doc.Get("content"); string blogTag = doc.Get("blogTag"); string url = doc.Get("url"); int flag = int.Parse(doc.Get("flag")); int clickQuantity = int.Parse(doc.Get("clickQuantity")); string createdate = doc.Get("createdate"); PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>"); PanGu.HighLight.Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new PanGu.Segment()); highlighter.FragmentSize = 50; content = highlighter.GetBestFragment(keyword, content); string titlehighlight = highlighter.GetBestFragment(keyword, title); if (titlehighlight != "") title = titlehighlight; list.Add(new SearchResult(title, content, url, blogTag, id, clickQuantity, flag)); } catch (Exception ex) { Console.WriteLine(ex.Message); } counter++; } return list; } //st.Stop(); //Response.Write("查詢時間:" + st.ElapsedMilliseconds + " 毫秒<br/>"); } #endregion #region 03在不一樣的分類下再根據title和content字段中查詢數據(分頁) /// <summary> /// 在不一樣的類型下再根據title和content字段中查詢數據(分頁) /// </summary> /// <param name="_flag">分類,傳空值查詢所有</param> /// <param name="keyword"></param> /// <param name="PageIndex"></param> /// <param name="PageSize"></param> /// <param name="TotalCount"></param> /// <returns></returns> public List<SearchResult> Search(string _flag, string keyword, int PageIndex, int PageSize) { if (PageIndex < 1) PageIndex = 1; Stopwatch st = Stopwatch.StartNew(); st.Start(); BooleanQuery bq = new BooleanQuery(); if (_flag != "") { QueryParser qpflag = new QueryParser(version, "flag", analyzer); Query qflag = qpflag.Parse(_flag); bq.Add(qflag, Occur.MUST);//與運算 } if (keyword != "") { string[] fileds = { "blogTag", "title", "content" };//查詢字段 QueryParser parser = null;// new QueryParser(version, field, analyzer);//一個字段查詢 parser = new MultiFieldQueryParser(version, fileds, analyzer);//多個字段查詢 Query queryKeyword = parser.Parse(keyword); bq.Add(queryKeyword, Occur.MUST);//與運算 } TopScoreDocCollector collector = TopScoreDocCollector.Create(PageIndex * PageSize, false); IndexSearcher searcher = new IndexSearcher(directory_luce, true);//true-表示只讀 searcher.Search(bq, collector); if (collector == null || collector.TotalHits == 0) { //TotalCount = 0; return null; } else { int start = PageSize * (PageIndex - 1); //結束數 int limit = PageSize; ScoreDoc[] hits = collector.TopDocs(start, limit).ScoreDocs; List<SearchResult> list = new List<SearchResult>(); int counter = 1; //TotalCount = collector.TotalHits; st.Stop(); //st.ElapsedMilliseconds;//毫秒 foreach (ScoreDoc sd in hits)//遍歷搜索到的結果 { try { Document doc = searcher.Doc(sd.Doc); int id = int.Parse(doc.Get("id")); string title = doc.Get("title"); string content = doc.Get("content"); string blogTag = doc.Get("blogTag"); string url = doc.Get("url"); int flag = int.Parse(doc.Get("flag")); int clickQuantity = int.Parse(doc.Get("clickQuantity")); content = Highlight(keyword, content); //string titlehighlight = Highlight(keyword, title); //if (titlehighlight != "") title = titlehighlight; list.Add(new SearchResult(title, content, url, blogTag, id, clickQuantity, flag)); } catch (Exception ex) { Console.WriteLine(ex.Message); } counter++; } return list; } } #endregion #region 把content按照keywords進行高亮 /// <summary> /// 把content按照keywords進行高亮 /// </summary> /// <param name="keywords"></param> /// <param name="content"></param> /// <returns></returns> private static string Highlight(string keywords, string content) { SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<strong>", "</strong>"); Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new Segment()); highlighter.FragmentSize = 200; return highlighter.GetBestFragment(keywords, content); } #endregion #region 04刪除索引 #region 刪除索引數據(根據id) /// <summary> /// 刪除索引數據(根據id) /// </summary> /// <param name="id"></param> /// <returns></returns> public bool Delete(string id) { bool IsSuccess = false; Term term = new Term("id", id); IndexWriter writer = new IndexWriter(directory_luce, analyzer, false, IndexWriter.MaxFieldLength.LIMITED); writer.DeleteDocuments(term); // writer.DeleteDocuments(term)或者writer.DeleteDocuments(query); writer.Commit(); IsSuccess = writer.HasDeletions(); writer.Dispose(); return IsSuccess; } #endregion #region 刪除所有索引數據 /// <summary> /// 刪除所有索引數據 /// </summary> /// <returns></returns> public bool DeleteAll() { bool IsSuccess = true; try { IndexWriter writer = new IndexWriter(directory_luce, analyzer, false, IndexWriter.MaxFieldLength.LIMITED); writer.DeleteAll(); writer.Commit(); IsSuccess = writer.HasDeletions(); writer.Dispose(); } catch { IsSuccess = false; } return IsSuccess; } #endregion #endregion #region 分詞測試 /// <summary> /// 分詞測試 /// </summary> /// <param name="keyword"></param> /// <returns></returns> public string Token(string keyword) { string ret = ""; System.IO.StringReader reader = new System.IO.StringReader(keyword); Lucene.Net.Analysis.TokenStream ts = analyzer.TokenStream(keyword, reader); bool hasNext = ts.IncrementToken(); Lucene.Net.Analysis.Tokenattributes.ITermAttribute ita; while (hasNext) { ita = ts.GetAttribute<Lucene.Net.Analysis.Tokenattributes.ITermAttribute>(); ret += ita.Term + "|"; hasNext = ts.IncrementToken(); } ts.CloneAttributes(); reader.Close(); analyzer.Close(); return ret; } #endregion }
public class SearchResult { public SearchResult() { } public SearchResult(string title, string content, string url, string blogTag, int id, int clickQuantity, int flag) { this.blogTag = blogTag; this.clickQuantity = clickQuantity; this.content = content; this.id = id; this.url = url; this.title = title; this.flag = flag; } /// <summary> /// 標題 /// </summary> public string title { get; set; } /// <summary> /// 正文內容 /// </summary> public string content { get; set; } /// <summary> /// url地址 /// </summary> public string url { get; set; } /// <summary> /// tag標籤 /// </summary> public string blogTag { get; set; } /// <summary> /// 惟一id /// </summary> public int id { get; set; } /// <summary> /// 點擊量 /// </summary> public int clickQuantity { get; set; } /// <summary> /// 標記(用戶) /// </summary> public int flag { get; set; } }
1.爲何要用必應搜索?
由於咱們如今作的主要功能是博客系統,搜索只是其中的一小塊環節。而 我對這搜索並不瞭解,因此就用第三方搜索,省事嘛。
2.爲何不用別的三方收索呢?
百度?不用說了,我們程序員都懂的。谷歌?我卻是想用,可生在天朝,也是沒得辦法。選來選去 仍是選了必應。
3.怎麼來使用第三方的站內搜索?
格式以下:http://cn.bing.com/search?q=關鍵字+site:網站地址
例如:http://cn.bing.com/search?q=博客+site:blog.haojima.net
效果圖:
嘿嘿,如此之簡單。既然都已經看到效果了,那麼 咱們能夠幹些什麼呢? 我打算 直接把結果 顯示在個人 站內搜索結果。爲何 不直接跳轉到這個頁面顯示 搜索結果?由於 這個頁面有廣告什麼的,不能按照我本身的方式顯示。我直接把結果放個人搜索頁面 能夠和 我上面用Lucene.net的搜索結果一塊兒顯示,這樣豈不是 顯得更專業。
,不知道的 還覺得 是我本身怎麼弄出來的。那麼 咱們怎麼解析 搜到的結果呢?我這裏推薦下 Jumony 以前我一直是用 HtmlAgilityPack ,如今爲何不用了,由於有了更好的。HtmlAgilityPack 缺點是 要去xpath,然 若是頁面存在js動態改變文檔結構的話,咱們直接F12 複製出來的 xpath是不許的。那麼有人 會說 HtmlAgilityPack 我已經用習慣了,不想 從新學習Jumony 。這裏我告訴你錯了,根本就須要從新學習,若是你會jquery 的話。經常使用功能語法基本同樣,還支持拉姆達表達式,爽的一逼。
咱們來看看 怎麼使用Jumony 解析 解鎖結果吧。
var document = jumony.LoadDocument(url); var list = document.Find("#b_results .b_algo").ToList().Select(t => t.ToString()).ToList();
兩行代碼搞定,還直接轉成了list集合。在頁面循環加載就ok了。
我我的以爲 這是個蠻實用的功能,咱們有時候 寫了博客(不少時候咱們寫博客就是把本身怕會忘記的知識點 整理記錄),然後期找不到。那麼經過這個功能 能夠很好的解決咱們的問題。咱們不想全站搜索,只搜索本身的內容就能夠了。
頁面仍是用全站的搜索頁面,咱們直接在搜索關鍵詞上作手腳就能夠了。好比,咱們想搜索 zhaopei 用戶下的內容,那麼咱們能夠要搜索的關鍵字前面加上 blog:zhaopei 那麼完整的搜索關鍵字就成了 blog:zhaopei 關鍵字
那麼 咱們要作的就是 在用戶頁面 搜索 就在關鍵字 前面加上 blog:用戶名 咱們在搜索 頁面解析的時候 須要作的就是 分解關鍵字 blog:用戶名 關鍵字 先用空格 分割 而後若是中間有 空格的話 ,而後判斷 前面五個字符是否是 blog: 而後截取 到用戶名和 關鍵字。
咱們下面具體看看 在Lucene.net 和 必應搜索裏面是怎麼作的。
1.Lucene.net
#region 加載 Lucene.net 的搜索結果 /// <summary> /// 加載 Lucene.net 的搜索結果 /// </summary> /// <returns></returns> public ActionResult ShowLuceneResult() { if (!Request.QueryString.AllKeys.Contains("key")) return null; string key = Request.QueryString["key"]; var zhankey = key.Split(' ');//分割關鍵字 var blogName = string.Empty; if (zhankey.Length >= 2) { var str = zhankey[0].Trim(); if (str.Length > 6 && str.Substring(0, 5) == "blog:") blogName = str.Substring(5);//取得用戶名 } string userid = Request.QueryString.AllKeys.Contains("userid") ? Request.QueryString["userid"] : ""; //這裏判斷是否 用戶名不爲空 而後取得用戶對應的 用戶ID (由於 我在作Lucene 是用用戶id 來標記的) if (!string.IsNullOrEmpty(blogName)) { key = key.Substring(key.IndexOf(' ')); var userinfo = CacheData.GetAllUserInfo().Where(t => t.UserName == blogName).FirstOrDefault(); if (null != userinfo) userid = userinfo.Id.ToString(); } string pIndex = Request.QueryString.AllKeys.Contains("p") ? Request.QueryString["p"] : ""; int PageIndex = 1; int.TryParse(pIndex, out PageIndex); int PageSize = 10; var searchlist = PanGuLuceneHelper.instance.Search(userid, key, PageIndex, PageSize); return PartialView(searchlist); } #endregion
2. 必應搜索
#region 加載 bing 的搜索結果 /// <summary> /// 加載 bing 的搜索結果 /// </summary> /// <returns></returns> public ActionResult ShowBingResult() { if (!Request.QueryString.AllKeys.Contains("key")) return null; string key = Request.QueryString["key"];//搜索關鍵字 JumonyParser jumony = new JumonyParser(); //http://cn.bing.com/search?q=AJAX+site%3ablog.haojima.net&first=11&FORM=PERE string pIndex = Request.QueryString.AllKeys.Contains("p") ? Request.QueryString["p"] : ""; int PageIndex = 1; int.TryParse(pIndex, out PageIndex); PageIndex--; //如:blog:JeffreyZhao 博客 var zhankey = key.Split(' ');//先用空格分割 var blogName = string.Empty; if (zhankey.Length >= 2) { var str = zhankey[0].Trim(); if (str.Length > 6 && str.Substring(0, 5) == "blog:") blogName = "/" + str.Substring(5);//這裏取得 用戶名 } if (!string.IsNullOrEmpty(blogName)) key = key.Substring(key.IndexOf(' ')); //如: var url = "http://cn.bing.com/search?q=" + key + "+site:" + siteUrl + blogName + "&first=" + PageIndex + "1&FORM=PERE"; var document = jumony.LoadDocument(url); var list = document.Find("#b_results .b_algo").ToList().Select(t => t.ToString()).ToList(); var listli = document.Find("li.b_pag nav ul li"); if (PageIndex > 0 && listli.Count() == 0) return null; if (listli.Count() > 1) { var text = document.Find("li.b_pag nav ul li").Last().InnerText(); int npage = -1; if (text == "下一頁") { if (listli.Count() > 1) { var num = listli.ToList()[listli.Count() - 2].InnerText(); int.TryParse(num, out npage); } } else int.TryParse(text, out npage); if (npage <= PageIndex) list = null; } return PartialView(list); } #endregion
看看 咱們的搜索結果的效果圖吧。
首先 搜索是必不可少的功能,但又不是主要功能。那麼咱們能夠直接用lucene.net 來作搜索,而後用必應搜索作備用。可是 用必應 有個弊端。就是 若是咱們 的文章頁面 被用戶本身刪除了,而 必應已經收錄了,那麼 咱們在搜索結果頁面 點擊 可能就是404 或 500 了。固然 咱們本身的 lucene.net 也會有這個 問題,咱們能夠在用戶刪除 文章的時候 也刪除 對應的那天搜索索引就行了。
演示地址:http://blog.haojima.net/Search/Index?key=blog:zhaopei 博客&p=1
若是您對本篇文章感興趣,那就麻煩您點個贊,您的鼓勵將是個人動力。 固然您還能夠加入QQ羣:討論。
若是您有更好的處理方式,但願不要吝嗇賜教。
一步步開發本身的博客 .NET版系列:http://www.cnblogs.com/zhaopei/tag/Hi-Blogs/