JavaScirpt/Node.js

[Node.js] Starting Node.js with Crawling (Parsing XML/RSS, PhantomJS?, CasperJS?)

Tree_Park 2020. 12. 7. 14:42
728x90
XML/RSS 해석

- eXtensible Markup Langauge / Really Simple Syncdication

 

- XML

  - eXtensible Markup Language의 약어로 목적에 맞게 사용될 수 있는 범용적인 데이터 형식

  - 기본적으로 텍스트 데이터, 각각의 데이터에 태그를 붙임으로써 문서나 데이터를 구조화 가능

  - XML의 목적 : 다른 종류의 시스템 간에 구조화된 문서와 데이터를 쉽게 공유하는 것

 

  - XML은 범용적인 형식이며, XML을 바탕으로 한 다양한 데이터 형식이 존재

    - 뉴스 사이트의 요약 정보인 RSS나 벡터 그래픽을 다루는 SVG도 XML을 바탕으로 한다.

    - 엑셀/워드 등 마이크로스프트의 오피스 저장 형식도 여러 XML파일을 ZIP으로 압축한 것

 

  - XML은 기계나 인간에게도 다루기 편한 데이터 형식

 

- XML의 구조

  - 기본 구조 : Element(요소)와 Attribute(속성)

    - <element_name attribute="value">content</element_name>

 

  - 요소는 내부에 자식 요소를 가질 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<상품 카탈로그>
  <상품 id="S001">
    <상품명>8GB SD카드</상품명>
    <값>4500원</값>
  </상품>
  <상품 id="S002">
    <상품명>USB 마우스</상품명>
    <값>4000원</값>
  </상품>
  <상품 id="S003">
    <상품명>USB 키보드</상품명>
    <값>3500원</값>
  </상품>
</상품 카탈로그>

  - XML은 트리 구조로 데이터를 표현할 수 있다.

  - XML 데이터 파일에 저장하는 경우에는 XML 선언을 포함할 수 있다.

 

- Node.js에서 XML을 다루는 법

  - Node.js에서는 XML을 파싱하기 위해 'cheerio-httpcli' 모듈을 포함하여 여러 가지 모듈이

    존재한다.

  - 이 글에서는, xml2js 모듈을 소개

 

xml2js

 - XML 데이터를 자바스크립트 객체로 변환 가능

 - 설치 : npm install xml2js

코드

// loading modules
const parseString = require('xml2js').parseString;

// XML data for Testing
const xml = "<fruits shop='AAA'>" +
	  "<item price='140'>Banana</item>" +
	  "<item price='200'>Apple</item>" +
	  "</fruits>";

// Transferring XML 
parseString(xml, function(err, result){
	// printing result of parsing process
	console.log(JSON.stringify(result));
});

결과

xml --> js

  - XML이 요소, 내용, 속성의 구조로 되어 있어 JSON과 일대일 대응이 되지 않기 때문에

    - 요소의 내용은 '_'이라는 키의 값으로 대입되며

    - 속성은 '$'키의 값으로 대입된다.

 

 - 좀 더 구체적으로 사용해보면

코드

// loading module
const parseString = require('xml2js').parseString

// xml data for testing
const xml = "<fruits shop='AAA'>" +
	  "<item price='140'>Banana</item>" +
	  "<item price='200'>Apple</item>" +
	  "</fruits>";

// Transferring xml
parseString(xml, function(err, result){
	// name of shop providing fruits
	let shop = result.fruits.$.shop;
	console.log(`shop= ${shop}`);
	
	// Displaying names and prices of fruits
	let items = result.fruits.item;
	for(let i in items) {
		let item = items[i];
		console.log(`--name= ${item._}`);
		console.log(` price= ${item.$.price}`);
	}
});

결과

js객체 파싱 결과

- 만약 요소에 자식 요소나 속성이 없을 경우에는 요소 이름에 내용이 바로 대입된다.

코드

// loading modules
const parseString = require('xml2js').parseString;

// XML data for Testing
const xml = "<item>Banna</item>"

// Transferring data
parseString(xml, function(err, result) {
	console.log(result.item); // result : Banana
});

 결과

결과

  - 위 결과 그림과 같이 XML 데이터이지만, '_', '$'를 포함하지 않는 데이터로 변환된다.

  - 이런 XML 데이터와 같은 경우 XML과 JS가 거의 1:1로 변환된다.

코드

