从数据抓取到应用分析


本例主要是为了学习大数据的相关知识,整个学习过程包含了:

  1. nutch抓取网上数据;
  2. Hbase数据筛取;
  3. 中文分词及mapreduce数据统计分析。

首先需要安装nutch2和hbase. nutch2只能通过源码编译安装,指定hbase为默认存放抓取数据的地方。因此,建议先安装hbase, 再编译安装nutch。同时,hbase可以存在hdfs上,方便空间扩展和mapreduce计算。所以,建议首先安装hadoop2环境。如果仅仅是为了测试用,可以只安装single mode. 环境安装顺序为.

  1. Hadoop -- 网上有很多安装攻略,不列举;
  2. Hbase -- 按照官网的quick start安装配置即可;
  3. Nutch2 -- 官网也有编译方法,编译过程很长,一定要耐心一点。一般除了仓库连接有问题基本不会遇到什么问题。

接下来就是用nutch爬取网站数据。nutch2的爬取方式有两种,一种是deploy的方式,一种是local的方式,我们测试就用local的方式。注意,nutch只能爬取静态页面的结果。如果想爬取服务器数据或者动态数据得另寻它法。先切换到$NUTCH_HOME/runtime/local/下面。以爬取新浪股票的网页为例。首先需要添加种子链接;

$ mkdir -p urls
$ echo http://finance.sina.com.cn/stock/ > urls/seed.txt

然后需要配置过滤规则,一般是在local/conf/regex-urlfilter.txt下面。主要看这几项内容

# skip file: ftp: and mailto: urls 这个是跳过file/ftp/mailto等链接
-^(file|ftp|mailto):

# skip image and other suffixes we can't yet parse
# for a more extensive coverage use the urlfilter-suffix plugin 这个是跳过资源文件和js
-\.(gif|GIF|jpg|JPG|png|PNG|ico|ICO|css|CSS|sit|SIT|eps|EPS|wmf|WMF|zip|ZIP|ppt|PPT|mpg|MPG|xls|XLS|gz|GZ|rpm|RPM|tgz|TGZ|mov|MOV|exe|EXE|jpeg|JPEG|bmp|BMP|js|JS)$

# skip URLs containing certain characters as probable queries, etc. 这个是跳过所有含有以下符号的链接
-[?*!@=]

# skip URLs with slash-delimited segment that repeats 3+ times, to break loops
-.*(/[^/]+)/[^/]+\1/[^/]+\1/

# accept anything else 这个是加上所有你希望添加的链接
+.

总之,‘-’就是丢弃链接,‘+’就是添加链接,可以根据自己情况添加filter.

接下来就是启动hadoop/hbase

$ start-all.sh
$ cd $HBASE_HOME
$ bin/start-hbase.sh

然后切换到nutch2/runtime/local下面,nutch抓取数据需要存放到hbase上面,因此还需要拷贝hbase下的jar包到local/lib下面。

$ bin/crawl urls stock 3

crawl就开始抓取工作了, 抓取的文件默认放到了hbase里面(nutch编译时决定的)。urls是种子目录,stock是id,用来命名hbase的表。3就是迭代次数。抓取时间较长。

抓取结束后可进入hbase shell里查看。

$ bin/hbase shell
$ : list

用list可以看到hbase里面多了个“stock_webpage”的表。接下来看看这个表的结构。
可以用scan tablename查看,但是内容太多了,抓不到重点。其实nutch抓取网页后会对网页进行分析,我们来看看这个文档。

$ more $NUTHC_HOME/runtime/local/gora_hbase_mapping.xml


    
    
        
        
        
        
        
        
        
    

hbase存取数据的方式是rowkey:family:colum:value. 什么意思呢?就是一个rowkey有多个family,一个family有多个column,每个column有对应的value. 再看看这个文件。
table name为webpage,加上id前缀就是‘stock_webpage‘. 它有好多family, p/f/s...
fetch_filed就是抓取网页是存放的信息. 这些filed name都属于'f'这个family;
parse_filed就是分析后存放的信息。这些filed name都属于’p‘这个family.

所以,如果我们想查看http://finance.sina.com.cn/stock/ 这个网页下的文本信息只需要这样查看。

http://finance.sina.com.cn/stock p:c:value

什么意思呢,即rowkey为这个链接,family为'p', column为'c'的value. 当然,这个是有接口可以调用的。后续会提及。

