Hadoop之仿写搜索引擎

这篇文章,可能比较长,如果你觉得写得好可以把它看完,希望对你有所帮助。

写搜索引擎先整理下思路大致分为三步:

从网上爬取数据,对拿到的数据进行整理即分词,然后通过关键字匹配拿到数据。我会详细介绍这三步。

先讲讲我要实现的效果,从网上拿到标题包括对应的地址,对标题进行分词,通过输入的关键字匹配分词,返回对应的网址。

一,爬取数据:

开始的时候对网站进行的一个垂直爬取,拿到它的个标题,A标签,后面发现在处理数据的时候,速度太慢了,就简化了操作,只对单个页面进行爬取。

1,爬取用到的包:

2,通过模拟浏览器,去访问网站,如果返回的code为200,说明访问成功,就可以将这个网页的数据进行下载。

3,拿到爬取后的数据,即将网页转化成一个String的形式,存下来了。然后通过正则表达式,选取我们所需要的标签,这里取的是a标签,这里会对A标签进行过滤和分组,只取到有连接的,在写正则时写多个分组,有利于后面拿到,标题,跟对应的地址。

4,拿到标题跟地址后,将地址中的“/”等进行替换,因为后面要将地址作为文件的名字,标题作为内容存到hdfs中。

爬取代码展示:

下载页面代码:

    import java.io.BufferedInputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.Scanner;
    public class DownLoadTool {
        //下载页面内容
        public String downLoadUrl(String addr){
            StringBuffer sb=new StringBuffer();
            try {
                URL url=new URL(addr);
                HttpURLConnection con= (HttpURLConnection) url.openConnection();
                con.setConnectTimeout(5000);
                con.connect();
                if(con.getResponseCode()==200){
                    BufferedInputStream bis=new BufferedInputStream(con.getInputStream());
                    Scanner sc=new Scanner(bis,"GBK");
                    while(sc.hasNextLine()){
                        sb.append(sc.nextLine());
                    }
                }
            } catch (Exception e) {

                e.printStackTrace();
            }
            return sb.toString();
        }
    }

正则表达式匹配代码:

import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//标题下载类
public class TiltelDownLoad {
    //取出图片的html标记<a[^>]*? href=[""‘](?<url>[^""‘]*?)[""‘][^>]*?>(?<text>[\w\W]*?)</a>
    //<\\s*a\\s+([^>]*)\\s*></a>
    //<\\s*a\\s+([^>]*)\\s*>([^>]*)</a>";
    //<\\s*a\\s+([^>]*)\\s*>([^>]*)</a>
    //<\\s*a\\s+href\\s*=\\s*\"?http://(.*?)(\"|>|\\s+)([^>]*)\\s*>([^>]*)</a>
    static  String a_url="<\\s*a\\s+href\\s*=\\s*\"?http://(.*?)(\"|>|\\s+)([^>]*)\\s*>(.+?)</a>";
    //取出图片中的src内容
    static String href_url="href\\s*=\\s*\"?(.*?)(\"|>|\\s+)";
    //取出图片中的alt内容
    static String alt_url="alt\\s*=\\s*\"?(.*?)(\"|>|\\s+)";

    //取出图片的标签
    public Set<String> getTilteLink(String html){
        Set<String> result=new HashSet<String>();
        //创建一个Pattern模式类,编译这个正则表达式
        Pattern p=Pattern.compile(a_url,Pattern.CASE_INSENSITIVE);
        //定义一个匹配器的类
        Matcher matcher=p.matcher(html);
        while(matcher.find()){
            result.add(matcher.group(4).trim()+"\t"+matcher.group(1).trim());
        }
        return result;
    }

    public Set<String> getTitleSrc(Set<String> tilteLinks){
        Set<String> result=new HashSet<String>();
        //创建一个Pattern模式类,编译这个正则表达式
        Pattern p=Pattern.compile(href_url,Pattern.CASE_INSENSITIVE);
        for(String tiltelLink:tilteLinks){
            Matcher matcher=p.matcher(tiltelLink);
            while(matcher.find()){
                result.add(matcher.group(0));
            }
        }
        return result;
    }
}

将数据上传到hdfs上:

public class Test {
public static void main(String[] args) throws URISyntaxException, IOException {
    Configuration conf=new Configuration();
    URI uri=new URI("hdfs://192.168.61.128:9000");
    FileSystem hdfs=FileSystem.get(uri,conf);
    TiltelDownLoad pcl=new TiltelDownLoad();
    String addr="http://www.sohu.com";
    DownLoadTool dlt=new DownLoadTool();
    String html=dlt.downLoadUrl(addr);
    Set<String> srcs=pcl.getTilteLink(html);
    for(String title:srcs){
        String[] sts=title.split("\t");
        String url=sts[1].replaceAll("/", "-");
        String url1=url.replaceAll("?", "#");
        //String url2=url1.replace("?","*");
        String content=sts[0];
        Path dfs=new Path("hdfs://192.168.61.128:9000/sohu/1/"+url1+".txt");
        FSDataOutputStream outputStream=hdfs.create(dfs);

        System.out.println(url+"-------"+sts[0]);
        outputStream.write((content+"\n").getBytes());
    }
}

}