// loading modules
const parseString = require('xml2js').parseString;

// xml data for Testing
const xml =
	`
	<items>
		<item><name>Banna</name><price>130</price></item>
		<item><name>Apple</name><price>300</price></item>
		<item><name>Pear</name><price>250</price></item>
	</items>
	`;

// transferring xml
parseString(xml, function(err, r){
	console.log(JSON.stringify(r));
	// display each elements
	console.log(`----
${r.items.item[0].name[0]}
${r.items.item[0].price[0]}		
	`);
});

결과

결과

 

- 만약, JS 객체로부터 XML을 작성하는 경우에는, Builder 클래스를 사용한다.

코드

// loading module
const xml2js = require('xml2js');

// javascript object
const obj = {item:{name:"Banana", price:150}};

// transforming js object to xml 
const builder = new xml2js.Builder();
let xml = builder.buildObject(obj);
console.log(xml);

결과

결과

 

 - XML 데이터를 JS 객체로 변환하고 다시 XML로 변환하는 프로그램을 작성해보자

코드

// loading module
const xml2js = require('xml2js');
const parseString = xml2js.parseString;
const Builder = xml2js.Builder;

// xml data for testing
const xml = 
`<fruits shop='AAA'>
	<item price='140'>Banana</item>
	<item price='200'>Apple</item>
</fruits>`;

// transforming xml to js object.
parseString(xml, function(err, res){
	console.log(JSON.stringify(res));
	
	// re-transforming js object to xml
	let xml = new Builder().buildObject(res);
	console.log(xml);
});

결과

xml-->js-->xml

RSS

- RSS는 뉴스와 블로그 등 각종 웹사이트의 갱신 정보를 요약하여 전송할 때 사용하는 데이터 형식이다.

- 유명한 포맥 형식 몇가지 : RSS1.0, RSS2.0, Atom 등 (모두 XML을 바탕)

 

- 기상청의 RSS 읽기

  - 현재 기상청에서 RSS로 제공하는 정보는 동네의 시간별 일기예보 및 지역별 중/장기 일기예보

  - RSS로 제공되므로 쉽게 기상 예보 정보를 획득 가능

 

- 기상청 기상예보 RSS

  - https://www.weather.go.kr/weather/lifenindustry/sevice_rss.jsp

 

RSS > 인터넷 > 서비스 > 생활과 산업 > 날씨 > 기상청

홈 > 생활과 산업 > 서비스 > 인터넷 > RSS |날씨|생활과 산업|서비스|인터넷|RSS

www.weather.go.kr

기상청 일기예보 RSS

  - 예를 들어, 서울/경기도의 RSS 데이터를 살펴보면 포맷은 RSS2.0이며 XML로 구조화되어 매우

    규칙적으로 데이터가 담겨있는 것을 알 수 있다.

 

코드

//Meteorological Agency weather forecast RSS
const RSS = "https://www.weather.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=109";

//loading module
const parseString = require('xml2js').parseString;
const request = require('request');

//downloading RSS
request(RSS, function(err, response, body){
	if(!err && response.statusCode == 200) {
		analyzeRSS(body);
	}
});

// Analyzing RSS
function analyzeRSS(xml) {
	// transforming xml to JS object.
	parseString(xml, function(err,obj) {
		if(err) {console.log(`err=${err}`); return;}
		
		// displaying weather forecast info
		let datas = obj.rss.channel[0].item[0].description[0].body[0].location[0].data;
		let city = obj.rss.channel[0].item[0].description[0].body[0].location[0].city;
		
		for(let i in datas) {
			let data = datas[i];
			console.log(`city ${data.tmEf} ${data.wf} ${data.tmn} ${data.tmx}`);
		}
	});
}

결과

- RSS '/res/channel/item/descrption/body/location' 경로의 데이터를 꺼내서 표시한 결과이다.

- 단계별로 프로그램을 분석해보자면

  - RSS를 웹에서 취득한다

    - request 모듈로 RSS를 요청하고, 콜백 함수 인자로 넘어온 statusCode가 정상일 때 가동

  - 데이터를 취득 후, analayzeRSS() 메소드를 호출

    - parseString() 메소드를 사용하여 XML 데이터를 자바스크립트 객체로 변환

    - 변환한 날씨 정보를 for 문으로 차례차례 콘솔에 출력

 

- XML/RSS 파싱에 'cheerio-httplci'를 사용하는 방법

코드

//Meteorological Agency weather forecast RSS for Node.js using cheerio

// weather RSS
const RSS = "https://www.weather.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=109";

// loading module
const client =require('cheerio-httpcli');

// downloading RSS
client.fetch(RSS, {}, function(err, $, res){
	if(err) {console.log(`err : ${err}`); return;}
	
	// display needed element by extracting 
	let city = $("location:nth-child(1) > city").text();
	$("location:nth-child(1) > data").each(function(idx) {
		
		let tmEf = $(this).find('tmEf').text();
		let wf = $(this).find('wf').text();
		let tmn = $(this).find('tmn').text();
		let tmx = $(this).find('tmx').text();
		
		console.log(`${city} ${tmEf} ${wf} ${tmn}~${tmx}`);
	})
});

결과

  - RSS 전체 경로를 지정하지 않고, 'location:nth-child(1) > city' 처럼 CSS 선택자를 지정해주고 있다.

  - 'cheerio-httpcli'를 사용하면 CSS 쿼리를 사용하여 원하는 부분의 정보를 쉽게 추출 할 수 있어

    더욱 편리하기 프로그램을 작성 가능하다.

 

정기적으로 다운로드

- 정기적인 처리를 수행

  - 정기적으로 처리를 실행하는 스케줄러

  - Mac OS X나 리눅스에는 cron이라는 데몬 프로세스가 존재

    - cron을 사용하면 스크립트를 원하는 시점에 자동으로 실행할 수 있다.

  - 비슷하게 윈도우에도 작업 스케줄러라는 것이 존재한다.

 

- 정기적인 실행을 위한 힌트

  - cron을 이용하여 정기적으로 수행하면 좋은 작업들

    - 데이터 수집과 같은 애플리케이션의 정기적인 처리 (데이터베이스 집계 등)

    - 로그, 백업 등 시스템과 관련된 정기적인 처리 (가벼운 시스템을 위한 로그 파일 옮기기 등)

    - 시스템이 제대로 동작하고 있는지 정기적으로 감시하는 처리 (시스템 장애시, 관리자 메일 통지 등)

 

- 환율의 변동을 확인하는 API 사용

  - '환율 확인 API' : 저자가 작성하고 제공하는 웹 API (데이터 갱신 빈도가 낮다)

  - JSON 형식의 활용 : http://api.aoikujira.com/kawase/get.php?code=USD&format=json 

  - XML 형식의 활용 : http://api.aoikujirda.com/kawase/get.php?code=USD&format=xml

  - API를 호출하면 미국 달러(USD)를 기반으로 한 값이 반환된다.

 

코드

//getting exchange rate info for Node.js

// Exchange Rate info API
const API = "http://api.aoikujira.com/kawase/get.php?code=USD&format=json";

// loading modules
const request = require('request');
const fs = require('fs');

// Requesting web API
request(API, function(err, response, body){
	// checking http err
	if(err || response.statusCode != 200) {
		console.log(`ERROR ${err}`); return;
	}
	
	// Transforming JSON to JS object
	let res = JSON.parse(body);
	let krw = res["KRW"];
	
	// saving exchange rate info to file(displaying data at a filename)
	let time = new Date();
	let fname = 
`USD_KRW_${time.getFullYear()}-${time.getMonth()+1}-${time.getDate()}.txt`;
	let text = `1usd=${krw}krw`;
	console.log(text);
	console.log(fname);
	fs.writeFileSync(fname, text);
});

결과

환율은 변동이 심하므로, 매일 정기적으로 자동 실행하도록 해보자

- 리눅스/Mac OS X의 경우

  - Linux 계열은 대부분 cron이 표준으로 설치되어 있다.

  - cron을 사용하려면 지정된 설정 파일에 지정된 형식으로 실행 간격을 기술하면 된다.

  - 텍스트 파일에 설정을 기술하는 UNIX 전통을 따르고 있다.

 

  - cron을 설정하려면 터미널에서 crontab이라는 명령을 수행하여 편집한다.

  - crontab -e

    - cron 설정 화면이 열린다. (처음에는 아무런 스케줄이 없는 상태일 것이다)

 

  - 작업 디렉토리에 주의

    - cron이 수행될 때의 작업 디렉토리는 사용자의 홈 디렉토리가 된다. 그래서 로그를 저장하거나

      할 때에는 전체 경로를 저장하거나 작업 디렉토리를 변경해주어야 한다.

 

   * 아쉽게도 GoormIDE 내에서는 corn이 동작하지 않았다.

 

