附上连接
javascript
对爬虫了解也不是很深,粗略的讲一下大致思路。
因为知识积累尚浅,平时通常采用这3种方式爬取基本的网页。
①观察url的规律,有些url多是id=xxx,这个xxx是从1开始递增的,这个时候咱们就是能够去遍历。具体规律看实际,这边只是一个思路
②查看网页源码,看看其属性class什么的,看看是否有规律,若是有,那就按照这个来,这个用Beautifulsoul4的时候常常用,叫作CSS 选择器。咱们在写 CSS 时,标签名不加任何修饰,类名前加点.
,id名前加 #
,在这里咱们也能够利用相似的方法来筛选元素。由于感受写正则匹配太麻烦(实际上是不太精通),喜欢这种懒人式的操做。
还有的话就是用xpath语法来获取你想要的东西,之前有用过python中的scrapy框架,里面就有xpath语法,刚刚查了一下,用java写的爬虫中也有。反正如今浏览器xpath路径已经给你弄好,复制粘贴修改一下就ok。html
③获取属性为<a href="xxx">
的连接,能够用jsoup或者其余语言的其余解析器,也能够用正则去匹配。最后进行筛选,再去请求,再去匹配....
首先观察网站结构,先大致翻看url结构,发现每篇正文都是info/xxxx/xxxx.htm,因而有了第一个思路,对xxxx进行遍历,获得每个url。再利用jsoup去进行解析,存进数据库。
而后又查看一下导航栏的源码,发现有惊喜,就是主干url的class都是menu0_0_
想到这就是继续深刻,猜想副干那些也有这种规律。果不其然
综合上述方法,权衡利弊后,选择第二种方法。
观察学院网站发现,学院网站有基本的三层,第一层就是导航栏,请求menu0_0_
,大概有几十个url
而后点击进去,到第二层,相似于文章的目录同样,这边的话上第一步的主干url请求之后,咱们用选择器选择class="c124907"
的连接
请求第二层的url,点击,到达第三层,这个时候咱们就是在正文中提取本身想要的信息了,好比选择title,正文内容啊,.contentstyle124904
等等
前端
这边的话还碰到个小坑,刚刚开始爬取的时候,大概只是爬取到了300多条连接,想一想以为有点少,再翻看目录的话发现,还有分页的没有考虑到,因而,把分页的给弄下来
,分页的弄下来后,发现url数仍是有点少,继续看,发现从当前页开始只显示7页的连接,其余剩余的这个从源码又爬取不到
观察这些页数的连接后,能够发现?a2t=19&a2p=3&a2c=10&urltype=tree.TreeTempUrl&wbtreeid=1114
,a2t
表明的是总页数, a2p
表明的是当前页数,webtreeid
这个不一样页面id不一样
因而正则去匹配,当a2t大于7的时候,把这个a2p里面的参数本身弄一个循环来添加。
java
到最后的话里面有重复的,要去一下重
以上的操做都是基于多线程来完成的。
获取到了所有url后(带有info正文的,一些可有可无的url去掉)。
而后去请求这些url,里面也是有些多是无效的,出现404 not found 啊什么的,利用jsoup,若是解析不到特定的字段,舍弃这个url。
这边的话利用多线程去请求正文标题等内容,请求完成之后就直接插入数据库了python
数据库的话采用dao模式
创建一个爬虫的类
mysql
刚刚开始作这个搜索引擎的时候是想直接用select * from xxx where xxxxx like '%xx%'
这个语法的来返回查询结果的,和老师交流了一下,改用Lucene+IkAnalyzer进行分词索引,而后查询。若是用 like
语法的话,这个搜索引擎就失去了大半部分意义了。由于搜索引擎讲究的是高效,在数据量小的时候采用sql语法查询和在索引中进行查询差异不是太大,但数据量大的时候差异就出来了。在网上看到过一个案例, lucene在查找100W 数据的时间 控制在0.02秒左右,相反的在Sql中100W的全文检索的话须要10秒左右。有一个正采用lucene开发小型的搜索引擎的人测试,sql 100w之内的数据 仍是勉强能OK . lucene 就不限制了.(目前 正在作的一个搜索引擎 数据量5T 将近5亿数据 时间维持在0.4秒之内)
。jquery
首先,咱们要来理解lucene是什么?能干什么?
Lucene 是一个高效的,基于Java 的全文检索库
全文检索的定义:全文检索就是先建立索引,而后根据索引来进行搜索的过程,就叫全文检索。
全文检索首先对要搜索的数据或者是文档进行分词,而后造成索引,经过查询索引来查询文档。原理和查字典同样,对于一个不认识的字,咱们先在偏旁部首表中找到偏旁,而后跳到全部带这个偏旁的字里面,再来搜寻这个字。再跳到这个词指定的页数,获得咱们想要的结果。创建索引的话就是把查字典的过程给逆过来。对于一大段内容,先利用分词器IKAnalyzer进行分词(拆分红单独的字或词,去除标点,停词等),而后创建索引。搜索的话就和查字典同样了
这边用processon
弄了一个简单的图git
咱们来看一下索引的内部结构,好比咱们有10条数据库的记录,建成索引文档后docid编号为1-10web
将其里面的内容进行建成索引文档
每一个字符串都指向包含此字符串的文档(Document)链表,此文档链表称为倒排表算法
好比咱们输入”计算机学院毕业生”进行查询,
这个时候IKAnalyzer会对其进行分词,拆分红计算机 算 学 学院 院 ..etc.
取出包含毕业、计算机、学院的文档链表进行合并,获得包含三者的文档,而后包含俩个的,一个的文档链表。
那么返回了那么多的结果,如何判断哪些是和咱们的想要的结果最接近的呢??
Term Frequency (tf):这个词在文档中出现的次数
Document Frequency (df):有多少文档包含这个Term
一个文档中包含了不少的词(Term),其中找出词在文档中重要性的过程称为计算出词的权重(Weight),若是一个词在一个文档中出现次数越多说明越重要,但在其余的文档中出现的次数也越多,那么重要性就会下降,好比的
字
判断Term之间的关系从而获得文档相关性的过程,也即向量空间模型的算法(VSM)
咱们把文档看做一系列词(Term),每个词(Term)都有一个权重(Term weight),不一样的词(Term)根据本身在文档中的权重来影响文档相关性的打分计算。因而咱们把全部此文档中词(term)的权重(term weight) 看做一个向量。
Document = {term1, term2, …… ,term N}
Document Vector = {weight1, weight2, …… ,weight N}
一样咱们把查询语句看做一个简单的文档,也用向量来表示。
Query = {term1, term 2, …… , term N}
Query Vector = {weight1, weight2, …… , weight N}
咱们把全部搜索出的文档向量及查询向量放到一个N维空间中,每一个词(term)是一维。
咱们通常认为两个向量之间的夹角越小,相关性越大。
因此咱们计算夹角的余弦值做为相关性的打分,夹角越小,余弦值越大,打分越高,相关性越大。
打分的计算表达式为
先后端也没有太多东西,就是一个美化后的表单+jquery进行分页和bootstrap+jquery修饰美化和数据交互,用表单接收到用户的数据后传到后端,而后再根据关键字从索引文件中查询出来,在前端进行展现
第一次用eclipsee,出现的问题就是eclipsee启动tomcat访问不到主页
查阅了相关资料后得知 在eclipsee中启动tomacat后,它去启动的web项目并非tomcat文件夹下的webapp下web工程,而是eclipsee中本身的一个文件夹下的web工程。
附上解决连接
而后第二个问题是,如何把我查询到的数据传到jsp中,第一次用jsp,不太熟,后面查询到jsp里面也是能够写java代码的,这个就是很是6了
直接把获取到的数据存到crawl里面去,而后再弄个list
而后jsp页面接收
还有一个就是采用jquery进行分页,大致思路就是,从查询获得的结果里面统计获取到几条结果,而后自定义每条显示几个结果,向上取整获得分页的数目
,若是分页数目为1就不分页,不为1的话就是采用append方法添加连接,咱们定义一个参数来接受,好比用户点倒第二页,那么参数分别为用户要查询的字段和要看的第几页,好比word=xxx&page=2,类推。
字段的话name就是至关于word,a就是至关于page=,变量命名是刚刚开始测试用的,也没有去改。
效果如前图。
try { Document doc=Jsoup.connect("http://cec.jmu.edu.cn/").get(); Elements links = doc.select(".menu0_0_"); for (Element link : links) { lis1.add(oriurl+link.attr("href")); } } catch (IOException e1) { e1.printStackTrace(); }
try { CloseableHttpResponse response = httpClient.execute(httpget, context); try { HttpEntity entity = response.getEntity(); Document doc=Jsoup.parse(EntityUtils.toString(entity,"utf8")); Elements links=doc.select(".c124907"); for (Element link : links) { lis1.add(url +link.attr("href")); } String pattern ="\\?a2t=([0-9]{1,})&a2p=[0-9]{1,}&a2c=10&urltype=tree.TreeTempUrl&wbtreeid=([0-9]{1,})"; Elements links1=doc.select("a[href]"); for (Element link1 : links1) { String line=link1.attr("href"); Pattern r = Pattern.compile(pattern); Matcher m = r.matcher(line); int i=0; if (m.find( )) { // System.out.println("Found value: " + m.group(0) ); int j=Integer.parseInt(m.group(1)); if(j>7){ for(int k=1;k<j+1;k++){ lis.add("?a2t="+String.valueOf(j)+"&a2p="+String.valueOf(k)+"&a2c=10&urltype=tree.TreeTempUrl&wbtreeid="+m.group(2)); } } else{ lis.add(m.group(0)); }
CloseableHttpResponse response = httpClient.execute(httpget, context); try { HttpEntity entity = response.getEntity(); Document doc=Jsoup.parse(EntityUtils.toString(entity,"utf8")); Elements links=doc.select(".c124907"); for (Element link : links) { lis.add(link.attr("href")); }
try { HttpEntity entity = response.getEntity(); Document doc=Jsoup.parse(EntityUtils.toString(entity,"utf8")); String title = doc.select(".contentstyle124904").text();
Crawl crawl=new Crawl(httpget.getURI().toString(),doc.title().toString(),title); CrawlDaoImpl test=new CrawlDaoImpl(); try { if(bool){ test.add(crawl); System.out.println(httpget.toString()+"添加成功"); } else{ System.out.println("添加失败");
jdbc.url=jdbc:mysql://localhost:3306/test jdbc.username=root jdbc.password=root jdbc.driver=com.mysql.jdbc.Driver
@Override public Crawl findById(int id) throws SQLException { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; Crawl p = null; String sql = "select url,abs,description from crawl where id=?"; try{ conn = DBUtils.getConnection(); ps = conn.prepareStatement(sql); ps.setInt(1, id); rs = ps.executeQuery(); if(rs.next()){ p = new Crawl(); p.setId(id); p.setUrl(rs.getString(1)); p.setAbs(rs.getString(2)); p.setDescription(rs.getString(3)); } }catch(SQLException e){ e.printStackTrace(); throw new SQLException("*"); }finally{ DBUtils.close(rs, ps, conn); } return p; }
public class IndexManager { @Test public void createIndex() throws Exception { // 采集数据 CrawlDao dao = new CrawlDaoImpl(); List<Crawl> list = dao.findAll(); // 将采集到的数据封装到Document对象中 List<Document> docList = new ArrayList(); Document document; for (Crawl crawl : list) { document = new Document(); // store:若是是yes,则说明存储到文档域中 Field id = new IntField("id", crawl.getId(), Store.YES); Field url = new StoredField("url", crawl.getUrl()); Field abs = new StoredField("abs", crawl.getAbs()); Field description = new TextField("description", crawl.getDescription(), Store.YES); document.add(id); document.add(url); document.add(abs); document.add(description); docList.add(document); } // 建立分词器,标准分词器 // Analyzer analyzer = new StandardAnalyzer(); // 使用ikanalyzer Analyzer analyzer = new IKAnalyzer(); // 建立IndexWriter IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer); // 指定索引库的地址 File indexFile = new File("C:\\test1\\aaa\\"); Directory directory = FSDirectory.open(indexFile); IndexWriter writer = new IndexWriter(directory, cfg); // 经过IndexWriter对象将Document写入到索引库中 for (Document doc : docList) { writer.addDocument(doc); } writer.close(); }
public class IndexSearch { List<Crawl> lis1=new ArrayList(); public List doSearch(Query query) throws InvalidTokenOffsetsException { // 建立IndexSearcher // 指定索引库的地址 try { File indexFile = new File("C:\\test1\\aaa\\"); Directory directory = FSDirectory.open(indexFile); IndexReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); // 经过searcher来搜索索引库 // 第二个参数:指定须要显示的顶部记录的N条 TopDocs topDocs = searcher.search(query, 20); // 根据查询条件匹配出的记录总数 int count = topDocs.totalHits; // ScoreDoc[] scoreDocs = topDocs.scoreDocs; String filed="description"; // TopDocs top=searcher.search(query, 100); QueryScorer score=new QueryScorer(query,filed);//传入评分 SimpleHTMLFormatter fors=new SimpleHTMLFormatter("<span style=\"color:red;\">", "</span>");//定制高亮标签 Highlighter highlighter=new Highlighter(fors,score);//高亮分析器 // highlighter.setMaxDocCharsToAnalyze(10);//设置高亮处理的字符个数 for(ScoreDoc sd:topDocs.scoreDocs){ Document doc=searcher.doc(sd.doc); String description=doc.get(filed); //Lucene中分词的全部信息咱们均可以从TokenStream流中获取. TokenStream token=TokenSources.getAnyTokenStream(searcher.getIndexReader(), sd.doc, "description", new IKAnalyzer(true));//获取tokenstream Fragmenter fragment=new SimpleSpanFragmenter(score); //根据这个评分新建一个对象 highlighter.setTextFragmenter(fragment); //必须选取最合适的 highlighter.setTextFragmenter(new SimpleFragmenter());//设置每次返回的字符数 String str=highlighter.getBestFragment(token, description);//获取高亮的片断,能够对其数量进行限制 Crawl crawl = new Crawl(); crawl.setDescription(str); crawl.setAbs(doc.get("abs")); crawl.setUrl(doc.get("url")); lis1.add(crawl); } reader.close(); } catch (IOException e) { e.printStackTrace(); } return lis1; }
<div id="test"> <img src="./img/logo.png" height="300" width="250"/></div> <form action="./query2.jsp" method="GET"> <div class="search-wrapper"> <div class="input-holder"> <input type="text" class="search-input" placeholder="" name="name"/> <button class="search-icon" onclick="searchToggle(this, event);"><span></span></button> </div> <span class="close" onclick="searchToggle(this, event);"></span> <div class="result-container"> </div> </div> </form>
<script src="js/jquery-1.11.0.min.js" type="text/javascript"></script> <script type="text/javascript"> function searchToggle(obj, evt){ var container = $(obj).closest('.search-wrapper'); if(!container.hasClass('active')){ container.addClass('active'); evt.preventDefault(); } else if(container.hasClass('active') && $(obj).closest('.input-holder').length == 0){ container.removeClass('active'); container.find('.search-input').val(''); container.find('.result-container').fadeOut(100, function(){$(this).empty();}); } } function submitFn(obj, evt){ value = $(obj).find('.search-input').val().trim(); _html = "Yup yup! Your search text sounds like this: "; if(!value.length){ _html = "Yup yup! Add some text friend :D"; } else{ _html += "<b>" + value + "</b>"; } $(obj).find('.result-container').html('<span>' + _html + '</span>'); $(obj).find('.result-container').fadeIn(100); evt.preventDefault(); } </script>
<script type="text/javascript"> $(function(){ var Count = "<%=i %>";//记录条数 var tmp = "<%=test %>"; var PageSize=5;//设置每页示数目 var PageCount=Math.ceil(Count/PageSize);//计算总页数 var currentPage =1;//当前页,默认为1。 //造个简单的分页按钮 for(var i=1;i<=PageCount;i++){ if(PageCount==1){ }//若是页数为1的话,那么咱们就是不分页 else{ var pageN='<li style=\"font-size:30px\"><a href="?name='+tmp+'&a='+i+'">'+i+'</a></li>'; $('.pagination').append(pageN); } } //显示默认页(第一页) }); </script>
<% String d =request.getParameter("a"); //out.print(d+"<br>"); int b=0; int k=0; if(i!=0&&d==null){ for(Crawl crawl: lis){ if(5>k&&k>=0){ out.print("<h3><p class=\"text-center\"><a href=\""+crawl.getUrl()+"\">"+crawl.getAbs()+"</a></p></h3>"); out.print("<p class=\"text-center\">"+crawl.getDescription()+"<br>"); out.print("<br>"); } k=k+1; } } else{ if(d!=null){ int c=Integer.valueOf(d); //out.print(c); for(Crawl crawl: lis){ if(c*5>b&&b>=(c-1)*5){ if(crawl.getDescription()==null){ out.print(""); } else{ out.print("<h3><p class=\"text-center\"><a href=\""+crawl.getUrl()+"\">"+crawl.getAbs()+"</a></p></h3>"); out.print("<p class=\"text-center\">"+crawl.getDescription()+"<br>"); out.print("<br>"); } } b=b+1; } } } %>
姓名 |
任务 |
---|---|
袁德兴 |
利用Lucene和IKanalyzer进行检索,部分先后端内容与模块衔接 |
陈芳毅 |
采用httpclient和jsoup,进行爬取和解析,部分数据库内容 |
韩烨 |
采用数据库的dao模式将jsoup解析后的内容进行存储,部分前端和logo的设计 |
刘兵 |
采用bootstrap和jsp等进行前端界面的设计和后端代码实现 |
张晨曦 |
采用jquery和jsp等进行前端界面的设计和后端代码的实现 |
httpclient官方文档
lucene学习教程
lucene学习5分钟
lucene学习
lucene4入门实例
lucene高亮
jsp教程
jquery教程
bootstrap教程