写点什么

【Howe 学爬虫】全国统计用区划代码爬取

用户头像
Howe
关注
发布于: 2020 年 05 月 15 日

网络爬虫(又称为网页蜘蛛,网络机器人,在 FOAF 社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。


如何开始

做一件事情,难得不是做什么,难得是怎么做,怎么开始。良好的开端成功的一半,下面让我们一起头脑风暴一下,这件事情应该怎么做。



总共有 31 个省,每个省有很多市,每个市有很多县,每个县有很多乡镇,每个乡镇有很多居委会。既然这样,第一反应肯定是循环,遍历。那么接下来,应该怎么循环。先将这个页面的静态页面 get 到,然后将东西保存并获取链接,再然后循环爬取。


想到了就干,但是写完之后碰到了很多问题。

  1. 文件怎么保存,总共有 6 级嵌套。

  2. 循环结束才可以保存文件,如果循环中抛异常会导致文件损坏。


关于这两个问题,我的解决方法是:

  1. 文件夹下保存一个省汇总的 xls,将省的信息保存下来,包括名称、代号、链接。然后创建每个省的文件夹,在这个文件夹中保存一个市汇总的 xls,保存市的名称、代号、链接。以此类推。

  2. 先将省列表爬完,期间不去爬市的信息,得到省的链接。然后再用保存下来的链接去获取每个省的市的信息,期间不去管下一级的信息。一次类推,很大程度上杜绝了文件损坏的发生。


代码分析

在详细研究了各个层次的请求参数之后,其中市县乡的爬取可以提取一个方法。我将请求提取了一个方法。


Get 请求方法


代码:

public String GetMethod(URL url, String encode) {    BufferedReader in = null;    String ret = null;    HttpURLConnection conn = null;    try {        conn = (HttpURLConnection) url.openConnection();		        if (conn != null) {            conn.setDoOutput(true);            conn.setDoInput(true);            conn.setRequestMethod("GET");            conn.connect();        }        in = new BufferedReader(new InputStreamReader(conn.getInputStream(), encode));        String line = null;        while ((line = in.readLine()) != null) {            ret += line;        }        return ret;    } catch (Exception e) {        e.printStackTrace();        return "Error :" + e.getMessage();    } finally {        try {            if (in != null) {                in.close();            }            conn.disconnect();        } catch (final IOException ex) {            ex.printStackTrace();        }    }}
复制代码

这个方法很简单的 get 请求。但是细心的同学可能注意到了,我给这个方法传了一个 String encode的参数。这就是我遇到的第一个坑,因为网页编码格式是 GB2312 ,而 JAVA 默认格式是 UTF-8,所以没有传这个参数的时候默认获取 UTF-8 的编码信息,存下来之后发现全部汉字变成了乱码。


获取省份信息

代码:

private static String getProvince() {    WebUtils webUtils = new WebUtils();    WritableWorkbook book = null;    try {        File file = new File(parentPath + File.separator + "汇总.xls");        if (!file.getParentFile().exists()) {            file.getParentFile().mkdirs();        }        book = Workbook.createWorkbook(file);        WritableSheet sheet = book.createSheet("省份", 0);        WritableFont font = new WritableFont(WritableFont.ARIAL, 12);        WritableCellFormat cellFormat = new WritableCellFormat(font);        sheet.setColumnView(0, 30);        sheet.setColumnView(1, 10);        sheet.setColumnView(2, 90);        sheet.addCell(new Label(0, 0, "名称", cellFormat));        sheet.addCell(new Label(1, 0, "代码", cellFormat));        sheet.addCell(new Label(2, 0, "链接", cellFormat));        String ret = webUtils.GetMethod(new URL(baseUrl + "index.html"), "GB2312");        Document doc = Jsoup.parse(ret);        Elements provincetrEles = doc.getElementsByClass("provincetr");        int i = 1;        for (Element element : provincetrEles) {            Elements tdEles = element.getElementsByTag("td");            for (Element td : tdEles) {                String href = td.select("a").attr("href");                String name = td.select("a").text().trim();                String areaCode = "";                try {                    areaCode = href.substring(0, href.indexOf("."));                } catch (Exception e) {                    // TODO: handle exception                }                if (href != "" && areaCode != null && name != "") {                    sheet.addCell(new Label(0, i, name, cellFormat));                    sheet.addCell(new Label(1, i, areaCode, cellFormat));                    sheet.addCell(new Label(2, i, baseUrl + href, cellFormat));                    System.out.println("-[Province]  " + i + "  SAVED:" + name + " - " + href);                    i++;                }            }        }        return file.getAbsolutePath();    } catch (MalformedURLException e) {        e.printStackTrace();    } catch (IOException e) {        e.printStackTrace();    } catch (RowsExceededException e) {        e.printStackTrace();    } catch (WriteException e) {        e.printStackTrace();    } finally {        if (book != null) {            try {                book.write();                book.close();                System.out.println("Complete".toUpperCase());            } catch (WriteException e) {                e.printStackTrace();            } catch (IOException e) {                e.printStackTrace();            }        }    }    return null;}
复制代码

这里面包含了自己写的 GetMethod 方法,用 jxl 来操作 xls 表格,用 Jsoup 来解析 get 下来的静态页面。值得注意的是,一定要记得释放资源。


获取市县乡


这三个获取大同小异,可以提取一个公共方法。

公共方法

private static void creatXls(String path, String name, String url, String className) {    WritableWorkbook book = null;    try {        File file = new File(path + File.separator + "汇总.xls");        if (!file.getParentFile().exists()) {            file.getParentFile().mkdirs();        }        book = Workbook.createWorkbook(file);        WritableSheet sheet = book.createSheet(name, 0);        WritableFont font = new WritableFont(WritableFont.ARIAL, 12);        WritableCellFormat cellFormat = new WritableCellFormat(font);        sheet.setColumnView(0, 30);        sheet.setColumnView(1, 30);        sheet.setColumnView(2, 90);        sheet.addCell(new Label(0, 0, "名称", cellFormat));        sheet.addCell(new Label(1, 0, "统计用区划代码", cellFormat));        sheet.addCell(new Label(2, 0, "链接", cellFormat));        WebUtils webUtils = new WebUtils();        String ret = webUtils.GetMethod(new URL(url), "GB2312");        Document doc = Jsoup.parse(ret);        Elements trElements = doc.getElementsByClass(className);        int i = 1;        for (Element tr : trElements) {            Elements tdElements = tr.getElementsByTag("td");            if (tdElements.size() != 0) {                String href = tdElements.first().select("a").attr("href");                String areaCode = tdElements.first().select("a").text().trim();                String cityName = tdElements.last().select("a").text().trim();                if (href != "" && areaCode != null && cityName != "") {                    sheet.addCell(new Label(0, i, cityName, cellFormat));                    sheet.addCell(new Label(1, i, areaCode, cellFormat));                    sheet.addCell(new Label(2, i, url.substring(0, url.lastIndexOf("/") + 1) + href, cellFormat));                    System.out.println("  --[" + className + "]  " + i + "  SAVED:" + cityName + " - " + areaCode                            + " - " + href);                    i++;                }            }        }    } catch (Exception e) {        e.printStackTrace();    } finally {        if (book != null) {            try {                book.write();                book.close();            } catch (WriteException e) {                // TODO Auto-generated catch block                e.printStackTrace();            } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }    }}
复制代码

没什么特别的,跟获取省的差不多。其中传入参数参见下表

  • String path 上级爬取之后创建的新文件夹路径

  • String name 这个传不传无所谓,是即将爬的单位名

  • String url 即将爬取的链接

  • String className 静态页面中的 class 名


获取市

private static String getCity(File file, String code) throws Exception {    if (!file.exists()) {        throw new Exception("【Error】文件不存在:" + file.getName());    }    try {        String[] nameList = readXLS(file, 0, 1);        String[] codeList = readXLS(file, 1, 1);        String[] urlList = readXLS(file, 2, 1);        if (!file.getParentFile().exists()) {            file.getParentFile().mkdirs();        }        if (code != null) {            // TODO 单独获取            int index = Arrays.binarySearch(codeList, code);            if (index < 0) {                throw new Exception("省份代码不存在");            }            String path = file.getParent() + File.separator + nameList[index];            creatXls(path, nameList[index], urlList[index], "citytr");            return path + File.separator + "汇总.xls";        } else {            String ret = "";            for (int i = 0; i < codeList.length; i++) {                if (nameList[i] != null && nameList[i] != "" && nameList[i] != "null") {                    String path = file.getParent() + File.separator + nameList[i];                    creatXls(path, nameList[i], urlList[i], "citytr");                    ret = ret + path + File.separator + "汇总.xls" + SPLITE_SYMBOL;                }            }            return ret;        }    } catch (Exception e) {        e.printStackTrace();    }    return null;}
复制代码


获取县

private static String getCountry(File file, String code) throws Exception {    if (!file.exists()) {        throw new Exception("【Error】文件不存在:" + file.getAbsolutePath());    }    try {        String[] nameList = readXLS(file, 0, 1);        String[] codeList = readXLS(file, 1, 1);        String[] urlList = readXLS(file, 2, 1);        if (!file.getParentFile().exists()) {            file.getParentFile().mkdirs();        }        if (code != null) {            // TODO 单独获取            int index = Arrays.binarySearch(codeList, code);            if (index < 0) {                throw new Exception("市代码不存在");            }            String path = file.getParent() + File.separator + nameList[index];            creatXls(path, nameList[index], urlList[index], "countytr");            return path + File.separator + "汇总.xls";        } else {            String ret = "";            for (int i = 0; i < codeList.length; i++) {                if (nameList[i] != null && nameList[i] != "" && nameList[i] != "null") {                    String path = file.getParent() + File.separator + nameList[i];                    creatXls(path, nameList[i], urlList[i], "countytr");                    ret = ret + path + File.separator + "汇总.xls" + SPLITE_SYMBOL;                }            }            return ret;        }    } catch (Exception e) {        e.printStackTrace();    }    return null;}
复制代码


获取乡镇

private static String getTown(File file, String code) throws Exception {    if (!file.exists()) {        throw new Exception("【Error】文件不存在:" + file.getName());    }    if (!file.getParentFile().exists()) {        file.getParentFile().mkdirs();    }    try {        String[] nameList = readXLS(file, 0, 1);        String[] codeList = readXLS(file, 1, 1);        String[] urlList = readXLS(file, 2, 1);        if (code != null) {            // TODO 单独获取            int index = Arrays.binarySearch(codeList, code);            if (index < 0) {                throw new Exception("县代码不存在");            }            String path = file.getParent() + File.separator + nameList[index];            creatXls(path, nameList[index], urlList[index], "towntr");            return path + File.separator + "汇总.xls";        } else {            String ret = "";            for (int i = 0; i < codeList.length; i++) {                if (nameList[i] != null && nameList[i] != "" && nameList[i] != "null") {                    String path = file.getParent() + File.separator + nameList[i];                    creatXls(path, nameList[i], urlList[i], "towntr");                    ret = ret + path + File.separator + "汇总.xls" + SPLITE_SYMBOL;                }            }            return ret;        }    } catch (Exception e) {        e.printStackTrace();    }    return null;}
复制代码

获取居委会

居委会内容比其他的多点,其他的大差不差,但是还是不能跟上面的提取公共方法,起码我不会。但是为了让单独获取和批量获取复用,我还是提取了一个方法。

公共方法

private static void createVillageXLS(File file, String url) {    WritableWorkbook book = null;    try {        String ret = null;        book = Workbook.createWorkbook(file);        WritableSheet sheet = book.createSheet(file.getName().replace(".xls", ""), 0);        WebUtils webUtils = new WebUtils();        ret = webUtils.GetMethod(new URL(url), "GB2312");        WritableFont font = new WritableFont(WritableFont.ARIAL, 12);        WritableCellFormat cellFormat = new WritableCellFormat(font);        sheet.setColumnView(0, 30);        sheet.setColumnView(1, 30);        sheet.setColumnView(2, 30);        sheet.addCell(new Label(0, 0, "名称", cellFormat));        sheet.addCell(new Label(1, 0, "统计用区划代码", cellFormat));        sheet.addCell(new Label(2, 0, "城乡分类代码", cellFormat));        Document doc = Jsoup.parse(ret);        Elements trElements = doc.getElementsByClass("villagetr");        int i = 1;        for (Element tr : trElements) {            Elements tdElements = tr.getElementsByTag("td");            if (tdElements.size() != 0) {                String[] strs = tdElements.text().split(" ");                String areaCode = strs[0];                String classifiCode = strs[1];                String villageName = strs[2];                sheet.addCell(new Label(0, i, villageName, cellFormat));                sheet.addCell(new Label(1, i, areaCode, cellFormat));                sheet.addCell(new Label(2, i, classifiCode, cellFormat));                System.out.println(                        " --[Village] " + i + " SAVED:" + villageName + " - " + areaCode + " - " + classifiCode);                i++;            }        }    } catch (Exception e) {        e.printStackTrace();    } finally {        if (book != null) {            try {                book.write();                book.close();            } catch (WriteException e) {                // TODO Auto-generated catch block                e.printStackTrace();            } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }    }}
复制代码


获取居委会

private static void getVillage(File file, String code) throws Exception {    if (!file.exists()) {        throw new Exception("【Error】文件不存在:" + file.getName());    }    try {        String[] nameList = readXLS(file, 0, 1);        String[] codeList = readXLS(file, 1, 1);        String[] urlList = readXLS(file, 2, 1);        String path = null;        if (code != null) {            int index = Arrays.binarySearch(codeList, code);            if (index < 0) {                throw new Exception("县代码不存在");            }            path = file.getParent() + File.separator + nameList[index];            file = new File(path, "村委会汇总.xls");            if (!file.getParentFile().exists()) {                file.getParentFile().mkdirs();            }            createVillageXLS(file, urlList[index]);        } else {            for (int i = 0; i < codeList.length; i++) {                if (nameList[i] != null && nameList[i] != "" && nameList[i] != "null") {                    path = file.getParent() + File.separator + nameList[i];                    file = new File(path, "村委会汇总.xls");                    if (!file.getParentFile().exists()) {                        file.getParentFile().mkdirs();                    }                    createVillageXLS(file, urlList[i]);                }            }        }        Thread.sleep(DELAY_TIME);    } catch (Exception e) {        e.printStackTrace();    }}
复制代码


其他方法

静态 changliang


    private static String baseUrl = "http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2019/";    private static final String parentPath = System.getProperty("user.dir") + File.separator + "NBS";    private static final int DELAY_TIME = 100;
复制代码


程序入口

public static void run() {    Scanner scanner = new Scanner(System.in);    System.out.println("欢迎使用");    String code = null;    while (true) {        System.out.println("请选择爬取方式:");        System.out.println("手动选择爬取  --  1");        System.out.println("自动全部爬取  --  2");        System.out.println("请输入编码:(只输入数字)");        code = scanner.nextLine().trim();        if (code.matches("\\d")) {            if (code.equals("1") || code.equals("2")) {                break;            } else {                System.out.println("只能输入1或者2,请重新输入");                continue;            }        } else {            System.out.println("输入错误,请重新输入");        }    }    chooseType(code, scanner);    System.out.println("获取完毕,按Enter关闭此软件");    scanner.nextLine();    scanner.close();}
复制代码

方式选择

private static void chooseType(String i, Scanner scanner) {    switch (i) {        case "1":            try {                // System.out.println("请按 Enter 继续...");                // scanner.nextLine();                // 获取省                String provinceSum = getProvince();                if (provinceSum == null || provinceSum == "") {                    System.out.println("加载失败");                    return;                }                // 获取市                File file = new File(provinceSum);                String code = topLine(file, scanner);                String citySum = getCity(file, code);                if (citySum == null || citySum == "") {                    System.out.println("加载失败");                    return;                }                // 获取县                file = new File(citySum);                code = topLine(file, scanner);                String countrySum = getCountry(file, code);                if (countrySum == null || countrySum == "") {                    System.out.println("加载失败");                    return;                }                // 获取乡镇                file = new File(countrySum);                code = topLine(file, scanner);                String townSum = getTown(file, code);                if (townSum == null || townSum == "") {                    System.out.println("加载失败");                    return;                }                // 获取村委会                file = new File(townSum);                code = topLine(file, scanner);                getVillage(file, code);            } catch (Exception e) {                e.printStackTrace();            }            break;        case "2":            try {                for (int j = 0; j < 3; j++) {                    if (j > 0) {                        System.out.println("请再次确认" + (3 - j) + "遍");                    }                    System.out.println("****************************");                    System.out.println("***********请注意************");                    System.out.println("*******将会全部批量爬取*******");                    System.out.println("********会消耗大量时间********");                    System.out.println("****爬取过程中会有可能封ip*****");                    System.out.println("******所以有可能中途失败*******");                    System.out.println("*****如果中途停止,请等待******");                    System.out.println("****************************");                    System.out.println("*****如果你已了解相关危险******");                    System.out.println("********请按Enter继续********");                    System.out.println("*****************************");                    scanner.nextLine();                }                // 获取省                String provinceSum = getProvince();                if (provinceSum == null || provinceSum == "") {                    System.out.println("加载失败");                    return;                }                File file = new File(provinceSum);                String[] citySums = getCity(file, null).split(SPLITE_SYMBOL);                for (String city : citySums) {                    System.out.println("************CITY***************");                    file = new File(city);                    String[] countrySums = getCountry(file, null).split(SPLITE_SYMBOL);                    for (String country : countrySums) {                        System.out.println("************COUNTRY***************");                        file = new File(country);                        String[] townSums = getTown(file, null).split(SPLITE_SYMBOL);                        for (String town : townSums) {                            System.out.println("************TOWN***************");                            file = new File(town);                            getVillage(file, null);                        }                    }                }            } catch (Exception e) {                e.printStackTrace();            }            break;    }}
复制代码

选择头

private static String topLine(File file, Scanner scanner) throws Exception {    String[] nameList = readXLS(file, 0, 1);    String[] codeList = readXLS(file, 1, 1);    String code = null;    while (true) {        for (int i = 0; i < codeList.length; i++) {            System.out.println(nameList[i] + "  --  " + i);        }        System.out.println("请输入编码:(只输入数字)");        code = scanner.nextLine();        if (code.trim().matches("\\d+")) {            return codeList[Integer.parseInt(code)];        } else {            System.out.println("输入错误,请重新输入:");        }    }}
复制代码


读取 xls 文件

private static String[] readXLS(File file, int lieStart, int hangStart) throws Exception {    String[] result;    try {        FileInputStream fis = new FileInputStream(file);        Workbook wb = Workbook.getWorkbook(fis);        Sheet sheet = wb.getSheet(0);        int hangEnd = sheet.getRows();        result = new String[hangEnd - hangStart + 1];        for (int i = hangStart; i < hangEnd; i++) {            result[i] = sheet.getCell(lieStart, i).getContents();        }        fis.close();        wb.close();        return result;    } catch (Exception e) {        e.printStackTrace();        throw new Exception("【异常】" + e.getMessage());    }}
复制代码


如何结束


本来到这里已经结束了,但是我这个人呢,就是想搞一些有意思的。如果我想在一台没装 JDK 的 windows 电脑上运行,可不可行?经过度娘畅游之后,还是被我发现了一些方法。这里只是顺带提一下,有机会以后再讲,或者各位看官也可以自己去找一下。下面我粗略的说一下方法:


这些代码我是用 Vscode 写的(别问为什么不用那么多的 IDEA,因为我主力是安卓并且不喜欢装软件),Maven 项目。但是呢,我又不会用这个导出为可执行 jar 文件。所以搬出了老大哥 Eclipse,用 Eclipse 导出为可执行 Jar 文件。


但是如果电脑没有装 JDK,那不是费了吗。jar 文件执行不了。所有我又找到了一个软件 exe4j 可以将 Jar 文件转为 exe 文件。这样就完美了,转 exe 的时候将 JRE 放进去。这样就可以满足要求了。


------------


废话时间

这玩意儿虽说简单,我再下班时间写,还写了两天。学艺不精啊,还需要多磨练。

加油吧,少年


------------


需要源码可以私信联系我


发布于: 2020 年 05 月 15 日阅读数: 46
用户头像

Howe

关注

人生苦短,保持前行 2020.05.02 加入

狗币程序员一枚

评论

发布
暂无评论
【Howe学爬虫】全国统计用区划代码爬取