js 爬虫抓取网页数据(编程的“回调地狱”和下方的新闻数据(组图))
优采云 发布时间: 2022-02-04 20:13js 爬虫抓取网页数据(编程的“回调地狱”和下方的新闻数据(组图))
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