這裏將會介紹兩種技術方案,都是基於nodejs相關技術進行展開的,唯一不同的就是選取的技術稍有不同,本質其實一樣
第一種技術方案:
需要用到的技術模塊:superagent、superagent-charset、cheerio、fs
安裝以上依賴模塊(也可以單獨依次安裝):npm install superagent superagent-charset cheerio fs
說明:
- Superagent是輕量級漸進式ajax API,具有靈活性,可讀性和低學習曲線,在受到許多現有請求API的挫敗之後。它也適用於Node.js!
- superagent-charset是superagent裏的一個功能模塊,主要用於對字符編碼格式的處理
- cheerio主要用於解析html頁面dom,用來操作dom
- fs是nodejs下的一個文件系統模塊,此處爲了將最終數據寫入文件當中去
相關技術文檔:
superagent技術文檔地址:superagent官方文檔
cheerio技術文檔地址:cheerio官方文檔
fs文檔地址:fs官方文檔
具體代碼如下(由於數據量過多,請求過於頻繁,代碼可能偶爾存在爬取數據不完整的情況,主要是技術的實踐與應用以及在實際當中的思路):
// 導入對象深拷貝方法函數
const copy = require('./deepClone');
// 引入請求模塊
const request = require('superagent')
// 加載cheerio庫,用來在服務端直接操作頁面dom
const cheerio = require('cheerio');
// 加載編碼格式處理模塊
require('superagent-charset')(request);
// 引入文件系統
const fs = require('fs');
// 目的網址
var firstUrl = "www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2018/index.html";
var staticUrl = "www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2018/";
// 緩存最終數據
var resAreas = [];
function start(url) {
request.get(url)
.set({ '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', 'Accept-Encoding': 'gzip, deflate', 'content-type': 'text/html', 'Content-Length': Buffer.byteLength(""), 'Connection': 'keep-alive' })
.buffer(true)
.charset('gb2312')
.end(function (err, res) {
if (err) {
console.log(err);
return;
}
let html = res.text;
let $provinceCheerio = cheerio.load(html);
let $province = $provinceCheerio('.provincetr');
let provinceObj = [];
let $td = '';
$province.each(function (i, item) {
$td = $provinceCheerio(item).find('td');
$td.each(function (i, item) {
let resObj = {
href: $provinceCheerio(item).find('a').attr('href'),
provinceCode: $provinceCheerio(item).find('a').attr('href').split('.')[0],
provinceName: $provinceCheerio(item).find('a').text()
}
provinceObj.push(resObj);
});
});
resAreas = copy.deepClone(provinceObj);
// 獲取城市
getCity(provinceObj);
// console.log(resAreas);
});
}
function getCity(provinceObj) {
for (let i = 0; i < provinceObj.length; i++) {
setTimeout(() => {
let url = staticUrl + provinceObj[i].href;
request.get(url)
.set({ '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', 'Accept-Encoding': 'gzip, deflate', 'content-type': 'text/html', 'Content-Length': Buffer.byteLength(""), 'Connection': 'keep-alive' })
.buffer(true)
.charset('gb2312')
.end(function (err, res) {
if (err) {
console.log(err);
return;
}
let html = res.text;
let $cityCheerio = cheerio.load(html);
let $city = $cityCheerio('.citytr');
let cityObj = [];
let $td = '';
$city.each(function (i, item) {
$td = $cityCheerio(item).find('td');
let href = '';
let code = '';
let name = '';
$td.each(function (i, item) {
if (i === 0) {
href = $cityCheerio(item).find('a').attr('href');
code = $cityCheerio(item).find('a').text();
} else {
name = $cityCheerio(item).text();
}
});
cityObj.push({
href: href,
cityCode: code,
cityName: name
});
});
resAreas[i].city = copy.deepClone(cityObj);
// 獲取區、縣
getCounty(i, cityObj);
// console.log(resAreas);
});
}, 30000 * i);
}
}
function getCounty(n, cityObj) {
for (let i = 0; i < cityObj.length; i++) {
// setTimeout(() => {
let url = staticUrl + cityObj[i].href;
request.get(url)
.set({ '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', 'Accept-Encoding': 'gzip, deflate', 'content-type': 'text/html', 'Content-Length': Buffer.byteLength(""), 'Connection': 'keep-alive' })
.buffer(true)
.charset('gb2312')
.end(function (err, res) {
if (err) {
console.log(err);
return;
}
let html = res.text;
let $countyCheerio = cheerio.load(html);
let $county = $countyCheerio('.countytr');
let countyObj = [];
let $td = '';
$county.each(function (i, item) {
$td = $countyCheerio(item).find('td');
let href = '';
let code = '';
let name = '';
$td.each(function (i, item) {
if ($countyCheerio(item).find('a').length > 0) {
href = $countyCheerio(item).find('a').attr('href');
if (i === 0) {
code = $countyCheerio(item).find('a').text();
} else {
name = $countyCheerio(item).find('a').text();
}
} else {
if (i === 0) {
code = $countyCheerio(item).text();
} else {
name = $countyCheerio(item).text();
}
}
});
countyObj.push({
href: href,
countyCode: code,
countyName: name
});
});
resAreas[n].city[i].county = copy.deepClone(countyObj);
// 獲取城鎮鄉
getTown(n, i, countyObj);
// console.log(resAreas);
});
// }, 1000*i);
}
}
function getTown(n, m, countyObj) {
for (let i = 0; i < countyObj.length; i++) {
if (countyObj[i].href != "") {
let link = countyObj[i].href.split('/')[1].slice(0, 2) + '/';
let url = staticUrl + link + countyObj[i].href;
request.get(url)
.set({ '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', 'Accept-Encoding': 'gzip, deflate', 'content-type': 'text/html', 'Content-Length': Buffer.byteLength(""), 'Connection': 'keep-alive' })
.buffer(true)
.charset('gb2312')
.end(function (err, res) {
if (err) {
console.log(err);
return;
}
let html = res.text;
let $townCheerio = cheerio.load(html);
let $town = $townCheerio('.towntr');
let townObj = [];
let $td = '';
$town.each(function (i, item) {
$td = $townCheerio(item).find('td');
let code = '';
let name = '';
$td.each(function (i, item) {
if ($townCheerio(item).find('a').length > 0) {
if (i === 0) {
code = $townCheerio(item).find('a').text();
} else {
name = $townCheerio(item).find('a').text();
}
} else {
if (i === 0) {
code = $townCheerio(item).text();
} else {
name = $townCheerio(item).text();
}
}
});
townObj.push({
townCode: code,
townName: name
});
});
resAreas[n].city[m].county[i].town = copy.deepClone(townObj);
// console.log(resAreas);
});
}
}
// 將數據寫入文件
fs.writeFile('2018國家行政區域.json', JSON.stringify(resAreas), (err) => {
if (err) throw err;
console.log('文件已保存');
});
}
start(firstUrl);
第二種技術方案:
需要用到的技術模塊:iconv-lite、cheerio、fs、http
安裝以上依賴模塊(也可以單獨依次安裝):npm install iconv-lite cheerio fs http
說明:
- iconv-lite主要用於處理編碼格式問題
- cheerio主要用於解析html頁面dom,用來操作dom
- fs是nodejs下的一個文件系統模塊,此處爲了將最終數據寫入文件當中去
- http是nodejs下的一個網絡請求模塊,此處爲了使用http中的get請求方法
相關技術文檔:
iconv-lite技術文檔地址:iconv-lite官方地址
cheerio技術文檔地址:cheerio官方文檔
fs文檔地址:fs官方文檔
http文檔地址:http官方文檔
具體代碼如下(由於數據量過多,請求過於頻繁,代碼可能偶爾存在爬取數據不完整的情況,主要是技術的實踐與應用以及在實際當中的思路):
// 導入對象深拷貝方法函數
const copy = require('./deepClone');
// 加載cheerio庫,用來在服務端直接操作頁面dom
const cheerio = require('cheerio');
// 引入http、https模塊,根據所爬網站協議來選擇合適的進行使用
const http = require('http');
//引入相關模塊
const iconv = require('iconv-lite');
// 引入文件系統
const fs=require('fs');
// 目的網址
var firstUrl = "http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2018/index.html";
var staticUrl = "http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2018/";
// 緩存最終數據
var resAreas = [];
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' }
function start(url){
startTime=new Date().getTime();
http.get(url,{headers: headers},(res) => {
let chunkBuffers=[];
// console.log(url);
res.on('data', (chunk) => {
chunkBuffers.push(chunk);
//下面拼接這行代碼存在的缺陷:
// 對不是utf8編碼的其他格式編碼字符內容的拼接會出現亂碼,如果純英文顯示出來沒問題,
// 如果是中文字符,就會出現亂碼問題,原因是一個漢字在UTF-8編碼下佔3個字節,在進行拼接操作時,
// 一個完整的漢字字符可能會被截斷,不能正常解析顯示,從而造成亂碼
//具體原因:
// 其原因是兩個chunk(Buffer對象)的拼接並不正常,
//相當於進行了buffer.toString() + buffer.toString()。
//如果buffer不是完整的,則toString出來後的string是存在問題的(比如一箇中文字被截斷)
//這樣出來的string就無法被iconv正常轉碼。
// 代碼:
// html += chunk;
});
res.on('end', () => {
try {
// 對字節進行處理
let buffer=Buffer.concat(chunkBuffers);
// 對window環境下的返回字符編碼爲gb2312編碼格式的字符內容進行處理
let html = iconv.decode(buffer, 'gb2312');
let $provinceCheerio=cheerio.load(html);
let $province = $provinceCheerio('.provincetr');
let provinceObj = [];
let $td = '';
$province.each(function (i, item) {
$td = $provinceCheerio(item).find('td');
$td.each(function (i, item) {
let resObj = {
href: $provinceCheerio(item).find('a').attr('href'),
provinceCode: $provinceCheerio(item).find('a').attr('href').split('.')[0],
provinceName: $provinceCheerio(item).find('a').text()
}
provinceObj.push(resObj);
});
});
resAreas=copy.deepClone(provinceObj);
// 獲取城市
getCity(provinceObj);
} catch (e) {
console.error(e.message);
}
}).on('error', (e) => {
console.error(`出現錯誤: ${e.message}`);
});
});
}
function getCity(provinceObj){
// console.log(provinceObj);
for(let i=0; i<provinceObj.length; i++){
setTimeout(() => {
let url=staticUrl + provinceObj[i].href;
http.get(url,{headers: headers},(res) => {
let chunkBuffers=[];
res.on('data', (chunk) => {
chunkBuffers.push(chunk);
});
res.on('end', () => {
try {
// 對字節進行處理
let buffer=Buffer.concat(chunkBuffers);
// 對window環境下的返回字符編碼爲gb2312編碼格式的字符內容進行處理
let html = iconv.decode(buffer, 'gb2312');
let $cityCheerio=cheerio.load(html);
let $city = $cityCheerio('.citytr');
let cityObj = [];
let $td = '';
$city.each(function (i, item) {
$td = $cityCheerio(item).find('td');
let href = '';
let code = '';
let name = '';
$td.each(function (i, item) {
if (i === 0) {
href = $cityCheerio(item).find('a').attr('href');
code = $cityCheerio(item).find('a').text();
} else {
name = $cityCheerio(item).text();
}
});
cityObj.push({
href: href,
cityCode: code,
cityName: name
});
});
resAreas[i].city=copy.deepClone(cityObj);
// 獲取區、縣
getCounty(i,cityObj);
} catch (e) {
console.error(e.message);
}
}).on('error', (e) => {
console.error(`出現錯誤: ${e.message}`);
});
});
},30000*i);
}
}
function getCounty(n,cityObj){
// console.log(cityObj);
for(let i=0; i<cityObj.length; i++){
setTimeout(() => {
let url=staticUrl + cityObj[i].href;
http.get(url,{headers: headers},(res) => {
let chunkBuffers=[];
res.on('data', (chunk) => {
chunkBuffers.push(chunk);
});
res.on('end', () => {
try {
// 對字節進行處理
let buffer=Buffer.concat(chunkBuffers);
// 對window環境下的返回字符編碼爲gb2312編碼格式的字符內容進行處理
let html = iconv.decode(buffer, 'gb2312');
let $countyCheerio=cheerio.load(html);
let $county = $countyCheerio('.countytr');
let countyObj = [];
let $td = '';
$county.each(function (i, item) {
$td = $countyCheerio(item).find('td');
let href = '';
let code = '';
let name = '';
$td.each(function (i, item) {
if($countyCheerio(item).find('a').length>0){
href = $countyCheerio(item).find('a').attr('href');
if(i === 0){
code = $countyCheerio(item).find('a').text();
}else{
name = $countyCheerio(item).find('a').text();
}
}else{
if(i === 0){
code = $countyCheerio(item).text();
}else{
name = $countyCheerio(item).text();
}
}
});
countyObj.push({
href: href,
countyCode: code,
countyName: name
});
});
resAreas[n].city[i].county=copy.deepClone(countyObj);
// 獲取城鎮鄉
getTown(n,i,countyObj);
} catch (e) {
console.error(e.message);
}
}).on('error', (e) => {
console.error(`出現錯誤: ${e.message}`);
});
});
},1000*i);
}
}
function getTown(n,m,countyObj){
// console.log(countyObj);
for(let i=0; i<countyObj.length; i++){
if(countyObj[i].href != ""){
let link=countyObj[i].href.split('/')[1].slice(0,2)+'/';
let url=staticUrl + link + countyObj[i].href;
http.get(url,{headers: headers},(res) => {
let chunkBuffers=[];
res.on('data', (chunk) => {
chunkBuffers.push(chunk);
});
res.on('end', () => {
try {
// 對字節進行處理
let buffer=Buffer.concat(chunkBuffers);
// 對window環境下的返回字符編碼爲gb2312編碼格式的字符內容進行處理
let html = iconv.decode(buffer, 'gb2312');
let $townCheerio=cheerio.load(html);
let $town = $townCheerio('.towntr');
let townObj = [];
let $td = '';
$town.each(function(i,item){
$td=$townCheerio(item).find('td');
let code='';
let name='';
$td.each(function(i,item){
if($townCheerio(item).find('a').length>0){
if(i===0){
code=$townCheerio(item).find('a').text();
}else{
name=$townCheerio(item).find('a').text();
}
}else{
if(i===0){
code=$townCheerio(item).text();
}else{
name=$townCheerio(item).text();
}
}
});
townObj.push({
townCode: code,
townName: name
});
});
resAreas[n].city[m].county[i].town=copy.deepClone(townObj);
// console.log(resAreas);
} catch (e) {
console.error(e.message);
}
}).on('error', (e) => {
console.error(`出現錯誤: ${e.message}`);
});
});
}
}
// 將數據寫入文件
fs.writeFile('2018國家行政區域.json', JSON.stringify(resAreas), (err) => {
if (err) throw err;
console.log('文件已保存');
});
}
// 獲取省城數據,入口函數
start(firstUrl);
注意:代碼中引入的deepClone是自己此處需要用到的一個深拷貝方法,這個可以自己根據自己需求寫一個;除此之外,上面的代碼運行可能會報錯,一般是套接字斷開或者一些其他類似錯,主要是因爲短時間內請求過於頻繁所導致的,代碼中的setTimeout也是爲了延遲請求,來優化效果,以及在請求中加headers頭部信息,來模擬瀏覽器,都是爲了減少報錯的情況,但是,網上查閱的所有解決方法及現實問題,無法到達百分之百的無誤,當然了,如果不是太多的數據以及遍歷層次不是太深,一般不會有問題的,這邊文章主要是爲了說明日常生活中運用nodeJs技術來寫滿足於自己需求的網絡爬蟲,並學會掌握使用不同技術來實現自己的爬蟲,從而達到自己想要的目的。
完整的行政地區數據地址(精確到鄉/鎮級別): 國家行政區域數據(xlsx表格文件和json格式文件)