只聽說過用Python做爬蟲,Java程序員笑了!
本文轉載自微信公眾號「Java極客技術」,作者鴨血粉絲 。轉載本文請聯系Java極客技術公眾號。
網絡爬蟲技術,早在萬維網誕生的時候,就已經出現了,今天我們就一起來揭開它神秘的面紗!
一、摘要
說起網絡爬蟲,相信大家都不陌生,又俗稱網絡機器人,指的是程序按照一定的規則,從互聯網上抓取網頁,然后從中獲取有價值的數據,隨便在網上搜索一下,排在前面基本都是 pyhton 教程介紹。
的確,pyhton 在處理網頁方面,有著開發簡單、便捷、性能高效的優勢!
但是我們 java 也不賴,在處理復雜的網頁方面,需要解析網頁內容生成結構化數據或者對網頁內容精細的解析時,java 可以說更勝一籌!
下面我們以爬取國家省市區信息為例,使用 java 技術來實現,過程主要分三部:
- 第一步:目標網頁分析
- 第二步:編寫爬蟲程序,對關鍵數據進行抓取
- 第三步:將抓取的數據寫入數據庫
廢話不多說,直接開擼!
二、網頁分析
網絡爬蟲,其實不是一個很難的技術,只是需要掌握的技術內容比較多,只會 java 技術是遠遠不夠,還需要熟悉 html 頁面屬性!
以爬取國家省市區信息為例,我們可以直接在百度上搜索國家省市區,點擊進入全國行政區劃信息查詢平臺。
在民政數據菜單欄下,找到最新的行政區域代碼公示欄。
點擊進去,展示結果如下!
可以很清楚的看到,這就是我們要獲取省市區代碼的網頁信息。
可能有的同學會問,這么直接干合不合法?
國家既然已經公示了,我們直接拿來用就可以,完全合法!而且國家省市區代碼是一個公共字典,在很多業務場景下必不可少!
當我們找到了目標網頁之后,我們首先要做的就是對網頁進行分析,打開瀏覽器調試器,可以很清晰的看到它是一個table表格組成的數據。
熟悉 html 標簽的同學,想必已經知道了它的組成原理。
其實table是一個非常簡單的 html 標簽,主要有tr和td組成,其中tr代表行,td代表列,例如用table標簽畫一個學生表格,代碼如下:
- <table>
- <!-- 定義表格頭部 -->
- <tr>
- <td>編號</td>
- <td>姓名</td>
- </tr>
- <!-- 定義表格內容 -->
- <tr>
- <td>100</td>
- <td>張三</td>
- </tr>
- <tr>
- <td>101</td>
- <td>李四</td>
- </tr>
- </table>
展示結果如下:
了解了table標簽之后,我們再對網頁進行詳細分析。
首先對整個內容進行觀察,很容易的看到,市級以上(包括市級),都是黑體字加粗的,區或者縣級地區,都是常規!
出現這個現象,其實是由樣式標簽CSS來控制的,點擊北京市,找到對應的代碼位置,從圖中我們可以很清晰的看到,市級對應的樣式class為xl7030796,區或者縣級地區對應的樣式class為xl7130796
除此之外,我們繼續來看看省和市級的區別!
可以很清晰的看到,市級相比省級信息,多了一個span占位符標簽。
于是,我們可以得出如下結論:
省級信息,樣式標簽為xl7030796
市級信息,樣式標簽為xl7030796,同時包含span占位符標簽
區或者縣級信息,樣式標簽為xl7130796
等會會通過這些規律信息來從網頁信息中抓取省、市、區信息。
三、編寫爬蟲程序
3.1、創建項目
新建一個基于 maven 工程 java 項目,在pom.xml工程中引入如下 jar 包!
- <!--解析HTML-->
- <dependency>
- <groupId>org.jsoup</groupId>
- <artifactId>jsoup</artifactId>
- <version>1.11.2</version>
- </dependency>
3.2、編寫爬取程序
先創建一個實體數據類,用于存放抓取的數據
- public class ChinaRegionsInfo {
- /**
- * 行政區域編碼
- */
- private String code;
- /**
- * 行政區域名稱
- */
- private String name;
- /**
- * 行政區域類型,1:省份,2:城市,3:區或者縣城
- */
- private Integer type;
- /**
- * 上一級行政區域編碼
- */
- private String parentCode;
- //省略get、set
- }
然后,我們來編寫爬取代碼,將抓取的數據封裝到實體類中
- //需要抓取的網頁地址
- private static final String URL = "http://www.mca.gov.cn//article/sj/xzqh/2020/202006/202008310601.shtml";
- public static void main(String[] args) throws IOException {
- List<ChinaRegionsInfo> regionsInfoList = new ArrayList<>();
- //抓取網頁信息
- Document document = Jsoup.connect(URL).get();
- //獲取真實的數據體
- Element element = document.getElementsByTag("tbody").get(0);
- String provinceCode = "";//省級編碼
- String cityCode = "";//市級編碼
- if(Objects.nonNull(element)){
- Elements trs = element.getElementsByTag("tr");
- for (int i = 3; i < trs.size(); i++) {
- Elements tds = trs.get(i).getElementsByTag("td");
- if(tds.size() < 3){
- continue;
- }
- Element td1 = tds.get(1);//行政區域編碼
- Element td2 = tds.get(2);//行政區域名稱
- if(StringUtils.isNotEmpty(td1.text())){
- if(td1.classNames().contains("xl7030796")){
- if(td2.toString().contains("span")){
- //市級
- ChinaRegionsInfo chinaRegions = new ChinaRegionsInfo();
- chinaRegions.setCode(td1.text());
- chinaRegions.setName(td2.text());
- chinaRegions.setType(2);
- chinaRegions.setParentCode(provinceCode);
- regionsInfoList.add(chinaRegions);
- cityCode = td1.text();
- } else {
- //省級
- ChinaRegionsInfo chinaRegions = new ChinaRegionsInfo();
- chinaRegions.setCode(td1.text());
- chinaRegions.setName(td2.text());
- chinaRegions.setType(1);
- chinaRegions.setParentCode("");
- regionsInfoList.add(chinaRegions);
- provinceCode = td1.text();
- }
- } else {
- //區或者縣級
- ChinaRegionsInfo chinaRegions = new ChinaRegionsInfo();
- chinaRegions.setCode(td1.text());
- chinaRegions.setName(td2.text());
- chinaRegions.setType(3);
- chinaRegions.setParentCode(StringUtils.isNotEmpty(cityCode) ? cityCode : provinceCode);
- regionsInfoList.add(chinaRegions);
- }
- }
- }
- }
- //打印結果
- System.out.println(JSONArray.toJSONString(regionsInfoList));
- }
運行程序,輸出結果如下:
json解析結果如下:
至此,網頁有效數據已經全部抓取完畢!
四、寫入數據庫
在實際的業務場景中,我們需要做的不僅僅只是抓取出有價值的數據,最重要的是將這些數據記錄數據庫,以備后續的業務可以用的上!
例如,當我們在開發一個給員工發放薪資系統的時候,其中的社保、公積金,可能每個城市都不一樣,這個時候就會到國家省市區編碼。
因此,我們可以將抓取的國家省市區編碼寫入數據庫!
在上面,我們已經將具體的省市區數據結構封裝成數組對象,寫入過程也很簡單。
首先,創建一張國家行政地域信息表china_regions
- CREATE TABLE `china_regions` (
- `id` bigint(20) unsigned NOT NULL COMMENT '主鍵ID',
- `code` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '行政地域編碼',
- `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '行政地域名稱',
- `type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '行政地域類型,1:省份,2:城市,3:區域',
- `parent_code` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '上一級行政編碼',
- `is_delete` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否刪除 1:已刪除;0:未刪除',
- `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
- `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
- PRIMARY KEY (`id`),
- KEY `idx_code` (`code`) USING BTREE,
- KEY `idx_name` (`name`) USING BTREE,
- KEY `idx_type` (`type`) USING BTREE,
- KEY `idx_parent_code` (`parent_code`) USING BTREE
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='國家行政地域信息表';
搭建一個springboot工程,通過mybatis-plus組件,一鍵生成代碼
最后,配置好數據源,重新封裝數組對象,調用批量插入方法,即可插入操作
- chinaRegionsService.saveBatch(regionsInfoList);
插入執行完之后,數據庫結果如下
至此,大部分工作基本已經完成!
但是,細心的你,可能會發現還有數據問題,因為我們國家在省級區域上,還有一個直轄市的概念,以北京市為例,在數據庫中type類型為1,表示省級類型,但是它的子級是一個區,中間還掉了一層市級類型。
因此,我們還需要對這些直轄市類型的數據進行修復,查詢出所有的直轄市類型的城市。
對這些編號的城市,單獨處理,中間加一層市級類型!
至此,國家省市區編碼數據字典,全部處理完畢!
五、總結
本篇主要以爬取國家省市區編號為例,以 java 技術為背景進行講解,在整個爬取過程中,最重要的一部分就是網頁分析,找出規律,然后通過jsoup工具包解析網頁,獲取其中的有效數據。
同時,技術是一把雙面刀,希望同學們能正當使用!
七、參考
1、2020年行政區劃代碼
2、jsoup -中文文檔