nodeJs網絡爬蟲技術(本例子爲爬去國家行政區域地區數據)

這裏將會介紹兩種技術方案,都是基於nodejs相關技術進行展開的,唯一不同的就是選取的技術稍有不同,本質其實一樣

第一種技術方案:

需要用到的技術模塊:superagent、superagent-charset、cheerio、fs
安裝以上依賴模塊(也可以單獨依次安裝):npm install superagent superagent-charset cheerio fs

說明:

  1. Superagent是輕量級漸進式ajax API,具有靈活性,可讀性和低學習曲線,在受到許多現有請求API的挫敗之後。它也適用於Node.js!
  2. superagent-charset是superagent裏的一個功能模塊,主要用於對字符編碼格式的處理
  3. cheerio主要用於解析html頁面dom,用來操作dom
  4. 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

說明:

  1. iconv-lite主要用於處理編碼格式問題
  2. cheerio主要用於解析html頁面dom,用來操作dom
  3. fs是nodejs下的一個文件系統模塊,此處爲了將最終數據寫入文件當中去
  4. 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格式文件)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章