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