로그인이 필요한 웹 사이트 크롤링

- PhantomJS와 CasperJS

  - 여러 페이지를 이동하거나 로그인 후의 데이터를 취득할 때의 전용 도구

  - PhantomJS : 화면이 없는 브라우저 (커맨드라인 방식)

  - CasperJS : PhantomJS를 쉽게 사용하기 위한 라이브러리

 

 - PhantomJS

    - 커맨드라인에서 쓸 수 있는 웹 브라우저

    - 렌더링 엔진으로 WebKit을 채용

    - 커맨드 랑니으로 브라우저를 조작하고, 브라우저 안의 데이터를 취득하거나 스크린샷을 찍을 수

      있다.

    - 웹 사이트에서 데이터를 스크래핑하거나 UI 테스트 자동화 등에 활용된다.

 

 - CapserJS

    - PhantomJS를 쉽게 사용하기 위한 라이브러리

 

 - 설치 

    - 설치하기전 Ubuntu 18.04에서 미리 설치할 것 : sudo apt-get install libfontconfig

    - PhantomJS 설치 : npm install -g phantomjs

    - CasperJS 설치 : npm install -g casperjs

 

- PhantomJS와 CasperJS를 사용해 웹 사이트의 타이틀을 표시하는 프로그램

코드

// displaying title of websites
const TARGET_URL = 'http://jpub.tistory.com';

// create CasperJS object
const casper = require('casper').create();

// open the websites
casper.start(TARGET_URL, function() {
	// priniting a title
	this.echo(casper.getTitle());
});

// executing process
casper.run();

결과

얻어온 title

  - 'jpub.tistory.com'에 접속하여 페이지의 타이틀을 획득하고 콘솔에 출력한다.

    - CasperJS 객체를 생성하고, 방문할 페이지 URL를 start() 메소드의 인자로 지정한다.

    - start()의 두번 째 인자로, 페이지가 로드되었을 때 수행되어야할 메소드를 지정한다.

    - 페이지가 로드된 후 실행되는 처리는 : 현재 접속한 사이트의 제목을 취득하고 출력하는 것

 

    - CapserJS의 런타임정보에 관해 더 자세히 알고 싶은 경우 실행 시 '-verbose'나 '-log-level=debug'

      를 인자로 주면 자세한 디버깅 정보가 색상과 함께 표시된다.

 

- 화면 캡처 프로그램

코드

var casper = require("casper").create();

casper.start();

casper.open("https://devel-up-tree.tistory.com");
casper.then(function(){
	casper.capture("screenshot2.png");
});

casper.run();

결과

결과가 좀.. 인코딩도 깨지고 화면도 제대로 나오진 않는다.

 

- 플리커 이미지 검색 결과 캡처하기

코드

// Capturing result of resarching on flick for CasperJS
// Creating CasperJS obj
const casper = require('casper').create();

// executing CasperJS process
casper.start()

// config of viewport
casper.viewport(1400,800);

// config of UserAgent
casper.userAgent('User-Agent: Mozila/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36');

// researching word 'cat' on flickr
const text2 = encodeURIComponent('고양이');
casper.open('https://www.flickr.com/search/?text='+text2);

// capturing view
casper.then(function(){
	this.capture('flickr-cat.png', {
		top:0, left:0, width:1400, height:800
	});
});

// run
casper.run();

결과

USER-AGENT와 VIEWPORT를 지정하니 더 깔끔하게 나오는 것 같다.

  - 위 코드처럼 CasperJS는 start() 메소드와, run()메소드 사이에 순서대로 실행하고자 하는 처리를

    then() 메소드를 사용하여 지정하면 된다.

  - CasperJS도 비동기 처리가 기본이지만, then() 메소드를 사용하면 then() 메소드 안에 정의한

    내용의 수행이 완료되기 전에 그 다음 then() 메소드로 넘어가지 않으므로 동기적인 함수 수행을

    쉽게 기술할 수 있다.

 

  - User-Agent란 웹사이트에 접속할 때 사용하는 프로그램을 말한다.

    - 이 사용자 에이전트에 값을 설정함으로써 모바일 전용 페이지로 접속할 수 있다.

    - 아이폰 인척하기 

casper.userAgent(‘Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) 
AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53’);
// 화면 사이즈 지정
casper.viewport(750, 1334);