相關推薦:《nodejs 教學》
由於Javascript有了巨大的改進,並且引入當了稱為NodeJS的運行時,因此它已成為最受歡迎和使用最廣泛的語言之一。無論是Web應用程式還是行動應用程序,Javascript現在都具有正確的工具。 本文說明如何用 Node.js 有效率地從 Web 爬取資料。
本文主要針對具有一定 JavaScript 經驗的程式設計師。如果你對 Web 抓取有深刻的了解,但對 JavaScript 並不熟悉,那麼本文仍然能夠對你有幫助。
透過本文你將學到:
Javascript 是一種簡單的現代編程語言,最初是為了在瀏覽器中的網頁添加動態效果。當網站載入後,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 用戶端是能夠將請求傳送到伺服器,然後接收伺服器回應的工具。下面提到的所有工具底的層都是用 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 函式庫,安裝它非常簡單。你也可以在 https://github.com/request/re... 找到棄用通知及其意義。
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 ,所以我們只好先用非同步函數來代替:
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
!可以在 https://github.com/axios/axios 上找到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) } }
可以在https://github.com/visionmedi... 找到Superagent。
在没有任何依赖性的情况下,最简单的进行网络抓取的方法是,使用 HTTP 客户端查询网页时,在收到的 HTML 字符串上使用一堆正则表达式。正则表达式不那么灵活,而且很多专业人士和业余爱好者都难以编写正确的正则表达式。
让我们试一试,假设其中有一个带有用户名的标签,我们需要该用户名,这类似于你依赖正则表达式时必须执行的操作
const htmlString = '<label>Username: John Doe</label>' const result = htmlString.match(/<label>(.+)<\/label>/) console.log(result[1], result[1].split(": ")[1]) // Username: John Doe, John Doe
在 Javascript 中,match()
通常返回一个数组,该数组包含与正则表达式匹配的所有内容。第二个元素(在索引1中)将找到我们想要的 <label></label>
标记的 textContent
或 innerHTML
。但是结果中包含一些不需要的文本( “Username: “),必须将其删除。
如你所见,对于一个非常简单的用例,步骤和要做的工作都很多。这就是为什么应该依赖 HTML 解析器的原因,我们将在后面讨论。
Cheerio 是一个高效轻便的库,它使你可以在服务器端使用 JQuery 的丰富而强大的 API。如果你以前用过 JQuery,那么将会对 Cheerio 感到很熟悉,它消除了 DOM 所有不一致和与浏览器相关的功能,并公开了一种有效的 API 来解析和操作 DOM。
const cheerio = require('cheerio') const $ = cheerio.load('<h2 class="title">Hello world</h2>') $('h2.title').text('Hello there!') $('h2').addClass('welcome') $.html() // <h2 class="title welcome">Hello there!</h2>
如你所见,Cheerio 与 JQuery 用起来非常相似。
但是,尽管它的工作方式不同于网络浏览器,也就这意味着它不能:
因此,如果你尝试爬取的网站或 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.js 中使用的文档对象模型的纯 Javascript 实现,如前所述,DOM 对 Node 不可用,但是 JSDOM 是最接近的。它或多或少地模仿了浏览器。
由于创建了 DOM,所以可以通过编程与要爬取的 Web 应用或网站进行交互,也可以模拟单击按钮。如果你熟悉 DOM 操作,那么使用 JSDOM 将会非常简单。
const { JSDOM } = require('jsdom') const { document } = new JSDOM( '<h2 class="title">Hello world</h2>' ).window const heading = document.querySelector('.title') heading.textContent = 'Hello there!' heading.classList.add('welcome') heading.innerHTML // <h2 class="title welcome">Hello there!</h2>
代码中用 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 项执行以下功能:
dangerously
时允许执行事件 handler 和任何 Javascript 代码。如果你不清楚将要运行的脚本的安全性,则最好将 runScripts 设置为“outside-only”,这会把所有提供的 Javascript 规范附加到 “window” 对象,从而阻止在 inside 上执行的任何脚本。<script></script>
标记声明的任何外部脚本(例如:从 CDN 提取的 JQuery 库)创建 DOM 后,用相同的 DOM 方法得到第一篇文章的 upvote 按钮,然后单击。要验证是否确实单击了它,可以检查 classList
中是否有一个名为 upmod
的类。如果存在于 classList
中,则返回一条消息。
打开终端并运行 node crawler.js
,然后会看到一个整洁的字符串,该字符串将表明帖子是否被赞过。尽管这个例子很简单,但你可以在这个基础上构建功能强大的东西,例如,一个围绕特定用户的帖子进行投票的机器人。
如果你不喜欢缺乏表达能力的 JSDOM ,并且实践中要依赖于许多此类操作,或者需要重新创建许多不同的 DOM,那么下面将是更好的选择。
顾名思义,Puppeteer 允许你以编程方式操纵浏览器,就像操纵木偶一样。它通过为开发人员提供高级 API 来默认控制无头版本的 Chrome。
Puppeteer 比上述工具更有用,因为它可以使你像真正的人在与浏览器进行交互一样对网络进行爬取。这就具备了一些以前没有的可能性:
它还可以在 Web 爬取之外的其他任务中发挥重要作用,例如 UI 测试、辅助性能优化等。
通常你会想要截取网站的屏幕截图,也许是为了了解竞争对手的产品目录,可以用 puppeteer 来做到。首先运行以下命令安装 puppeteer,:npm install puppeteer
这将下载 Chromium 的 bundle 版本,根据操作系统的不同,该版本大约 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 的高级浏览器自动化库,该库使用 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
属性的值。
最后,完成所有操作后,链接将打印到控制台。
原文地址:https://www.scrapingbee.com/blog/web-scraping-javascript/
作者:Shenesh Perera
更多编程相关知识,请访问:编程教学!!
以上是怎麼用 Node.js 高效率地從 Web 爬取資料?的詳細內容。更多資訊請關注PHP中文網其他相關文章!