如何使用node.js、request和cheerio来设置简单的网页抓取
介绍:
在本教程中,我们将抓取 Hacker News 的首页,以获取所有排名靠前的链接及其元数据 - 例如标题、URL 和收到的点数/评论数。 这是使用 node.js 从网页中提取数据的众多技术之一,主要使用 Matthew Mueller 的名为 cheerio 的模块,该模块实现了专为服务器使用而设计的 jQuery 子集。
如果您已经习惯使用 jQuery,那么 Cheerio 是轻量级、快速、灵活且易于使用的。 我们还将使用 Mikael Rogers' 优秀的 request 模块作为简化的 HTTP 客户端。
要求:
我假设您已经熟悉 node.js、jQuery 和基本的 Linux 管理任务,例如使用 SSH 连接到您的 VPS。
如果您不熟悉 node.js 或尚未安装它,请参阅上面的文章和教程部分以查找适用于您的操作系统的安装说明。
代码:
要使用 NPM 安装所需的模块,只需键入以下命令:
npm install request cheerio
这只会将模块安装在您当前的工作目录中。
要全局安装模块,请运行:npm install -g request cheerio
创建一个名为 scrape.js
的文件并添加以下行:
var request = require('request'); var cheerio = require('cheerio');
这将加载我们所有的模块依赖项。
现在我们将通过一个简单的请求加载 Hacker News 的首页并显示页面的 HTML 代码,如果没有发生错误并且 HTTP 状态代码等于 200。
将这些行附加到文件中:
request('https://news.ycombinator.com', function (error, response, html) { if (!error && response.statusCode == 200) { console.log(html); } });
尝试使用 node scrape.js
运行脚本,您应该会在终端窗口中看到 HTML 代码。
为了知道如何提取我们想要的元数据,我们需要知道元素在 HTML 代码中的结构。 一种首选方法是使用内置于 Google Chrome 中的 Web 开发人员工具来检查网页上所需的目标元素,只需右键单击它并选择“检查元素”即可。
在这个例子中,我在 Chrome 中打开了 Hacker News,右键单击并检查了排名靠前的故事的标题:
在快速浏览了 web-developer 控制台后,我得出的结论是,我们可以简单地选择每个“span”元素,其中添加了一个名为“comhead”的类,然后使用 jQuery 选择它上面的“a”元素API 函数 prev() 可以轻松解析所有 30 个排名靠前的链接及其标题。
我们可以通过将我们的请求代码更改为这样来测试我的假设:
request('https://news.ycombinator.com', function (error, response, html) { if (!error && response.statusCode == 200) { var $ = cheerio.load(html); $('span.comhead').each(function(i, element){ var a = $(this).prev(); console.log(a.text()); }); } });
正如预期的那样,通过运行代码,我们得到了一个包含 30 个标题的列表。 让我们对其进行更多修改以解析剩余的元数据:
request('https://news.ycombinator.com', function (error, response, html) { if (!error && response.statusCode == 200) { var $ = cheerio.load(html); $('span.comhead').each(function(i, element){ var a = $(this).prev(); var rank = a.parent().parent().text(); var title = a.text(); var url = a.attr('href'); var subtext = a.parent().parent().next().children('.subtext').children(); var points = $(subtext).eq(0).text(); var username = $(subtext).eq(1).text(); var comments = $(subtext).eq(2).text(); // Our parsed meta data object var metadata = { rank: parseInt(rank), title: title, url: url, points: parseInt(points), username: username, comments: parseInt(comments) }; console.log(metadata); }); } });
以下是添加代码功能的概述:
选择上一个元素:
var a = $(this).prev();
通过解析“a”元素上方两级的元素来获取排名:
var rank = a.parent().parent().text();
解析链接标题:
var title = a.text();
从“a”元素解析 href 属性:
var url = a.attr('href');
从 HTML 表的下一行获取子文本:
var subtext = a.parent().parent().next().children('.subtext').children();
从孩子身上提取相关数据:
var points = $(subtext).eq(0).text(); var username = $(subtext).eq(1).text(); var comments = $(subtext).eq(2).text();
运行修改后的脚本应该会输出一个对象数组,如下所示:
[ { rank: 1, title: 'The Meteoric Rise of DigitalOcean ', url: 'http://news.netcraft.com/archives/2013/06/13/the-meteoric-rise-of-digitalocean.html', points: 240, username: 'beigeotter', comments: 163 }, { rank: 2, title: 'Introducing Private Networking', url: 'https://www.digitalocean.com/blog_posts/introducing-private-networking', points: 172, username: 'Goranek', comments: 75 }, ...
That's it! 您现在可以将提取的数据存储在 MongoDB 或 Redis 等数据库中以进一步处理它。 这是我们的 scrape.js 文件的完整源代码:
var request = require('request'); var cheerio = require('cheerio'); request('https://news.ycombinator.com', function (error, response, html) { if (!error && response.statusCode == 200) { var $ = cheerio.load(html); var parsedResults = []; $('span.comhead').each(function(i, element){ // Select the previous element var a = $(this).prev(); // Get the rank by parsing the element two levels above the "a" element var rank = a.parent().parent().text(); // Parse the link title var title = a.text(); // Parse the href attribute from the "a" element var url = a.attr('href'); // Get the subtext children from the next row in the HTML table. var subtext = a.parent().parent().next().children('.subtext').children(); // Extract the relevant data from the children var points = $(subtext).eq(0).text(); var username = $(subtext).eq(1).text(); var comments = $(subtext).eq(2).text(); // Our parsed meta data object var metadata = { rank: parseInt(rank), title: title, url: url, points: parseInt(points), username: username, comments: parseInt(comments) }; // Push meta-data into parsedResults array parsedResults.push(metadata); }); // Log our finished parse results in the terminal console.log(parsedResults); } });