Express + Node 爬取网站数据

前言

因为自己写的demo需要历史天气的统计数据,但是国内很难找到免费的api接口,很多都需要付费和审核。而国外的网站虽然免费但需要提前知道观测站,城市id等信息。所以就有了这么一篇文章的诞生。


准备工作

作用
superagent 发送请求
superagent-charset 设置请求的编码
cheerio 让解析html文档像jquery一样简单


实现思路

  1. 找到目标网站
    东方天气网(本文以此为例)
    天气网
  2. 分析网站结构
    通过分析,可以绘制如下流程图
Created with Raphaël 2.1.2 访问基址(异步),拿到城市的根地址 按城市和年月拼成新的地址 访问新地址(异步) 拿到城市在指定月的数据 处理数据
  1. 代码编写思路
    从流程图可以看出,需要先异步请求基址拿到城市的根地址。
    拿到根地址后拼接时间和城市,接着异步访问才能拿到我们要的数据。
    这种嵌套异步可以使用Promise来实现(eventProxy基本已不用)。
  2. 代码实例
var express = require('express');
var router = express.Router();
var superagent = require("superagent");
var charset = require("superagent-charset");    //解决编码问题
var cheerio = require("cheerio");

/* GET users listing. */
var mysql = require("mysql");
var responseJson = require("../util/responseJson"); 

//导入mysql模块
var dbConfig = require("../db/DBconfig");

//使用DBConfig.js的配置信息创建一个MySQL连接池
var pool = mysql.createPool(dbConfig.mysql);

charset(superagent); 

//需要遍历的信息
var BaseUrl = "http://tianqi.eastday.com";
var Cities = ["成都"]; //需要获取的城市
var indexArr = ['cd'];
var Years = ["2018"]; //年份,因为2018年以前dom结构不一样,所以这里只取2018
var Months = ["01", "02", "03", "04", "05", "06", "07", "08"]; //月份

function getCityUrl (city) {
  //返回Promise
  return new Promise((resolve, reject) => {
      superagent.get(BaseUrl + "/history.html")
          .charset("utf-8")
          .end((err, sres) => {
              if (err) {
                  next(err);
                  return;
              }
              let $ = cheerio.load(sres.text);
              //后续继续遍历的基址
              let href = $(".letter-box").find("a[title='" + city + "']").attr("href");
              resolve(href);
          });
  })
}

//获取指定城市在指定时间的数据
function getData (href, city) {
  let year = Years[0];
  return  Months.map(month => {
      let url = BaseUrl + href.replace(".html", "_" + year + month + '.html' );
      //获取天气数据
     return new Promise((resolve, reject1) =>
      {
          superagent.get(url)
              .charset("utf-8")
              .end((err1, sres1) => {
                  if (err1) {
                    reject1(err1);
                    return;
                  }

                  let $ = cheerio.load(sres1.text);
                  let arr = [];
                  $("#weaDetailContainer").find(".weatherInfo-item").each((index, item) => {
                      let $item = $(item);
                      arr.push({
                          time: year + "-" + month + "-" + $item.find(".dateBox").text().substr(0, 2),
                          wea: $item.find(".weather-name").text(),
                          tempL: $item.find(".low-temp").text(),
                          tempH: $item.find(".high-temp").text(),
                          wind: $item.find(".wind").text(),
                      });
                  });
                  resolve(arr);
              });
      });
  });
}

function dispatch(groups) {
  var results = []
  return (function () {
      var fun = arguments.callee
          , group = groups.shift()
      if (!group) {
          return Promise.resolve(results)
      }

      var promises = []
      group.forEach(function (task) {
          promises.push(
              Promise.resolve(task)
          )
      })

      return Promise.all(promises).then(function (rets) {
          results.push(rets)
          return fun()
      })
  }())
} 

function query (sql) {
  return new Promise((resolve, reject) => {
    pool.getConnection((err, conn) => {
      if(err){
        reject(err);
      } else {
        conn.query(sql, (err1, rows, fields) => {
          conn.release();
          if(err1){
            reject(err1);
          } else {
            resolve({
              rows: rows,
              fields: fields
            });
          }
        });
      }
    });
  });
}

function makeSql (item, index) {
  let sql = "INSERT INTO weather_" + indexArr[index] + " (time, wea, tempH, tempL, wind) values ";
  let arr = [].concat.apply([], item);
  arr.map(group => {
    sql += "('"
      + group.time + "', '"
      + group.wea + "', '"
      + group.tempH + "', '"
      + group.tempL + "', '"
      + group.wind
      + "'),";
  });

  return sql.substring(0, sql.length-1);
} 

router.get('/', function(req, res, next) {
  let promiseArr = [];
  promiseArr = Cities.map(city => {
      //遍历城市
      return getCityUrl(city);
  });

  Promise
      .all(promiseArr)
      .then(hrefArr => {
        return hrefArr.map(href => {
          return getData(href);
        });
      })
      .then(arr => {
        return dispatch(arr);
      })
      .then(data => {
        let arr = data.map((item, index) => {
          return query(makeSql(item, index))
        });

        Promise
          .all(arr)
          .then(() => {
            res.json({
              status: true,
              msg: 'success'
            })
          })
          .catch(e => {
            res.json({
              status: false,
              msg: e.message
            })
          })

      })
      .catch(e => {
        res.send(e.message);
      });
});

module.exports = router;

不足

虽然能够实现需求,但是感觉我的Promise在这里用着好像挺乱,没有完全解决嵌套问题。后续会增进学习,对这一部分更加完善。也希望大家能够给出宝贵意见~~

你可能感兴趣的:(Node,Express)