使用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
- 引入
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
標籤class
為citytr
。區縣和街道、居委會佈局都一樣,只是class
不同,所以可以抽取出共同程式碼,封裝成一個函式。
需要注意的是,越往下級爬取,資料量會越來越大,這時可能會報錯,甚至會被原網站封ip的風險。
這裡因為只是爬取到區縣級資料,所以沒有做過多考慮,只是引入的
async
庫的mapLimit
,用於併發請求。
- 最後,配置
package.json
,使用npm
指令碼啟動專案,不再使用node index.js
時間倉儲,只是為了滿足專案中的一個小需求,臨時寫的一個爬蟲,也是第一次使用nodejs爬取網頁,問題還有很多,希望多多指正。後續抽出時間,對程式碼進行完善,解決爬取資料量大就報錯的問題,並對省級id進行位數完善等。