那我们在分析数据的时候就可以根据这个存放规则去找到相应的文本信息了。关于分析的部分,我首先把rowkey里面包含finance的文本信息提取出来放到本地。可以放到hdfs,也可以直接放到另一个hbase表。

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.TextOutputFormat;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
  
import org.apache.hadoop.hbase.client.Result;  
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.CompareFilter;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.apache.hadoop.hbase.filter.SubstringComparator;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;  
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;  
import org.apache.hadoop.hbase.mapreduce.TableMapper;
import org.apache.hadoop.hbase.util.Bytes;  
 


public class HBaseToHdfs {
    public static void main(String[] args) throws Exception {
        Configuration conf = HBaseConfiguration.create();
        Job job = Job.getInstance(conf, HBaseToHdfs.class.getSimpleName());
        job.setJarByClass(HBaseToHdfs.class);
         
        job.setMapperClass(HBaseToHdfsMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);
         
        job.setNumReduceTasks(0);
         Scan scan = new Scan();
        //过滤hbase里面所有含有finance的rowkey
         Filter filter3 = new RowFilter(CompareFilter.CompareOp.EQUAL,
                 new SubstringComparator("finance"));
         scan.setFilter(filter3);

        TableMapReduceUtil.initTableMapperJob("stock_webpage", scan,HBaseToHdfsMapper.class ,Text.class, Text.class, job);
         
        job.setOutputValueClass(TextOutputFormat.class);
        FileOutputFormat.setOutputPath(job, new Path("/stock"));
         
        job.waitForCompletion(true);
    }
     
     
    public static class HBaseToHdfsMapper extends TableMapper {
        private Text outKey = new Text();
        private Text outValue = new Text();
        @Override
        protected void map(ImmutableBytesWritable key, Result value, Context context) throws IOException, InterruptedException {
            //key在这里就是hbase的rowkey
            byte[] name = null;

            try {
                //得到family为'p',column为'c'的值
                name = value.getColumnLatestCell(Bytes.toBytes("p"), Bytes.toBytes("c")).getValue();
            } catch (Exception e) {}

            outKey.set(key.get());
            String temp = (name==null || name.length==0)?"NULL":new String(name);
            System.out.println(temp);
            outValue.set(temp);
            context.write(outKey, outValue);
        }
 
    }
}

现在我的数据放到了workspace/HBaseToHdfs/stock里面,接下来放到hdfs上并作mapreduce. 简单的做个中文分词并计算wordcount。

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.ByteArrayInputStream;

import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;

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.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;

public class ChineseCount {
    
      public static class TokenizerMapper 
           extends Mapper{
        
        private final static IntWritable one = new IntWritable(1);
        private Text word = new Text();
          
        public void map(Object key, Text value, Context context
                        ) throws IOException, InterruptedException {
            
            byte[] bt = value.getBytes();
            InputStream ip = new ByteArrayInputStream(bt);
            Reader read = new InputStreamReader(ip);
            //map之前先做分词
            IKSegmenter iks = new IKSegmenter(read,true);
            Lexeme t;
            while ((t = iks.next()) != null)
            {
                word.set(t.getLexemeText());
                context.write(word, one);
            }
        }
      }
  
  public static class IntSumReducer 
       extends Reducer {
    private IntWritable result = new IntWritable();

    public void reduce(Text key, Iterable values, 
                       Context context
                       ) throws IOException, InterruptedException {
      int sum = 0;
      for (IntWritable val : values) {
        sum += val.get();
      }
      result.set(sum);
      context.write(key, result);
    }
  }

  public static void main(String[] args) throws Exception {
    Configuration conf = new Configuration();
    conf.set("fs.default.name", "hdfs://localhost:9000");
    
    String[] args1 = new String[] { "/input", "/output" };
    String[] otherArgs = new GenericOptionsParser(conf, args1).getRemainingArgs();
    if (otherArgs.length != 2) {
      System.err.println("Usage: wordcount  ");
      System.exit(2);
    }
    Job job = new Job(conf, "word count");
    job.setJarByClass(ChineseCount.class);
    job.setMapperClass(TokenizerMapper.class);
    job.setCombinerClass(IntSumReducer.class);
    job.setReducerClass(IntSumReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
    FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }
}

好了,大功告成,可以去hdfs上的/ouput里面查看分词结果,后续的分析待追加。

你可能感兴趣的:(从数据抓取到应用分析)