后台数据同步思路

项目中有一个需求,需要与民政部的数据实时同步。

思路:民政部官网的数据不会直接告诉我们,需要我们去官网查看,每个月民政部会更新两条href链接,是县级一下或者县级以上的两条数据,人工手动定期拷贝略显的low了,使用爬虫来获取网页数据,再将数据经过筛选存入数据库,开启一起定时任务,定时获取网页中最新的两条数据;

实现:所需要的数据,民政部网址是 http://www.mca.gov.cn/article/sj/tjbz/a/2017/


利用爬虫框架 webmagic,抓取这个链接下的数据,只需要抓取a标签下的href内容,这样就能得到我们想存入数据库中的页面内容


利用webmagic框架中有的一个方法“xpath”,进行一个xpath的模糊查询语句进行抓取出指定内容,这个模糊语句表示抓取这个数据中title包含县一下的数据,用String返回一条就可以,这肯定是最新的一条

if (page.getUrl().toString().matches(".*2017/")){
    String link = page.getHtml().xpath("//a[@class='artitlelist'and contains(@title,'县以下') ").links().get();
    page.addTargetRequest(link);
}

这就获取页面中href标签里的内容,但是有些页面的href链接不完整,就要自己手动将抓取的链接加上域名补全,这个可以做个判断实现的;会发现在最外层有一个if判断,这个判断的意思是如果抓取的链接中满足某个条件就返回true,在执行这个条件要做的事情,而后用webmagic中的addTargetRequest方法,将抓取到的链接进行抓取,也就是二次抓取

后来发现一个问题,这个href抓取的链接是shtml格式的,不是传统的html,且点进去这个shtml之后,页面的URL会有变化,这下怎么办呢?经过多番观察测试,发现这个跳转是在shtml中的js里中的事情,在页面中看不到,但是将shtml全部抓取过来之后能看到js里的内容,有个window.location.href,跟在=号后面的才是真实地址,既然知道这个就好办了,照旧第一次抓取循环两次得到shtml链接,将链接放入二次抓取,用选择器将script中的内容提取出来经过筛选组成

else if (page.getUrl().toString().matches(".*shtml")){
    String url = "http://www.mca.gov.cn/" + page.getHtml().css("body > script").get().replaceAll("<[^>]+>|window.location.href=|\"|;|\n","").trim();
    System.out.println(url);
    page.addTargetRequest(url);
}


webmagic框架可以用$方法,css方法,xpath方法,抓取想要的内容,而后用字符串替换正则,得到真实地址拼接,这个else if跟上一个一样,判断哪个URL抓取进来的数据,而做哪些事,之前是循环两次抓取两条,这里会进来两次,跟循环一样,所以这里用get方法获取单条即可,在调用addTargetRequest方法,将拼接的链接进行第三次抓取

else if (page.getUrl().toString().matches(".*html")){
    List<String> data = page.getHtml().xpath("table/tbody/tr/tidyText()").all();

再做第三次判断,这次进来的是不是html,是的话,正式开始抓取表单中的县以下数据然后存放到List里

县级以下的内容有多列,而且还有不要的内容,但是抓取的时候他是不会管你这些的,他只负责抓取,那数据处理还是要自己来


 String[] strArr = data.toString().substring(data.toString().indexOf("序号"), data.toString().lastIndexOf("注")).split(",\\s*|\t|\r|\n");
        List strArrList = new ArrayList();
        for (int i = 0; i < strArr.length; i++) {
            //放入list中
            strArrList.add(strArr[i]);
        }
        //放入list中方便删除不要的内容下标
        strArrList.remove(0);
        List<List> lists = new ArrayList<>();
        //两层循环,根据Html表单下标获取数据
        for (int i = 6; i < strArrList.size() + 6; i++) {
            List<String> list = new ArrayList<>();
            for (int j = 1; j < 8; j++) {
                String rowSpan = page.getHtml().xpath("table/tbody/tr[" + i + "]/td[" + j + "]").get();
                if(rowSpan != null){
                    list.add(rowSpan);
                }
            }
            lists.add(list);
        }
        List dataList = new ArrayList();
        //根据上一次循环数据获取的List进行做二层循环取出表单列的数据
        for (int i = 0; i < lists.size(); i++) {
            List newData = new ArrayList();
                for (int j = 1; j < 7; j++) {
                    String htmlText = lists.get(i).get(j).toString();
                    //判断是否包含Html rowspan属性
                    if (htmlText != null && htmlText.contains("row")) {
                        //将rowspan中的文本取出
                        String text = htmlText.replaceAll("<[^>]+>", "");
                        //将rowspan中的数字取出
                        Integer num = getSpanNumber("rowspan", htmlText);
                        //根据取出的数字循环
                        if (j == 6) {
                            for (int k = 1; k < num; k++) {
                                //根据rowspan中的文本内容对下一级补全
                                lists.get(i + k).add(text);
                            }
                        } else {
                            for (int k = 1; k < num; k++) {
                                //根据rowspan中的文本内容对下一级补全
                                lists.get(i + k).add(j, text);
                            }
                        }
                    }
                    newData.add(htmlText);
                }
                dataList.add(newData);

        }
        Map<String, Object> map = new HashMap<>();
        //取出整理过的数据
        try {
            for (int i = 0; i < lists.size(); i++) {
                String newData = dataList.get(i).toString().replaceAll("<[^>]+>|\\[|\\]", "");
                String[] strings = newData.split(",");
                map.put("原区划代码", strings[0]);
                map.put("原名称", strings[1]);
                map.put("变更原因", strings[2]);
                map.put("现区划代码", strings[3]);
                map.put("现名称", strings[4]);
                map.put("批准文件", strings[5]);
                System.out.println(map);

            }
        } catch (IndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        }
    }
}

