안드로이드를 개발하면서 가장 어려운 부분 중 하나가 '파싱' 이라고 생각할 수 있겠네요. 어렵기 보다는 귀찮다는 말이 어울리려나요. 데이터 마다 각자 다른 형태로 데이터를 얻어와야 하기때문에 형태에 맞게 파서를 변형해야하는 귀찮음이 반드시 따릅니다.
그중에서도 기본적으로 데이터 구조화 체제가 잘 갖춰진 JSON, XML 과 달리 생 HTML 을 파싱하는 부분은 소스에 따라 상당히 복잡해질수도 있으니 개발자 분의 충분한 이해가 동반해야 합니다.
데이터 파싱전 브라우저에 내장된 '개발자 옵션' 의 네트워크 요청 부분을 활용해 혹시라도 게시판 데이터가 제이슨 형태로 먼저 노출되고 있는지 확인해주세요. 훨씬 쉬운 방법으로 데이터를 가공할 수 있습니다.
dependencies { ... implementation group: 'net.htmlparser.jericho', name: 'jericho-html', version: '3.4' ... }
일단 jericho 라이브러리를 추가해주세요 (App단 build.gradle, 그래들 3.0 버전 이상 기준 'implementation', 3.0 버전 이하는 'compile' 로 바꿔주세요)
public class ArticleParser extends AsyncTask<String, String, String> { public static final String URL_BOARD = "http://www.hermuseum.go.kr/sub.asp?pid=38"; @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected String doInBackground(String... strings) { return null; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); } }
저는 파싱할때 AsyncTask 를 사용하겠습니다. 어떤 방식이라도 좋으니 파싱 프로세스가 백그라운드, 멀티쓰레드 상태에서 동작하게 해주세요. AsyncTask 와 따로 인터페이스를 생성해 콜백하는 방식으로 활용하시면 더욱 좋습니다.
간단하게 설명을 드리자면 onPreExecute() -> 백그라운드 실행 전 수행할 작업, doInBackground() ->백그라운드 작업, onPostExecute() -> 백그라운드 작업 수행 후 행할 UI 작업 으로 순차적으로 이루어 집니다. 이부분에 대해서는 다른 AsyncTask 글을 찾으시길 바랍니다.
리턴 타입은 귀찮아서 String, String, String 으로 설정했는데, 따로 생성자에서 인자를 받으시거나 데이터에 따라 수정하시면 됩니다.
제리코를 이용하는 소스는 백그라운드에서 처리되야 하므로 doInBackground() 내부에 코딩하셔야 합니다.
본 글에서 파싱할 페이지는 제가 관련된 프로젝트를 진행중인 일본군 '위안부' 피해자 e-역사관의 게시판 입니다.
본 글을 읽기전 HTML 의 태그에 관련된 개념이 숙지된 상태여야 합니다.
// URL에 접속해 모든 문자열을 색인합니다 final Source source = new Source(new URL(URL_BOARD)); source.fullSequentialParse();
URL 을 통해 소스를 가져오는 부분입니다. 선언된 source 에서 HTML 소스에 맞게 데이터를 적출해 낼 것 입니다.
final List<Element> allOfTables = source.getAllElements(HTMLElementName.TABLE); final List<Element> allOfTds = source.getAllElements(HTMLElementName.TD); final List<Element> allOfDivs = source.getAllElements(HTMLElementName.DIV);
제리코 라이브러리는 '한 태그의 처음 부터 끝' 을 하나의 Element 객체로 구분합니다. 위 소스 처럼 전체 소스에서 각 태그의 모든 요소를 Element 리스트로 불러올 수 있습니다. HTMLElementName 은 거의 모든 HTML 태그들을 포함하고 있습니다.
// ID 로 해당 Element 를 가져옵니다 Element appleElement = source.getElementById("apple"); // 해당 클래스에 속하는 모든 Element 를 가져옵니다 List<Element> boards = source.getAllElementsByClass("board"); // 해당 클래스에 첫번째 Element 를 가져옵니다 Element grapeElement = source.getFirstElementByClass("grape");
다음 소스 처럼 ID, 클래스 이름에 따라 Element 를 가져 올 수 있습니다. 이글에서 모든 함수들을 설명할 수 없기 때문에 자세한 부분은 라이브러리 레퍼런스를 참고해주세요.
final Element appleElement = source.getElementById("apple"); final List<Element> appleBoards = appleElement.getAllElements(HTMLElementName.TABLE);
다음과 같이 Element 에서 하위 Element 들을 탐색하는것 또한 가능합니다.
이제는 데이터를 담고 있는 Element 에서 Attribute (속성), Content 를 가져와 보겠습니다
<p id="token" align="center">This is Content</p>
다음 Element 에서 Attributes 는 해당 Element 의 속성값, 컨텐츠는 "This is Content" 가 될 것입니다. 제리코에서 Attribute 객체는 각 송성의 name와 key, value 를 포함하고 있습니다.
final Element tokenElement = source.getElementById("token"); tokenElement.getContent().toString(); // = This is content // 해당 Element 의 모든 Attributes 를 리턴합니다. final List<Attribute> attributes = tokenElement.getAttributes(); attributes.get(0).getName(); // = id attributes.get(0).getValue(); // = token
Element.getContent() 는 해당 Element 의 컨텐츠를 가져옵니다, 이어 getAttributes() 로 속성 객체를 로드 할 수 있고 각 Attribute 로 부터 이름과 값을 가져옵니다. Element.getAttributeValue(속성이름) 으로 해당 속성의 값을 가져올 수 있습니다.
기본적으로 HTML 태그의 관계성과 이글에서 설명한 Element, Attribute 에 대해 숙지하셨다면, HTML 표준 규격을 지키는 소스들은 모두 데이터화가 가능할 것 입니다. 일본군 '위안부' 피해자 e-역사관의 게시판 파싱 예제는 기능 구현이 끝나는 대로 추가하도록 하겠습니다.
감사합니다. 오늘도 즐거운 코딩 되시길 바랍니다.
'곰상 > 안드로이드, 감성을 품다' 카테고리의 다른 글
| 안드로이드, 감성을 품다 (0) | 2016.11.19 |
|---|