全文検索エンジンを作ろうと思って、まずはクローラーを作ってみた
何を使ったのかと言うと、crawler4j を使って、対象のサイトをかたっぱしから動き回る実装をしてみた。
ただし、ディレイとか入れなくて DOS 攻撃になるんじゃねーかとか不安もあるので、応用するときは自己責任で。
※ スレッド数指定でマジでサーバに負荷かけられてしまうのでマジ自己責任で
使い方は簡単で、libraryDependency に crawler4j を突っ込むところから始める。
libraryDependencies ++= Seq( "edu.uci.ics" % "crawler4j" % "4.1", // crawler "com.typesafe" % "config" % "1.3.0" )
そしたら、src/main/resources/application.conf ファイルを作成して、動作設定を記述する。
crawler { baseUrl = "http://white-azalea.hatenablog.jp" ignoreRegex = ".*(\\.(css|xml|js|gif|jpg|png|mp3|mp3|zip|gz))$" cache = "./cache" threads = 1 }
- baseUrl : 検索開始位置、兼、検索対象URL (このサイト以外は検索しない)
- ignoreRegex : 画像やCSS,XMLや明らかなバイナリはクロールしない。
- cache : キャッシュ保存ディレクトリ
- threads : 何スレッドでクロールするのか
で、クロールする対象の処理をぺたり
package models import java.util.regex.Pattern import edu.uci.ics.crawler4j.crawler.{CrawlController, CrawlConfig, Page, WebCrawler} import edu.uci.ics.crawler4j.fetcher.PageFetcher import edu.uci.ics.crawler4j.parser.HtmlParseData import edu.uci.ics.crawler4j.robotstxt.{RobotstxtServer, RobotstxtConfig} import edu.uci.ics.crawler4j.url.WebURL class SearchCrawler extends WebCrawler { val fileFilter = Pattern.compile(Configure.ignoreRegex); override def shouldVisit(page: Page, webURL: WebURL): Boolean = { // 調べてないけど、page にはリンク元ページが格納されてると思われ。 // webURL の指定しているページをクロールするかどうかの判定処理。 val currentUrl = webURL.getURL.toLowerCase !fileFilter.matcher(currentUrl).matches() && currentUrl.startsWith(Configure.baseUrl) } override def visit(page: Page): Unit = { // クロールした結果を受け取るハンドラ import scala.collection.JavaConversions._ val currentUrl = page.getWebURL if (page.getParseData.isInstanceOf[HtmlParseData]) { val data = page.getParseData.asInstanceOf[HtmlParseData] val allTextData = data.getText val allLink = data.getOutgoingUrls println(s"CurrentURL : $currentUrl") println(s"CurrentText : ${allTextData.length}") println(s"Links :") allLink.foreach(url => { println(s"Link : ${url.getURL}, ${url.getAnchor}, ${url.getTag}") }) } } } object StartCrawler { val crawlConfig = { val conf = new CrawlConfig conf.setCrawlStorageFolder(Configure.cacheDir) conf } val controller = { val fetcher = new PageFetcher(crawlConfig) val robotTextConf = new RobotstxtConfig val robotTextServer = new RobotstxtServer(robotTextConf, fetcher) new CrawlController(crawlConfig, fetcher, robotTextServer) } def start(): Unit = { controller.addSeed(Configure.baseUrl) controller.start(classOf[SearchCrawler], Configure.threads) } }
呼び出し方は…わかるよね?
出力した例が下記。
CurrentURL : http://white-azalea.hatenablog.jp/entries/2011/05/16 CurrentText : 9908 Links : Link : http://white-azalea.hatenablog.jp/archive/category/Linux, Linux (8), a Link : https://blog.st-hatena.com/images/theme/hatena-star-quote-star.png?version=0deb8348676c873485aaafc310112165, null, img Link : https://blog.st-hatena.com/images/common/meta-icon-global.png, null, link Link : http://d.hatena.ne.jp/keyword/iPhone, iPhone, a Link : http://white-azalea.hatenablog.jp/archive/category/Jetty, Jetty (2), a Link : http://white-azalea.hatenablog.jp/archive/category/Javascript, Javascript (3), a Link : http://white-azalea.hatenablog.jp/archive/category/Cocoa, Cocoa (5), a Link : http://white-azalea.hatenablog.jp/archive/category/C%E8%A8%80%E8%AA%9E, C言語 (2), a Link : http://d.hatena.ne.jp/keyword/Android, Android, a Link : http://white-azalea.hatenablog.jp/archive/category/Akka, Akka (2), a Link : http://white-azalea.hatenablog.jp/archive/category/AS3, AS3 (4), a Link : http://white-azalea.hatenablog.jp/archive/category/F%23, F# (3), a Link : http://white-azalea.hatenablog.jp/archive/category/%E6%97%A5%E8%A8%98, 日記, a Link : http://white-azalea.hatenablog.jp/entry/2015/05/01/180029, play framework で react.js やろうとしてみたメモ, a Link : http://white-azalea.hatenablog.jp/entry/2015/04/30/203148, HTML5+JS でメニューをそこそこカッコよく出してみる, a Link : http://white-azalea.hatenablog.jp/archive/category/Java, Java (17), a Link : http://white-azalea.hatenablog.jp/archive/category/Windows, Windows (4), a Link : http://white-azalea.hatenablog.jp/archive/category/Twitter, Twitter (3), a ...