- Korea times
- 끌리면 오라...BGM 광고음악 라이브러리
- KartOO visual meta search engi…
- E-Book
- Channel9
- MSDN
- 여리의 작업실
- 유경상의 .NET 블로그
- window 쪼물딱 거리기
- 블루 홈(소현이 누님)
- IT 관련 전반 내용(정환이네)
- 비너스의 정보 공유(유틸리티들)
- 형기의 자료공간(디지털ERA에서 콘텐츠ERA로)
- EzineArticles (여러 분야의 글들이 올라옴)
- Relationship을 보여주는 라이브러리
- OpenRCE
- 젠틀의 블로그(무선 통신의 모든것)
- 헐랭이와 IT보안
- 워니. 추억ㅇㅔ ㅂㅣ추ㅇㅓ.
- Computer Forensics
- 토익 광장(YBM)
- Korea Times 이용하기
- Larkware Software
- TCP/UDP
- Black Hat
- DEF CON
- Slashdot
- ReallyUsefulEbooks.com Update
- 실리콘밸리 뉴스
- Application Development Trends
- Visual Studio Hacks
- MIT OCW
- Redmond Developer News
- SecurityFocus
- Microsoft Window Hacking Porta…
- Darknet - Don't Learn to Hack …
- Windows Tips, Tricks and Hacks
- Hack In the Box
- (IN)SECURE Magazine
- SuperSite Windows Vista
- Government Security
- Life is Still Talking (Good)
- PHRACK
- Found+Read(resource for startu…
- Jonathan Boutelle
- Venture Hacks
- 스마트플레이스
- All about Intellipedia
- Undocumented Windows 2000 Secr…
- HexBlog (Decompiler)
- TED (Ideas worth spreading)
- Crash Dump Analysis and Debugg…
- Rootkit
- DDK Developers(MS)
- 미친 감자의 블로그
- The Art of Assembly Language
- Chpie (키보드 후킹)
- Drivers Online
- (음악) Delicate SONG
- Reverse Engineering Community
- Software Best Practices
- Sara Ford's WebLog
- Cheat Happens
- Debugging,Unpacking,Assembling…
- 윤석찬님 블로그
- OK 괜찮아 다 잘 될거야
- RingBlog
- Art Life :: 하늘소
- IT's Paradise
- John Robbins!
- Wintellect
- Hacked Gadgets
- 소프트웨어 이야기
- Ryan Naraine's Zero Day
- VULN
- Stay Secure
- EBS 영어 공부(블루워터)
- 101BLoG : "Bright Size Life" o…
- Hacker Challenge
- Hackers Center
- White Hat, Chicago Con
- Ethical Hacker Network
- ChaseNet (Security)
- TechTarget
- Entrepreneur
- Infopackets
- Popular Science
- Dark Reading - The Business of…
- How Stuff Works
- codeDriver - Crack (역공학)
- Gadget (Windows)
- Serious Code
- Iguacu Blog(블루문)
- SecurityProof
- Power of Community(Hacker)
- Crack ?
- Security Freak
- Data Network Resource
- FoundStone - Security Consulti…
- Google Online Security Blog
- (BOOK) Cool DogBooks
- SachaBarber (좋은 개발자)
- System Software Incorporation
- 스카이 벤처
- NewsTorrent
- 글로벌 IT 네트워크
- Ethical Hacking and Infosec
- Realms of Hacking tricks
- CodeBreakers Journal
- Anti Rootkit Blog
- The Reverse Code Engineering C…
- Anti-Debug Tools
- Reverse Code Engineering Video…
- Damn Vulnerable linux
- Security Problems
- French Reverse Engineering Tea…
- Monac
- Open Source Vulnerability Data…
- Viruschaser 검사(바이러스)
- Windows Tips
- 보안 대처 연습
- [Download] Kartz CD
- [Download] FlMS Download
- [Download] DDL2
- 중국 해킹 사이트(안전중국)
- 바이러스 분석
- Javascript 전문가
- Virus Alert Zone (바이러스 분석)
- Computer World
- 문스랩닷컴(보안)
- Unpack China
- Black Storm Reverse Engineerin…
- 역공학 Reverser
- 문화 망명지 - 시, 소설
- WPF MVP
- Research Channel
- The Problem Solver - C# MVP
- Reversing - 리버스 엔지니어링
- Nigel Spencer's Blog (.NET)
- Kirill Osenkov (.NET C# IDE Te…
- H33T (BitTorrnet 검색 사이트)
- ITL (해킹, 보안)
- ITL (Invisible Things Lab) Blo…
- ebook, pdf, chm
- 주식 - 멘토클리닉
- CherryLove - 바이러스, 백신, 악성코드
- PMP
- 영원한 해커, hacker
- 리버싱, PE
- 신호철 - dsphome
- TechEd 2009
- SHOUT
- [도서] 오디오북
- [도서] 전자책
- [도서] 국내도서요약
- [도서] 해외도서요약
- TopCorder - 프로그래밍 연습
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 책
- security
- 마이크로소프트
- 해킹
- VSTS
- 보안
- 비주얼스튜디오
- 구글
- WPF
- 디버그랩
- C#
- MVP
- english
- 역공학
- hacking
- 닷넷
- .net framework 4
- VSTS 2010
- visual studio 2010
- Windows 7
- .NET Framework
- .net
- Windows
- Visual Studio
- debugging
- Microsoft
- 디버깅
- 비주얼 스튜디오
- Today
- Total
NaggingMachine
ElasticSearch를 이용한 PDF와 Word 문서 검색 서비스 만들기 본문
PDF 문서를 검색하는 방법은 다양하다. Windows의 탐색기를 이용하는 방법에서부터 Google Desktop Search를 이용하는 방법도 있다. 물론 그 외에도 로컬 데스크톱의 문서를 검색해주는 다른 도구를 사용할 수도 있다. 하지만 조직내의 PDF (Word도 가능해요. Tika에서 지원하는 문서를 참고하세요)를 검색해야 하는 방법을 제공해야 한다면 어떻게 해야 할까? 얼마전 누군가가 그런 고민을 하고 있던터라 '그거 그렇게 어렵지 않을텐데요..'라는 생각을 하고선 만들어 봤다. ElasticSearch(이하 ES)에 대해서 잘 알고 계신분께는 큰 도움이 안될 수 있도 있고, 어쩌면 내가 한 방법이 틀릴수도 있다는 사실을 미리 말씀드린다. 구축에 필요한 시간은 서버 설치 포함해서 3시간 정도이다.
ES는 루씬 기반의 검색 엔진이다. 그냥 Json 데이터를 넣어주기만 하면 알아서 인덱싱을 걸어주고 웹 API를 호출하는 것만으로도 구글과 같은 검색 엔진을 구축할 수 있다. 구현할 시스템의 전체적인 구조는 다음과 같다.
1. ES를 활용한 문서 검색 서버
- ES
- 텍스트 추출을 위한 TIKA 라이브러리
- 분석 자동화를 위한 PHP 스크립트
2. 사용자가 입력한 키워드로 문서를 검색할 수 있는 검색 전용 사이트
- Bootstrap을 이용한 사이트 구축
3. 검색에 사용할 문서를 자동으로 등록할 FTP 서버 또는 공유 폴더
- Samba 활용
문서 검색 서버는 다음과 같은 순서대로 작동한다.
1. 문서를 수집하여 특정 폴더에 저장한다.
2. 주기적으로 해당 폴더에 올려진 문서를 분석한다.
2-1. 하나의 문서를 선택한 후 메타데이터(작성자, 생성일, 문서 종류 등)와 본문(텍스트)를 추출한다.
2-2. 이미지로 저장된 PDF 문서의 경우에는 OCR 엔진으로 텍스트를 추출한다.
3. 생성된 JSON 데이터를 ES에 등록한다.
검색 전용 사이트는 다음과 같이 작동한다.
1. 사이트가 로드되면 전체 인덱싱된 문서의 개수를 가져온다.
2. 사용자가 키워드를 입력하면,
2-1. AND 검색인지 OR 검색인지를 판단한다(체크박스 형태로 제공, ES의 기본검색은 OR임)
2-2. "_search?q=" 쿼리를 통해 서버로 전달
2-3. JSON 형태로 결과를 전달 받는다.
3. JSON 데이터를 분석하여 화면에 리스트 형태로 뿌려준다.
ES 설치는 구글神을 통해 쉽게 알 수 있는데, 한글 문서를 분석하기 위해서는 반드시 인덱싱 저장공간을 생성할 때 한글 형태소 분석기를 플러그인으로 등록해야 한다. 이와 관련된 자세한 내용은 다음 링크를 참고하자.
>> ElasticSearch 설치 및 샘플 사용기 (http://mimul.com/pebble/default/2012/02/23/1329988075236.html)
>> ElasticSearch로 로그 검색 시스템 만들기 (http://helloworld.naver.com/helloworld/273788)
>> elasticsearch 설치 및 한글형태소분석기 적용 따라하기 (http://jjeong.tistory.com/711)
또는 이전에 올린 글처럼 자바로된 한글 형태소 분석기를 통해 본문을 형태소 분석한 후 해당 텍스트를 저장하는 방법도 있을것 같다.
>> 형태소 분석기로 웹 문서 파싱하여 단어만 추출해보자 (http://naggingmachine.tistory.com/823)
세상에는 참 福 받아야 하는 사람들이 많다. 소중하게 공유한 자료는 소중하게 공유하는 것으로~
ES 설치는 그렇다고 하더라도 PDF에서 텍스트를 어떻게 추출해야 할까?라는 고민이 될텐데, 사실 처음부터 별다르게 고민하지도 않았다. 어딘가에서 이미 오픈소스로 만들었을테고 이렇게 중요한 라이브러리인 경우에는 보통 Apache 프로젝트로 등록되어 있는 경우가 많으니까. 역시나 찾아보니 있다. tika! 그냥 가져다가 옵션 주고 사용하면 된다. 메타데이터는 XML이나 JSON 형태로 추출이 가능하고 텍스트도 별도로 추출할 수 있다. 이 옵션들을 이용하면 인덱싱에 사용할 데이터를 JSON 으로 구성할 수 있고 만들어진 데이터를 curl을 이용하여 ES에 등록하면 된다.
문서를 분석하기 위해서 사용한 스크립트는 다음과 같다.
이 코드를 살펴보기 위해서는 다음과 같은 디렉터리 구조임을 이해해야 한다.
Search (directory, 루트 디렉터리)
--> elasticsearch (directory)
--> elasticsearch-0.90.7 (directory)
--> create_index.php (file)
--> tika-app-1.4.jar (file)
--> home (directory, 웹 사이트 홈)
--> pdf (directory, 분석할 문서가 있는 디렉터리)
--> download (directory, 분석하고 난 후 다운로드가 가능한 디렉터리)
다음은 create_index.php의 소스 코드이다.
<?php
$dir = "../pdf"; $count = 0; changeDirFileNames($dir); if ($handle = opendir($dir)) { while(false !== ($readdir = readdir($handle))) { if ($readdir != '.' && $readdir != '..' && (strpos($readdir, '.pdf') !== FALSE || strpos($readdir, '.doc') !== FALSE)) { $path = $dir . '/' . $readdir; if (is_file($path)) { process($path); } } } closedir($handle); }
// 디렉터리의 모든 파일 이름에서 열리지 않는 문자를 _로 대체한다. function changeDirFileNames($dir) { $order = array("(", "&", "'", ",", " ", "+" , "@", "-", ")"); if ($handle = opendir($dir)) { while(false !== ($readdir = readdir($handle))) { if ($readdir != '.' && $readdir != '..' && (strpos($readdir, '.pdf') !== FALSE || strpos($readdir, '.doc') !== FALSE)) { $path = $dir . '/' . $readdir; if (is_file($path)) { // 파일 이름 변경 시도 $new_path = str_replace($order, "_", $path); if ($path != $new_path) { exec("mv \"" . $path . "\" \"" . $new_path . "\""); } } } } closedir($handle); } } function process($filename) { ob_start(); passthru("java -jar tika-app-1.4.jar -t ". $filename); $text = ob_get_clean(); $text = trim(preg_replace('/\s+/u', ' ' , $text)); ob_start(); passthru("java -jar tika-app-1.4.jar -j ". $filename); $json = ob_get_clean(); $json_arr = json_decode($json, true); $json_arr['text'] = $text; // 파일 이름을 title로 넣어준다. $order = array(".", "_", "/"); $title = str_replace($order, " ", $filename); $json_arr['title'] = $title; $str = json_encode($json_arr); exec("curl -silent -XPOST 'http://localhost:9200/hr/file/' -d '" . $str . "'"); if (strlen($text) == 0) { echo $filename . " might be image.\n"; } else { echo $filename . " processed\n"; } exec("mv \"" . $filename . "\" ../download"); return ""; }
?>
이 소스 코드를 실행하면 pdf 디렉터리에 있는 문서들을 가져다가 ES에 등록하는데, 인덱스의 이름은 hs 이며, 디렉터리는 file 이고 문서의 아이디는 자동 생성(생략되어 있음)하도록 했다. 만약 문서의 이름에 따라서 업데이트를 하고 싶다면 문서의 이름을 hash 값으로 사용해야한다. 그렇지 않으면 문서가 중복해서 등록된다.
이제 문서의 분석이 준비되었으며, 마지막으로 구글과 같은 검색 인터페이스를 제공하기만 하면 된다.
나의 경우에는 PDF로 작성된 이력서를 검색하기 위한 용도였으므로 다음과 같은 화면을 구성하였다.
키워드들이 포함된 가능한 많은 문서를 검색해 달라는 요구 사항이 있어서 키워드 양쪽에 *를 추가해서 검색하고 있으며, 페이징 기능을 추가하지 않았다(가져오는 갯수는 10000으로 설정). 원한다면 얼마든지 코드를 편집할 수 있을 것이다.
다음은 index.html 파일의 소스 코드이다.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>PDF Search System</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Loading Bootstrap --> <link href="css/bootstrap.css" rel="stylesheet"> <!-- Loading Flat UI --> <link href="css/flat-ui.css" rel="stylesheet"> <link rel="shortcut icon" href="images/favicon.ico"> <!-- HTML5 shim, for IE6-8 support of HTML5 elements. All other JS at the end of file. --> <!--[if lt IE 9]> <script src="js/html5shiv.js"></script> <![endif]--> </head> <body> <div class="container"> <div class="demo-headline"> <h1 class="demo-logo"> Resume Search </h1> <div id="count_index"></div> <br><br> <div class="span3"></div> <div class="span3"> <input type="text" class="login-field" value="" placeholder="Keyword" id="search-keyword" /> </div> <div class="span2"> <a class="btn btn-primary btn-large btn-block" href="#" id="search-button">Search</a> </div> <br><br><br> <div class="span12"><input type="checkbox" name="chk_operator" id="chk_operator" value="OR 검색">OR 검색 <br><br> <div id="count_result"></div> </div> <!-- /demo-headline --> <div class="span12"> <div class="block"> <div id="result_table"> </div> </div> </div> <!-- Load JS here for greater good =============================--> <script src="js/jquery-1.8.2.min.js"></script> <script src="js/jquery-ui-1.10.0.custom.min.js"></script> <script src="js/jquery.dropkick-1.0.0.js"></script> <script src="js/custom_checkbox_and_radio.js"></script> <script src="js/custom_radio.js"></script> <script src="js/jquery.tagsinput.js"></script> <script src="js/bootstrap-tooltip.js"></script> <script src="js/jquery.placeholder.js"></script> <script src="http://vjs.zencdn.net/c/video.js"></script> <script src="js/application.js"></script> <!--[if lt IE 8]> <script src="js/icon-font-ie7.js"></script> <script src="js/icon-font-ie7-24.js"></script> <![endif]--> <script type="text/javascript"> var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); </script> <script type="text/javascript"> try{ var pageTracker = _gat._getTracker("UA-19972760-2"); pageTracker._trackPageview(); } catch(err) {} $("#search-button").click(function() { search(); }); $("#search-keyword").live('keypress', function(e) { if (e.which == 13) { search(); } }); $(document).ready( function() { update_count(); }); // count 업데이트 function update_count() { $.ajax( { url: 'http://192.168.1.198:9200/hr/file/_count', dataType:'json', type: 'get' }).done(function(data) { if (data != null) { html = "총 " + data.count + "개의 문서가 색인되어 있습니다."; $("#count_index").html(html); } }).error(function(e) { alert("error"); }); } // 검색 메뉴 실행 function search() { var keyword = $("#search-keyword").val(); if (keyword.length == 0) { alert('입력된 키워드가 없습니다.'); return; } var start = new Date().getTime(); var checked = "&default_operator=AND"; if ($("#chk_operator").is(':checked')) { checked = "&default_operator=OR"; } $.ajax( { url: 'http://192.168.1.198:9200/hr/file/_search?size=10000&q=*' + keyword + '*' + checked, dataType:'json', type: 'get' }).done(function(data) { if (data != null) { var count = 0; var html = "<table class='table table-striped'><thead><th width=30>순서</th><th>파일이름</th><th width=50>구분</th><th width=150>생성시간</th></thead>"; var end = new Date().getTime(); var time = end - start; html_count = "총 <b>" + data.hits.hits.length + "</b>개의 문서가 검색되었습니다.<br>" + (time/1000) + "초의 시간이 소요되었습니다."; $("#count_result").html(html_count); $.each(data.hits.hits, function(index, value) { count++; var docu_type = "문서"; if (value["_source"]["text"] == "") { docu_type = "이미지"; } html += "<tr><td>" + count + "</td><td>" + "<a href='../download/" + value["_source"]["resourceName"] + "' target=_blank>" + value["_source"]["resourceName"] + "</a></td><td>" + docu_type + "</td><td>" + value["_source"]["created"] + "</td></tr>"; }); html += "</table>"; $("#result_table").html(html); } }).error(function(e) { alert("error"); }); } </script> </body> </html>
'TechnoBabbler' 카테고리의 다른 글
Global company의 Global Promotion 작업을 마무리 하며... (0) | 2013.12.13 |
---|---|
Google AppEngine의 데이터를 export 하는 방법 (0) | 2013.12.10 |
AppInventor2 소스 코드 구동시키기 (0) | 2013.10.21 |
Facebook App을 이용해서 Wall에 글쓰기 How write to Facebook wall via Facebook App (0) | 2013.09.23 |
짧은 주기의 (모바일) 프로젝트 진행하기 (0) | 2013.08.23 |