Node.js로 크롤링(스크래핑)하기
2가지 모듈을 이용한다.
axios 모듈과 cheerio 모듈.
axios 모듈의 공식 설명 : Promise based HTTP client for the browser and node.js
간단한 설명 :
axios는 HTTP 클라이언트 라이브러리로써, 비동기 방식으로 HTTP 데이터 요청을 실행한다.
내부적으로 AXIOS는 직접적으로 XMLHttpRequest 를 다루지 않고 “AJAX 호출”을 할 수 있다.
이러한 일을 하므로, axios를 html 문서 긁어오는 용도로 사용 할 것이다.
사실 request 모듈 사용해도 큰 차이는 없을 것 같다.
cheerio 공식 설명 : Fast, flexible & lean implementation of core jQuery designed specifically for the server.
간단한 설명 : HTML 태그에 접근하는데, 서버에서 jQuery의 방식으로 접근 할 수 있도록 해준다.
이번 포스트에서는 데이터 추출해내는 파싱용 모듈로 사용 될 것이다.
1.모듈 설치
npm install --save axios cheerio
모듈 2개 설치해준다.
2.선언
1
2
3
4
5
6
7
const axios = require("axios");
const cheerio = require("cheerio");
const url = 'https://store.steampowered.com/search/?specials=1&ignore_preferences=1';
axios 모듈과 cheerio 모듈 선언하고, url 변수를 생성한다.
이번 포스트에서는 스팀 할인 정보를 긁어올것이다.
3.크롤링
1
2
3
4
5
6
7
8
9
const getHtml = async (url) => {
try {
return await axios.get(url);
} catch (error) {
console.error(error);
}
};
getHtml 변수에 axios.get(url) 함수의 리턴값을 담는다.
동기적 실행을 위해 함수는 async, axios.get 함수는 await으로 선언.
4.파싱
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
router.get('/', function(req, res, next) {
getHtml(url)
.then(html => {
let ulList = [];
const $ = cheerio.load(html.data);
const $bodyList = $("div#search_resultsRows").children("a");
$bodyList.each(function(i, elem) {
const price = $(this).find('div.search_price').text().replace(/,/gi,"")
.replace(/ /gi,"").split("₩");
ulList[i] = {
title: $(this).find('span.title').text(),
sale: $(this).find('div.search_discount span').text(),
original: price[1],
salePrice: price[2],
href: $(this).attr('href'),
imgSrc: $(this).find('div.col.search_capsule img').attr('src'),
};
});
return ulList.filter(n => n.title);
})
.then(result => res.render('index', { title: result }));
});
결과를 웹으로 보여주기위해 express 프레임워크 같이 사용했다.
getHtml(url)을 동기적으로 실행하고, then()에 다음 할일을 담는다.
ulList 배열 변수 선언하고, $변수에 cheerio를 load한다.
cheerio 내부 함수 설명
- load : 인자로 html 문자열을 받아 cheerio 객체를 반환합니다.
- children : 인자로 html selector를 문자열로 받아 cheerio 객체에서 선택된 html 문자열에서 해당하는 모든 태그들의 배열을 반환합니다.
- each : 인자로 콜백 함수를 받아 태그들의 배열을 순회 하면서 콜백함수를 실행합니다.
- find : 인자로 html selector 를 문자열로 받아 해당하는 태그를 반환합니다.
그리고 스팀 할인정보의 저 부분을 긁어오고 싶으므로, const $bodyList = $("div#search_resultsRows").children("a") 를 선언.
이러면 div#search_resultsRows 안에 a태그들이 나뉘어서 배열로 $bodyList 변수에 저장된다.
이제 원하는 정보들을 cheerio 내부함수 each,find 이용해 가공해서 프론트로 넘겨줬다.
그 결과.
간단한 크롤링을 끝냈고, 이제 이 코드를 좀 더 손봐서 할인 전체 정보 긁어오고, DB에 저장할 차례.5.전체코드
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 32 33 34 35 36 37 38 39 40 41 42
const express = require('express'); const router = express.Router(); const axios = require("axios"); const cheerio = require("cheerio"); const url = 'https://store.steampowered.com/search/?specials=1&ignore_preferences=1'; const getHtml = async (url) => { try { return await axios.get(url); } catch (error) { console.error(error); } }; /* GET home page. */ router.get('/', function(req, res, next) { getHtml(url) .then(html => { let ulList = []; const $ = cheerio.load(html.data); const $bodyList = $("div#search_resultsRows").children("a"); console.log(typeof($bodyList)); $bodyList.each(function(i, elem) { const price = $(this).find('div.search_price').text().replace(/,/gi,"") .replace(/ /gi,"").split("₩"); ulList[i] = { title: $(this).find('span.title').text(), sale: $(this).find('div.search_discount span').text(), original: price[1], salePrice: price[2], href: $(this).attr('href'), imgSrc: $(this).find('div.col.search_capsule img').attr('src'), }; }); return ulList.filter(n => n.title); }) .then(result => res.render('index', { title: result })); }); module.exports = router;