사담
저는 스파르타 코딩클럽 node.js 강의를 들으면서 간단한 쇼핑몰 웹을 구축하던 중
파이썬이 아닌 node.js를 이용해서도 크롤링을 할 수 있다는 것을 알게되어 진행해보면서 기록하기위해 글을 적게 되었습니다.
필요한 패키지 설치
1. Node.js 서버에서 외부의 웹브라우저에 요청해서 특정 HTML을 내려받기 위해 필요한 패키지 => axios
2. 가져온 HTML에서 특정 위치(depth)를 가져오기 위해 필요한 패키지 => cheerio
3. 사이트에서 한글을 가져오면, 발생하는 문제(인코딩 문제 등..)를 해결하기 위해 필요한 패키지 => iconv-lite
=> npm install axios cheerio iconv-lite -s
설치하고 나면, package.json에 설치가 된 걸 확인할 수 있어요!
크롤링 페이지 분석
크롤링할 대상 페이지를 정해봅시다. 저는 교보문고의 베스트셀러 페이지를 분석해보도록 하겠습니다.
http://www.kyobobook.co.kr/bestSellerNew/bestseller.laf?orderClick=d79
교보문고 종합 주간 집계 | 국내도서 | 베스트셀러 - 교보문고
www.kyobobook.co.kr
F12로 개발자도구에 들어가보면, 각 랭킹들의 목차들이 <li> 태그로 묶여있는 것을 확인할 수 있습니다.
가리킨 곳이 랭킹1 도서이니, 밑에 <li>태그는 랭킹2 도서임을 알 수 있습니다.
랭킹1위의 도서를 뜯어보겠습니다.
이미지, 책제목, 가격 정보를 얻기위해 각각의 태그를 확인해봐야합니다.
이미지는 <img> 태그에, 책제목은 "title" 클래스인 <div>태그 내에 <strong> 태그로 존재하네요.
간단히 정리하면
<ul> - <li> -
- 이미지는 "cover" 클래스인 <div> - <a> - <img>
- 제목은 "title"클래스인 <div> - <a> - <strong>
- 가격은 "price"클래스인 <div> - <strong>
이렇게 되겠네요!
HTML 코드를 가져와 준비하기 : axios, iconv-lite
해당 웹사이트에 요청해서 HTML을 가져오는 단계를 해봅시다.
cheerio를 이용하면 JQuery처럼 특정 element 이름을 지정하면 곧바로 찾아서 접근할 수 있습니다.
태그로 접근하는 방법은 간단합니다.
ul 태그 안에 li태그전부를 가져와 모든 정보를 list라는 변수에 저장한다?
>> const list = $("ul li")
class가 'cover'이면서 div인 태그 아래에 a 태그의 src 속성을 찾으려면!
>> $(tag).find("div.cover a").attr("src")
위와 같은 방법으로 필요한 짧은 설명글, 이미지, 제목, 가격 네가지를 가져온다했을 때
1. 필요한 패키지 import
2. api - GET요청 응답 (url, method, responseType)을 적어준다.
3. 응답이 성공하면, html을 로드하여 태그에 따라 값을 가져온다.
4. '/goods/add/crawling' 에서는 다음과 같이 뜬다.
{"result":"success","message":"크롤링이 완료 되었습니다."}
[index.js 코드]
// 필요한 패키지 import
const cheerio = require("cheerio");
const axios = require("axios");
const iconv = require("iconv-lite");
const url =
"http://www.kyobobook.co.kr/bestSellerNew/bestseller.laf?orderClick=d79";
//api로 '/goods/add/crawling' 요청이 오면
//해당 url로 GET요청! arraybuffer(html과 유사)로 데이터를 받는다.
router.get("/goods/add/crawling", async (req, res) => {
try {
await axios({
url: url,
method: "GET",
responseType: "arraybuffer",
}).then(async (html) => {
//데이터를 수집 시 한글깨짐 오류 방지!
//한글을 어떻게 인코딩하냐에 따라 오류가 발생하므로. 이를 재가공해야함.
const content = iconv.decode(html.data, "EUC-KR").toString(); //해당 사이트에서는 charset=euc-kr 으로 설정하여 인코딩하였으므로!
const $ = cheerio.load(content);
const list = $("ul li");
await list.each( async (i, tag) => { // 태그마다 반복접근하도록 each문 사용
let desc = $(tag).find("div.detail div.subtitle").text()
let image = $(tag).find("div.cover a img").attr("src")
let title = $(tag).find("div.cover a img").attr("alt")
let price = $(tag).find("div.detail div.price strong.book_price").text()
console.log(desc, image, title, price);
})
});
res.send({ result: "success", message: "크롤링이 완료 되었습니다." });
//try에서 오류가 발생하면 catch로.
} catch (error) {
console.log(error)
res.send({ result: "fail", message: "크롤링에 문제가 발생했습니다", error:error });
}
});
결과는 다음과 같이 뜨네요. 해당 페이지에 있는 태그들(책 정보)을 다 크롤링하고 나니 undefined로 뜹니다.
undefined를 제외한, 값이 있을 때만 db에 저장해주도록 하겠습니다.
if문으로 값이 있는지 확인해주면 되겠죠?
if(desc && image && title && price){
}
DB 저장
그리고 값이 있을 경우, DB에 저장을 해주어야하는데요. 저의 스키마는 현재 schemas/goods.js 안에 정의했습니다.
goodsId, name, thumbnailUrl, category, price로 구성되어있는데,
goodsId는 고유한 값이어야하니 Date()를 이용해서 날짜값을 넣어주겠습니다.
let date = new Date()
let goodsId = date.getTime()
price는 숫자 타입이니까 DB에 넣기 전에 가공이 필요하겠네요?
14,850원을 예로 들면, 마지막에 위치한 '원'과 중간에 있는 ','을 제거해줘야합니다.
이렇게 되면 숫자로 넣어줄수있겠죠?
price = price.slice(0,-1).replace(/(,)/g, "")
이제 스키마에 값을 넣기 위해 객체를 생성해봅시다.
await Goods.create({
goodsId: goodsId,
name: title,
thumbnailUrl: image,
category: "도서",
price: price
})
if문은 다음과 같이 작성합니다.
if(desc && image && title && price){
price = price.slice(0,-1).replace(/(,)/g, "")
let date = new Date()
let goodsId = date.getTime()
await Goods.create({
goodsId:goodsId,
name:title,
thumbnailUrl:image,
category:"도서",
price:price
})
[전체코드]
router.get("/goods/add/crawling", async (req, res) => {
try {
//크롤링 대상 웹사이트 HTML 가져오기
await axios({
url: url,
method: "GET",
responseType: "arraybuffer",
}).then(async (html) => {
//크롤링 코드
const content = iconv.decode(html.data, "EUC-KR").toString();
const $ = cheerio.load(content);
const list = $("ol li");
await list.each( async (i, tag) => {
let desc = $(tag).find("p.copy a").text()
let image = $(tag).find("p.image a img").attr("src")
let title = $(tag).find("p.image a img").attr("alt")
let price = $(tag).find("p.price strong").text()
if(desc && image && title && price){
price = price.slice(0,-1).replace(/(,)/g, "")
let date = new Date()
let goodsId = date.getTime()
await Goods.create({
goodsId:goodsId,
name:title,
thumbnailUrl:image,
category:"도서",
price:price
})
}
});
})
res.send({ result: "success", message: "크롤링이 완료 되었습니다." });
} catch (error) {
//실패 할 경우 코드
res.send({ result: "fail", message: "크롤링에 문제가 발생했습니다", error:error });
}
});
결과
잘 저장된 것을 확인할 수 있습니다.
'언어 > node.js' 카테고리의 다른 글
Express로 웹서버 만들기 (Basic) (0) | 2021.10.15 |
---|---|
html의 유용한 기능 (0) | 2021.08.31 |
[npm] 필요한 모듈 한번에 설치하기 (0) | 2021.07.28 |
[개념] Routing (0) | 2021.07.20 |
[개념] 비동기 처리를 위한 문법 (ES6) (0) | 2021.07.20 |