記一次node爬蟲經歷,手把手教你爬蟲

今天業務忽然來了個爬蟲業務,爬出來的數據以Excel的形式導出,下班前一個小時開始作,加班一個小時就作好了。由於過久沒作爬蟲了!作這個需求都是很興奮!

需求說明

  1. 訪問網站
  2. (循環)獲取頁面指定數據源
  3. 根據頁面數據源再(循環)訪問詳情數據
  4. 記錄詳情數據,以Excel形式導出。

所需模塊

根據需求所得五個模塊javascript

// 請求模塊(1.訪問網站)
const request = require('request');

// 能夠看作成node版的jQuery(2.獲取頁面指定數據源)
const cheerio = require("cheerio");

// node異步流程控制 異步循環(3.根據頁面數據源再訪問詳情數據)
const async = require("async");

// Excel表格導出+node自帶文件系統(4.以Excel形式導出)
const excelPort = require('excel-export');
const fs         = require("fs");

安裝模塊:php

npm install request cheerio async excel-export --save-dev

開始發送請求

一開始我直接用request請求網站,但直接返回了404,但我在瀏覽器上看又是沒毛病的。而後我就改了下請求的header。嘻嘻html

request({
    url: 'http://www.foo.cn?page=1',
    method: 'get',
    headers: {
      'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
      // 這裏巨坑!這裏開啓了gzip的話http返回來的是Buffer。
      // 'Accept-Encoding': 'gzip, deflate',
      'Accept-Language': 'zh-CN,zh;q=0.9',
      'Cache-Control': 'no-cache',
    },
    // 想請求回來的html不是亂碼的話必須開啓encoding爲null
    encoding: null
  }, (err, res, body) => {
      // 這樣就能夠直接獲取請求回來html了
      console.log('打印HTML', body.toString()); // <html>xxxx</html>
    }
  );

獲取指定數據源

request({
    url: 'http://www.foo.cn?page=1',
    method: 'get',
    headers: {
      'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
      // 'Accept-Encoding': 'gzip, deflate',
      'Accept-Language': 'zh-CN,zh;q=0.9',
      'Cache-Control': 'no-cache',
    },
    encoding: null
  }, (err, res, body) => {
      console.log('打印HTML', body.toString()); // <html>xxxx</html>
      const list = [];
      const $ = cheerio.load(body.toString());
      // 獲取指定元素
      let item = $('.className tbody tr');
      // 循環獲得元素的跳轉地址和名稱
      item.map((i, index) => {
        let obj = {};
        obj.link = $(index).children('a').attr('href');
        obj.name = $(index).children('a').text();
        list.push(obj);
      });
      console.log('list', list); // [{ link: 'http://xxxx.com', name: 'abc' }]
    }
  );

異步流程控制

先將request封裝多一層,傳入page值和async.series的callbackjava

async function requestPage(page = 1, callback) {
  request({
    url: 'http://www.masuma.cn/product.php?lm=21&page=' + page,
    method: 'get',
    headers: {
      'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
      // 'Accept-Encoding': 'gzip, deflate',
      'Accept-Language': 'zh-CN,zh;q=0.9',
      'Cache-Control': 'no-cache',
    },
    encoding: null
  }, async (err, res, body) => {
     console.log('打印HTML', body.toString()); // <html>xxxx</html>
      const list = [];
      const $ = cheerio.load(body.toString());
      // 獲取指定元素
      let item = $('.className tbody tr');
      // 循環獲得元素的跳轉地址和名稱
      item.map((i, index) => {
        let obj = {};
        obj.link = $(index).children('a').attr('href');
        obj.name = $(index).children('a').text();
        list.push(obj);
      });
      console.log('list', list); // [{ link: 'http://xxxx.com', name: 'abc' }]
      callback(null, list);
    }
  );
}

打印出數據 + 導出Excel

async function main() {
  const requestList = [];
  // 在這裏爲何要用到async.series?
  // 是由於這個爬蟲須要具備順序性,必須得異步請求完一個地址並獲取數據而後再存到一個變量裏才能執行下一個
  // 在此期間我想過其餘方法。例如:
  // for循環 + await 直接否認了
  // Promise.all這個並不能保證數據具備順序
  // 最終敲定用async.series 用完以後!真香啊!
  // 很好奇async.series找個時間也作個源碼解析
  for (let i = 1; i < 36; i++) {
    requestList.push(callback => {
      requestPage(i, callback);
    });
  }
  console.log('requestList', requestList); // [Function, Function] 全是function的數組
  async.series(requestList, (err, result) => {
    // 由於async.series返回來的結果是[[], [], []]這種二維數組形式,每一個function返回來的值都放在一個數組裏,咱們須要將它弄成一維數組好作導出列表
    const arry = [].concat.apply([], result);
    console.log('最終結果!!!!', arry); // [{ link: 'http://xxxx.com', name: 'abc' }, ...]
    writeExcel(arry);
  });
}

const writeExcel = (datas) => {
  // 定義一個對象,存放內容
  let conf = {};
  // 定義表頭
  conf.cols = [
     {caption:'瑪速瑪編碼', type:'string', width:40},
     {caption:'原廠編碼', type:'string', width:60},
  ];
  // 建立一個數組用來屢次遍歷行數據
  let array = [];
  // 循環導入從傳參中獲取的表內容
  for (let i=0;i<datas.length;i++){
      //依次寫入
    array[i] = [
      datas[i].name,
      datas[i].code,
    ];
  }
  // 寫入道conf對象中
  conf.rows = array;
  // 生成表格
  const result = excelPort.execute(conf);
  // 定義表格存放路徑
  fs.writeFile('./表格.xlsx', result, 'binary',function(err){
      if(err){
          console.log(err);
      }
  });
}

main();

總結

其實爬蟲就是:node

  1. 模擬瀏覽器請求,獲取到HTML
  2. 對HTML作解析,將須要數據提取出來
  3. 把數據進一步處理,導出Excel,保存數據庫等等

最後

其實這個爬蟲最終是web

  1. 循環訪問帶有分頁的表格
  2. 提取表格的連接並訪問連接 去到詳情頁
  3. 在詳情頁獲取到我所須要的數據
  4. 最終輸出Excel

但我在這裏就寫了獲取各頁表格裏的連接地址,由於在這裏我只想作一個簡單的分享。數據庫

這些分享應該都足以舉一反三了。npm

相關文章
相關標籤/搜索