[Tool] クローリングとスクレイピングでツール作成 #1「Webサイトのリンク一覧を取得」

2015年11月2日

Javascript Shell Tool テクノロジー プログラミング 特集

WEBページをクローリングして必要な情報を取得する事は非常に効率的な作業です。 決まったニュースサイトを毎回Googleで検索している人は、パケットも時間もロスしてますね。 ニュースであれば、スマホアプリやRSSリーダーで取得して片っ端から読んでいる人は、かなりの効率家だと思われます。 今回は、自分でニュースアプリを作ったり、何かの情報を集める時に必須である「クローリング」と「スクレイピング」を利用したツール作成を行っていきたいと思います。

クローリングって何?

Googleの検索で有名になったワードで、WEBページを事前にサーチして情報を収集する事です。 検索サイトというのは、その場で全世界のサイトを探しに言っているのではなく、事前に情報を取得しておくことで検索した時のスピードを早く出来るようにしているんですね。 こんな事は、当たり前でしたね。

スクレイピングって何?

クローリングに対してスクレイピングは、WEBページの中の情報をピンポイントで取得する作業の事なんですね。 ニュースページをRSSではない状態で取得する場合に、ニュース記事とタイトル、画像などを取得する必要があります。 WEBページには、広告や他のサイトのリンクやサイトのメニューなどの色々なリンクが張り巡らされています。 だけど、必要な内容は、同じサイトであれば、だいたい決まったclass名やID名が付いていたり、DOM構造が一定になっている事がほとんどなので、事前にDOM構造の特定の箇所を指定しておく事で、比較的簡易に情報ゲットすることができます。 ただし注意としては、WEBページの情報の著作権はそのサイト主にあるので、取得した情報を二次利用的に公開する場合は、著作物の確認を行うことを忘れないようにしましょう。

WEBサイト内のリンク一覧の取得

今回は第1回目という事もあり、スクレイピングというよりは、WEBサイトを一括処理する方法として、WEBページ内のリンクを辿っていくスクリプトを書いてみました。 リンク一覧から、ページ内構造の把握ができたり、サイトマップを作れたりできるので活用方法は無限大かもしれませんね。

仕様検討

クローリングを行うツールは大体のサーバー言語で存在すると思いますが、最近AJAX処理でのページ動的表示などが増えてきていることから、PHPなどを使った静的サイト対象のみだと、かなり寒い結果になりそうなので、以下の構成で行うようにしました。 Nodejs + SpookyJS(CasperJS) + Shell Nodejsは、サーバーサイドで行う必要があったので、casperjsを汎用性の高いnodejsで利用できるspookyJSを利用したかったので選択 shellは、nodejsの起動コントロールや、spookyjsが基本的に1回の起動セッションで1URLしか対応できない事をカバーするために、総合管理ツールとして利用

プログラム

getLinks.js

// Set Library var SPK = require('spooky'); var FS = require('fs'); // Today var dt = new Date(); var y = dt.getYear()+1900; var m = (dt.getMonth()+1>=10)?dt.getMonth()+1:"0"+dt.getMonth()+1; var d = (dt.getDate()>=10)?dt.getDate():"0"+dt.getDate(); var ymd = y.toString() + m.toString() + d.toString(); // Set init var targetUrl = process.argv[2]; var dataDir = "siteData"; var sp = targetUrl.split("/"); // Data-Directory if(!FS.existsSync(dataDir)){FS.mkdirSync(dataDir,644)} if(!FS.existsSync(dataDir+"/domain")){FS.mkdirSync(dataDir+"/domain",644)} if(!FS.existsSync(dataDir+"/tmp/")){FS.mkdirSync(dataDir+"/tmp/",644)} if(!FS.existsSync(dataDir+"/tmp/"+ymd)){FS.mkdirSync(dataDir+"/tmp/"+ymd,644)} // saveFile var dataDomain = dataDir+"/domain/"+sp[2]+".dat"; var fileTmp = process.argv[3]; if(!fileTmp){ fileTmp = dataDir+"/tmp/"+ymd+"/"+sp[2]+".dat"; } // Crawling var spooky = new SPK({ casper:{ logLevel:'debug', verbose:true, sslProtocol:'any' }, child:{ "ssl-protocol":"tlsv1", "ignore-ssl-errors":true } }, function(err){ spooky.start(targetUrl,function(init){ var data = this.evaluate(function(){ var links = document.links; var urls = []; for(var i=0;i<links.length;i++){ if(!links[i].href){continue} if(urls.indexOf(links[i].href)!=-1){continue} urls.push(links[i].href); } return {links:urls , title:document.title , load:location.href}; }); this.emit("setLinks",data.links); this.emit("setPage",{access:init.url , load:data.load , title:data.title}); }); spooky.run(); }); // saveLinks spooky.on("setLinks",function(urls){ //既存データ読み込み var lists = []; if(FS.existsSync(fileTmp)){ lists = FS.readFileSync(fileTmp,"utf-8").split("\n"); } else{ //初回の1行 lists = [targetUrl]; FS.appendFileSync(fileTmp, targetUrl +"\n"); } //取得データ一覧処理 var add=0; for(var i=0; i<urls.length; i++){ //登録済み確認 if(lists.indexOf(urls[i])!=-1){continue} //書き込み FS.appendFileSync(fileTmp, urls[i] +"\n"); add++; } console.log("-add : "+add); }); // date , load-url , location.href , page-title spooky.on("setPage",function(data){ var jsonText = '{"date":"'+(+new Date())+'","accessUrl":"'+targetUrl+'","loadUrl":"'+data.load+'","title":"'+data.title+'"}'; FS.appendFileSync(dataDomain, jsonText +"\n"); console.log("-page : "+jsonText); });

getLinks.sh

#!/bin/bash # get-id ymd=`date +%Y%m%d` his=`date +%H%M%S` # get-domain DOMAIN=`echo $1|cut -d/ -f 3` echo ${DOMAIN} # tmp-data tmpDir="siteData/tmp/$ymd" mkdir -p $tmpDir TMP="$tmpDir/$DOMAIN.$his.dat" # Max-List(Default 100) maxList=100 #if [ -z $2 ];then maxlist=$2;fi # getLinks-access node getLinks.js $1 $TMP currentList=0 while read line;do echo "$currentList)" # Max-Check if [ $currentList -ge $maxList ];then break;fi currentList=$((currentList +1)) if [ $currentList -gt 1 ];then # Same-Domain-Only(sub-domain) DOMAIN2=`echo ${line}|cut -d/ -f 3` if [ "$DOMAIN" != "$DOMAIN2" ];then continue;fi node getLinks.js ${line} ${TMP} #currentList=`expr $currentList +1` fi done < ${TMP}

実行

ターミナルで以下のコマンドを実行 ※qiitaのトップページを初回アクセスにした例 $ bash getLinks.sh http://qiita.com/

改造ポイント

「getLinks.sh」内の「maxList」の値をデフォルトで100にしてます。 URLアクセスを100回までに制限してますが、この値を変更することで、上限値の変更ができます。 ただし、一気に大量アクセスすると、IPアドレスでロックされる事もあるので、適正値を見極めて使ってください。 ※spookyjsはあまり速度が出ないのでそんなに心配がいらないかもしれないですけどね。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