1. 程式人生 > 其它 >使用nodejs寫個簡單爬蟲獲取行政區劃資訊

使用nodejs寫個簡單爬蟲獲取行政區劃資訊

最近在開發一個房產管理系統,有個功能是需要對行政區劃進行存入資料庫管理,在網上找了很久,關於國家的行政區劃資料都比較久遠或者不完整,不能匹配自己的需求。
後來就想到用爬蟲獲取國家統計局網站的資料,因為Python還在學習中,所以就用自己熟悉的nodejs寫了份簡單的爬蟲,能獲取到國家統計局網站的全國行政區劃資訊(不包含港澳臺)。

目錄

  • 建立src檔案價,作為生成的行政區劃資訊存放目錄
  • 建立index.js 檔案作為主要檔案

初始化

使用npm初始化專案,按照提示輸入相應內容(專案名稱、版本、描述等資訊)

npm init

安裝依賴

  • cheerio: jquery核心功能的一個快速靈活而又簡潔的實現,主要是為了用在伺服器端需要對DOM進行操作的地方
  • axios:是一個基於 promise 的 HTTP 庫(也可以使用nodejs的request或其他http庫)
  • iconv-lite:解決nodejs中編碼問題
  • async:區別與ES的async/await,這裡作為一個庫引入,主要使用async.mapLimit控制請求併發數

下文具體介紹各個庫的使用

開發

分析目標網站

進入國家統計局網站,找到行政區劃網頁,目前最新的資料是2020年的,網址:http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2020/index.html

通過檢視網頁原始碼發現,其實這個網站比較簡單,我們想獲取的資訊也直接在html內,所以可以直接使用cheerio

來獲取元素資訊,並最終得到想要的資料。

對被爬取網站的分析應該放在第一步,然後再根據不同網站的分析結果,確定使用的依賴庫。

首先定義幾個變數

const HOST = 'http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2020/';
const headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' };

HOST: 就是要爬取的網站,這裡給單獨出來,方便後面的操作,(可以進一步優化,把年份拿出來,每次獲取時手動輸入,獲取指定年份的資料)
headers

: 就是訪問該網站時的header資訊,這裡就是為了模擬瀏覽器環境,防止出問題,其實不寫也可以請求網站資訊

  • 引入axios庫,獲取網站資料
    檔案頭部引入axios
const axios = require('axios');

然後定義一個函式用於請求地址獲取相應資料。因為根據分析網站的結果可知,原網站資料是按層級一層一層劃分並每個連結顯示一級的資料。如:首頁顯示了所有省級資訊,然後每個省級攜帶對應的不同連結,連線到下屬城市,再根據城市資訊連結到區縣再到街道、居委會等。所以把請求介面的方法封裝到一個函式裡,通過引數訪問,獲取資訊。

const fetchData = async (url) => {
  const res = await axios(url, { headers });

定義一個main函式,作為程式的主入口,再在main內執行

const main = async () => {
  const provinceData = await fetchData(HOST);
}

main();

儲存後執行node index.js即可獲取網頁資訊。可用console打印出檢視。

此時會發現,獲取的資料,中文會亂碼,這是因為,該網站編碼採用的是GB2312。我們需要對獲取的資料進行轉譯。

  • 引入iconv-lite
const iconv = require('iconv-lite');

修改請求函式:

let data = null;
const res = await axios(url, { responseType: 'arraybuffer', headers });
// iconv-lite 解析buffer資料為gb2312
data = iconv.decode(res.data, 'gb2312');

注意

axios會轉換資料格式為utf-8,所以這裡需要把獲取的資料轉換為流,再使用iconv-lite轉為GB2312格式

獲取到正確的資料後就可以使用cheerio進行精準的獲取了。

  • 引入cheerio
const cheerio = require('cheerio');

分析網頁結構,省級資訊在table標籤內,分四行顯示,共四個tr標籤,每個tr標籤有個class="provincetr",然後名稱在td下的a標籤內,如下:

<tr class="provincetr">
	<td><a href="11.html">北京市<br></a></td>
	<td><a href="12.html">天津市<br></a></td>
	<td><a href="13.html">河北省<br></a></td>
	<td><a href="14.html">山西省<br></a></td>
	<td><a href="15.html">內蒙古自治區<br></a></td>
	<td><a href="21.html">遼寧省<br></a></td>
	<td><a href="22.html">吉林省<br></a></td>
	<td><a href="23.html">黑龍江省<br></a></td>
</tr>

發現規律遍可以編寫程式碼:

const proviceStr = (html) => {
  const $ = cheerio.load(html)
  let result = [];
  $(".provincetr a").each(function (index, element) {
    let name = $(element).text().trim();
    let url = $(element).attr("href");
    let id = url.replace('.html', '');
    result.push({
      pid: '',
      id,
      name,
      url: HOST+url,
    })
  });
  return result;
};

這裡遍歷class下的元素,獲取a標籤內的內容和href資訊,並需要獲取對應的省級id(使用了通過連結擷取,其實href內的資訊也是對應的id)。並把url資訊儲存下來,用於下一級資料獲取的一句

列印result應該就能看到獲取的資料了。

接下來需要把資料存到檔案中

  • 引入nodejs的fs模組
const fs = require('fs');

定義存放的變數,為了方便,直接把各個級別的資訊分開儲存:

const filePath = {
  province: 'src/province.json',
  city: 'src/city.json',
  country: 'src/country.json'
}

修改main方法:

const main = async () => {
  const Index = joinUrl('index.html');
  const provinceData = await fetchData(Index, 'province', '');
  fs.writeFileSync(filePath.province, JSON.stringify(provinceData));
}

此時開啟src資料夾便能看見多了個province.json,開啟是個壓縮的json檔案。

然後再分析市級資料。市級網頁結構佈局與省級類似,只是變成縱向排列,tr標籤classcitytr。區縣和街道、居委會佈局都一樣,只是class不同,所以可以抽取出共同程式碼,封裝成一個函式。

需要注意的是,越往下級爬取,資料量會越來越大,這時可能會報錯,甚至會被原網站封ip的風險。

這裡因為只是爬取到區縣級資料,所以沒有做過多考慮,只是引入的async庫的mapLimit,用於併發請求。

  • 最後,配置package.json,使用npm指令碼啟動專案,不再使用node index.js

時間倉儲,只是為了滿足專案中的一個小需求,臨時寫的一個爬蟲,也是第一次使用nodejs爬取網頁,問題還有很多,希望多多指正。後續抽出時間,對程式碼進行完善,解決爬取資料量大就報錯的問題,並對省級id進行位數完善等。