此次开发的博客主要功能或特色:
第一:能够兼容各终端,特别是手机端。
第二:到时会用到大量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/