js提取指定网站内容(HTTP客户端/awaitawait//async/)
优采云 发布时间: 2021-12-20 00:24js提取指定网站内容(HTTP客户端/awaitawait//async/)
HTTP 客户端是一种工具,可以向服务器发送请求,然后接收服务器的响应。下面提到的所有工具的底层都是使用一个HTTP客户端来访问你想要抓取的网站。
要求
Request 是 Javascript 生态系统中使用最广泛的 HTTP 客户端之一,但 Request 库的作者已正式声明它已被弃用。但这并不意味着它不可用,相当多的库仍在使用它,并且非常易于使用。使用 Request 发出 HTTP 请求非常简单:
1const request = require('request')
2request('https://www.reddit.com/r/programming.json', function (
3 error,
4 response,
5 body
6) {
7 console.error('error:', error)
8 console.log('body:', body)
9})
你可以在 Github 上找到 Request 库,安装非常简单。您还可以找到弃用通知及其含义。
阿克西奥斯
Axios 是一个基于 Promise 的 HTTP 客户端,可以在浏览器和 Node.js 中运行。如果您使用 Typescript,那么 axios 将为您覆盖内置类型。通过 Axios 发起 HTTP 请求非常简单。默认情况下,它带有 Promise 支持,而不是在请求中使用回调:
1const axios = require('axios')
2
3axios
4 .get('https://www.reddit.com/r/programming.json')
5 .then((response) => {
6 console.log(response)
7 })
8 .catch((error) => {
9 console.error(error)
10 });
如果你喜欢 Promises API 的 async/await 语法糖,你也可以使用它,但由于顶级 await 仍处于第 3 阶段,我们不得不使用异步函数来代替:
1async function getForum() {
2 try {
3 const response = await axios.get(
4 'https://www.reddit.com/r/programming.json'
5 )
6 console.log(response)
7 } catch (error) {
8 console.error(error)
9 }
10}
您所要做的就是致电 getForum!可以在 Axios 库上找到。
超级代理
与 Axios 一样,Superagent 是另一个强大的 HTTP 客户端,支持 Promise 和 async/await 语法糖。它有一个类似 Axios 的相当简单的 API,但由于更多的依赖关系,Superagent 不太受欢迎。
使用 promise、async/await 或回调向 Superagent 发出 HTTP 请求如下所示:
1const superagent = require("superagent")
2const forumURL = "https://www.reddit.com/r/programming.json"
3
4// callbacks
5superagent
6 .get(forumURL)
7 .end((error, response) => {
8 console.log(response)
9 })
10
11// promises
12superagent
13 .get(forumURL)
14 .then((response) => {
15 console.log(response)
16 })
17 .catch((error) => {
18 console.error(error)
19 })
20
21// promises with async/await
22async function getForum() {
23 try {
24 const response = await superagent.get(forumURL)
25 console.log(response)
26 } catch (error) {
27 console.error(error)
28 }
29}
您可以在以下位置找到 Superagent。
正则表达式:艰难的方式
在没有任何依赖的情况下,进行网络爬虫最简单的方法是在使用 HTTP 客户端查询网页时,在接收到的 HTML 字符串上使用一堆正则表达式。正则表达式不是那么灵活,许多专业人士和业余爱好者都很难写出正确的正则表达式。
试试看,假设有一个带有用户名的标签,而我们需要那个用户名,这类似于你依赖正则表达式时必须做的
1const htmlString = 'Username: John Doe'
2const result = htmlString.match(/(.+)/)
3
4console.log(result[1], result[1].split(": ")[1])
5// Username: John Doe, John Doe
在 Javascript 中,match() 通常返回一个数组,其中收录与正则表达式匹配的所有内容。第二个元素(在索引 1 中)将找到我们想要的标记的 textContent 或 innerHTML。但结果收录一些不必要的文本(“用户名:”),必须删除。
如您所见,对于一个非常简单的用例,有许多步骤和工作要做。这就是为什么你应该依赖 HTML 解析器的原因,我们将在后面讨论。
Cheerio:用于遍历 DOM 的核心 JQuery
Cheerio 是一个高效且可移植的库,它允许您在服务器端使用 JQuery 丰富而强大的 API。如果您之前使用过 JQuery,您就会熟悉 Cheerio。它消除了 DOM 的所有不一致和浏览器相关功能,并公开了一个有效的 API 来解析和操作 DOM。
1const cheerio = require('cheerio')
2const $ = cheerio.load('Hello world')
3
4$('h2.title').text('Hello there!')
5$('h2').addClass('welcome')
6
7$.html()
8// Hello there!
如您所见,Cheerio 与 JQuery 非常相似。
但是,即使它的工作方式与 Web 浏览器不同,这也意味着它不能:
因此,如果您尝试抓取的网站 或 web 应用程序严重依赖 Javascript(例如“单页应用程序”),那么 Cheerio 不是最佳选择,您可能不得不依赖其他选项稍后讨论。
为了展示 Cheerio 的强大功能,我们将尝试爬取 Reddit 中的 r/programming 论坛,并尝试获取一个帖子名称列表。
首先,通过运行以下命令安装 Cheerio 和 axios:npm installcheerio axios。
然后新建一个名为crawler.js的文件,复制粘贴以下代码:
1const axios = require('axios');
2const cheerio = require('cheerio');
3
4const getPostTitles = async () => {
5 try {
6 const { data } = await axios.get(
7 'https://old.reddit.com/r/programming/'
8 );
9 const $ = cheerio.load(data);
10 const postTitles = [];
11
12 $('div > p.title > a').each((_idx, el) => {
13 const postTitle = $(el).text()
14 postTitles.push(postTitle)
15 });
16
17 return postTitles;
18 } catch (error) {
19 throw error;
20 }
21};
22
23getPostTitles()
24.then((postTitles) => console.log(postTitles));
getPostTitles() 是一个异步函数,它将抓取旧的 reddit r/编程论坛。首先通过axios HTTP客户端库使用简单的HTTP GET请求获取网站的HTML,然后使用cheerio.load()函数将html数据输入到Cheerio中。
然后在浏览器的开发工具的帮助下,你可以得到一个可以定位所有列表项的选择器。如果你用过JQuery,你一定对$('div> p.title> a')非常熟悉。这将获得所有帖子,因为您只想单独获取每个帖子的标题,因此您必须遍历每个帖子。这些操作是在 each() 函数的帮助下完成的。
要从每个标题中提取文本,您必须在 Cheerio 的帮助下获取 DOM 元素(el 指的是当前元素)。然后在每个元素上调用 text() 为您提供文本。
现在,打开终端,运行node crawler.js,你会看到一个近似titles的数组,这个数组会很长。虽然这是一个非常简单的用例,但它展示了 Cheerio 提供的 API 的简单本质。
如果您的用例需要执行 Javascript 并加载外部源,那么以下选项会有所帮助。
JSDOM:节点的 DOM
JSDOM 是 Node.js 中使用的文档对象模型的纯 Javascript 实现。如前所述,DOM 对 Node 不可用,但 JSDOM 是最接近的。它或多或少地模仿了浏览器。
因为DOM是创建的,所以你可以通过编程与web应用或者你想爬取的网站进行交互,也可以模拟点击一个按钮。如果你熟悉 DOM 操作,使用 JSDOM 会非常简单。
1const { JSDOM } = require('jsdom')
2const { document } = new JSDOM(
3 'Hello world'
4).window
5const heading = document.querySelector('.title')
6heading.textContent = 'Hello there!'
7heading.classList.add('welcome')
8
9heading.innerHTML
10// Hello there!
在代码中用JSDOM创建一个DOM,然后你就可以用和浏览器DOM一样的方法和属性来操作这个DOM了。
为了演示如何使用JSDOM与网站进行交互,我们将获取Reddit r/programming论坛的第一篇帖子并对其进行投票,然后验证该帖子是否已被投票。
首先运行以下命令安装jsdom和axios: npm install jsdom axios
然后创建一个名为 crawler.js 的文件,并复制并粘贴以下代码:
1const { JSDOM } = require("jsdom")
2const axios = require('axios')
3
4const upvoteFirstPost = async () => {
5 try {
6 const { data } = await axios.get("https://old.reddit.com/r/programming/");
7 const dom = new JSDOM(data, {
8 runScripts: "dangerously",
9 resources: "usable"
10 });
11 const { document } = dom.window;
12 const firstPost = document.querySelector("div > div.midcol > div.arrow");
13 firstPost.click();
14 const isUpvoted = firstPost.classList.contains("upmod");
15 const msg = isUpvoted
16 ? "Post has been upvoted successfully!"
17 : "The post has not been upvoted!";
18
19 return msg;
20 } catch (error) {
21 throw error;
22 }
23};
24
25upvoteFirstPost().then(msg => console.log(msg));
upvoteFirstPost() 是一个异步函数,它将获得 r/programming 中的第一篇文章,然后对其进行投票。axios 发送 HTTP GET 请求以获取指定 URL 的 HTML。然后从之前获得的 HTML 创建一个新的 DOM。JSDOM 构造函数将 HTML 作为第一个参数,将选项作为第二个参数。添加的两个选项执行以下功能:
创建DOM后,使用相同的DOM方法获取第一篇文章文章的upvote按钮,然后点击。要验证它是否真的被点击,您可以检查 classList 中是否有名为 upmod 的类。如果它存在于 classList 中,则返回一条消息。
打开终端并运行 node crawler.js,然后您将看到一个整洁的字符串,表明该帖子是否已被点赞。虽然这个例子很简单,但你可以在这个基础上构建强大的东西,例如,一个对特定用户帖子进行投票的机器人。
如果你不喜欢缺乏表达能力的 JSDOM,并且在实践中依赖很多这样的操作,或者需要重新创建很多不同的 DOM,那么下面的将是更好的选择。
Puppeteer:无头浏览器
顾名思义,Puppeteer 允许您以编程方式操纵浏览器,就像操纵木偶一样。默认情况下,它为开发人员提供了高级 API 来控制无头版本的 Chrome。
摘自 Puppeter Docs Puppeteer 比上述工具更有用,因为它允许您像真人与浏览器交互一样抓取网络。这开辟了一些以前不可用的可能性:
它还可以在网络爬虫以外的任务中发挥重要作用,例如 UI 测试、辅助性能优化等。
通常你想截取网站的截图,也许是为了了解竞争对手的产品目录,你可以使用puppeteer来做。首先运行以下命令安装puppeteer: npm install puppeteer
这将下载 Chromium 的捆绑版本,大约 180 MB 到 300 MB,具体取决于操作系统。如果要禁用此功能。
我们尝试在 Reddit 中获取 r/programming 论坛的截图和 PDF,创建一个名为 crawler.js 的新文件,并复制并粘贴以下代码:
1const puppeteer = require('puppeteer')
2
3async function getVisual() {
4 try {
5 const URL = 'https://www.reddit.com/r/programming/'
6 const browser = await puppeteer.launch()
7 const page = await browser.newPage()
8
9 await page.goto(URL)
10 await page.screenshot({ path: 'screenshot.png' })
11 await page.pdf({ path: 'page.pdf' })
12
13 await browser.close()
14 } catch (error) {
15 console.error(error)
16 }
17}
18
19getVisual()
getVisual() 是一个异步函数,它将获取 URL 变量中的 url 对应的屏幕截图和 pdf。首先通过 puppeteer.launch() 创建一个浏览器实例,然后创建一个新页面。您可以将此页面视为常规浏览器中的选项卡。然后以 URL 为参数调用 page.goto() 将之前创建的页面定向到指定的 URL。最终,浏览器实例与页面一起被销毁。
操作完成并加载页面后,将分别使用 page.screenshot() 和 page.pdf() 获取屏幕截图和pdf。也可以*敏*感*词*javascript的load事件,进行这些操作,生产环境中强烈推荐使用。
在终端上运行 node crawler.js。几秒钟后,您会注意到已创建两个文件,名为 screenshot.jpg 和 page.pdf。
Nightmare:Puppeteer 的替代品
Nightmare 是一个类似于 Puppeteer 的高级浏览器自动化库。该库使用 Electron,但据说它的速度是其前身 PhantomJS 的两倍。
如果您在某种程度上不喜欢 Puppeteer 或对 Chromium 包的大小感到沮丧,那么 nightmare 是一个理想的选择。首先,运行以下命令安装 nightmare 库: npm install nightmare
然后,一旦下载了 nightmare,我们将使用它通过 Google 搜索引擎找到 ScrapingBee 的 网站。创建一个名为 crawler.js 的文件,然后将以下代码复制并粘贴到其中:
1const Nightmare = require('nightmare')
2const nightmare = Nightmare()
3
4nightmare
5 .goto('https://www.google.com/')
6 .type("input[title='Search']", 'ScrapingBee')
7 .click("input[value='Google Search']")
8 .wait('#rso > div:nth-child(1) > div > div > div.r > a')
9 .evaluate(
10 () =>
11 document.querySelector(
12 '#rso > div:nth-child(1) > div > div > div.r > a'
13 ).href
14 )
15 .end()
16 .then((link) => {
17 console.log('Scraping Bee Web Link': link)
18 })
19 .catch((error) => {
20 console.error('Search failed:', error)
21 })
首先创建一个 Nightmare 实例,然后通过调用 goto() 将该实例定向到 Google 搜索引擎。加载后,使用其选择器获取搜索框,然后使用搜索框的值(输入标签)将其更改为“ScrapingBee”。完成后,单击“Google 搜索”按钮提交搜索表单。然后告诉 Nightmare 等到第一个链接加载完毕。一旦完成,它将使用 DOM 方法获取收录链接的锚标记的 href 属性的值。
最后,在所有操作完成后,将链接打印到控制台。
总结