nodejs抓取动态网页(HTTP客户端将请求发送到服务器,然后从服务器服务器来查询)
优采云 发布时间: 2022-04-13 12:29nodejs抓取动态网页(HTTP客户端将请求发送到服务器,然后从服务器服务器来查询)
HTTP 客户端是一种向服务器发送请求,然后从服务器接收响应的工具。本文中讨论的大多数工具在后台使用 HTTP 客户端来查询您将尝试抓取的 网站 服务器。
RequestRequest 是 Javascript 生态系统中使用最广泛的 HTTP 客户端之一,但是现在 Request 库的作者已经正式表示不建议大家继续使用它。不是说不能用,还有很多库还在用,真的很好用。使用 Request 发出 HTTP 请求非常简单:
const request = require('request')<br />request('https://www.reddit.com/r/programming.json', function (<br /> error,<br /> response,<br /> body) {<br /> console.error('error:', error)<br /> console.log('body:', body)<br />})
你可以在 Github 上找到 Request 库并运行 npm install request 来安装它。在这里您可以参考弃用通知和详细信息:
AxiosAxios 是一个基于 Promise 的 HTTP 客户端,在浏览器和 NodeJS 中运行。如果你使用 Typescript,axios 可以覆盖内置类型。通过axios发起HTTP请求非常简单。默认情况下它具有内置的 Promise 支持,不像 Request,它必须使用回调:
const axios = require('axios')<br />axios<br /> .get('https://www.reddit.com/r/programming.json')<br /> .then((response) => {<br /> console.log(response)<br /> })<br /> .catch((error) => {<br /> console.error(error)<br /> });
如果你喜欢 Promises API 的 async/await 语法糖,你也可以使用它们,但由于顶级 await 仍处于第 3 阶段,我们只能使用 Async Function 代替:
async function getForum() {<br /> try {<br /> const response = await axios.get(<br /> 'https://www.reddit.com/r/programming.json'<br /> )<br /> console.log(response)<br /> } catch (error) {<br /> console.error(error)<br /> }<br />}
你只需调用getForum!你可以在 Github 上找到 Axios 库并运行 npm install axios 来安装它。
超级代理
与 Axios 类似,Superagent 是另一个强大的 HTTP 客户端,支持 Promises 和 async/await 语法糖。它的 API 和 Axios 一样简单,但 Superagent 依赖较多,不太流行。
在 Superagent 中,HTTP 请求是使用 Promise、async/await 或回调发出的,如下所示:
const superagent = require("superagent")<br />const forumURL = "https://www.reddit.com/r/programming.json"<br />// callbacks<br />superagent<br /> .get(forumURL)<br /> .end((error, response) => {<br /> console.log(response)<br /> })<br />// promises<br />superagent<br /> .get(forumURL)<br /> .then((response) => {<br /> console.log(response)<br /> })<br /> .catch((error) => {<br /> console.error(error)<br /> })<br />// promises with async/await<br />async function getForum() {<br /> try {<br /> const response = await superagent.get(forumURL)<br /> console.log(response)<br /> } catch (error) {<br /> console.error(error)<br /> }
你可以在 Github 上找到 Superagent 库并运行 npm install superagent 来安装它。
对于下面介绍的网页抓取工具,本文将使用 Axios 作为 HTTP 客户端。
正则表达式:艰难之路
在没有任何依赖关系的情况下开始抓取 Web 内容的最简单方法是在使用 HTTP 客户端查询网页时收到的 HTML 字符串上应用一组正则表达式 - 但这种方法绕路太远了。正则表达式不是那么灵活,许多专业人士和爱好者很难编写正确的正则表达式。
对于复杂的网页抓取任务,正则表达式很快就会成为瓶颈。无论如何,让我们先尝试一下。假设有一个带有用户名的标签,我们需要用户名,那么使用正则表达式时的方法几乎是这样的:
const htmlString = 'Username: John Doe'<br />const result = htmlString.match(/(.+)/)<br />console.log(result[1], result[1].split(": ")[1])<br />// Username: John Doe, John Doe
在 Javascript 中,match() 通常返回一个收录与正则表达式匹配的所有内容的数组。第二个元素(在索引 1 处)将找到标签的 textContent 或 innerHTML,这正是我们想要的。但是这个结果将收录一些我们不需要的文本(“用户名:”),必须将其删除。如您所见,这种方法对于一个非常简单的用例来说很麻烦。所以我们应该使用 HTML 解析器之类的工具,这些工具将在后面讨论。
Cheerio:在其核心遍历 DOM JQuery Cheerio 是一个高效且轻量级的库,允许您在服务器端使用 JQuery 丰富而强大的 API。如果您以前使用过 JQuery,那么使用 Cheerio 很容易上手。它消除了 DOM 的所有不一致和与浏览器相关的特性,并公开了一个用于解析和操作 DOM 的高效 API。
const cheerio = require('cheerio')<br />const $ = cheerio.load('
你好世界 ')
$('h2.title').text('你好!')
$('h2').addClass('欢迎')
$.html()
// 你好呀!
如您所见,Cheerio 的工作方式与 JQuery 非常相似。但是,它与 Web 浏览器的工作方式不同,这意味着它不能:
因此,如果您尝试抓取的 网站 或 Web 应用程序有很多 Javascript 内容(例如“单页应用程序”),那么 Cheerio 不是您的最佳选择,您可能不得不依赖以下内容讨论其他一些选项。
为了展示 Cheerio 的强大功能,我们将尝试爬取 Reddit 中的 r/programming 论坛以获取帖子标题列表。
首先,运行以下命令安装 Cheerio 和 axios:npm install Cheerio axios。
然后创建一个名为 crawler.js 的新文件并复制/粘贴以下代码:
const axios = require('axios');<br />const cheerio = require('cheerio');<br />const getPostTitles = async () => {<br /> try {<br /> const { data } = await axios.get(<br /> 'https://old.reddit.com/r/programming/'<br /> );<br /> const $ = cheerio.load(data);<br /> const postTitles = [];<br /> $('div > p.title > a').each((_idx, el) => {<br /> const postTitle = $(el).text()<br /> postTitles.push(postTitle)<br /> });<br /> return postTitles;<br /> } catch (error) {<br /> throw error;<br /> }<br />};<br />getPostTitles()<br />.then((postTitles) => console.log(postTitles));
getPostTitles() 是一个异步函数,用于抓取旧 reddit 的 r/programming 论坛。首先,使用来自 axios HTTP 客户端库的简单 HTTP GET 请求获取 网站 的 HTML,然后使用cheerio.load() 函数将 html 数据提供给 Cheerio。
接下来,使用浏览器的开发工具,您可以获得通常可以针对所有明信片的选择器。如果您使用过 JQuery,那么 $('div > p.title > a') 非常熟悉。这将获取所有帖子,因为您只想获取每个帖子的标题,您必须遍历每个帖子(使用 each() 函数进行迭代)。
要从每个标题中提取文本,必须在 Cheerio 的帮助下获取 DOM 元素(当前元素的 el)。然后在每个元素上调用 text() 以获取文本。
现在,您可以弹出一个终端并运行 node crawler.js,您会看到一长串大约 25 或 26 个帖子标题。虽然这是一个非常简单的用例,但它显示了 Cheerio 提供的 API 是多么容易使用。
如果您的用例需要执行 Javascript 和加载外部资源,这里有几个选项可供考虑。
JSDOM:节点的 DOM
JSDOM 是 NodeJS 中使用的文档对象模型 (DOM) 的纯 Javascript 实现。如前所述,DOM 不适用于 Node,而 JSDOM 是最接近的替代品。它或多或少地模拟了浏览器的机制。
一旦创建了 DOM,我们就可以通过编程方式与要抓取的 Web 应用程序或 网站 进行交互,还可以完成单击按钮之类的操作。如果您熟悉 DOM 的工作原理,那么 JSDOM 也非常易于使用。
const { JSDOM } = require('jsdom')<br />const { document } = new JSDOM(<br /> '
你好世界 '
)。窗户
constheading = document.querySelector('.title')
heading.textContent = '你好!'
heading.classList.add('欢迎')
标题.innerHTML
// 你好呀!
如您所见,JSDOM 创建了一个 DOM,然后您可以使用与浏览器 DOM 相同的方法和属性对其进行操作。为了演示如何使用 JSDOM 与 网站 进行交互,我们将在 Redditr/programming 论坛上发表第一篇文章,点赞它,然后我们将验证该帖子是否已被点赞。
首先运行以下命令安装jsdom和axios:
npm install jsdom axios
然后创建一个名为 rawler.js 的文件并复制/粘贴以下代码:
const { JSDOM } = require("jsdom")<br />const axios = require('axios')<br />const upvoteFirstPost = async () => {<br /> try {<br /> const { data } = await axios.get("https://old.reddit.com/r/programming/");<br /> const dom = new JSDOM(data, {<br /> runScripts: "dangerously",<br /> resources: "usable"<br /> });<br /> const { document } = dom.window;<br /> const firstPost = document.querySelector("div > div.midcol > div.arrow");<br /> firstPost.click();<br /> const isUpvoted = firstPost.classList.contains("upmod");<br /> const msg = isUpvoted<br /> ? "Post has been upvoted successfully!"<br /> : "The post has not been upvoted!";<br /> return msg;<br /> } catch (error) {<br /> throw error;<br /> }<br />};<br />upvoteFirstPost().then(msg => console.log(msg));
upvoteFirstPost() 是一个异步函数,它将获得 r/programming 中的第一个帖子并对其进行投票。为此,axios 发送一个 HTTP GET 请求以获取指定 URL 的 HTML。然后将先前获取的 HTML 馈送到 JSDOM 以创建新的 DOM。JSDOM 构造函数将 HTML 作为第一个参数,将选项作为第二个参数。添加的 2 个选项执行以下功能: