js 爬虫抓取网页数据(编程的“回调地狱”和下方的新闻数据(组图))

优采云 发布时间: 2022-02-04 20:13

  js 爬虫抓取网页数据(编程的“回调地狱”和下方的新闻数据(组图))

  1、首先我们来分析一下百度新闻首页的页面信息。

  

  

  百度新闻首页大致分为“热点新闻”、“本地新闻”、“国内新闻”、“国际新闻”……等。这次,我们尝试从左侧的“热点新闻”和下方的“本地新闻”中抓取新闻数据。

  

  F12 打开Chrome控制台,查看页面元素,查看左侧“热点新闻”信息所在的DOM结构后,发现所有“热点新闻”信息(包括新闻头条和新闻页面链接)在#pane-news 的 id 中

  > 在下面的选项卡中。在 jQuery 选择器中:#pane-news ul li a.

  2、为了爬取新闻数据,首先我们使用superagent请求目标页面,获取整个新闻首页信息

  // 引入所需要的第三方包

const superagent= require('superagent');

let hotNews = []; // 热点新闻

let localNews = []; // 本地新闻

/**

* index.js

* [description] - 使用superagent.get()方法来访问百度新闻首页

*/

superagent.get('http://news.baidu.com/').end((err, res) => {

if (err) {

// 如果访问失败或者出错,会这行这里

console.log(`热点新闻抓取失败 - ${err}`)

} else {

// 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res

// 抓取热点新闻数据

hotNews = getHotNews(res)

}

});

  3、获取到页面信息后,我们定义一个函数getHotNews()来抓取页面中的“热点新闻”数据。

  /**

* index.js

* [description] - 抓取热点新闻页面

*/

// 引入所需要的第三方包

const cheerio = require('cheerio');

let getHotNews = (res) => {

let hotNews = [];

// 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res.text中。

/* 使用cheerio模块的cherrio.load()方法,将HTMLdocument作为参数传入函数

以后就可以使用类似jQuery的$(selectior)的方式来获取页面元素

*/

let $ = cheerio.load(res.text);

// 找到目标数据所在的页面元素,获取数据

$('div#pane-news ul li a').each((idx, ele) => {

// cherrio中$('selector').each()用来遍历所有匹配到的DOM元素

// 参数idx是当前遍历的元素的索引,ele就是当前便利的DOM元素

let news = {

title: $(ele).text(), // 获取新闻标题

href: $(ele).attr('href') // 获取新闻网页链接

};

hotNews.push(news) // 存入最终结果数组

});

return hotNews

};

  这里多说几句:

  async/await 据说是异步编程的终极解决方案,它让我们能够以同步的思维方式进行异步编程。Promise 解决了异步编程的“回调地狱”。Async/await 还使异步过程控制变得友好和清晰。有兴趣的同学可以了解一下。它真的很有用。superagent模块提供了get、post、delte等多种方法,可以方便的进行ajax请求操作。请求结束后执行 .end() 回调函数。.end() 接受一个函数作为参数,该函数又具有两个参数 error 和 res。当请求失败时,error 将收录返回的错误信息。如果请求成功,则错误值为null,返回的数据会收录在res参数中。这 。Cheerio 模块的 load() 方法将 HTML 文档作为参数传入函数中,然后可以使用类似于 jQuery 的 $(selectior) 的方法来获取页面元素。您还可以使用类似于 jQuery 的 .each() 来迭代元素。另外,还有很多方法,你可以自己google/baidu。

  4、将抓取的数据返回给前端浏览器

  之前, const app = express(); 实例化一个 express 对象应用程序。

  app.get('', async() => {}) 接受两个参数,第一个参数接受一个String类型的路由路径,代表Ajax的请求路径。第二个参数接受一个函数Function,该函数中的代码在请求路径时执行。

  /**

* [description] - 跟路由

*/

// 当一个get请求 http://localhost:3000时,就会后面的async函数

app.get('/', async (req, res, next) => {

res.send(hotNews);

});

  在DOS下执行项目根目录baiduNews下的node index.js,使项目运行起来。之后打开浏览器,访问:3000,你会发现抓取到的数据返回到前端页面。我运行代码后浏览器显示的返回信息如下:

  注意:因为我的 Chrome 上安装了 JSONView 扩展,所以在显示页面时,返回的数据会自动格式化成结构化的 JSON 格式,方便查看。

  

  行!!这样,一个简单的百度“热点新闻”爬虫就完成了!!

  总结一下,步骤其实很简单:

  Express启动一个简单的Http服务,分析目标页面的DOM结构,找到要抓取信息的相关DOM元素,使用superagent请求目标页面,使用cheerio获取页面元素,获取目标数据并返回数据到前端浏览器

  现在,继续我们抓取“本地新闻”数据的目标(在编码时,我们会遇到一些有趣的问题)

  有了之前的基础,我们自然会想到对“地方新闻”数据采用和上面一样的方法。

  1、 分析页面“本地新闻”板块的DOM结构,如下图:

  

  F12打开控制台查看“本地新闻”DOM元素,我们发现“本地新闻”主要分为“左侧新闻”和右侧“新闻”两部分。所有这些目标数据都在一个 ID 为 #local_news 的 div 中。“左新闻”数据在 ul 标签下的 li 标签下的 a 标签中,id 为#localnews-focus,包括新闻标题和页面链接。“本地新闻”数据在id为#localnews-zixun的div下的ul标签下的li标签下的a标签中,包括新闻头条和页面链接。

  2、好的!分析DOM结构并确定数据的位置。接下来就像爬取“热点新闻”一样,一步一步定义一个getLocalNews()函数来爬取数据。

  /**

* [description] - 抓取本地新闻页面

*/

let getLocalNews = (res) => {

let localNews = [];

let $ = cheerio.load(res);

// 本地新闻

$('ul#localnews-focus li a').each((idx, ele) => {

let news = {

title: $(ele).text(),

href: $(ele).attr('href'),

};

localNews.push(news)

});

// 本地资讯

$('div#localnews-zixun ul li a').each((index, item) => {

let news = {

title: $(item).text(),

href: $(item).attr('href')

};

localNews.push(news);

});

return localNews

};

  相应的,在superagent.get()中请求页面后,我们需要调用getLocalNews()函数来抓取本地新闻数据。

  superagent.get() 函数修改为:

  superagent.get('http://news.baidu.com/').end((err, res) => {

if (err) {

// 如果访问失败或者出错,会这行这里

console.log(`热点新闻抓取失败 - ${err}`)

} else {

// 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res

// 抓取热点新闻数据

hotNews = getHotNews(res)

localNews = getLocalNews(res)

}

});

  同时,我们需要在 app.get() 路由中将数据返回给前端浏览器。app.get() 路由代码修改为:

  /**

* [description] - 跟路由

*/

// 当一个get请求 http://localhost:3000时,就会后面的async函数

app.get('/', async (req, res, next) => {

res.send({

hotNews: hotNews,

localNews: localNews

});

});

  代码完成了,好激动!!让项目在DOS下运行,使用浏览器访问:3000

  尴尬的事情发生了!!返回的数据只是热点新闻,本地新闻返回一个空数组[ ]。检查代码,发现没有问题,但是为什么一直返回一个空数组呢?

  经过一番搜索,我发现问题出在哪里!!

  一个有趣的问题 为了找到原因,首先我们看一下使用 superagent.get('').end((err, res) => {}) 在回调函数 .end() 中请求第二个百度新闻首页 参数 res 究竟得到了什么?

  // 新定义一个全局变量 pageRes

let pageRes = {}; // supergaent页面返回值

// superagent.get()中将res存入pageRes

superagent.get('http://news.baidu.com/').end((err, res) => {

if (err) {

// 如果访问失败或者出错,会这行这里

console.log(`热点新闻抓取失败 - ${err}`)

} else {

// 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res

// 抓取热点新闻数据

// hotNews = getHotNews(res)

// localNews = getLocalNews(res)

pageRes = res

}

});

// 将pageRes返回给前端浏览器,便于查看

