Hadoop学习之自己动手做搜索引擎【网络爬虫+倒排索引+中文分词】

来源:互联网 发布:windows smb远程漏洞 编辑:程序博客网 时间:2024/06/07 14:47

一、使用技术

  • Http协议
  • 正则表达式
  • 队列模式
  • Lucenne中文分词
  • MapReduce

二、网络爬虫

  1. 项目目的
    通过制定url爬取界面源码,通过正则表达式匹配出其中所需的资源(这里是爬取csdn博客url及博客名),将爬到的资源存入文件中便于制作成倒排索引。根据页面源码垂直爬取csdn网站中的所有博客资源(找到一个超链接就爬取该超链接中的内容)。
  2. 设计思想
    建立一个队列对象,首先将传入的url存入代表未爬取的队列中,循环如果未爬取队列中所有url进行爬取,并将爬取的url转移到代表已爬取的队列中。使用HttpURLConnection获得页面信息,使用正则表达式从页面信息中所需的信息输出到文件中,并将从页面信息中匹配到的超链接存入代表未爬取的队列中,实现垂直爬取数据。
  3. 源码及分析
    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标签的内容(其实还可以加入该页面的头,但是这里没做这么复杂)

三、倒排索引制作

  1. 设计目的
    使用MapReduce及中文分词将爬到的文件制作成倒排索引,索引文件格式为
    Key(分词器分出的词)+“\t”+url1:sum;url2:sum

  2. 设计思想及源码
    在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();            }        }}

结果展示:
测试

0 0
原创粉丝点击