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

node.jsで再帰的にリンクを探すクローラーを自作してみた

$
0
0
概要 目的 node.jsの勉強、特に非同期処理を学びたくてクローラーを自作してみた。 機能 HTMLを指定すると、そのHTMLのタイトルを取得すると同時に、ページ内に存在するリンク(a.href)を探して再帰的にアクセス 最初に指定したURL以外へのリンク(=外部リンク)は対象外にする(でないと無限に検索を続けるから) 一度アクセスしたURLは2度とアクセスしないように注意 GAパラメータ、ページアンカー、index.htmlなどのデフォルトページ、などをURLから取り除いて整形 - 結果はグローバル変数urlsに格納する 苦労したポイント 非同期処理がわかりづらい。クロールしている最中に処理が最後まで流れてしまうのを防止するのが大変だった。 最初はpromiseで実装を試みたけど、途中からasync/awaitに方向転換。 とにかく動作するものが構築できたからよかった 参考リンク teratailにあげた質問 Node.js - node.jsでpromiseを使って再帰的にURL一覧を作成したい|teratail maisumakun様にteratailでアドバイスをいただきました。本当にありがとうございます。 非同期と反復 ループと反復処理 - JavaScript | MDN HTTPリクエストとnode-fetch node-fetch - npm Node.jsでAysnc/Awaitを使ってHTTPリクエストを行う5つの方法 NodeJSでAsync/AwaitしてREST API 叩いて json を処理する - Qiita DOM操作 jsdom - npm Node.js+https+jsdomで超簡単にHTMLの要素やテキストを調べる方法 | iwb.jp Node.jsでウェブスクレイピングする色々な方法 - Qiita コード /* * Modules */ const fetch = require('node-fetch'); const { JSDOM } = require('jsdom'); /* * Config */ const BASE_URL = "https://www.example.com/"; const IGNORE_QUERY_PARAMS = ['utm_campaign', "utm_source", "utm_keyword", "utm_content", "utm_medium"]; /* * Global Object(s) */ let urls = {}; /* * Functions */ // 非同期で HTMLのタイトルを取得 const getHtmlByUrl = async(target_url, source_url) => { try { const res = await fetch(target_url); if (!res.ok) throw (res.statusText); return res.text(); } catch (e) { var error_msg = 'Fetch failed: "' + target_url + '"' + (source_url ? ' from "' + source_url + '"' : "") + ' ' + e; console.log(error_msg); return; } }; // URLの有効性をチェック const isUrlEligible = (url) => { if (typeof url !== 'undefined' && url.indexOf(BASE_URL) !== 0) return false; if (url.match(/\.(css|jpg|png|gif|pdf)($|\?.*)/)) return false; return true; } // URLのフォーマット const formatUrl = (url) => { var formatted = url; IGNORE_QUERY_PARAMS.forEach(param => { var regexp = new RegExp(param + "=[^&]*"); formatted = formatted.replace(regexp, ""); }); formatted = formatted .replace(/#.*$/, '') .replace(/[\?&]*$/, '') .replace(/\/index\.(html?|php|asp|cgi|jsp)\??/, '') .replace(/\/\/$/, ''); formatted += formatted.match(/(\.(php|html?|jsp|cgi)|\/)$/) ? '' : '/'; return formatted; } // 重複を除外したURL一覧を生成 const getLinkUrlsFromDom = (dom) => { var url_list = {}; dom.window.document.querySelectorAll('a') .forEach(a => { if (!isUrlEligible(a.href)) return; url_list[formatUrl(a.href)] = 1 }); return Object.keys(url_list); } // Webページのクロール const crawlWebPage = async(target_url, source_url) => { // グローバルオブジェクトの初期化 if (typeof urls[target_url] !== 'undefined') return; urls[target_url] = {}; // HTMLの取得 const html = await getHtmlByUrl(target_url, source_url); if (!html) { urls[target_url].is_success = false; return; } // HTMLのパース const dom = new JSDOM(html, { url: BASE_URL }); // 取り出したい情報 urls[target_url].title = dom.window.document.querySelector('title').textContent; // HTMLに含まれるリンクの一覧を生成(重複は除外) urls[target_url].links = getLinkUrlsFromDom(dom); for (link of urls[target_url].links) { await crawlWebPage(link, target_url); } } /* * main */ (async() => { // URLからページの情報を取得してグローバルオブジェクトを更新 await crawlWebPage(BASE_URL); // 結果を出力 console.log(urls); })();

Viewing all articles
Browse latest Browse all 9309

Trending Articles