美文网首页
WebMagic框架爬取区划代码

WebMagic框架爬取区划代码

作者: pilisiyang | 来源:发表于2019-11-18 14:05 被阅读0次

    背景

    项目涉及到行政区划,需要全国对应的行政区划代码,从省级到乡级,行政区划代码虽然不会变,但是每年也会有撤县设市或者改县为区这样的情况,为了保持这份数据的即时性,就需要每年跟随国家统计局政务网站更新。

    2017年统计用区划代码和城乡划分代码(截止2017年10月31日)

    WebMagic

    java的爬虫框架有很多,WebMagic 使用起来比较简单,结构化模块也很容易扩展。

    webmagic文档

    WebMagic爬虫主要分为三个模块,PageProcessor,Selectable,Pipeline。

    PageProcessor

    PageProcessor 主要用来定义爬虫的配置、页面元素的抽取和链接的发现。
    官网给的列子如下:

    public class GithubRepoPageProcessor implements PageProcessor {
    
        // 部分一:抓取网站的相关配置,包括编码、抓取间隔、重试次数等
        private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);
    
        @Override
        // process是定制爬虫逻辑的核心接口,在这里编写抽取逻辑
        public void process(Page page) {
            // 部分二:定义如何抽取页面信息,并保存下来
            page.putField("author", page.getUrl().regex("https://github\\.com/(\\w+)/.*").toString());
            page.putField("name", page.getHtml().xpath("//h1[@class='entry-title public']/strong/a/text()").toString());
            if (page.getResultItems().get("name") == null) {
                //skip this page
                page.setSkip(true);
            }
            page.putField("readme", page.getHtml().xpath("//div[@id='readme']/tidyText()"));
    
            // 部分三:从页面发现后续的url地址来抓取
            page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/[\\w\\-]+/[\\w\\-]+)").all());
        }
    
        @Override
        public Site getSite() {
            return site;
        }
    
        public static void main(String[] args) {
    
            Spider.create(new GithubRepoPageProcessor())
                    //从"https://github.com/code4craft"开始抓
                    .addUrl("https://github.com/code4craft")
                    //开启5个线程抓取
                    .thread(5)
                    //启动爬虫
                    .run();
        }
    }
    
    

    其中 Site 主要用来定义爬虫的配置,包括编码、抓取间隔、超时时间、重试次数等,也包括一些模拟的参数,例如User Agent、cookie,以及代理的设置。

    Selectable

    Selectable 是 WebMagic 抽取元素的链式API,简单来说就是根据特定的方法抽取html页面的信息。

    Pipeline

    这部分是用来保存爬取结果的组件,WebMagic 内置了一部分,ConsolePipeline(输出到控制台),JsonFilePipeline(保存成json文件),FilePipeline(保存成文件),像我们项目中,如果需要保存到 MySQL 中,就需要自定义 Pipeline 来实现了。

    实现

    image.png

    可以看到html页面中特点很明显,省级被 class 为 provincetr 的 tr 包围,每一个 td 的 a 标签内容就是省市名,href 属性就是省市 code ,子连接点进去就是下一级行政规划。

    import us.codecraft.webmagic.Page;
    import us.codecraft.webmagic.Site;
    import us.codecraft.webmagic.processor.PageProcessor;
    import us.codecraft.webmagic.selector.Selectable;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    import static com.gsoft.area.spider.SpiderCommon.SPIDER_URL;
    import static java.util.regex.Pattern.compile;
    
    /**
     * 基于webmagic 爬取国家统计局行政规划数据
     *
     * @author plsy
     */
    public class AreaSpider implements PageProcessor {
    
        /**
         * 抓取网站的相关配置,包括编码、抓取间隔、重试次数等
         */
        private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);
    
        /**
         * 爬取数据的年度标识,用于区分批次
         */
        private String year;
    
        /**
         * 爬取从省级到包含的那一级数据
         * 例如: contain = "city,county";  即爬取省级到县级数据
         */
        private String contain;
    
        public AreaSpider(String year, String contain) {
            this.year = year;
            this.contain = contain;
        }
    
        @Override
        public void process(Page page) {
            page.setCharset("UTF-8");
            String url = page.getUrl().toString();
            String[] replaceUrl = url.replace(SPIDER_URL, "").replace(".html", "").split("/");
            if (url.matches(SPIDER_URL + "index.html")) {
                //抓取省级行政区划
                List<Map<String, Object>> province = page.getHtml().xpath("//tr[@class='provincetr']/td").nodes().stream()
                        .filter(selectable -> selectable.xpath("//a/text()") != null)
                        .filter(selectable -> selectable.links().all().size() > 0)
                        .map(selectable -> {
                            String name = selectable.xpath("//a/text()").toString();
                            String newUrl = selectable.links().all().get(0);
                            if (contain.contains("city")) {
                                page.addTargetRequest(newUrl);
                            }
                            String replace = newUrl.replace(SPIDER_URL, "").replace(".html", "");
                            String areaCode = replace + "0000";
                            HashMap<String, Object> map = new HashMap<>();
                            map.put("C_NAME", name);
                            map.put("C_CODE", areaCode);
                            map.put("C_LEVEL", 1);
                            map.put("C_CASCADE", "/");
                            map.put("C_PARENT_CODE", 0L);
                            map.put("C_YEAR", year);
                            return map;
                        }).collect(Collectors.toList());
    
                page.putField("area", province);
            }
    
            //抓取市级行政单位
            if (replaceUrl.length == 1 && !replaceUrl[0].equals("index") && contain.contains("city")) {
                List<Map<String, Object>> city = new ArrayList<Map<String, Object>>();
                List<Selectable> cityNodes = page.getHtml().xpath("//tr[@class='citytr']/td").nodes();
                cityNodes.stream().forEach(node -> {
                    String name = node.xpath("//a/text()").toString();
                    if (!compile("[0-9]*").matcher(name).matches()) {
                        String newUrl = node.links().all().get(0);
                        if (contain.contains("county")) {
                            page.addTargetRequest(newUrl);
                        }
                        String replace = newUrl.replace(SPIDER_URL, "").replace(".html", "");
                        String[] split = replace.split("/");
                        String parentId = split[0] + "0000";
                        String areaCode = split[split.length - 1] + "00";
                        HashMap<String, Object> map = new HashMap<>();
                        map.put("C_NAME", name);
                        map.put("C_CODE", areaCode);
                        map.put("C_LEVEL", 2);
                        map.put("C_CASCADE", "/" + parentId + "/" + areaCode);
                        map.put("C_PARENT_CODE", Long.valueOf(parentId));
                        map.put("C_YEAR", year);
                        city.add(map);
                    }
                });
                page.putField("area", city);
            }
    
            //抓取县级行政单位
            if (replaceUrl.length == 2 && contain.contains("county")) {
                List<Map<String, Object>> county = new ArrayList<Map<String, Object>>();
                List<Selectable> countyNodes = page.getHtml().xpath("//tr[@class='countytr']/td").nodes();
                for (int i = 0; i < countyNodes.size(); i += 2) {
                    List<String> code = countyNodes.get(i).xpath("//*/text()").all();
                    List<String> name = countyNodes.get(i + 1).xpath("//*/text()").all();
                    String areaCode = code.get(0);
                    String areaName = name.get(0);
                    if (code.size() > 1) {
                        areaCode = code.get(1);
                        areaName = name.get(1);
                        String newUrl = countyNodes.get(i).links().all().get(0);
                        if (contain.contains("town")) {
                            page.addTargetRequest(newUrl);
                        }
                    }
                    areaCode = areaCode.substring(0, 6);
                    String parentId = areaCode.substring(0, 4) + "00";
                    HashMap<String, Object> map = new HashMap<>();
                    map.put("C_NAME", areaName);
                    map.put("C_CODE", areaCode);
                    map.put("C_LEVEL", 3);
                    map.put("C_CASCADE", "/" + areaCode.substring(0, 2) + "0000/" + parentId + "/" + areaCode);
                    map.put("C_PARENT_CODE", Long.valueOf(parentId));
                    map.put("C_YEAR", year);
                    county.add(map);
                }
                page.putField("area", county);
            }
    
            //抓取镇级行政单位
            if (replaceUrl.length == 3 && contain.contains("town")) {
                List<Map<String, Object>> town = new ArrayList<Map<String, Object>>();
                List<Selectable> countyNodes = page.getHtml().xpath("//tr[@class='towntr']/td").nodes();
                for (int i = 0; i < countyNodes.size(); i += 2) {
                    List<String> code = countyNodes.get(i).xpath("//*/text()").all();
                    List<String> name = countyNodes.get(i + 1).xpath("//*/text()").all();
                    String areaCode = code.get(0);
                    String areaName = name.get(0);
                    if (code.size() > 1) {
                        areaCode = code.get(1);
                        areaName = name.get(1);
                        String newUrl = countyNodes.get(i).links().all().get(0);
                        if (contain.contains("village")) {
                            page.addTargetRequest(newUrl);
                        }
                    }
                    areaCode = areaCode.substring(0, 9);
                    String parentId = areaCode.substring(0, 6);
                    HashMap<String, Object> map = new HashMap<>();
                    map.put("C_NAME", areaName);
                    map.put("C_CODE", areaCode);
                    map.put("C_LEVEL", 4);
                    map.put("C_CASCADE", "/" + areaCode.substring(0, 2) + "0000/" + areaCode.substring(0, 4) + "00/" + parentId + "/" + areaCode);
                    map.put("C_PARENT_CODE", Long.valueOf(parentId));
                    map.put("C_YEAR", year);
                    town.add(map);
                }
                page.putField("area", town);
            }
    
            //抓取乡级行政单位
            if (replaceUrl.length == 4 && contain.contains("village")) {
                List<Map<String, Object>> village = new ArrayList<Map<String, Object>>();
                List<Selectable> countyNodes = page.getHtml().xpath("//tr[@class='villagetr']/td").nodes();
                for (int i = 0; i < countyNodes.size(); i += 3) {
                    String areaCode = countyNodes.get(i).xpath("//*/text()").get();
                    String areaName = countyNodes.get(i + 2).xpath("//*/text()").get();
                    String parentId = areaCode.substring(0, 9);
                    HashMap<String, Object> map = new HashMap<>();
                    map.put("C_NAME", areaName);
                    map.put("C_CODE", areaCode);
                    map.put("C_LEVEL", 4);
                    map.put("C_CASCADE", "/" + areaCode.substring(0, 2) + "0000/" + areaCode.substring(0, 4) + "00/" + areaCode.substring(0, 6) + "/" + parentId + "/" + areaCode);
                    map.put("C_PARENT_CODE", Long.valueOf(parentId));
                    map.put("C_YEAR", year);
                    village.add(map);
                }
                page.putField("area", village);
            }
        }
    
        @Override
        public Site getSite() {
            return site;
        }
    }
    

    通过这样的特征找到每层行政区划,使用 page.putField 保存起来,再使用自定义的 SqlPipeline 将数据保存到 MySQL 中。

    import com.gsoft.cos3.table.service.SingleTableService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import us.codecraft.webmagic.ResultItems;
    import us.codecraft.webmagic.Task;
    import us.codecraft.webmagic.pipeline.Pipeline;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * webmagic 处理爬虫抽取结果
     *
     * @author plsy
     */
    @Component
    public class SqlPipeline implements Pipeline {
    
        @Autowired
        SingleTableService singleTableService;
    
        @Override
        public void process(ResultItems resultItems, Task task) {
            List<Map<String, Object>> area = resultItems.get("area");
            if (area.size() == 0) {
                System.out.println(resultItems.getRequest().getUrl() + " 此页面未爬取数据,请稍后重试!");
            } else {
                area.stream().forEach(stringObjectMap -> {
                    singleTableService.save("cos_sys_administrative_area", stringObjectMap);
                });
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:WebMagic框架爬取区划代码

          本文链接:https://www.haomeiwen.com/subject/zszvictx.html