参考文章:https://blog.csdn.net/weixin_43638187/article/details/110220928。

一、爬取思路分析

爬取思路:首先就是获取每一个省,然后根据每一个省层层往下扒,直到获取完这个省下边的所有市县村,在获取下一个省的数据。

网址:http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/,如下所示:

可见最新的是2021年的,点击进入

1、获取所有省

获取所有省的URL:http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/index.html

页面如下图所示:

查看网页源代码:

此时,北京市的<a>标签下的href属性为11.html

可以看到需要的数据是从一个class=‘provincetr’ 下的 <a> 标签中拿到的,我们主要的任务就是拿到class=‘provincetr’,并遍历获取所有 <td> 下的 <a> 标签 中数据,根据这个 *.html获取这个省下边的所有市信息。

2、获取所有市

获取所有市的URL:http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/11.html,

可以看出获取所有市的数据是在baseUrl(“http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/”) 的基础上添加省获取到的herf=11.html得到的

页面如下所示:

查看网页源代码

此时,市辖区的<a>标签下的href属性为11/1101.html。根据这个11/1101.html获取这个市下边的所有区/县信息。

 可以看到是拿到所有class=‘citytr’ 下的 <td> 标签中拿到的,与省的信息不同,这里市的信息有两个 <a> 标签我们需要的是第二个 <a> 标签里的数据,从href中可以获取code信息

3、获取所有区/县

获取所有区/县的URL:http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/11/1101.html

可以看出获取所有区、县的数据是在baseUrl(“http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/”) 的基础上添加市获取到的herf=11/1101.html得到的

页面如下所示:

查看网页源代码

此时,东城区的<a>标签下的href属性为01/110101.html。根据这个01/110101.htmll获取这个区下边的所有街道办/镇信息。

 区县数据,第一个市辖区,没 <a> 标签,除此之外和市区的数据格式一样,取class=‘countytr’,遍历获取所有的 <td> ,并获取 <td> 下第二个 <a> 中数据,其中href值为01/110101.html。

4、获取街道办/乡镇

获取街道办、镇的URL:http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/11/01/110101.html

 从区、县获取的herf=01/110101.html,对比url前边缺一个省的代码,因此使用省的code(即11)+区县herf(即01/110101.html)

页面如下图所示:

查看网页源代码

此时,东华门街道的<a>标签下的href属性为01/110101001.html。根据这个01/110101001.html获取这个街道下边的所有村信息。

可以看出来和市的数据结构差不多一样,只需要修改获取类的明显就,取class=‘towntr’,遍历获取所有的 <td> ,并获取 <td> 下第二个 <a> 中数据

5、获取社区/村

获取社区/村的URL:http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/11/01/01/110101001.html

从乡、镇获取的herf=01/110101001.html,对比url前边缺一个省的代码+市(市代码为1101)的后两位代码,因此使用省的code(即11)+市的code(即01)+乡镇herf(即01/110101001.html)

页面如下所示:

查看网页源代码

6、注意事项

在获取街道办、乡镇的时候,在herf前边会新增一个省的code,在获取村、居委会的时候,在herf前边会增加一个市的code。

二、项目搭建

1、创建一个springboot工程并添加依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.10.2</version>
        </dependency>
        <dependency>
            <groupId>com.ejlchina</groupId>
            <artifactId>okhttps</artifactId>
            <version>2.4.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>

        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
            <version>1.4.7</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

发送http请求使用的okhttps;解析数据用的jsoup;数据库操作使用的mybatis(数据量比较大,可以使用批插入);commons-lang3 使用了一些其他工具类,如StringUtils,ObjectUtils等;

2、编写启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

3、编写application.yml配置文件

server:
  port: 8080
