分分钟搞定node爬虫

Author Avatar
xiongzhaoyang 8月 07, 2019
  • 在其它设备中阅读本文章

教大家如何用 node 写一个简单的爬虫程序

什么是爬虫

爬虫,是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。

在大数据时代,数据就是第一生产力,我们可以拿数据做我们想做的事各种事情。例如我们想买到低价的机票,那可能需要时不时的去各个网站上去看实时的价格。但如果我们掌握了爬虫,我们可以编写脚本定时的去爬取各个网站上实时的机票价格,然后设置一个阈值,当到达这个价格的时候就给自己发通知,这样就可以抢到我们心仪价格的机票了~


准备工作

用 node 做爬虫,我们需要一些工具(npm包)来帮助我们更好的去爬取和解析数据。

  • request 常用的网络请求库
  • iconv-lite 用于将 GBK 编码转成 utf8
  • cheerio 用于解析HTML编码的字符串,并可通过类jquery的方式来获取


网站常见的数据渲染方式

目前网站中的数据基本是通过以下两种方式展示的。

  • 服务端渲染,数据和页面的HTML在一起的。我们直接通过请求页面的URL,就可以拿到 HTML 编码的字符串,然后再去解析它就可以拿到我们想要的数据。
  • 前端渲染,一般通过接口来获取数据并渲染,这种情况我们需要通过控制台的 network 模块找出获取数据的接口,就可以通过调用接口来获取到我们想要的数据。

接下来的实战中会涵盖这两种渲染方式的爬取。


爬虫实战

爬取百度风云榜的电影热搜榜(服务端渲染)

百度风云榜地址

import request from 'request-promise'
import cheerio from 'cheerio'
import iconv from 'iconv-lite'

async function task () {
  // 风云榜 - 娱乐
  const pageUrl = 'http://top.baidu.com/category?c=1'
  let html = await request({
    url: pageUrl,
    encoding: null,
  })
  // 不设置 encoding: null 的话,其内部会自动帮我们将 buffer 转成 string
  // 例如 html = html.toString('utf8')
  html = iconv.decode(html, 'gbk')
  // 解析 HTML 编码的数据
  const $ = cheerio.load(html)

  // 通过类 jquery 的方式解析并输出排行榜数据
  $('.box-cont').each((_, item) => {
    const boxCont = $(item)
    const title = boxCont.find('.hd .title a').text()
    console.log(title)

    Array.from(boxCont.find('.list-title')).forEach((item, idx) => {
      console.log(idx + 1, $(item).attr('title'))
    })
    console.log('\n')
  })
}


爬取京东商品评论接口并分析热销的型号(前端渲染)

iphonexs max 评论区地址

import request from 'request-promise'
import iconv from 'iconv-lite'

// 颜色
const colorMap: any = {}
// 内存
const memoryMap: any = {}
// 版本
const editionMap: any = {}
let count = 1

async function task () {
  // 爬取 10 页评论
  for (let i = 1; i <= 10; i += 1) {
    await analyseComment(i)
  }

  console.log(colorMap)
  console.log(memoryMap)
  console.log(editionMap)
}

/**
 * 分析评论
 * @param {number} [page=1] 分析的页码
 */
async function analyseComment (page = 1) {
  // jd 评论接口
  const pageUrl = 'https://sclub.jd.com/comment/productPageComments.action'
  let data = await request({
    url: pageUrl,
    headers: {
      // 模拟浏览器 UA
      'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',
      // 来源字段必须存在,否则返回数据为空。(jd 基本的反爬虫策略)
      referer: 'https://item.jd.com/100000287113.html',
    },
    qs: {
      productId: 100000287113,
      score: 0,
      sortType: 5,
      page,
      pageSize: 10,
      isShadowSku: 0,
      rid: 0,
      fold: 1,
    },
    encoding: null,
  })
  // 编码转换
  data = iconv.decode(data, 'gbk')
  // 解析 JSON 字符串
  data = JSON.parse(data)

  // 分析数据并显示
  const { comments } = data
  comments.forEach((comment: any) => {
    // 分析颜色,内存和型号(版本)
    const color = comment.productColor
    const memory = comment.productSales[0].saleValue
    const edition = comment.productSize

    colorMap[color] = colorMap[color] ? colorMap[color] + 1 : 1
    memoryMap[memory] = memoryMap[memory] ? memoryMap[memory] + 1 : 1
    editionMap[edition] = editionMap[edition] ? editionMap[edition] + 1 : 1

    console.log(`--第${count}条评论分析成功--`)
    count += 1
  })
  console.log(`--第${page}页评论分析成功--`)
  // 避免请求频繁被封 IP >_<
  await new Promise(r => setTimeout(r, 2000))
}

task()