app.get('/', async (req, res, next) => {

res.send({

// {}hotNews: hotNews,

// localNews: localNews,

pageRes: pageRes

});

});

  访问浏览器:3000,页面显示如下内容:

  

  可以看到,返回值中的文本字段应该是整个页面的HTML代码的字符串格式。为了方便我们观察,我们可以直接将文本字段值返回给前端浏览器,这样就可以清楚的看到浏览器渲染的页面。

  修改返回值到前端浏览器

  app.get('/', async (req, res, next) => {

res.send(pageRes.text)

}

  访问浏览器:3000,页面显示如下内容:

  

  回顾元素后发现我们抓取的目标数据所在的DOM元素是空的,里面也没有数据!

  在这里,一切都归结为它!当我们使用superagent.get()访问百度新闻首页时,在获取到的res收录的页面内容中,还没有生成我们想要的“本地新闻”数据,并且DOM节点元素为空,所以出现了前面的情况!抓取后返回的数据始终为空数组[ ]。

  

  在控制台的Network中,我们发现页面曾经请求过这样一个接口:

  :3000/widget?id=LocalNews&ajax=json&t=17,接口状态404。

  这应该是百度新闻获取“本地新闻”的接口。一切都在这里了!“本地新闻”是在页面加载完成后动态请求上述接口获取的,所以当我们使用superagent.get()请求的页面请求这个接口时,接口URL的hostname部分就变成了本地IP地址,而本机没有这个接口,所以404,没有请求数据。

  找到原因,让我们想办法解决这个问题!!

  直接使用superagent访问正确合法的百度“本地新闻”界面,获取数据返回给前端浏览器。使用第三方npm包模拟浏览器访问百度新闻首页。在这个模拟浏览器中,当“本地新闻”加载成功后,数据被抓取并返回给前端浏览器。

  上面的方法都可以用,我们试试第二种更有趣的方法

  使用 Nightmare 自动化测试工具 Electron,您可以使用纯 JavaScript 调用 Chrome 丰富的原生接口来创建桌面应用程序。您可以将其视为 Node.js 的变体,专注于桌面应用程序而不是 Web 服务器。其基于浏览器的应用方式可以很方便的进行各种响应式交互

  Nightmare 是一个基于 Electron 的 Web 自动化测试和爬虫框架,因为它具有和 PlantomJS 一样的自动化测试功能,可以模拟用户在页面上的行为来触发一些异步数据加载,也可以像 Request 一样直接访问 URL图书馆。抓取数据,您可以设置页面的延迟时间,因此手动或行为触发脚本变得轻而易举。

  安装依赖项

  // 安装nightmare

yarn add nightmare

  要获取“本地新闻”,请继续编码...

  将以下代码添加到 index.js:

  const Nightmare = require('nightmare'); // 自动化测试包,处理动态页面

const nightmare = Nightmare({ show: true }); // show:true 显示内置模拟浏览器

/**

* [description] - 抓取本地新闻页面

* [nremark] - 百度本地新闻在访问页面后加载js定位IP位置后获取对应新闻,

* 所以抓取本地新闻需要使用 nightmare 一类的自动化测试工具,

* 模拟浏览器环境访问页面,使js运行,生成动态页面再抓取

*/

// 抓取本地新闻页面

nightmare

.goto('http://news.baidu.com/')

.wait("div#local_news")

.evaluate(() => document.querySelector("div#local_news").innerHTML)

.then(htmlStr => {

// 获取本地新闻数据

localNews = getLocalNews(htmlStr)

})

.catch(error => {

console.log(`本地新闻抓取失败 - ${error}`);

})

  将 getLocalNews() 函数修改为:

  /**

* [description]- 获取本地新闻数据

*/

let getLocalNews = (htmlStr) => {

let localNews = [];

let $ = cheerio.load(htmlStr);

// 本地新闻

$('ul#localnews-focus li a').each((idx, ele) => {

let news = {

title: $(ele).text(),

href: $(ele).attr('href'),

};

localNews.push(news)

});

// 本地资讯

$('div#localnews-zixun ul li a').each((index, item) => {

let news = {

title: $(item).text(),

href: $(item).attr('href')

};

localNews.push(news);

});

return localNews

}

  修改 app.get('/') 路由为:

  /**

* [description] - 跟路由

*/

// 当一个get请求 http://localhost:3000时,就会后面的async函数

app.get('/', async (req, res, next) => {

res.send({

hotNews: hotNews,

localNews: localNews

})

});

  此时,让项目再次在DOS命令行下运行,浏览器访问:3000查看页面显示的信息,看看“本地新闻”数据是否被抓取!

  至此,一个简单完整的爬取百度新闻页面“热点新闻”和“本地新闻”的爬虫就完成了!!

  最后,总结一下,整体思路如下:

  Express 启动一个简单的 Http 服务来分析目标页面的 DOM 结构,并找到要捕获的信息的相关 DOM 元素。使用superagent请求目标页面的动态页面(需要运行JS的页面或者加载页面后请求界面)可以使用Nightmare模拟浏览器访问使用cheerio获取页面元素并获取目标数据完整代码爬虫完成代码GitHub地址:完整代码

  后面应该有一些进阶的步骤来爬取网站上的一些好看的图片(手动搞笑),这里面会涉及到一些并发控制和反反爬虫的策略。然后使用爬虫抓取一些需要登录并输入验证码的网站。欢迎大家关注和正确沟通。

  再次感谢大家的点赞、关注和评论,感谢大家的支持,谢谢!我自己认为我是一个热爱文字、音乐和编码的半文学程序员。我一直在考虑写关于技术和其他文学的文章。虽然我的基础不是那么好,但我总觉得在写文章的过程中,无论是技术还是文学,都能督促自己去思考,督促自己去学习和交流。毕竟,除了每天忙碌之外,我还要过自己不一样的生活。所以,如果以后有什么好的文章,我会积极的分享给大家!再次感谢大家的支持!

  节点.js

  阅读34.8k更新于2020-01-09

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线