Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8732

Node でお手軽スクレイピング 2020 年夏

$
0
0

皆さんは Web ページのスクレイピングって書いた事ありますか?私はあります。だってどんなに平和で平穏な生活を送っていても数年に一度はスクレイピングってしたくなりますよね。「うわーまじか!API ないのかよ…。」的な。

そうしたら HTTP クライアントと HTML パーサのライブラリを探してきてインストールした上でごりごり書くことになると思います。でも実際に書いてみると、そうやってライブラリのインストールをしたりサンプルコードで動作確認している時間よりも、HTML を解析して実際にパースしたところから対象の要素を取得して欲しい値を取り出す試行錯誤の時間の方が長かったっていう事はないですか?

今日ご紹介する Node でお手軽スクレイピングは、その辺の試行錯誤の手間を極力減らすことが出来る方法です。2020 年夏の最新版です。

まずは環境から。特に古いものを使う理由もないので 2020-07-20 時点の最新版 14.5.0を使っています。

$ node -v
v14.5.0

そしてプロジェクトの初期化を行って、2 つほどライブラリをインストールします。

$ npm init
$ npm install node-fetch jsdom --save-dev

node-fetchは Node 上でウェブブラウザと同じような fetchを使えるようにするライブラリです。普段 Web ベースの JS を書いてると、HTTP アクセスするにも fetchが直感的で楽だなーと思うので選びました。GitHub 上のスターは 5.3k。素晴らしいですね。

jsdomはウェブブラウザと同様の API セットを持った HTML DOM ツリーをメモリ上に構築することが出来るライブラリです。Pure JavaScript で実装されたウェブブラウザのサブセットと思うと理解しやすいかも知れません。GitHub 上のスターは 14.4k。今回の記事の要です。

必要なライブラリが揃ったところで早速スクリプトを書いていきましょう。サンプルに気象庁の東京都の週間天気予報のページを選びました。

index.mjs
#!/usr/bin/env node
importfetchfrom'node-fetch';importjsdomfrom'jsdom';const{JSDOM}=jsdom;(async()=>{constres=awaitfetch('https://www.jma.go.jp/jp/week/319.html');consthtml=awaitres.text();constdom=newJSDOM(html);constdocument=dom.window.document;constnodes=document.querySelectorAll('#infotablefont tr:nth-child(4) td');consttokyoWeathers=Array.from(nodes).map(td=>td.textContent.trim());console.log(tokyoWeathers);})();

これだけ見て「あー、なるほど!」ってならない方のために詳細な解説は後ほど加えていきますが、まず一番のポイントは const nodesから始まる行以降です。お気づきでしょうか?この行以降はそのままウェブブラウザ上でも実行可能なことに。

従来のスクレイピングでは、必要な DOM 要素を取得するためのクエリを探したり、得られたノードを加工して必要なリストに変換する試行錯誤に時間がかかっていました。その試行錯誤自体を無くすことは不可能ですが、ウェブブラウザ上のデベロッパーツールであれば、リアルタイムに結果を見ながら試行錯誤することでその手間を大幅に減らすことが出来ます。

そしてデベロッパーツール上で欲しい結果が得られるようになったら、そのコードをスクリプトファイルに貼り付ければそれだけでもうスクレイピングの完成です。このスクリプトを実行すると以下のような結果が得られます。

$ ./index.mjs 
['曇',       '曇一時雨',
  '曇一時雨', '曇',
  '曇',       '曇時々晴',
  '曇時々晴']

従来に比べると革命的に楽に書ける事がお分かりいただけたのではないでしょうか。

さて、では約束通り詳細な解説を加えていきましょう。

#!/usr/bin/env node

今回、コマンドラインから直接スクリプトを実行しようかなと思ったので追加しています。nodeコマンドにファイルを渡して実行するのであれば不要です。

importfetchfrom'node-fetch';importjsdomfrom'jsdom';const{JSDOM}=jsdom;

import記法が使えるようになったのは嬉しいのですが、v14 のデフォルトではファイルの拡張子を .mjsにしておく必要があるので注意して下さい。また jsdomに関しては直接 import { JSDOM } from 'jsdom'と書きたくなるところですが、現状では jsdomが ES2015 Modules 構文をサポートしていないため、こういったまどろっこしい書き方になります。

(async()=>{// ...})();

非同期処理があるので awaitを使いたいのですが、await自体も非同期関数の中じゃないと使えないので、非同期の無名関数を作って即時実行しています。

constres=awaitfetch('https://www.jma.go.jp/jp/week/319.html');consthtml=awaitres.text();

Web プログラミングで見慣れた書き方ですね。非同期に fetchした結果から、HTML を文字列として取得しています。XHRを使っていた期間が長かったので私もうっかり間違えがちですが、XHRresponseTextと違って、fetchで得られるレスポンスの textメソッドは非同期なのでそこにも注意が必要です。

constdom=newJSDOM(html);constdocument=dom.window.document;

さあ本記事の最大の見せ場です。JSDOMコンストラクタに HTML を文字列で渡すと、それをパースして DOM ツリーにしてくれます。そこには Web プログラミングでおなじみ、windowオブジェクトがあり、その中に documentオブジェクトがあります。

constnodes=document.querySelectorAll('#infotablefont tr:nth-child(4) td');consttokyoWeathers=Array.from(nodes).map(td=>td.textContent.trim());console.log(tokyoWeathers);

この部分は、デベロッパーツール上で動作確認したものを貼り付けると言っていた部分です。ウェブブラウザ上でだと :nth-child(4)に相当する部分を楽に探せるのがいいですね。そこで得られた NodeListオブジェクトを Array.fromArrayに変換するというのは、今どきなテクニックかもしれません。

以上でスクリプトの解説は終わりです。

最後に、忘れてはならないのはスクレイピングは最終手段であるという事です。API が提供されているサービスであれば必ずそちらを使うべきですし、やむを得ずスクレイピングする際はサーバに過度な負荷を与えることの無いよう気をつけましょう。


Viewing all articles
Browse latest Browse all 8732

Trending Articles