代码看着复杂,实际上理解之后不难看懂,将List里面的数据返回字符串类型,经过字符串的subString方法进行分割,再利用String的indexOf以及lastIndexOf获取输入字符串的坐标数字,好交给subString精确分割,而后利用字符串的split切割方法,用指定正则,去除逗号换行等,返回到数组中,这样就达到了将抓取到的数据分行的效果,再将数组内容取出存放到list中,利用ArrayList的remove方法删除序号这一行,只要表单内容,之前说过数据不完整,要自己手动补全,想了好几天,唯一补全的办法只有一个,那就是利用html标签属性的下标,用两层循环第几行第几列的数据取出,不经过去除html标签筛选,存放到list中,再放入大的list里,对后续进行操作,根据这个大的list大小进行循环取出,取出后再做一层循环取出列的内容,判断这个表单中是否有rowspan这个属性,用到了contains这个方法,有的话进行筛选,筛选出这个rowspan的数字进行循环,下一列补个null的方式将下一个进行存入list中,再存入大的list中,这样小的list就是每一行的内容,大的就是包含了每个行的内容,再将大的list循环取出进行筛选就可以了。

完整代码:

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class reptileTest implements PageProcessor {
    private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);

    @Override
    public void process(Page page) {
        //第一次抓取
        if (page.getUrl().toString().matches(".*2017/")){
            String link = page.getHtml().xpath("//a[@class='artitlelist'and contains(@title,'县以下') and contains(@title,'11') ").links().get();
            if (link.contains("www")){
                page.addTargetRequest(link);
            }else {
                String newLink = "http://www.mca.gov.cn" + link;
                page.addTargetRequest(newLink);
            }

        }
        //第二次抓取
        else if (page.getUrl().toString().matches(".*shtml")){
            String url =  page.getHtml().css("body > script").get().replaceAll("<[^>]+>|window.location.href=|\"|;|\n","").trim();
            if (url.contains("www")){
                page.addTargetRequest(url);
            }else {
                String newUrl = "http://www.mca.gov.cn" + url;
                page.addTargetRequest(newUrl);
            }
        }
        //第三次正式抓取表单内容
        else if (page.getUrl().toString().matches(".*html")){
            //获取表单内容
            List<String> data = page.getHtml().xpath("table/tbody/tr/tidyText()").all();
            //对获取的表单内容进行subString取出不要的内容
            String[] strArr = data.toString().substring(data.toString().indexOf("序号"), data.toString().lastIndexOf("注")).split(",\\s*|\t|\r|\n");
            List strArrList = new ArrayList();
            for (int i = 0; i < strArr.length; i++) {
                //放入list中
                strArrList.add(strArr[i]);
            }
            //放入list中方便删除不要的内容下标
            strArrList.remove(0);
            List<List> lists = new ArrayList<>();
            //两层循环,根据Html表单下标获取数据
            for (int i = 6; i < strArrList.size() + 6; i++) {
                List<String> list = new ArrayList<>();
                for (int j = 1; j < 8; j++) {
                    String rowSpan = page.getHtml().xpath("table/tbody/tr[" + i + "]/td[" + j + "]").get();
                    if(rowSpan != null){
                        list.add(rowSpan);
                    }
                }
                lists.add(list);
            }
            List dataList = new ArrayList();
            //根据上一次循环数据获取的List进行做二层循环取出表单列的数据
            for (int i = 0; i < lists.size(); i++) {
                List newData = new ArrayList();
                    for (int j = 1; j < 7; j++) {
                        String htmlText = lists.get(i).get(j).toString();
                        //判断是否包含Html rowspan属性
                        if (htmlText != null && htmlText.contains("row")) {
                            //将rowspan中的文本取出
                            String text = htmlText.replaceAll("<[^>]+>", "");
                            //将rowspan中的数字取出
                            Integer num = getSpanNumber("rowspan", htmlText);
                            //根据取出的数字循环
                            if (j == 6) {
                                for (int k = 1; k < num; k++) {
                                    //根据rowspan中的文本内容对下一级补全
                                    lists.get(i + k).add(text);
                                }
                            } else {
                                for (int k = 1; k < num; k++) {
                                    //根据rowspan中的文本内容对下一级补全
                                    lists.get(i + k).add(j, text);
                                }
                            }
                        }
                        newData.add(htmlText);
                    }
                    dataList.add(newData);

            }
            Map<String, Object> map = new HashMap<>();
            //取出整理过的数据
            try {
                for (int i = 0; i < lists.size(); i++) {
                    String newData = dataList.get(i).toString().replaceAll("<[^>]+>|\\[|\\]", "");
                    String[] strings = newData.split(",");
                    map.put("原区划代码", strings[0]);
                    map.put("原名称", strings[1]);
                    map.put("变更原因", strings[2]);
                    map.put("现区划代码", strings[3]);
                    map.put("现名称", strings[4]);
                    map.put("批准文件", strings[5]);
                    System.out.println(map);

                }
            } catch (IndexOutOfBoundsException e) {
                System.out.println(e.getMessage());
            }
        }
    }

    @Override
    public Site getSite() {
        return site;
    }

    /**
     * 自定义方法
     * @param span 传入rowspan或者colspan
     * @param htmlText 传入html代码
     * @return 分割指定内容返回
     */
    public Integer getSpanNumber(String span, String htmlText){
        String[] split = htmlText.split(span + "=\"");
        split = split[1].split("\"");
        return Integer.parseInt(split[0]);
    }

    public static void main(String[] args) {
        Spider.create(new reptileTest()).addUrl("http://www.mca.gov.cn/article/sj/tjbz/a/2017/").thread(5).start();
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值