Java爬虫(三)

一、 任务

获取某网站的各个数据,其中该网站包含地方性数据

二、步骤

1. 遍历该网站中各个地方网址,获得有效地方ID并保存在文件中
2. 将地方ID放入队列中,由于该网站有两个不同的页面(功能),所以需要两个该队列
3. 用两个类分别从两个队列中获取网页数据,并分别保存到对应队列中
4. 用两个类分别从上一步的队列获取数据,通过布隆过滤器进行筛选,并对数据进行加工,将不全的数据获取完整,并统一JSon键值对中的键,把数据保存在一个队列中
5. 直接从上一步的队列中取数据保存到Hbase中

三、详细步骤

1. 获取地方ID

一般、几乎所有的有关地方的网址中有一串数字指的是代表该地方的ID,这个id就国家行政区划代码,如下图是我目标网站中有效的地方ID:
这里写图片描述

具体办法就是把所有的行政区划代码带入网址,查看网页是否存在,可以根据网站实际情况做优化。
该步骤基本上只需要做一次,除非某些地方加入了该网站

2. 读取有效id到队列

这一步最简单,就是直接把文件中的数据以JSon的格式放入队列中,不过因为我的部分代码是用的别人写的,所有目前还没有去了解代码是如何与redis队列进行数据交互的,有时间可以去了解一下Jedis操作redis工具类 JedisUtil

3. 获取网页数据
  • 首先从队列中获取id来获取网页的地址,然后通过url获取对应网页【这里也是用别人写的代码(使用CloseableHttpClient进行http请求)】
  • 再判断网页是否有数据,比如通过判断网页的长度、解析网页后是否有数据
  • 根据具体网页解析网页获取有效数据
    一般主要分为html和xhr
    对于html页面最常见的是在获取正文内容时,直接可以通过标签获取数据,如代码:
//html为页面,url为页面链接
Document doc=Jsoup.parse(html,url);
Elements es=doc.getElementsByTag("p");
ret= es.text();//正文

对于xhr,可能比较复杂,一般是采用字符串匹配(startsWith(String)-判断字符串是否以字符串String开头;indexOf(String)、indexOf(String,int)-返回匹配字符串String的下标)+字符串剪切(substring(int)、substring(int,int))
另外,对于格式为”{},{},{}…{}”的字符串,若大括号中是键值对,直接将字符串转化为json格式则会只转换第一个大括号的内容,因此也可以利用这一特性去裁剪数据

JSONObject info = JSONObject.fromObject(String);
  • 最后把获取到的数据放入队列中
4. 去重

这一步主要功能就是通过布隆过滤器,去掉重复的数据,并把数据添加完整(比如部分网页的正文需要跳转页面才能获取,部分时间的显示是数字、或者格式不统一),再把json的键值对的键统一名字

  • 布隆过滤器:
    简单来说就是通过多个不同的哈希算法来判断该值是否存在,因为多个不同哈希算法得出的值会有多个映射,根据判断每个映射位是否都为1,若存在一个映射不为1,则该值肯定不存在,若都为1,则该值可能存在也可能不存在;
    所以一套布隆过滤器下来,数据中不会有重复的,只会把少量的不重复的当做重复的扔掉,即误算率;
    当然,数据越多,误算率也就越高
5. 把数据保存到Hbase中

当然,Hbase部分的代码也不是我写的,我也不懂Hbase具体是什么,我目前只需要利用Hbase的接口设置好传入的数据格式即可;

HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System)所提供的分布式数据存储一样,HBase在Hadoop之上提供了类似于Bigtable的能力。HBase是Apache的Hadoop项目的子项目。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式。【百度百科】

在传数据到Hbase前首先要对数据进行判断处理,去除乱码、历史数据(主要是根据时间字段来判断该数据距今多久了)、字段缺失(即接口所需的部分数据为空或者缺失)
后两者好处理,主要问题是怎么识别乱码,同样我也是别人的代码(网上还挺多的)

四、注意事项

1. 第一个主要是主要文件的读取和写入,可能会存在编码不正确
2. 第二步还是文件读入编码可能存在问题
3. 第三步就是解析页面,特别是xhr页面,存在数据中出现”{“,”}”等界限符号,导致字符串截取错误的
4. 第四步就是注意布隆过滤器的使用,我感觉如果数据量太大的话,会丢失很多不重复的数据,我觉得以后想个优化的办法;还有就是部分的时间是以目前时间为基准的,比如“刚刚”、“3分钟前”、“昨日”,“8月8日”等,此为还要注意跨年的时间判断,我写的代码:
SimpleDateFormat format0 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public String getDateString(String s_date) throws ParseException{
        String ret=null;
        boolean flag=true;
        Long l=System.currentTimeMillis();
        //Date date=new Date(Long.valueOf(l));
        //System.out.println(format0.format(new Date(Long.valueOf(l))));
        int i=0;
        int d;
        //刚刚
        if("刚刚".equals(s_date)){

        }
        else if((i=s_date.indexOf("分钟"))!=-1){
            d=Integer.valueOf(s_date.substring(0,i));
            l=l-d*60*1000;
        }
        else if((i=s_date.indexOf("小时"))!=-1){
            d=Integer.valueOf(s_date.substring(0,i));
            l=l-d*60*60*1000;
        }
        else if("昨日".equals(s_date)){
            l=l-24*60*60*1000;
        }
        else{
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
            String year=sdf.format(new Date(l));
            sdf = new SimpleDateFormat("MM");
            String month=sdf.format(new Date(l));
            sdf = new SimpleDateFormat("dd");
            String day=sdf.format(new Date(l));
            sdf = new SimpleDateFormat("HH:mm:ss");
            String time=sdf.format(new Date(l));

            if((i=s_date.indexOf("月"))!=-1){
                month=s_date.substring(0, i);
            }
            if((i=s_date.indexOf("日"))!=-1){
                int j;
                if((j=s_date.indexOf("月"))==-1){
                    j=i-3;
                }
                day=s_date.substring(j+1, i);
            }
            ret=year+"-"+month+"-"+day+" "+time;
            if(format0.parse(ret).getTime()>l){
                year=String.valueOf(Integer.valueOf(year)-1);
                ret=year+"-"+month+"-"+day+" "+time;
            }
            flag=false;
            //System.out.println(year+"-"+month+"-"+day+" "+time);
        }
        if(flag){
            Date date=new Date(Long.valueOf(l));
            ret=format0.format(date);
        }
        return ret;
    }
5. 最后一步没有什么注意的,但对于每一步的线程,都需要先判断该线程待写入的队列中是否有数据,若有,等待写完,这样就不会造成数据累积。另外,对于线程的分配需要合理点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值