YGHTML V0.1.1버전에 다음의 기능을 추가하거나 개선하였습니다.

- HTML DOM Tree 생성 기능 추가
- DOM Tree Viewer 추가
- 주석 파싱 오류 제거
- 약간의 리펙토링(-_-;)

보고된 문제점
- Style Tag 처리불가

아래는 Sample 소스를 이용하여, DOM Tree를 생성, Viewer로 출력된 이미지 입니다.

파싱대상 소스(test.html)

<!document html>
<html>
<head>
  <title>가라지하 사바하</title>
</head>
<script>
<!--
  <a href="dcinside.com">아햏햏</a> fdsf <
  adf, < "" <:>
-->
</script>
<body bgcolor=red background="img/bg.jpg">
<br>
<br>
<br>
  <a href="http://kr.yahoo.com">야후</a>
  <a href="kkk"><img src="yahoo.jpg"></img><img src="yahoo.jpg"/></a>
  <table bgcolor=lightgrey width=95%>
  <tr><td>..</td></tr>
  </table>
  <!-- 비정규 파싱 -->
</body>
</html>


사용자 삽입 이미지

아래는 Naver 메인화면 소스를 적용해 본 이미지 입니다. (역시 복잡합니다.)

사용자 삽입 이미지

해당 소스파일 다운로드 ( Eclipse Project )

정확한 요구사항 분석도 없이,
설계에 대한 개념도 없이 HTML Parser가 절실한 상황에서 급조된 코드입니다..
분명히 저같이 Parser가 절실한 사람이 있다고 생각되기에, 저질 코드라도 용감, 무식하게 공개합니다..

소스의 질적인 부분에 대한 지적은 할말 없습니다..
또한 제가 파서에 대한 개념이 많이 부족한 상태라,
해당소스에서 더 괜찮은 아이디어나 제안이 있으시다면 언제든지 리플 부탁해요..

- 김영곤 (gonni21c@gmail.com)

Java로 만들어진 Html Parser는 제법 많이 있다.

- org.htmlparser
- nekoParser

정도가 있는데, htmlParser는 다소 무겁기는 하지만 잘 정의된 구조에, 4만 페이지 정도의 Hard한 환경에서 Parsing Test를 해보았으나, 모든 문서를 Parsing해 내는(파싱 알고리즘 통과) 안정성을 자랑하였다.

하지만, HTML Parser를 한번이라도 개발한 경험이 있는 사람들은 알겠지만..
HTML Parser의 최대 난제는 아무래도 script value element 처리를 비롯한 html 문법을 무시해야 하는 영역의 구현이 아닐까 싶다.
불행히도 대부분의 Open Source 파서는 이러한 부분에 대한 정확한 처리를 하지 못한다. Script 내부의 Text까지 HTML 문법을 적용하여 Parsing하고, Source 구조상 이부분의 수정이 쉽지가 않았다.

Parser의 생명은 Parsing의 정확도와 속도다.

 정확도에 있어서, HTML은 XML과 틀리게 상당히 변칙적인 문법을 허용한다. 이것은 Parse 트리 구축시 모호성이 발생한다는 것을 의미한다. IE를 비롯한 대부분의 Browser에 내장된 HTML Parser의 경우 HTML 문서의 전처리를 거쳐 well-formed 문서로 만들고(1Pass), XML Parser등을 적용, DOM Tree를 생성한다.(2Pass)
 최소 2Pass를 거쳐야 하므로 속도는 궁극적으로 떨어지며, 일반적으로 구현시 고성능의 ReadBuffer의 구현으로 하거나 1Pass시 불완전 DOM Tree 구축후 2Pass 단계에서 보정방식을 사용하기도 한다.

 속도에 있어 또다른 문제는, SCRIPT부분의 처리이다. SCRIPT 내부 value 처리시 Script 처리 Parser로 동적 전환되고, </SCRIPT>라는 문자열이 발견되기 이전 시점에 Html Parser로 전환되는 과정을 거쳐야 한다. 이것은 Parser 구현시 미리읽기 Buffer를 구현하거나 혹은 Read Cursor가 Lexing 과정중 Back 될 수도 있음을 의미한다. 이런 구현은 속도에는 치명적이며 구현 복잡도는 매우 증가한다.
