node.js - crawler爬虫

1,799 阅读4分钟

初始化 & 安装依赖

npm init --yes
npm i express superagent cheerio -s

superAgent 是一个轻量的Ajax API,服务器端(Node.js)客户端(浏览器端)均可使用 SuperAgent中文文档

cheerio 为服务器特别定制的,快速、灵活、实施的jQuery核心实现 cheerio中文文档

搭建简单的服务器

// 创建服务器实例
const express = require('express')
const app = express()

app.get('/', (req,res)=> {
  res.send('爬虫实战')
})

// 获取服务器信息并打印
let server = app.listen(3000, () => {
  let host = server.address().address;
  let port = server.address().port;
  // %s 另一种拼接字符串方法
  console.log('程序已运行 http://%s:%s', host, port);
})

运行服务器

nodemon index.js

浏览器输入 localhost:3000

image-20211004194253481

分析页面内容

百度新闻——海量中文资讯平台 (baidu.com)

示例,打开百度新闻首页,控制台,审查元素

image-20211004200203931

通过 cheerio 来获取 id > ul > li > a 来获取 a标签中的文字

获取页面

引入 superagent 模块,并调用get方法,传入页面地址

const superagent = require('superagent')

superagent.get('http://news.baidu.com/').end((err,res) => {
  if(err) {
    console.log('热点新闻抓取失败' + err);
  }
  console.log(res);
})

保存后服务器更新,终端会打印出结果,由于内容太多,终端容不下,已被覆盖掉了上部

image-20211004201918277

页面地址返回的所有数据,会包含在res中

处理数据

接下来开始处理这些数据

  1. 引入 cheerio库
  2. 下方声明对res的处理函数
  3. 上方声明需要返回的结果
  4. superagent.get() 处理函数内调用方法,并将结果返回到事先声明的变量

这里需要注意请求方法 app.get('/', (req,res)=> { }) 要放到处理函数下面

const express = require('express')
const superagent = require('superagent')
const cheerio = require('cheerio')
const app = express()

let hotNews = []

superagent.get('http://news.baidu.com/').end((err,res) => {
  if(err) {
    console.log('热点新闻抓取失败' + err);
  }
  // 调用函数,返回的结果直接赋值给外部变量
  hotNews = getHotNews(res)
})

let getHotNews = res =>  {
  // 通过 cheerio库的load方法传入res.text (获取res的全部字符串) 得到 $
  let $ = cheerio.load(res.text)
  // 对$方法传入选择器选择元素,得到的是一个
  // $('#pane-news ul li a')
  console.log($('#pane-news ul li a'));

}

app.get('/', (req,res)=> {
  res.send(hotNews)
})

// 获取服务器信息并打印
let server = app.listen(3000, () => {
  let host = server.address().address;
  let port = server.address().port;
  // %s 另一种拼接字符串方法
  console.log('程序已运行 http://%s:%s', host, port);
})

$('#pane-news ul li a') 返回的是一个包含所有对应节点对象的数组

image-20211004204536588
let getHotNews = res => {
  // 声明空数组
  let hotNews = []
  // 通过 cheerio库的load方法传入res.text (获取res的全部字符串) 得到 $
  let $ = cheerio.load(res.text)
  // 对$方法传入选择器选择元素,得到一个包含所有对应元素的数组
  // 对数组进行遍历,获取每个元素内的 text 和 href 放入 news 对象
  $('#pane-news ul li a').each((index, ele) => {
    let news = {
      title: $(ele).text(), // 获取新闻标题
      href: $(ele).attr('href') // 获取新闻页面链接
    }
    hotNews.push(news) // 每次遍历的结果 把news推入事先声明的数组
  })
  // 遍历结束将结果返回出去,通过调用将结果赋值给最上方的空对象
  return hotNews
}

返回数据

在函数调用后打印结果

image-20211004214311801

传递的值改为返回的值

app.get('/', (req,res)=> {
  res.send(hotNews)
})
image-20211004215440777

当然获取数据后,也可能不是要直接显示的客户端的

这部分接下来可以在superagent这里处理

superagent.get('http://news.baidu.com/').end((err,res) => {
  if(err) {
    console.log('热点新闻抓取失败' + err);
  }
  // 调用函数,返回的结果直接赋值给外部变量
  hotNews = getHotNews(res)
  /*
  1.存入数据库
  2.跳转到对应的路由,显示数据页面 /echarts
  3.路由页面请求数据库中的数据,显示到echarts图表中
  */
})

抓取本地新闻失败的原因

image-20211004230426463image-20211004230350804

因为代码是重复写了一遍,这里就不写了,之所以获取不到这部分的内容,是因为这部分的数据是要在浏览器当前页面发起请求,动态获取的

通过superagent访问 news.baidu.com 获取的是这个域名下的全部静态内容,并不能触发函数请求完成动态内容的加载

解决办法就是使用第三方插件,模拟浏览器访问百度新闻首页,在这个模拟浏览器中当 动态内容 加载成功后,抓取数据,返回给前端浏览器

nightmare 实现动态数据抓取

segmentio/nightmare: A high-level browser automation library. (github.com)

使用 nightmare 自动化测试工具

electron 可以使用纯 javascript 调用 chrome 丰富的原生接口来创造桌面应用,可以把它看做一个专注于桌面应用的 node.js 变体,而不是 web 服务器,其基于浏览器的应用方式可以极为方便的做各种响应式交互

nightmare 是一个基于 electron 的框架,针对 web 自动化测试和爬虫,因为其具有跟 plantomJS一样的自动化测试的功能可以在页面上模拟用户的行为触发一些异步数据加载,也可以跟 request 库一样直接访问 URL 来抓取数据,并且可以设置页面的延迟时间,所以无论是手动触发脚本还是行为触发脚本都是轻而易举的

安装依赖

npm i nightmare -s

使用

引入模块,获取实例,并调用方法动态获取数据

const express = require('express')
const app = express()
const Nightmare = require('nightmare')
// 设置 show: true 会显示一个自动化的内置浏览器
const nightmare = Nightmare({ show: true})
const cheerio = require('cheerio')

let localNews = []
//---------------------------------------------------------------------------------
nightmare
  .goto('http://news.baidu.com')// 需要访问的链接
  .wait('div#local_news') //等待加载的节点
  .evaluate(() => document.querySelector('div#local_news').innerHTML)// 评估节点内容 
  .then(htmlStr => { //获取到html字符串
    localNews = getLocalNews(htmlStr) // 调用方法
  })
  .catch(err => {
    console.error(err)
  })
//----------------------------------------------------------------------------------
let getLocalNews = htmlStr => {
  let localNews = []
  let $ = cheerio.load(htmlStr) // 这里因为获取到的已经是字符串了,不需要.text
  $('ul#localnews-focus li a').each((index, ele) => {
    let news = {
      title: $(ele).text(),
      href: $(ele).attr('href')
    }
    localNews.push(news)
  })
  return localNews
}

app.get('/', (req,res)=> {
  res.send(localNews)
})

// 获取服务器信息并打印
let server = app.listen(3000, () => {
  let host = server.address().address;
  let port = server.address().port;
  // %s 另一种拼接字符串方法
  console.log('程序已运行 http://%s:%s', host, port);
})

现在打开链接,已经可以看到动态加载的内容了

image-20211004234806548