上传部分结果展示:

单个文件内容展示:

二,分词:

我们会对内容进行分词,即对标题进行分词,为后面的搜索配对做准备。

实际上这个过程有点像倒排索引,写的那个单词计数,不过以前的倒排因为是对单词进行计数,默认的是用空格分隔,可是这里的是包含中英文的,所以就引入了lucene分词器。

1,在map过程引入分词,默认的是这个StringTokenizer itr = new StringTokenizer( value.toString());现在我们需要用分词替换它,来对标题进行分词。

2,将词分好后,将词对应的地址,权重,这里是出现的次数,写出。

词—>key 地址,权重—–>value;权重默认值1

3,通过两个Reducer过程,对分好的词做一个权重的统计,和地址的合并,要是同一个词,对应两个地址,会用“;”分开。

4,最后将结果写到hdfs上。

代码展示:

导入的包:

public class InvertedIndex {
    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();
             // 自定义停用词
            String st=value.toString();
            String[] self_stop_words = {  "了",  ",",  ":", "," };
            CharArraySet cas = new CharArraySet( 0, true);
            for (int i = 0; i < self_stop_words.length; i++) {
                cas.add(self_stop_words[i]);
            }
            // 加入系统默认停用词
            Iterator<Object> itor = SmartChineseAnalyzer.getDefaultStopSet().iterator();
            while (itor.hasNext()) {
                cas.add(itor.next());
            }
             // 中英文混合分词器(其他几个分词器对中文的分析都不行)
            SmartChineseAnalyzer sca = new SmartChineseAnalyzer( cas);  

            TokenStream ts = sca.tokenStream("field", st);
            CharTermAttribute ch = ts.addAttribute(CharTermAttribute.class);  

            ts.reset();
            while (ts.incrementToken()) {
                String path=split.getPath().toString();
                System.out.println("path====="+path);
                int indexone=path.lastIndexOf("/");
                int indextow=path.lastIndexOf(".");
                String path1=path.substring(indexone+1,indextow);
                //System.out.println("path1====="+path1);
                String path2=path1.replaceAll("-","/");
                String path3=path2.replaceAll("#", "?");
                keyInfo.set(ch.toString()+":"+path3);
                valueInfo.set("1");
                context.write(keyInfo, valueInfo);
            }
            ts.end();
            ts.close();
        }
    }

    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 {
    //System.out.println("***Combiner***values===="+values.toString());
            //统计词频
            int sum = 0;
            for (Text value : values) {
                sum += Integer.parseInt(value.toString() );
            }
        //System.out.println("--Combiner----sum====="+sum);
            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);
        }
    }

    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);
        }

    }

    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(InvertedIndexMapper.class);
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(Text.class);
            job.setCombinerClass(InvertedIndexCombiner.class);
            job.setReducerClass(InvertedIndexReducer.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(Text.class);
            FileInputFormat.addInputPath(job, new Path("hdfs://192.168.61.128:9000/sohu/"));
            FileOutputFormat.setOutputPath(job, new Path("hdfs://192.168.61.128:9000/sohuout/"+System.currentTimeMillis()+"/"));
            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();
        }

    }
}

部分结果展示:

三,搜索:

我们输入一句话,通过系统返回网址。

前面已经将标题分好了词,相当于建立好了索引文件,我们拿到这个文件,进行操作。

1,先在map过程中将我们输入的句子,按照想开始相同的分词分法将句子分好词。

2,拿着本次分好的词,去匹配输入文本的key,这个key 就是我们开始标题分好的词。如果相同,就将其key和value写出。

3,在Reducer过程,会对value进行一个降噪的处理,将权重消去,只返回一个地址,和关键字。将结果输出。

代码展示:

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 {
        try {
            // 要处理的文本
            String text = "我的万套别墅";

            // 自定义停用词
            String[] self_stop_words = { "的", "了", "呢", ",", "0", ":", ",", "是", "流" };
            CharArraySet cas = new CharArraySet(0, true);
            for (int i = 0; i < self_stop_words.length; i++) {
                cas.add(self_stop_words[i]);
            }

            // 加入系统默认停用词
            Iterator<Object> itor = SmartChineseAnalyzer.getDefaultStopSet().iterator();
            while (itor.hasNext()) {
                cas.add(itor.next());
            }

            // 中英文混合分词器(其他几个分词器对中文的分析都不行)
            SmartChineseAnalyzer sca = new SmartChineseAnalyzer(cas);

            TokenStream ts = sca.tokenStream("field", text);
            CharTermAttribute ch = ts.addAttribute(CharTermAttribute.class);

            ts.reset();
            while (ts.incrementToken()) {
                if (key.toString().equals(ch.toString())) {
                    context.write(key, value);
                }
            }
            ts.end();
            ts.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

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 {
        String val=null;
        // 生成文档列表
        for (Text text : values) {
            System.out.println("********"+text.toString());
            String sts[] =text.toString().split(";");
            for(int i=0;i<sts.length;i++){
                    String stt=sts[i].toString().substring(0,sts[i].toString().indexOf(":"));
                    val+=stt+";";
            }
            Text value=new Text();
            value.set(val);
            context.write(key, value);
        }
    }
}

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.setReducerClass(FindReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);
        FileInputFormat.addInputPath(job,
                new Path("hdfs://192.168.61.128:9000/sohuout/1462889264074/part-r-00000"));
        FileOutputFormat.setOutputPath(job, new Path("hdfs://192.168.61.128:9000/sohufind/"+System.currentTimeMillis()+"/"));

        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();
    }
}

}