(그런데 이문제에 대해 별다른 해결책은 없어 보인다, 물론 돈과 시간이 적절히 주어진다면 대체할수 있는 구현은 가능 하겠지만.. 하드웨어가 저질코드의 성능을 충분히 커버해 주는 시대이므로 거기 까지 갈일은 없을것 같다..ㅋㅋ)

 주말 이틀 반납해가며, SCRIPT 부분에 대해 Parsing이 되는 HTML Parser를 만들어 보았다.

 Version은 0.1 정도로 보면 되겠다. 가장 문법이 변태적인 Naver정도는 정확하게 Parsing을 해냈으나 아직까지 많은 테스트와 수정, 추가가 필요한 상황이다. 연구용 정도로 쓰기는에는 불편함이 없을듯 하며, 아직 DOM Tree를 생성하는 부분의 구현은 안되었다.(시간 부족)

 Lexer가 어느정도 안정화 되었으므로 Stack 정도만 적절히 쓴다면 DOM Tree 정도는 쉽게 구현이 가능할 것이다.

소스코드(Eclipse Project) 다운로드


아래는 대략적인 사용법이다.

파싱대상 소스(test.html)

<html>
<head>
  <title>가라지하 사바하</title>
</head>
<script>
<!--
  <a href="dcinside.com">아햏햏</a> fdsf <
  adf, < "" <:>
-->
</script>
<body bgcolor=red background="img/bg.jpg">
  <a href="http://kr.yahoo.com">야후</a>
</body>
</html>

출력결과

tag>html
tag>head
tag>title
latest Tag :title
text>가라지하 사바하
tag>/title
tag>/head
tag>script
text>
<!--
  <a href="dcinside.com">아햏햏</a> fdsf <
  adf, < "" <:>
-->

tag>/script
tag>body
 -bgcolor=red
 -background=img/bg.jpg
tag>a
 -href=http://kr.yahoo.com
latest Tag :a
text>야후
tag>/a
tag>/body
tag>/html


Lexer Code

File file = new File("d:\\test.html");
   Lexer lexer = new Lexer(file);
   Node node = null;
   while((node = lexer.getNextToken()) != null)
   {
    if(node instanceof TagNode)
    {
     String isClosed = "";
     TagNode tag = (TagNode)node;
     if(tag.isClosedTag()) isClosed = "/";
     System.out.println("tag>" + tag);
    }
    else if(node instanceof TextNode)
    {
     TextNode txNode = (TextNode)node;
     System.out.println("text>" + txNode.getParsedText());
    }
   }

막 구현된 소스라 리펙토링이 전혀 안된 상태이며, 구현과 설계를 동시에 하는 본인의 업무 특성상,
스파게티 코드를 보고 충격을 받지 말길 바란다. 차츰차츰 바로 잡아갈테니.. ㅋㅋ



웹페이지 주내용 추출은 기술적으로 쉽지않은 기술 중에 하나다.
HTML 문법 자체도 워낙이 변칙적이고, HTML의 정규화 과정을 거쳐 DOM Tree를 생성했다 해도..
변화무쌍한 웹페이지의 특성상, 단순 Parse Tree의 탐색에 의한 알고리즘만으로는 한계가 있으며,
데이타 마이닝이나 크롤링에 의한 문서 Template 추출에 의한 방법등의 고수준의 2차 이후 보정작업이 필요하다.

하지만..
단순 DOM Tree만을 이용하여, 간단한 알고리즘만으로도 70% 정도의 정확도를 가지는 주내용 추출기의 구현은 가능하다..

[추후추가 그림]
[주내용 추출의 원리]

아래의 사진은 해당 알고리즘을 이용하여 원본 페이지에서 주내용 추출후, 주 내용에서 주제문장을 추출하는 기능의 일부분이다.

사용자 삽입 이미지
              [원본문서]


사용자 삽입 이미지
            [축약처리된 문서]

위 그림에서 처럼, 원본 페이지의 광고영역, 네비게이션 영역, 기타 정보 영역은 제거되고 문서의 핵심내용만 추출된 모습을 볼 수 있다. 붉은 색에 해당하는 문장은 추가적으로 핵심내용에서 주제문 추출 알고리즘을 적용하여 추출된 문서를 가장 잘 대표할 수 있는 문장이다.

해당 기능을 활용하면 아래와 같은 응용구현도 가능하다.
아래의 그림은 원본 문서에서 필터링을 적용, 주내용, 그림을 추출하여, 해상도가 제한적인 모바일 장비에서 해당페이지의 내용의 손실없이 볼 수 있도록 하는 페이지 제가공 기능이다.

사용자 삽입 이미지
                                      [원본 페이지]
사용자 삽입 이미지
                                      [300px 해상도로 변환된 페이지]

