本文讲解怎样用 Node.js 高效地从 Web 爬取数据前提条件本文主要针对具有一定 JavaScript 经验的程序员如果你对 Web 抓取有深刻的了解,但对 JavaScript 并不熟悉,那么本文仍然能够对你有所帮助。
✅ 会 JavaScript✅ 会用 DevTools 提取元素选择器✅ 会一些 ES6 (可选)你将学到通过本文你将学到:学到更多关于 Node.js 的东西用多个 HTTP 客户端来帮助 Web 抓取的过程
利用多个经过实践考验过的库来爬取 Web了解 Node.jsJavascript 是一种简单的现代编程语言,最初是为了向浏览器中的网页添加动态效果当加载网站后,Javascript 代码由浏览器的 Javascript 引擎运行。
为了使 Javascript 与你的浏览器进行交互,浏览器还提供了运行时环境(document、window等)这意味着 Javascript 不能直接与计算机资源交互或对其进行操作例如在 Web 服务器中,服务器必须能够与文件系统进行交互,这样才能读写文件。
Node.js 使 Javascript 不仅能够运行在客户端,而且还可以运行在服务器端为了做到这一点,其创始人 Ryan Dahl 选择了Google Chrome 浏览器的 v8 Javascript Engine,并将其嵌入到用 C++ 开发的 Node 程序中。
所以 Node.js 是一个运行时环境,它允许 Javascript 代码也能在服务器上运行与其他语言(例如 C 或 C++)通过多个线程来处理并发性相反,Node.js 利用单个主线程并并在事件循环的帮助下以非阻塞方式执行任务。
要创建一个简单的 Web 服务器非常简单,如下所示:const http = require(http); const PORT = 3000; const server = http.createServer(
(req, res) => { res.statusCode = 200; res.setHeader(Content-Type, text/plain); res.end(Hello World
); }); server.listen(port, () => { console.log(`Server running at PORT:${port}/`); });如果你已安装了 Node.js,可以试着运行上面的代码。
Node.js 非常适合 I/O 密集型程序HTTP 客户端:访问 WebHTTP 客户端是能够将请求发送到服务器,然后接收服务器响应的工具下面提到的所有工具底的层都是用 HTTP 客户端来访问你要抓取的网站。
RequestRequest 是 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 库,安装它非常简单你还可以在 https://github.com/request/request/issues/3142 找到弃用通知及其含义Axios。
Axios 是基于 promise 的 HTTP 客户端,可在浏览器和 Node.js 中运行如果你用 Typescript,那么 axios 会为你覆盖内置类型通过 Axios 发起 HTTP 请求非常简单,默认情况下它带有 Promise 支持,而不是在 Request 中去使用回调:。
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 仍处于 stage 3 ,所以我们只好先用异步函数来代替:
asyncfunctiongetForum() { try { const response = await axios.get( https://www.reddit.com/r/programming.json
) console.log(response) } catch (error) { console.error(error) } }你所要做的就是调用 getForum!可以在 https://github.com/axios/axios 上找到Axios库。
Superagent与 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/awaitasync
functiongetForum() { try { const response = await superagent.get(forumURL) console.log(response) }
catch (error) { console.error(error) } }可以在 https://github.com/visionmedia/superagent 找到 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但是结果中包含一些不需要的文本( “Username: “),必须将其删除如你所见,对于一个非常简单的用例,步骤和要做的工作都很多这就是为什么应该依赖 HTML 解析器的原因,我们将在后面讨论。
Cheerio:用于遍历 DOM 的核心 JQueryCheerio 是一个高效轻便的库,它使你可以在服务器端使用 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 用起来非常相似但是,尽管它的工作方式不同于网络浏览器,也就这意味着它不能:渲染任何解析的或操纵 DOM 元素应用 CSS 或加载外部资源执行 JavaScript。
因此,如果你尝试爬取的网站或 Web 应用是严重依赖 Javascript 的(例如“单页应用”),那么 Cheerio 并不是最佳选择,你可能不得不依赖稍后讨论的其他选项为了展示 Cheerio 的强大功能,我们将尝试在 Reddit 中抓取 r/programming 论坛,尝试获取帖子名称列表。
首先,通过运行以下命令来安装 Cheerio 和 axios:npm install cheerio 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/programming 论坛进行爬取首先,用带有 axios HTTP 客户端库的简单 HTTP GET 请求获取网站的 HTML,然后用 。
cheerio.load() 函数将 html 数据输入到 Cheerio 中然后在浏览器的 Dev Tools 帮助下,可以获得可以定位所有列表项的选择器如果你使用过 JQuery,则必须非常熟悉 $(div> p.title> a)
这将得到所有帖子,因为你只希望单独获取每个帖子的标题,所以必须遍历每个帖子,这些操作是在 each() 函数的帮助下完成的要从每个标题中提取文本,必须在 Cheerio 的帮助下获取 DOM元素( el
指代当前元素)然后在每个元素上调用 text() 能够为你提供文本现在,打开终端并运行 node crawler.js,然后你将看到大约存有标题的数组,它会很长尽管这是一个非常简单的用例,但它展示了 Cheerio 提供的 API 的简单性质。
如果你的用例需要执行 Javascript 并加载外部源,那么以下几个选项将很有帮助JSDOM:Node 的 DOMJSDOM 是在 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 作为第一个参数,把 option 作为第二个参数,已添加的 2 个 option 项执行以下功能:runScripts:设置为 dangerously 时允许执行事件 handler 和任何 Javascript 代码。
如果你不清楚将要运行的脚本的安全性,则最好将 runScripts 设置为“outside-only”,这会把所有提供的 Javascript 规范附加到 “window” 对象,从而阻止在 inside
上执行的任何脚本resources:设置为“usable”时,允许加载用 标记声明的任何外部脚本(例如:从 CDN 提取的 JQuery 库)创建 DOM 后,用相同的 DOM 方法得到第一篇文章的 upvote 按钮,然后单击。
要验证是否确实单击了它,可以检查 classList 中是否有一个名为 upmod 的类如果存在于 classList 中,则返回一条消息打开终端并运行 node crawler.js,然后会看到一个整洁的字符串,该字符串将表明帖子是否被赞过。
尽管这个例子很简单,但你可以在这个基础上构建功能强大的东西,例如,一个围绕特定用户的帖子进行投票的机器人如果你不喜欢缺乏表达能力的 JSDOM ,并且实践中要依赖于许多此类操作,或者需要重新创建许多不同的 DOM,那么下面将是更好的选择。
Puppeteer:无头浏览器顾名思义,Puppeteer 允许你以编程方式操纵浏览器,就像操纵木偶一样。它通过为开发人员提供高级 API 来默认控制无头版本的 Chrome。
Puppeteer 比上述工具更有用,因为它可以使你像真正的人在与浏览器进行交互一样对网络进行爬取这就具备了一些以前没有的可能性:你可以获取屏幕截图或生成页面 PDF可以抓取单页应用并生成预渲染的内容自动执行许多不同的用户交互,例如键盘输入、表单提交、导航等。
它还可以在 Web 爬取之外的其他任务中发挥重要作用,例如 UI 测试、辅助性能优化等通常你会想要截取网站的屏幕截图,也许是为了了解竞争对手的产品目录,可以用 puppeteer 来做到首先运行以下命令安装 puppeteer,:。
npm install puppeteer这将下载 Chromium 的 bundle 版本,根据操作系统的不同,该版本大约 180 MB 至 300 MB如果你要禁用此功能让我们尝试在 Reddit 中获取 r/programming 论坛的屏幕截图和 PDF,创建一个名为 。
crawler.js的新文件,然后复制粘贴以下代码:const puppeteer = require(puppeteer) asyncfunctiongetVisual() { 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.pdfNightmare: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) })首先创建一个 Nighmare 实例,然后通过调用 goto() 将该实例定向到 Google 搜索引擎,加载后,使用其选择器获取搜索框,然后使用搜索框的值(输入标签)更改为“ScrapingBee”。
完成后,通过单击 “Google搜索” 按钮提交搜索表单然后告诉 Nightmare 等到第一个链接加载完毕,一旦完成,它将使用 DOM 方法来获取包含该链接的定位标记的 href 属性的值最后,完成所有操作后,链接将打印到控制台。
总结✅ Node.js 是 Javascript 在服务器端的运行时环境由于事件循环机制,它具有“非阻塞”性质✅ HTTP客户端(例如 Axios、Superagent 和 Request)用于将 HTTP 请求发送到服务器并接收响应。
✅ Cheerio 把 JQuery 的优点抽出来,在服务器端 进行 Web 爬取是唯一的目的,但不执行 Javascript 代码✅ JSDOM 根据标准 Javascript规范 从 HTML 字符串中创建一个 DOM,并允许你对其执行DOM操作。
✅ Puppeteer and Nightmare 是高级(high-level )浏览器自动化库,可让你以编程方式去操作 Web 应用,就像真实的人正在与之交互一样。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容