部分结果展示:

新手上路做的不好的还请多多包涵!

时间: 2024-08-29 19:48:23

Hadoop之仿写搜索引擎的相关文章

ASP.NET WebAPI 14 仿写Filter管道

WebAPI中有设计了几种管道(Channel),大概如下:HttpMessageHandler,ActionFilter管道,ExceptionFilter管道.在三种管道中HttpMessageHandler管道与ExceptionFilter管道是基于级别设计的,ActionFilter管道是基于方法级别设计的.对于ActionFilter管道,因为采用的是AOP的思想,所以按方法级别去设计. 在看WebAPI源码过程中对ActionFilter管道中何用的表达示树与闭包等内容之前并没有下

仿写网易云-排行榜页面

第一次仿写页面(内心还是很鸡冻啊~~~) 还有很多地方没有完善 暂时就先这样吧 以后有机会在加效果 多练习 熟能生巧~ 好喜欢网易云音乐啊~音乐的天堂~希望以后自己也能去网易云工作就好了了~~~~~ 1 <!DOCTYPE html> 2 <html lang="zh-cn"> 3 <head> 4 <meta charset="utf-8"> 5 <meta http-equiv="X-UA-Comp

怎么样仿写已知网址的网页?

今天上午在实验室里学习,无意中看到湖北老乡群里爆出了一则外包的消息. 是问有没有回搭建网站.我接了这单活儿.需求很简单,仿照这一个已知的网站做一个静态页面. 工作量不大.他说了,做一个静态网站.因为之前,我曾做过类似的工作,所以我就答应了. 遇到的第一个问题就是评价.他让我开个价.说实话,我当时也蒙了.这个外包估价这个事情,我之前也没做过.在网上搜了下,每个人说法都不一样.看到一个我觉得可以参考的回答.按照做的页面收费. 每个30~50.于是我给他报价是500,理由就是 大概做10个左右的页面,

礼物说仿写项目iOS源码

礼物说仿写(updating...) 源码下载:http://code.662p.com/view/14507.html api: 礼物说 首页精选 banner2: http://api.liwushuo.com/v2/secondary_banners?gender=1&generation=2 banner1: http://api.liwushuo.com/v2/banners?channel=iOS 精选: http://api.liwushuo.com/v2/channels/101

仿写一个简单的网站,以及初学前端的一点感悟

本文同时发布在本人个人博客上www.yaoxiaowen.com 这段时间在学习前端,并仿着我们公司官网的设计 tongshai.net, 把官网重新实现了一遍. 主要是因为我们公司的官网设计很简单(就几个静态页面,连什么控件都没用),很适合作为练手项目, 模仿的工程放在了我的另一个测试服务器上. 点击查看. 在仿写过程中,因为网页很简单,所以也没有碰到过什么很纠结的技术问题.仿写一下也就是熟悉一下相关概念. 但是按照我的前端同事的建议,使用了bootstrap 框架,快速浏览了一下这个框架的教

仿写及比较标哥的iOS时钟动画

一.前言 以前看各种绚丽的UI特效动画代码,采用的方法是会先运行一篇,然后直接去看实现代码.初学时抱着瞻仰的态度去接触,去认识,是没有错的.但是在了解了像素.动画渲染机制,CoreAnimation API,推导过二维.三维的仿射矩阵之后,我们可以改变阅读UI动画博文或者是源码的方式了. Talk is cheap, show me the code——Linus Torvalds. 大量的仿写:一定一定要多写——叶孤城__ 在CodeReview线下大会上的发言. 最近安居客.猿题库.蘑菇街.

HTML: 仿写一个财经类静态的网页

要求:仿写一个静态的网页,主要采用HTML+CSS+DIV的布局方式, 新建两个文件:demo.html.demo.css 图片素材:image.zip demo.html代码如下: <!doctype html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta name="description" content="t

html仿写电子科技大学首页(html首页开发实战)

说明:本来是要仿写www.zixue.it网站首页的,但由于该首页比较复杂,写了好几天才写了一半.想着自己是自学.初学,而且考虑到往后搭建后台时需要的html知识不多,故改为仿写电子科技大学的网站首页(http://www.uestc.edu.cn).仿写电子科技大学网站首页时,有些地方没有做深入的优化,会跟原版的网页有细微的差别,但不影响,里面的所有按钮及链接都可以点击获取数据. 开发时长:两天晚上的空余时间+一个早上的时间,大约8小时.代码不到1000行. 到今天为止,html的自学暂时告一

Jquery 仿百度搜索引擎自动完成功能

源代码如下所示: <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>自动完成例子</title> <script type="text/javascript" src="js/jquery-1.4.1.min.js"><