아래의 기능은 소스페이지를 제가공(주내용 추출, 해상도 변환)을 거쳐 실제로 핸드폰으로 실제 웹사이트상의 웹페이지의 내용을 보여주고 있다.

사용자 삽입 이미지
사용자 삽입 이미지

               [실제 인터넷 상의 페이지를 해상도가 낮은 장비에서도 손쉽게 확인 할 수 있다]

해상도가 제한적인 장비에서 별도의 좌우 스크롤 없이도 손실없는 내용을 확인 할 수 있으며, 하나의 페이지에 대해서 주내용 모드와 네비게이션 모드로 나누어 작은 화면에서 실 인터넷 사이트의 내용을 손쉽게 확인 할 수 있다.

** 위 사진은 2006년 제작된 PIAS의 실제 구현 기능임.

란에보에 비할바 못 되지만..
아반떼 정도 크기에 155마력이면, 투스카니 정도와는 겨뤄볼만한 성능은 나올듯..
참고로, 소나타2.0에 올라가는 엔진과 같은 엔진을 사용한다..
대우자판에서 9월 출시, 예상 출시가는 2천 초반대 예상..

대략 기획에만 2년 정도 걸렸고..
2005년엔 혼자서 1년동안 자바로 구현하다 지쳐서..
2006년, 2명이 더 붙어서 3명이 1년동안 만든 프로그램..

최종 이름은 거창하게 PIAS(Personalied Internet Agent System) 이다..

대략적인 프로그램의 구동은 이렇다.

1. 프로그램은 트레이아이콘의 형태로 24시간 사용자의 PC에서 작동된다.
사용자가 웹브라우저를 열어서 웹서핑을 하는 동안의 모든 Event를 Catch하여,
자체 개발한 알고리즘으로 사용자의 관심사를 추출한다.
즉, 사용자의 추상화된 정신 세계를 인터넷 행위를 감시하여 추상화 하고 PC에 동기화 시키는 것이다.

2. 동기화된 사용자 데이타(User Profile)을 기반으로,
프로그램에 내장된 Web Crawler가 사용자가 자주 방문하는 사이트를 중심으로 웹페이지를 수집, 분석한다.
데이타마이닝의 연관분석 및 자연어 처리에서 사용되는 유사도 분석, 문서 벡터 추출 등의 기법을 통하여,
사용자가 가장 관심을 가질 만한 문서를 추출한다.
즉, 사용자는 검색을 하지않고, 별도의 질의어의 입력없이, 필요할 것으로 예상되는 자료를 추천 받을 수 있다.

3. 문서의 추천을 위한 작업들은 많은 CPU사용과 DB IO를 필요로 하므로,
프로그램 자체적으로 스케쥴러를 내장,
사용자의 PC의 최대 유휴시간대를 추출, 해당작업을 수행하므로, 사용자는 PC 사용상 불편을 느끼지 않고 문서의 추천을 받을 수 있다.
예제 ) 밤새도록 문서의 수집, 분석을 거쳐 사용자는 아침에 일어났을때, 추천 문서를 신문 읽듯이 확인 할 수 있다

4. 에이전트는 사용자에게 비서와 같은 프로그램이므로,
시간과 장소에 구애받지 않고 서비스를 제공하고, 제공된 서비스에 대한 사용자의 반응(피드백)을 분석하여,
사용자 관심사 추출의 정확도를 높일 수 있다.
본 프로그램에서는 Cell phone 을 지원할 수 있는 서비스 서버를 내장하여, 사용자가 외부에서도 추천 문서를 읽는 다거나 할 수 있다. 또한 모바일 장치의 특성상, 웹페이지 자동요약, 해상도 변환등의 기능을 내장하였다.

5. 사용자의 관심사의 추출을 통해 개인화 검색 뿐만아니라,
관심사가 유사한 사용자간 친구추천과 유사한 기능을 구현하였다.
별도로 내장된 p2p 기반 메신저를 통하여 프로그램은 유사한 사용자(UDP, WS-Discovery)를 찾고,
사용자간 연결이 이루어지만 해당 사용자간 보유 자료를 공유할 수 있는 기능을 지원한다.

위에서 정의한 사용 시나리오외에도 많은 기능들이 있음..

아무튼 실제 구현물은 아래와 같음..

사용자 삽입 이미지

 프로그램의 메인화면, 에이전트가 문서를 추천하고, 사용자는 필요한 웹문서를 스크랩하여 확인할 수 있다.


