node简易爬虫

196 阅读1分钟

最近想写一个vue3.0的电商demo,但是苦于缺少海量的数据,于是便百度了很久,最终用node做出一个简易的爬虫。

一、首先整理下这次用到的模块

  • https
  • request
  • fs
  • path
  • cheerio (https和request都选用的原因,纯粹是想都学习一点)

二、选择需要爬取的网站

let url = "https://www.XXXXXXXX.com/";

三、爬取并分析数据

https
  .get(url, (res) => {
    //安全判断
    const { statusCode } = res; //状态码
    const contentType = res.headers["content-type"]; //文件类型

    let error;
    //判断响应码是否为200,文件类型是否为网页
    if (statusCode !== 200) {
      error = new Error("Request Failed.\n" + `Status Code: ${statusCode}`);
    } else if (!/^text\/html/.test(contentType)) {
      error = new Error(
        "Invalid content-type.\n" +
          `Expected application/json but received ${contentType}`
      );
    }

    if (error) {
      console.error(error.message);
      // 消费响应数据以释放内存
      res.resume();
      return;
    }
    
    /*   上面照抄就好   */

    let rawData = "";
    res.on("data", (chunk) => {
      //因为数据流足够大的时候,是分段传输的,所以要拼接
      rawData += chunk;
    });
    res.on("end", (err) => {
      if (err) throw err;
      //将获取到的数据写入文件,找不到文件会自动创建
      fs.writeFileSync("./rawData.html", rawData);//这里就已经得到整个页面了
      
      //用cherrio模块过滤页面数据
      //1.首页商品分类图片及title
      let $ = cheerio.load(rawData);
      let classify = new Array();
      let obj = new Object();
      //遍历选中的元素节点
      //因为我想要得到一个双层的json数组对象,所以这里稍微处理的有点麻烦,要遍历两次,第一层遍历到商品种类的名称,第二层在遍历到这个种类下细分的分支名称和图片。
      $("div.nav-classify-item").each((index, el) => {
        //定位到每个商品的具体种类
        $(el)
          .find("div.nav-classify-title>a")
          .each((childIndex, childEl) => {
            obj = {};
            obj.classifyName = $(childEl).text();
            //获取每种商品下的title和img
            let details = new Array();
            let childObj = new Object();
            $(el)
              .find("div.nav-classify-list-item")
              .each((secondIndex, secondItem) => {
                childObj = {};
                childObj.img = $(secondItem).find("img").attr("data-image");
                //数据中的“/”需要注意,用来命名文件时,是不支持的,需要用正则替换掉
                childObj.title = $(secondItem)
                  .find("span")
                  .text()
                  .replace(/\//g, "-");
                details.push(childObj);
              });
            obj.details = details;
            classify.push(obj);
          });
      });
      //写入文件
      fs.writeFileSync("./homeClassify.json", JSON.stringify(classify));
    });
  })
  .on("error", (err) => {
    console.log(err.message);
  });

四、等待写入完毕、调取下载函数

//5秒后执行,文件读取,不然还没写入完就读取会报错!!
setTimeout(() => {
  downLoad();
}, 5000);

一定要等待写入完成以后再去读取数据!不然会报错,我这里暂时就用个定时器控制下,达到顺序执行的目的,有好的方法的帮忙指导下。

五、定义下载函数

function downLoad() {
  let state = fs.existsSync(path.join("./", "classifyImgFile"));
  //2.1.1如果返回true,说明文件目录存在
  if (state) {
    console.log("目录已存在!");
  } else {
    //2.1.2否则说明不存在,可以创建
    fs.mkdirSync("./classifyImgFile");
    console.log("目录创建完毕!");
    //2,2读取文件数据
    let result = JSON.parse(fs.readFileSync("./homeClassify.json").toString());
    console.log("读取需要下载图片文件的数据!");
    for (let itm of result) {
      //获取到每个商品种类的名称,并创建文件夹
      let name = itm.classifyName;
      fs.mkdirSync("./classifyImgFile/" + name);
      //2.3下载图片
      itm.details.forEach((value, index) => {
        request(value.img).pipe(
          fs.createWriteStream(`./classifyImgFile/${name}/${value.title}.png`)
        );
      });
    }
    console.log("商品图片种类-子级目录创建完毕!");
  }
}

最终看下成果:

微信截图_20211109145804.png 微信截图_20211109145818.png