spring:
  application:
    name: smallTools
  datasource:  #数据库配置
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://127.0.0.1:3306/region?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
mybatis:
  mapper-locations: /mapper/*Mapper.xml

4、创建数据库region和表base-region-detail

脚本如下:

  CREATE TABLE `base_region_detail` (
      `region_code` varchar(255) NOT NULL COMMENT '区域代码',
      `region_name` varchar(255) DEFAULT NULL COMMENT '区域名称',
      `pid` varchar(255) DEFAULT NULL COMMENT '父级区域代码',
      `type` varchar(255) DEFAULT NULL COMMENT '0 省级/1  市级/2  区、县/3  乡镇、街道办/其他 村、社区111 主城区、112 城乡结合区、121 镇中心区、122 镇乡结合区、123 特殊区域、210 乡中心区、220 村庄',
      PRIMARY KEY (`region_code`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

5、创建实体类

@Data
public class BaseRegionDetail {
    /***
     * 区域代码
     */
    private String regionCode;

    /***
     * 区域名称
     */
    private String regionName;

    /***
     * 父级区域代码
     */
    private String pid;

    /***
     * 区域类型
     */
    private String type;
}

6、编写dao接口

@Mapper
public interface BaseRegionDetailDao {

    /***
     * 根据 regionCode 查找
     * @param regionCode 区域CODE
     * @return
     */
    List<BaseRegionDetail> find(String regionCode, String regionName, String pid);

    /***
     * 批量插入
     * @param list 皮插入参数
     */
    void insert(List<BaseRegionDetail> list);

    /***
     * 获取数据库保存的最后一个省
     * @return
     */
    List<String> getAllProvince();

    /***
     * 获取当前省的最后一个市信息
     * @param regionCode
     * @return
     */
    String getLastCityForPro(String regionCode);

}

7、编写mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zwh.dao.BaseRegionDetailDao">
    <!--  namespace指向对应的dao  -->
    <select id="find" resultType="com.zwh.domain.BaseRegionDetail">
        select * from base_region_detail where 1 = 1

        <if test="regionCode != null and regionCode != ''">
            and region_code = #{regionCode,jdbcType=VARCHAR}
        </if>

        <if test="regionName != null and regionName != ''">
            and region_name = #{regionName,jdbcType=VARCHAR}
        </if>

        <if test="pid != null and pid != ''">
            and pid = #{pid,jdbcType=VARCHAR}
        </if>
    </select>

    <insert id="insert" parameterType="List">
        insert into
        base_region_detail
        (
        region_code, region_name, pid, type
        ) values
        <foreach collection="list" item="detail" index="index" separator=",">
            (
            #{detail.regionCode,jdbcType=VARCHAR},
            #{detail.regionName,jdbcType=VARCHAR},
            #{detail.pid,jdbcType=VARCHAR},
            #{detail.type,jdbcType=VARCHAR}
            )
        </foreach>
    </insert>


    <select id="getAllProvince" resultType="string">
        select region_code from base_region_detail where type = "0"
    </select>

    <select id="getLastCityForPro" resultType="string">
        select MAX(region_code) from base_region_detail where pid = #{regionCode,jdbcType=VARCHAR}
    </select>
</mapper>

8、编写配置类SpiderPropertyModel

@Data
@Component
@ConfigurationProperties(prefix = "spider")
public class SpiderPropertyModel {

    /***
     * 爬取年份数据
     */
    private String spiderYear = "2021";

    /***
     * 是否开启动态IP代理
     */
    private Boolean proxyEnable = false;

    /***
     * 设置ip代理后ip.txt地址
     */
    private String ipLocation = "111";

    /***
     * 是否开启 市级睡眠
     */
    private Boolean isCitySleep = true;

    /***
     * 市级睡眠默认时间  s级
     */
    private Integer citySleepTime = 30;

    /***
     * 是否开启 区、县 级睡眠
     */
    private Boolean isCountySleep = false;

    /***
     * 区、县 级睡眠默认时间 s级
     */
    private Integer countySleepTime = 30;

    /***
     * 是否获取乡镇、街道信息
     */
    private Boolean getTowntr = false;

    /***
     * 是否获取村、社区信息
     */
    private Boolean getVillagetr = false;
}

spiderPropertyModel是一个配置类,定义了一些配置信息,如是否开启Ip代理,sleep的时间等

