Hadoop学习之自己动手做搜索引擎【网络爬虫+倒排索引+中文分词】
来源:互联网 发布:windows smb远程漏洞 编辑:程序博客网 时间:2024/06/07 14:47
一、使用技术
- Http协议
- 正则表达式
- 队列模式
- Lucenne中文分词
- MapReduce
二、网络爬虫
- 项目目的
通过制定url爬取界面源码,通过正则表达式匹配出其中所需的资源(这里是爬取csdn博客url及博客名),将爬到的资源存入文件中便于制作成倒排索引。根据页面源码垂直爬取csdn网站中的所有博客资源(找到一个超链接就爬取该超链接中的内容)。 - 设计思想
建立一个队列对象,首先将传入的url存入代表未爬取的队列中,循环如果未爬取队列中所有url进行爬取,并将爬取的url转移到代表已爬取的队列中。使用HttpURLConnection获得页面信息,使用正则表达式从页面信息中所需的信息输出到文件中,并将从页面信息中匹配到的超链接存入代表未爬取的队列中,实现垂直爬取数据。 - 源码及分析
a.LinkCollection.java
package com.yc.spider;import java.util.ArrayList;import java.util.Collections;import java.util.HashSet;import java.util.List;import java.util.Set;/** * 链接地址队列 * @author wrm *当爬到一个超链接后,将其加入到队列中,接着爬这个超链接,并将这个超链接放入标示已查的队列中 */public class LinkCollection { //待访问url的集合:队列 private List<String> unVisitedUrls=Collections.synchronizedList(new ArrayList<String>()); private Set<String> visitedUrls=Collections.synchronizedSet(new HashSet<String>()); /** * 入队操作 */ public void addUnVisitedUrl(String url){ if(url!=null&&!"".equals(url.trim())&&!visitedUrls.contains(url)&&!unVisitedUrls.contains(url)){ unVisitedUrls.add(url); } } /** * 出队 */ public String deQueueUnVisitedUrl(){ if(unVisitedUrls.size()>0){ String url=unVisitedUrls.remove(0); visitedUrls.add(url); return url; } return null; } /** * 判断队列是否为空 */ public boolean isUnVisitedUrisEmpty(){ if(unVisitedUrls!=null&&!"".equals(unVisitedUrls)){ return false; }else{ return true; } } /** * hadoop出队 */ public String deQueueVisitedUrl(){ if(visitedUrls.iterator().hasNext()){ String url=visitedUrls.iterator().next(); visitedUrls.remove(0); return url; } return null; } /** * 判断Visited队列是否为空 */ public boolean isVisitedUrisEmpty(){ if(visitedUrls!=null&&!"".equals(visitedUrls)){ return false; }else{ return true; } }}
该类是url的队列,该说的注释中都有
b.DownLoadTool.java
package com.yc.spider;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.PrintWriter;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;import java.util.List;import java.util.Map;import java.util.Random;import java.util.Scanner;import java.util.Set;/** * 下载工具类 * @author wrm * */public class DownLoadTool { /** * 编码集 */ private String encoding="GBK"; /** * 下载的文件保存的位置 */ private String savePath=System.getProperty("user.dir")+File.separator; /** * 自动生成保存的目录 * 目录名的命名规范:yyyyMMddHHmmss */ public static File createSaveDirectory(){ DateFormat df=new SimpleDateFormat("yyyyMMddHHmmss"); String directoryName=df.format(new Date()); return createSaveDirectory(directoryName); } /** * 根据指定目录名 * @param directoryName * @return */ public static File createSaveDirectory(String directoryName) { File file=new File(directoryName); if(!file.exists()){ file.mkdirs(); } return file; } /** * 下载页面的内容 */ static String downLoadUrl(String addr){ StringBuffer sb=new StringBuffer(); try { URL url=new URL(addr); HttpURLConnection con=(HttpURLConnection) url.openConnection(); con.setConnectTimeout(5000); con.connect(); //产生文件名 Random r=new Random(); try { Thread.sleep(r.nextInt(2000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(con.getResponseCode()); System.out.println(con.getHeaderFields()); if(con.getResponseCode()==200){ BufferedInputStream bis=new BufferedInputStream(con.getInputStream()); Scanner sc=new Scanner(bis,encoding); while(sc.hasNextLine()){ //读取拼接页面信息 sb.append(sc.nextLine()); } } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); }}
该类使用HttpURLConnection.getInputStream()获得页面内容,其中
Random r=new Random(); try { Thread.sleep(r.nextInt(2000)); } catch (InterruptedException e) { e.printStackTrace(); }
是为了防止被网站识别出是爬虫在访问而进行的睡眠操作
con.getResponseCode()==200是判断访问该网页获得的状态码是否为200(成功)
如果想要获得http头的话可以使用以下代码
con.getHeaderField(name); //获得头中的name数据con.getHeaderFields(); //获得头中的所有数据
某些网站的防爬虫做得实在太好!就算睡眠了也依旧不让你爬,这时可以冲firfox中获得头,通过该请求头方面便可骗过。
c.HtmlNodeParser.java
package com.yc.spider;import java.util.HashSet;import java.util.Set;import org.htmlparser.Node;import org.htmlparser.NodeFilter;import org.htmlparser.Parser;import org.htmlparser.filters.NodeClassFilter;import org.htmlparser.filters.OrFilter;import org.htmlparser.tags.LinkTag;import org.htmlparser.util.NodeList;import org.htmlparser.util.ParserException;public class HtmlNodeParser { /** * 解析url地址中对应的页面中的a标签与frame标签 * @throws ParserException * */ public Set<String> parseNode(String url,NodeFilter filter) throws ParserException{ //NodeFilter表明是否要全网爬行 Set<String> set=new HashSet<String>(); Parser parser=new Parser(url); if(!url.startsWith("http:/")){ url="http:/"+url; } //这个过滤器用户过滤frame NodeFilter framefilter=new NodeFilter(){ @Override public boolean accept(Node node) { if(node.getText().indexOf("frame src=")>=0){ return true; }else{ return false; } } }; //创建过滤器 LinkTag表示超链接标记 OrFilter linkFilter=new OrFilter(new NodeClassFilter(LinkTag.class),framefilter); NodeList list=parser.extractAllNodesThatMatch(linkFilter); for(int i=0;i<list.size();i++){ Node node=list.elementAt(i); String linkurl=null; if(node instanceof LinkTag){ //href LinkTag linkTag=(LinkTag) node; linkurl=linkTag.getLink(); }else{ //是frame节点 src String frame=node.getText(); int start=frame.indexOf("src="); frame=frame.substring(start); int end=frame.indexOf(" "); if(end==-1){ end=frame.indexOf(">"); } linkurl=frame.substring(4,end-1); } if(linkurl==null||"".equals(linkurl)||(!linkurl.startsWith("http://")&&!linkurl.startsWith("https://"))){ continue; } if( filter!=null&&filter.accept(node)==false){ continue; } set.add(linkurl); } return set; }}
d.TitleDown.java
package com.yc.spider;import java.util.HashSet;import java.util.Set;import java.util.regex.Matcher;import java.util.regex.Pattern;public class TitleDown { /** * 取html标记 */ static String A_URL="<\\s*a\\s+([^>]*)\\s*>([^<]*)</a>"; static String HREF_URL="href\\s*=\\s*\"*(http://blog.csdn.net/?.*?/article/details/?.*?)(\"|>|\\s+)";// static String HREF_URL="href\\s*=\\s*\"*(topic/?.*?)(\"|>|\\s+)";// static String HREF_URL="href\\s*=\\s*\"*(http://news.sohu.com/?.*?)(\"|>|\\s+)"; static Set<String> getImageLink(String html){ System.out.println(html); Set<String> result=new HashSet<String>(); String g1=""; //创建一个Pattern模式类,编译这个正则表达式 Pattern p=Pattern.compile(A_URL,Pattern.CASE_INSENSITIVE); Pattern p1=Pattern.compile(HREF_URL, Pattern.CASE_INSENSITIVE); //定义一共饿 匹配器的类 Matcher matcher=p.matcher(html); while(matcher.find()){ g1=matcher.group(1); Matcher m1=p1.matcher(g1); while(m1.find()){ String word=matcher.group(2); result.add(m1.group(1)+"\t"+word.trim().trim()); } } return result; } public static void main(String[] args) { String addr="http://www.csdn.com"; String html=DownLoadTool.downLoadUrl(addr);// String html="<title>根本没问题啊!</title>"; System.out.println(html); Set<String> imagetags1=getImageLink(html); for(String imagetag:imagetags1){ System.out.println(imagetag); } }}
该类使用正则表达式来匹配我所需要的数据。
static String A_URL="<\\s*a\\s+([^>]*)\\s*>([^<]*)</a>";
用于匹配a标签和a标签中的内容
static String HREF_URL="href\\s*=\\s*\"*(http://blog.csdn.net/?.*?/article/details/?.*?)(\"|>|\\s+)";
用于匹配url,因为这里我是要csdn的博客地址,所以作此匹配
e.Spider.java
package com.yc.spider;import java.io.FileNotFoundException;import java.io.IOException;import java.net.URI;import java.net.URISyntaxException;import java.util.Set;import java.util.regex.Matcher;import java.util.regex.Pattern;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.FSDataOutputStream;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.Text;import org.htmlparser.util.ParserException;public class Spider { private LinkCollection lc=new LinkCollection(); private DownLoadTool dlt=new DownLoadTool(); private HtmlNodeParser hnp=new HtmlNodeParser(); public String getFileName(String url){ String filename=url.toString().substring(7); filename=filename.replaceAll("/", "-"); filename=filename.replace(".", ","); return filename; } public void crawling(String url,String directory) throws FileNotFoundException{ //1.先添加url到待取队列中 lc.addUnVisitedUrl(url); try { Configuration conf=new Configuration(); URI uri=new URI("hdfs://192.168.1.123:9000"); //hdfs主机uri FileSystem hdfs=FileSystem.get(uri, conf); //2.循环这个队列,到这个队列为空时 while(lc.isUnVisitedUrisEmpty()==false){ //3.取出待取地址 String visiturl=lc.deQueueUnVisitedUrl(); //4.下载这个页面 try { String html=dlt.downLoadUrl(visiturl); Set<String> allneed=TitleDown.getImageLink(html); for (String addr : allneed) { String a=addr.substring(addr.indexOf("\t")+1); String filename=addr.substring(0,addr.indexOf("\t")); filename=getFileName(filename); System.out.println(filename); Path p=new Path("/spider/"+filename); FSDataOutputStream dos=hdfs.create(p); try { System.out.print(a); dos.write(a.getBytes()); } catch (IOException e) { e.printStackTrace(); }finally { dos.close(); //这里一定要将dos关闭,不然内容无法写入 } } //5.从页面中分析出超链接地址,放入待取地址中 Set<String> newurl=hnp.parseNode(visiturl, null);// dlt.createLogFile(TitleDown.getImageLink(html)); //将这些地址又加入到待取地址中 for(String s:newurl){ String httpregex="http://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?"; Pattern p2=Pattern.compile(httpregex,Pattern.CASE_INSENSITIVE); Matcher matcher=p2.matcher(s); while(matcher.find()){ lc.addUnVisitedUrl(s); //boolean b=matcher. } } } catch (ParserException e) { e.printStackTrace(); } } } catch (IllegalArgumentException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (URISyntaxException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } }}
因为我要将URL作为文件名,而文件名不能含有某些字符,所以用该方法进行替换
public String getFileName(String url){ String filename=url.toString().substring(7); filename=filename.replaceAll("/", "-"); filename=filename.replace(".", ","); return filename; }
生成的文件
每一个文件中只有改a标签的内容(其实还可以加入该页面的头,但是这里没做这么复杂)
三、倒排索引制作
设计目的
使用MapReduce及中文分词将爬到的文件制作成倒排索引,索引文件格式为
Key(分词器分出的词)+“\t”+url1:sum;url2:sum设计思想及源码
在Map阶段获得文件名,并将文件名还原为url,作为value。将文件内容通过分词器分词后将分出的每个词作为key,输出。
源码:
public static class InvertedIndexMapper extends Mapper<Object, Text, Text, Text>{ private Text keyInfo = new Text(); // 存储单词和URI的组合 private Text valueInfo = new Text(); //存储词频 private FileSplit split; // 存储split对象。 @Override protected void map(Object key, Text value, Mapper<Object, Text, Text, Text>.Context context) throws IOException, InterruptedException { //获得<key,value>对所属的FileSplit对象。 split = (FileSplit) context.getInputSplit(); Analyzer sca = new SmartChineseAnalyzer( ); TokenStream ts = sca.tokenStream("field", value.toString()); CharTermAttribute ch = ts.addAttribute(CharTermAttribute.class); ts.reset(); while (ts.incrementToken()) { System.out.println(ch.toString()); String url=split.getPath().toString(); url=url.substring(url.lastIndexOf("/")); url=url.replaceAll("-", "/"); url=url.replace(",", "."); url="http:/"+url; System.out.println(url); // key值由单词和URI组成。 keyInfo.set( ch.toString()+";"+url); //词频初始为1 valueInfo.set("1"); context.write(keyInfo, valueInfo); } ts.end(); ts.close(); } }
Combiner阶段:将相同key值的词频累加获得词频
public static class InvertedIndexCombiner extends Reducer<Text, Text, Text, Text>{ private Text info = new Text(); @Override protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException { //统计词频 int sum = 0; for (Text value : values) { sum += Integer.parseInt(value.toString() ); } int splitIndex = key.toString().indexOf(";"); //重新设置value值由URI和词频组成 info.set( key.toString().substring( splitIndex + 1) +":"+sum ); //重新设置key值为单词 key.set( key.toString().substring(0,splitIndex)); context.write(key, info); } }
Reducer阶段,组合出最后的数据输出
public static class InvertedIndexReducer extends Reducer<Text, Text, Text, Text>{ private Text result = new Text(); @Override protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException { //生成文档列表 String fileList = new String(); for (Text value : values) { fileList += value.toString()+";"; } result.set(fileList); context.write(key, result); } }
四、用户搜索模拟
原理:将用户数据的关键字分词后与倒排索引分别匹配,只要匹配到的在Combiner中统计词频,并在Reduce中操作后输出。
源码:
package com.yc.hadoop;import java.io.IOException;import java.util.StringTokenizer;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.Mapper;import org.apache.hadoop.mapreduce.Reducer;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.input.FileSplit;import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import org.apache.lucene.analysis.Analyzer;import org.apache.lucene.analysis.TokenStream;import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;public class FindWord { public static class FindMapper extends Mapper<Text, Text, Text, Text>{ @Override protected void map(Text key, Text value, Mapper<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException { String text="android可行性"; //用户输入的关键字 Analyzer sca = new SmartChineseAnalyzer( ); TokenStream ts = sca.tokenStream("field", text); CharTermAttribute ch = ts.addAttribute(CharTermAttribute.class); ts.reset(); while (ts.incrementToken()) { if(ch.toString().equals(key.toString())||ch.toString().equals(key.toString())){ System.out.println(value.toString()); String[] urls=value.toString().split(";"); int count=0; for (String url : urls) { String oneurl=url.split(":")[0]+url.split(":")[1]; count=Integer.parseInt(url.split(":")[2]); String newvalue=ch.toString()+";"+count; System.out.println(">>>>>>>>"+oneurl+">>>>>>>>>>"+newvalue); context.write(new Text(oneurl),new Text( newvalue)); } } } ts.end(); ts.close(); } } public static class FindCombiner extends Reducer<Text, Text, Text, Text>{ @Override protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException { //统计词频 int sum = 0; for (Text value : values) { String count=value.toString().split(";")[1]; sum += Integer.parseInt(count ); } context.write(new Text(String.valueOf(sum)),new Text(key.toString()) ); } } public static class FindReducer extends Reducer<Text, Text, Text, Text>{ @Override protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException { //生成文档列表 for (Text text : values) { context.write(key, text); } } } public static void main(String[] args) { try { Configuration conf = new Configuration(); Job job = Job.getInstance(conf,"InvertedIndex"); job.setJarByClass(InvertedIndex.class); //实现map函数,根据输入的<key,value>对生成中间结果。 job.setMapperClass(FindMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(Text.class); job.setInputFormatClass(KeyValueTextInputFormat.class); job.setCombinerClass(FindCombiner.class); job.setReducerClass(FindReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.addInputPath(job, new Path("hdfs://192.168.1.123:9000/spiderout/1462887403514/part-r-00000")); FileOutputFormat.setOutputPath(job, new Path("hdfs://192.168.1.123:9000/1")); System.exit(job.waitForCompletion(true) ? 0 : 1); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } }}
结果展示:
- Hadoop学习之自己动手做搜索引擎【网络爬虫+倒排索引+中文分词】
- Hadoop学习之网络爬虫+分词+倒排索引实现搜索引擎案例
- Hadoop学习之网络爬虫+分词+倒排索引实现搜索引擎案例
- Hadoop学习之网络爬虫+分词+倒排索引实现搜索引擎案例
- hadoop--之搜索引擎,倒排索引
- hadoop 学习笔记之倒排索引
- 搜索引擎之倒排索引
- 搜索引擎之倒排索引
- hadoop学习-倒排索引
- hadoop学习-倒排索引
- hadoop倒排索引---学习
- Hadoop之倒排索引
- Hadoop之倒排索引
- Hadoop之倒排索引
- hadoop之倒排索引
- hadoop之倒排索引
- 搜索引擎-倒排索引
- 搜索引擎-倒排索引
- Find Minimum in Rotated Sorted Array
- centos下安装jdk1.7
- 第十一周项目1——点-圆-圆柱类族的设计
- Android之MediaProjectionManager实现手机截屏总结
- Codeforces Round #349 (Div. 2)
- Hadoop学习之自己动手做搜索引擎【网络爬虫+倒排索引+中文分词】
- java多线程-线程同步
- bestcoder 百度之星 1001 大搬家
- Python笔记之入门(基础篇)
- Unity3D射线
- 正则表达式
- JVM调优Demo(三)合适的Survivor
- Synchronization, Part 8: Ring Buffer Example
- String StringBuffer与StringBuilder的区别