JOJO毕设要用微博数据,网上找的爬虫不知道为啥用着一直有问题,一直催我,没办法就自己写了一个试试

项目代码在这里

参考了如下内容

爬虫主体

爬虫主要结构如下

 - config # 配置文件
   - index.js # 全局配置
   - region.json # 地区json
   - sublist.json # 包含项json
   - typelist.json # 类型json
 - index.js # 启动项

仔细分析,爬虫主要有以下几部分构成

  1. 构造url字符串
  2. 请求url对应的html
  3. 对html进行分析,获取对应信息
  4. 将所需信息保存并下载

事实上我完全没有按照这个流程写2333

构造URL

这里分析了微博搜索时的url结构,主要有以下几个字段

# 搜索文字
# 搜索类型
# 搜索包含项
# 搜索时间区间
# 搜索地区

考虑到参数内容过多,我在这里将url完全拆分出来,使用单独的config进行配置,具体内容如下


var isTopic = true // 如果是 #话题# 类型 则true
var searchWords = ''
var keywords = encodeURI(searchWords)

var typeList = require('./typelist.json')
// 可选类型:全部  热门  原创  关注人  认证用户  媒体  观点
var type = ''

var subList = require('./sublist.json')
// 可选参数:全部  含图片  含视频  含音乐  含短链
var sub = ''

// timescope=custom:2020-05-01-0:2020-05-03-9
var startTime = '' // yyyy-mm-dd-h
var endTime = '' // yyyy-mm-dd-h


var area = require('./region.json') // 陕西西安
var province = '' // 省份
var city = '' // 城市

var provinceAll = true // 如果province == 全部, 则provinceAll = true

// 下载时间间隔 单位:秒(s)
var timeDelay = 10 // default

var pageConfig = {
  'q': isTopic ? `%23${keywords}%23` : `${keywords}`,
  'type': `${typeList[type]}`,
  'include': `${subList[sub]}`,
  'timescope':`${startTime}:${endTime}`,
  'region': provinceAll ? ``: `${area[province].code}:${area[province].city[city]}`,
  'delay': timeDelay * 1000
}

module.exports = pageConfig

请求HTML

因为我习惯了superagent,这里就用的这个

return superagent
  .get(url)
  .set({
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7',
    'Cookie': cookie,
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36'
  })

分析HTML,获取对应信息

思考了很久,最终放弃了cheerio,选择使用jquery + JSDOM,不得不说真香

这部分代码写的挺杂乱的

  const { window } = new JSDOM(res.text)
  const $ = require('jquery')(window)

  var userName = $('.name') // 用户名 数组对象
  var userNameArr = Array.from(userName).map(val => val.text.trim()) // 用户名 数组值

  var userText = $('.txt') // 用户发言文本 数组对象
  var userTextArr = Array.from(userText).map(val => val.innerHTML) // 用户发言文本
  var regText = /展开全文/g
  var trueTextInfo = userTextArr.filter(val => !regText.test(val))
  var regTag = /<\/?.+?\/?>/g
  var userTextInfo = trueTextInfo.map(val => val.replace(regTag, '')).map(val => val.trim()) // 用户发言文本 数组

  var infoFrom = $('.from') // 用户from 信息数组对象
  var regcreateTime = /(\d{2}月\d{2}日.*?\d{2}:\d{2})|(\d{1}.*?前)/
  var regFrom = /rel="nofollow">(.*?)<\/a>/

  var infoFromArr = Array.from(infoFrom).map(val => val.innerHTML) // 数组值

  var createTime = infoFromArr.map(val => val.match(regcreateTime)).map(val => val ? val[1]: '') // 创建时间
  var deviceInfo = infoFromArr.map(val => val.match(regFrom)).map(val => val ? val[1] : '') // 设备信息

  var controlPanel = $('.card-act ul li a')

  var repeat = controlPanel.filter(val => val % 4 === 1) // 转发数 数组对象
  var repeatArr = Array.from(repeat).map(val => val.text.trim().split(" ")[1]) // 转发数 数组值

  var comment = controlPanel.filter(val => val % 4 === 2) // 评论数 数组对象
  var commentArr = Array.from(comment).map(val => val.text.trim().split(" ")[1]) // 评论数 数组值

  var star = controlPanel.filter(val => val % 4 === 3) // 点赞数 数组对象
  var starArr = Array.from(star).map(val => val.text.trim()) // 点赞数 数组值

保存信息并下载

这里使用了node-xlsx将每次发送请求后获取的页面单独保存

因为我不会合并23333

  // 页面信息存放在数组中
  var pageInfoArr = []

  for (let i = 0; i < userNameArr.length; i++) {
    pageInfoArr.push([userNameArr[i], userTextInfo[i], createTime[i], deviceInfo[i], repeatArr[i], commentArr[i], starArr[i]])
  }

  // xlsx 表头
  var title = ['name', 'text', 'createTime', 'deviceInfo', 'repeat', 'comment', 'star']

  var arr = []

  arr.push(title)
  arr = arr.concat(pageInfoArr)

  writeXls(arr)

  /**
    * @description
    * @param {Array} datas
    */
  function writeXls(datas) {
    let buffer = xlsx.build([
      {
        name: 'sheet1',
        data: datas
      }
    ])
    fs.writeFile(`./${xlsxName}.xlsx`, buffer, err => {
      if (err) {
        console.log(err)
      } else {
        console.log('OK')
      }
    })
  }

后续

因为没考虑好如何将每次请求的数据都写入同一个xlsx

同时考虑到爬虫不能一次发送过多请求,以免(逃~~~

这里单独用定时器设定了启动间隔,以及xlsx的名称

  urlList.push(url)
  for (let i = 1; i < totalPage; i++) {
    urlList.push(`${url}&page=${i+1}`)
  }

  var xlsxCount = 1 // xlsx name +
  var urlCount = 0 // url list +

  function parse() {
    if (xlsxCount <= totalPage) {
      xlsxName = `page${xlsxCount++}`

      setText(urlList[urlCount++])
      console.log(xlsxName)
    }
  }

  setInterval(parse, config['delay'])
Last modification:May 4th, 2020 at 08:03 pm
如果觉得我的文章对你有用,请随意赞赏