application.yml中新增配置如下:

spider:
  spider-year: 2021 # 爬取年份的数据
  proxy-enable: false  # 是否开启动态代理
  ip-location: 222     #开启的话需要ip地址的存放位置
  is-city-sleep: true #是否开启市级睡眠
  city-sleep-time: 30   #开启市级睡眠 的睡眠时间 s
  is-county-sleep: true #是否开启区 县睡眠
  county-sleep-time: 15  #开启县级睡眠 的睡眠时间 s
  get-villagetr: false # 是否获取村、社区信息
  get-towntr: false # 是否获取乡镇、街道信息

注意:如果只获取省市县,则get-villagetr和get-towntr的值设为false。如果还要获取乡镇/街道、村/社区的信息,则将get-villagetr和get-towntr的值设为true。

9、编写service

import com.ejlchina.okhttps.HTTP;
import com.ejlchina.okhttps.HttpResult;
import com.zwh.config.SpiderPropertyModel;
import com.zwh.dao.BaseRegionDetailDao;
import com.zwh.domain.BaseRegionDetail;
import okhttp3.ConnectionPool;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class BaseRegionDetailService {

    @Autowired
    private SpiderPropertyModel spiderPropertyModel;

    @Autowired
    private BaseRegionDetailDao baseRegionDetailDao;


    private static HTTP http = null;

    private static List<Map<String, Object>> proxyList = null;

    /***
     * 初始化 http
     * @Param isChange 是否发需要切换代理Ip
     * @return
     */
    private void initHttpClint() {
        http = HTTP.builder()
                .config(new HTTP.OkConfig() {
                    @Override
                    public void config(OkHttpClient.Builder builder) {
                        // 配置连接池 最小10个连接(不配置默认为 5)
                        builder.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES));
                        // 配置连接超时时间(默认10秒)
                        builder.connectTimeout(1000, TimeUnit.SECONDS);
                        // 配置 WebSocket 心跳间隔(默认没有心跳)
                        builder.pingInterval(60, TimeUnit.SECONDS);
                        // 配置拦截器
                        builder.addInterceptor((Interceptor.Chain chain) -> {
                            Request request = chain.request();
                            // 必须同步返回,拦截器内无法执行异步操作
                            return chain.proceed(request);
                        });
                        // 其它配置: CookieJar、SSL、缓存、代理、事件监听...
                        // 是否需要开启动态代理Ip
                        if (spiderPropertyModel.getProxyEnable()) {
                            // 从ipLocation 中获取 ipList
                            getProxy(spiderPropertyModel.getIpLocation());
                            Map<String, Object> proxy = proxyList.get(new Random().nextInt(proxyList.size()));
                            String ip = proxy.get("ip").toString();
                            System.out.println("--------代理Ip------------" + ip);
                            int port = Integer.parseInt(proxy.get("port").toString());
                            System.out.println("----------端口------------" + port);
                            builder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(ip, port)));
                        }
                    }
                })
                .baseUrl("http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/" + spiderPropertyModel.getSpiderYear() + "/")
                .build();
    }

    /***
     * 发送http请求
     * @param childHref 访问得子页面
     * @return
     */
    private String toGet(String childHref) {
        BufferedReader in = null;
        String ret = "";
        HttpResult httpResult = null;// GET请求
        try {
            httpResult = http.sync(childHref)
                    .get();
            if (httpResult.isSuccessful()) {
                try {
//                    in = new BufferedReader(new InputStreamReader(httpResult.getBody().toByteStream(), "GB2312"));
                    in = new BufferedReader(new InputStreamReader(httpResult.getBody().toByteStream(), "UTF-8"));
                    String line = "";
                    while ((line = in.readLine()) != null) {
                        ret += line;
                    }
                    if (StringUtils.isBlank(ret)) {
                        System.out.println("-----获取到得ret == '' ----");
                        initHttpClint();
                        toGet(childHref);
                    }
                } catch (IOException e) {
                    in.close();
                }
            }
        } catch (Exception e) {
            // e.printStackTrace();
            System.out.println("-----连接超时,请求切换代理IP----");
            initHttpClint();
            toGet(childHref);
        }
        return ret;
    }

    /***
     * 获取省信息
     */
    public void getProvince() throws InterruptedException {
        // 初始化 httpClint
        initHttpClint();
        // 从index.html 中获取所有 省、直辖市、自治区 数据
        Document doc = Jsoup.parse(toGet("/index.html")); // 解析html文档,返回Document对象
        Elements provincetrEles = doc.getElementsByClass("provincetr"); // 根据类名获取元素对象集合
        // 查找数据库中保存的最后一个省的regionCode
        List<String> provinceCodeList = baseRegionDetailDao.getAllProvince();
        for (Element e : provincetrEles) {
            Elements tdEles = e.getElementsByTag("td"); // 获取provincetr下面所有的td
            for (Element td : tdEles) { // 遍历所有 <td>
                Elements a = td.select("a"); // 查找a标签元素
                if (!a.isEmpty()) {
                    // 解析数据 获取 指向市的href
                    String href = td.select("a").attr("href"); // 获取a标签的href属性的值
                    // 获取省份名称
                    String name = td.select("a").text().trim(); // 获取a标签文本内容
                    // 获取省份代码
                    String areaCode = "";
                    areaCode = href.substring(0, href.indexOf(".")) + "00"; // 两位省的code,后面补两个零,如北京11后补两个零为1100
                    String last2code = getLast2code(href, 0, 2); // 根据href值取前两位得到省的代码,如11

                    // 判断 数据库中 没有省信息 则证明是第一次爬取
                    if (provinceCodeList == null || !provinceCodeList.contains(areaCode)) {
                        System.out.println("--------------省-------------");
                        System.out.println("省: " + name + " 代码:" + areaCode + " href:" + href);
                        // 保存省信息数据
                        ArrayList<BaseRegionDetail> list = new ArrayList<>();
                        list.add(createModel(name, areaCode, "0", "0")); // 将该省的name和areaCode、pid、type存入实体类对象,再将实体类对象存入list集合中
                        baseRegionDetailDao.insert(list); // 将list集合中的数据批量插入数据库
                        getCity(toGet(href), areaCode, last2code); // 根据每一个省层层往下爬取
                        provinceCodeList.add(areaCode); // 将areaCode存入集合,就不会重复插入表
                    } else {
                        // 则证明 最后spider 的是个省 不进行保存 直接开始进入 此省的市判断
                        getCity(toGet(href), areaCode, last2code);
                    }
                    System.out.println("--------------省-------------");
                }

            }
        }
    }

    /***
     * 获取城市
     * @param cityHtmlStr http 请求回来的市body
     * @param pid 父Id
     * @param provinceCode 省ID
     */
    private void getCity(String cityHtmlStr, String pid, String provinceCode) throws InterruptedException {
        List<BaseRegionDetail> list = null;
        Document doc = Jsoup.parse(cityHtmlStr); // 解析html文档,返回Document对象
        Elements citytrEles = doc.getElementsByClass("citytr"); // 根据类名获取元素对象集合
        for (Element e : citytrEles) {
            // 每个市及市下边的数据 保存一下
            list = new ArrayList<>();
            Elements tdEles = e.getElementsByTag("td"); // 根据标签名称获取元素对象集合
            Element td = tdEles.last(); // 得到第二个td
            Elements a = td.select("a"); // 查找第二个td中a标签元素
            if (!a.isEmpty()) {
                String href = td.select("a").attr("href"); // 获取第二个td中a标签的href属性值
                String name = td.select("a").text().trim(); // 获取第二个td中a标签的文本内容
                String areaCode = "";
                // href = 14/1401.html
                areaCode = href.substring(href.indexOf("/") + 1, href.indexOf(".")); // 获取/后面.前面的code,该code为市的code
                System.out.println("市: " + name + " 代码:" + areaCode + " href:" + href);
                // 判断此城市是否已经存在数据库中
                List<BaseRegionDetail> detailModels = baseRegionDetailDao.find(areaCode, null, null);
                if (detailModels == null || detailModels.size() == 0) {
                    // 获取 code 末尾两码
                    list.add(createModel(name, areaCode, pid, "1")); // 将市的name、areaCode、pid、type存入对象中,再将对象存入list集合中
                    // 获取市下边的 县、区 信息
                    getCounty(toGet(href), list, areaCode, provinceCode);
                    if (list.size() > 0) {
                        baseRegionDetailDao.insert(list); // 将list集合中的数据批量插入数据库
                        if (spiderPropertyModel.getIsCitySleep()) { // 每个市之间要歇息15s,否则频繁的访问网站可能被加入黑名单
                            System.out.println("-----------市级睡眠 " + spiderPropertyModel.getCitySleepTime() + " s 开始---------");
                            TimeUnit.SECONDS.sleep(spiderPropertyModel.getCitySleepTime());
                            System.out.println("-----------市级睡眠结束----------");
                        }
                    }
                }
            }
        }
    }

    /***
     * 获取 区县 代码
     * @param cityHtmlStr
     * @param list
     * @param pid
     * @param provinceCode
     */
    private void getCounty(String cityHtmlStr, List<BaseRegionDetail> list, String pid, String provinceCode) throws InterruptedException {
        Document doc = Jsoup.parse(cityHtmlStr);  // 解析html文档,返回Document对象
        Elements countytrEles = doc.getElementsByClass("countytr");  // 根据类名获取元素对象集合
        for (Element tr : countytrEles) {
            // 如果没有获取到 a标签
            Elements tdEles = tr.getElementsByTag("td");
            if (tdEles.select("a").isEmpty()) {
                String name = tdEles.last().text();
                String areaCode = tdEles.first().text().substring(0, 6);
                // // href = 01/140105.html 区往街道走 要获取上一级的14/01/140105
                System.out.println("区、县: " + name + " 区、县:" + areaCode);
                list.add(createModel(name, areaCode, pid, "2"));
            } else {
                Element td = tdEles.last();
                Elements a = td.select("a");
                if (!a.isEmpty()) {
                    String href = td.select("a").attr("href"); // 获取第二个td中a标签的href属性值
                    String name = td.select("a").text().trim(); // 获取第二个td中a标签的文本内容
                    String areaCode = "";
                    areaCode = href.substring(href.indexOf("/") + 1, href.indexOf(".")); // 获取/后面.前面的code,该code为区/县的code
                    System.out.println("区、县: " + name + " 区、县:" + areaCode + " href:" + href);
                    list.add(createModel(name, areaCode, pid, "2")); // 将该区/县加入list集合
                    String cityCode = getLast2code(href, 0, 2); // 获取href属性值的前两位作为区/县的code
                    href = "/" + provinceCode + "/" + href; // 使用省的code(即11)+区县herf(即01/110101.html)
                    //  是否获取乡镇、街道信息
                    if (spiderPropertyModel.getGetTowntr()) {
                        getTowntr(toGet(href), list, areaCode, provinceCode, cityCode);
                    }
                }
            }
            if (spiderPropertyModel.getIsCountySleep()) {
                System.out.println("-----------区、县 级睡眠 " + spiderPropertyModel.getCountySleepTime() + " s 开始---------");
                TimeUnit.SECONDS.sleep(spiderPropertyModel.getCountySleepTime()); // 每个区/县之间要歇息30s,否则频繁的访问网站可能被加入黑名单
                System.out.println("-----------区、县 市级睡眠结束----------");

            }
        }
    }

    /***
     * 获取 乡镇 街道办 信息
     */
    private void getTowntr(String towntrHtmlStr, List<BaseRegionDetail> list, String pid, String provinceCode, String cityCode) {
        Document doc = Jsoup.parse(towntrHtmlStr); // 解析html文档,返回Document对象
        Elements countytrEles = doc.getElementsByClass("towntr"); // 根据类名获取元素对象集合
        for (Element tr : countytrEles) {
            // 如果没有获取到 a标签
            Elements tdEles = tr.getElementsByTag("td");
            Element td = tdEles.last(); // 取第二个td
            Elements a = td.select("a");
            if (!a.isEmpty()) {
                String href = td.select("a").attr("href"); // 获取第二个td中a标签的href属性值
                String name = td.select("a").text().trim(); // 获取第二个td中a标签的文本内容
                String areaCode = "";
                areaCode = href.substring(href.indexOf("/") + 1, href.indexOf(".")); // 获取/后面.前面的code,该code为乡镇/街道办的code
                //System.out.println("街道办: " + name + " 代码:" + areaCode + " href:" + href);
                list.add(createModel(name, areaCode, pid, "3"));
                href = "/" + provinceCode + "/" + cityCode + "/" + href; // 使用省的code(即11)+市的code(即01)+乡镇herf(即01/110101001.html)
                //  是否获取村、社区信息
                if (spiderPropertyModel.getGetVillagetr()) {
                    getVillagetr(toGet(href), list, areaCode);
                }
            }
        }
    }

    /***
     * 获取 村 社区 居委会信息
     * @param villagetrHtmlStr
     * @param list
     * @param pid
     */
    private void getVillagetr(String villagetrHtmlStr, List<BaseRegionDetail> list, String pid) {
        Document doc = Jsoup.parse(villagetrHtmlStr);
        Elements countytrEles = doc.getElementsByClass("villagetr");
        for (Element tr : countytrEles) {
            Elements tdEles = tr.getElementsByTag("td");
            if (tdEles != null && tdEles.size() > 0) {
                Element first = tdEles.get(0);
                Element last = tdEles.get(2);
                // 解析 获取 regionName,regionCode
                list.add(createModel(last.text().trim(), first.text().trim(), pid, tdEles.get(1).text().trim()));
            }
        }
    }


    /***
     * 取herf得前两位
     * @param regionCode
     * @return
     */
    private String getLast2code(String regionCode, int startIndex, int endIndex) {
        return regionCode.substring(startIndex, endIndex);
    }

    /***
     * 获取 id代理 库
     */
    private void getProxy(String ipLocation) {
        // 1,建立联系
        File file = new File(ipLocation);
        //2,选择流
        InputStream is = null;
        try {
            is = new FileInputStream(file);

            BufferedReader in2 = new BufferedReader(new InputStreamReader(is));
            // 设置缓冲区域,就像搬家一样,定义一个车子来搬家
            byte[] data = new byte[1024];
            // 记录实际读取的长度,就像搬家一样,不是说每个都是刚好一车就装满了的
            proxyList = new ArrayList<>();
            String y = "";
            // read 将字节流读取到 定义 的 data 中,len 记录每次读取的长度,当 is 的数据读完之后len的值则为-1
            while ((y = in2.readLine()) != null) {
                HashMap<String, Object> map = new HashMap<>();
                String[] split = y.split(":");
                map.put("ip", split[0]);
                map.put("port", split[1]);
                proxyList.add(map);
            }
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    // 4,释放资源
                    is.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }


    /***
     * 创建 regionModel
     * @param regionName
     * @param regionCode
     * @param pid
     * @param type
     * @return
     */
    private BaseRegionDetail createModel(String regionName, String regionCode, String pid, String type) {
        BaseRegionDetail model = new BaseRegionDetail();
        model.setRegionName(regionName);
        model.setRegionCode(regionCode);
        model.setPid(pid);
        model.setType(type);
        return model;
    }
}

 

10、编写controller

@RestController
@RequestMapping("/utils")
public class BaseRegionDetailsController {
    @Autowired
    BaseRegionDetailService baseRegionDetailService;

    /***
     * 批量插入
     */
    @RequestMapping("/getAllRegion")
    public void insert() {
        try {
            baseRegionDetailService.getProvince();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

总结:全国共有31个省/直辖市,共有342市,共有3238个县。

 

posted on 2022-08-08 13:54  周文豪  阅读(1920)  评论(2编辑  收藏  举报