ホームページ >ウェブフロントエンド >jsチュートリアル >Node.js を使用して Web からデータを効率的に収集するにはどうすればよいですか?
関連する推奨事項: 「nodejs チュートリアル 」
JavaScript が大幅に改善され、ランタイムが導入されたためNodeJS と呼ばれる言語は、最も人気があり広く使用されている言語の 1 つになりました。 Web アプリケーションであってもモバイル アプリケーションであっても、JavaScript には適切なツールが備わっています。 この記事では、Node.js を使用して Web からデータを効率的にクロールする方法について説明します。
この記事は主に、JavaScript の経験があるプログラマを対象としています。 Web スクレイピングについてはよく理解しているが、JavaScript については初めてという場合でも、この記事は役に立ちます。
この記事を通じて次のことを学びます:
JavaScript はシンプルで現代的なプログラミング言語であり、元々は Web ページに動的な効果を追加するために開発されました。ブラウザ。 Web サイトが読み込まれると、ブラウザの Javascript エンジンによって Javascript コードが実行されます。 Javascript がブラウザと対話するために、ブラウザはランタイム環境 (ドキュメント、ウィンドウなど) も提供します。
これは、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 クライアントを使用して、クロールする Web サイトにアクセスすることです。
Request は、JavaScript エコシステムで最も広く使用されている HTTP クライアントの 1 つですが、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) })
Request ライブラリは Github にあり、インストールは非常に簡単です。非推奨の通知とその意味については、https://github.com/request/re... で確認できます。
Axios は、ブラウザーと Node.js で実行される Promise ベースの HTTP クライアントです。 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 ライブラリは、https://github.com/axios/axios にあります。
Axios と同様、Superagent も Promise および async/await 構文シュガーをサポートする強力な HTTP クライアントです。 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.. . スーパーエージェントを検索します。
在没有任何依赖性的情况下,最简单的进行网络抓取的方法是,使用 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 中国語 Web サイトの他の関連記事を参照してください。