사용자 삽입 이미지
 실시간 웹문서 문서변화 감지 기능을 내장하여, 웹페이상에 변화가 생겼을때, 사용자가 높은 관심을 가질 것이라고 생각되는 문구나 단어간 조합이 발생하였을때 에이전트가 즉시 통보해주는 기능, 바뀐부분을 Red Font로 변환하여 표시해 준다.


사용자 삽입 이미지

웹브라우저 실시간 감시기, 사용자의 모든 행위를 감시하고 DB에 동기화하며,  동기화된 데이터는 별도의 연산을 거쳐 사용자의 관심사 추출을 위한 데이터로 사용된다.

사용자 삽입 이미지

웹페이지 핵심영역 추출및 주제문장 추출, 자체 개발한 웹문서 필터링 방법을 적용 특정 문서에 주내용만 추출, 내용중에서 주제문장 만을 추출함.

사용자 삽입 이미지

사용자가 자주 방문하는 사이트를 추출, 새롭게 업로드 되는 Link만을 추출하여, 사용자의 관심도가 높을 것으로 생각되는 자료를 Sorting하여, 사용자가 특정사이트에 주기적인 방문없이도 한눈에 내용을 확인 할 수 있는 기능.


사용자 삽입 이미지

관심도가 유사한 사용자간 관련 자료를 공유 할 수 있음, Web 2.0 Tag 형식의 화면

사용자 삽입 이미지

원격지의 사용자가 추천 문서 확인이나 웹서핑을 할 수 있도록, 자체개발 해상도 및 의미기반 문서 최적화 기능을 사용, 해상도가 낮은 모바일 장비에서도 필요한 내용을 원활히 확인 할 수 있음.

상기의 이미지는 PIAS V3의 이미지임.

현재 Java 기반으로 V4 제작중..(회사다니면서 할려니 힘드네요.. -_-;)
아래의 문서를 번역 및 요약하였습니다.
http://www.cs.brown.edu/courses/cs161/papers/j-nio-ltr.pdf

------------------------------------------------------------

3. 채널과 버퍼(Channels and Buffers)

 3-1. 개요
 Channel과 Buffer는 NIO의 핵심 Object로 모든 I/O시에 사용된다.
 Channel은 original I/O와 유사하다.
 NIO의 모든 데이터는 어디든 Input, Output 될 수 있으며 모든 데이터는 Channel Object를 통해 이동된다.
 
 Buffer는 일반적으로 Container역할을 하는 Object이다.
 채널을 통해 이동되는 모든 데이터는 처음에 Buffer상에 로드된다, 즉 채널로 읽어들인 데이터는 Buffer로 읽을 수 있다.
 
 3-2. Buffer란 무엇인가?
 Buffer는 Write하거나 읽기위한 데이터가 저장되는 Object로 Original I/O에는 없는 NIO만의 특별한 개념이다
 NIO엣의 모든 data는 Buffer를 통해서만 Access하거나 사용할 수 있다
 Buffer는 기본적으로 배열이다.(A buffer is essentially an array)
 일반적으로 bytes 배열의 형태를 취하며, 다른 type의 배열로도 사용될 수 있다.
 물론 단순 배열이 아니라, 데이터의 처리를 위해 특별한 기능이 추가된 배열이라 보는것이 맞을 것이다.

 3-3. Buffer의 종류
  일반적으로 사용되는 Buffer는 ByteBuffer이다. 하지만 사용자는 용도에 따라서 아래의 Buffer를 사용할 수 있다.
  - ByteBuffer
  - CharBuffer
  - ShortBuffer
  - IntBuffer
  - LongBuffer
  - FloatBuffer
  - DoubleBuffer
  모든 Buffer는 Buffer Interface의 상속을 받는 형태이다.

 3-4. Channel이란 무엇인가?
  Channel은 데이터를 쓰거나 읽을수 있는 객체이다.
  Original I/O와 비교했을때 NIO는 Stream과 유사하다.
  앞서 언급했듯이 모든 데이터의 access는 Buffer object를 통해서만 가능하며, Channel에 직접 데이터를 쓰거나 읽을 수 없다.

 3-5. Channel의 종류
  Channel은 양방향(bi-directional)이라는 측면에서 stream과는 다르다.
  Stream이 inputstream이나 outputstream과 같이 단방향성 data의 이동이 있을수 있는 반면, Channel은 단일 object로 read와 write를 동시에 할 수 있다. 이것은 운영체제(특히 UNIX 계열)의 IO특성을 잘 반영한 모델이라 볼 수 있다.
 
 
 

+ Recent posts