프론트단( Front-End )에서 API를 사용( request할 때 )할 때, Ajax를 사용한 적이 있습니다.
--> 서버에서 만든 기능이 담긴 주소를 호출 할 때 사용
반대로 백엔드단( Back-End ),
즉 서버에서 외부에 있는 자료를 가져오거나 API 주소를 호출 할 때 어떻게 할까?
--> 여러 방법이 있겠지만, 이런 목적으로 axios 라이브러리를 사용
npm install axios cheerio iconv-lite -s
--> 서버에서 외부에 있는 자료나 api 주소를 호출( request )하기 위해 사용하는 라이브러리
--> Ajax( 프론트엔드에서 api 주소를 호출( request )하는 도구 )와 동일한 기능을 서버에서 사용하는 도구
axois 사이트 : https://www.npmjs.com/package/axios
Ajex와 사용법은 유사함
url: 대상 url (혹은 API )
method: API 호출 방식
responseType: 어떤 방식으로 데이터를 받을 것인지
여기서 url과 method만 적절히 바꿔 사용한다 생각
const express = require("express");
const Goods = require("../schemas/Goods");
const Cart = require("../schemas/Cart");
const cheerio = require("cheerio");
const axios = require("axios");
const iconv = require("iconv-lite");
const url =
"http://www.yes24.com/24/Category/BestSeller";
const router = express.Router();
router.get("/goods/add/crawling", async (req, res) => {
try {
await axios({
url: url,
method: "GET",
responseType: "arraybuffer",
}).then(async (html) => {
});
res.send({ result: "success", message: "크롤링이 완료 되었습니다." });
} catch (error) {
console.log(error)
res.send({ result: "fail", message: "크롤링에 문제가 발생했습니다", error:error });
}
});
try-catch로 묶은 이유
1) 네트워크 상 API 호출 오류
2) 크롤링 할 웹사이트의 오류
등등의 오류발생 가능성 존재
const cheerio = require("cheerio");
const axios = require("axios");
const iconv = require("iconv-lite");
const url =
"http://www.yes24.com/24/Category/BestSeller";
await axios({
url: url,
method: "GET",
responseType: "arraybuffer",
}).then(async (html) => {
});
--> 위에 설정해 놓은 url 주소에 request
--> get으로 request ----> ( 조회 )
responseType은 arraybuffer
--> responseType은 Json형태나 xml형태 등등으로 설정할 수 있음 --> 여기서 arraybuffer라는 건 html 형태라는 뜻
즉, arraybuffer로 response를 받는다는 말은 해당 api에 request하여 response 받는 내용을 html코드 형태로 받을 것이라는 뜻
--> 그렇게 받은 response를 람다함수에 넣어, html이라는 매개변수에 대한 요소로 사용
--> 크롤링하려는 html데이터는 한글이 깨지는 형태로 인코딩 되어 있을 가능성이 있음
--> 가져오는 사이트의 데이터 구조와 응답 방식에 따라 한글이 깨지기 쉽상
--> 따라서 한글이 꺠지는 것을 방지하기 위해 가지고 온 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();
});
res.send({ result: "success", message: "크롤링이 완료 되었습니다." });
} catch (error) {
console.log(error)
res.send({ result: "fail", message: "크롤링에 문제가 발생했습니다", error:error });
}
});
await axios({
url: url,
method: "GET",
responseType: "arraybuffer",
}).then(async (html) => {
const content = iconv.decode(html.data, "EUC-KR").toString();
});
--> 이 부분의 람다함수 부분에 코드 하나가 추가됨 ( 데이터를 한글이 가능한 형태로 가공하기 위해 디코딩 후 인코딩 )
우리의 request에 대한 response는 html파라미터에 요소로 들어가며, 그 주내용은 data에 할당되어 있음
--> 그래서 html.data로 그 내용을 불러오는 것
우리가 request하는 url의 서버는 EUC-KR의 형태로 이루어져있음
( 해당 url이 그렇다는 것 --> 크롤링하려는 주소마다 다를 수 있음 )
--> EUC-KR 형태의 경우, 해당 형태로 response해준 데이터를 그대로 GET할 경우 한글이 깨져서 나옴
--> 따라서 response 받은 EUC-KR형태의 데이터를 byte단위로 디코딩한 후,
다시 toString으로 (String형태로) 재인코딩해주는 것으로 한글이 문제없이 나오게 만듬
cheerio를 사용하면 jQuery처럼 Node.js에서 HTML데이터를 다룰 수 있게 됨
--> HTML의 요소들을 조작하는, 편리한 Javascript를 미리 작성해둔 라이브러리
--> HTML 코드중 특정 코드를 가르켜 조작하거나, 데이터를 가져 올 때 편리하게 사용할 수 있는 도구
JavaScript
document.getElementById("element").style.display = "none";
jQuery 사용 --> 훨씬 직관적으로 html코드의 내용을 다룰 수 있음
$('#element').hide();
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();
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()
})
});
res.send({ result: "success", message: "크롤링이 완료 되었습니다." });
} catch (error) {
console.log(error)
res.send({ result: "fail", message: "크롤링에 문제가 발생했습니다", error:error });
}
});
......
}).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()
})
});
......
const $ = cheerio.load(content);
--> 정제한 데이터를 cheerio의 load() 함수에 다음과 같이 넣으면
cheerio.load(content)
jQuery 처럼 사용할 수 있는 준비가 완료됨
--> $라는 변수에 담아서 태그 이름 또는 class, id 이름을 지칭하여 태그를 가르켜 데이터를 가져올 준비를 함
==> cheerio.load() 함수는 데이터를 HTML 코드로 변환하는 변환기 정도의 역할을 함
예를 들어)
우리가 사용할 html에서 우리가 가져올 데이터는 ol 태그 안에 li 태그 내부에 들어 있음
--> 따라서
const list = $("ol li");
--> 이 부분에 대해서 jQuery와 같은 문법이 들어있는데, 아래에 정리해놓음
--> cheerio가 js에서도 jQuery처럼 손쉽게 html을 조작하게 도와주는 도구이기 떄문
await list.each( async (i, tag) => {
--> 여기서 Jquery each 함수는 map 함수와 동일한 기능
즉, 리스트의 요소 하나하나를 마지막 요소까지 꺼내 확인 할 수 있는 반복문
Jquery each
$.each(object, function(index, item){ });
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()
})
--> each 의 람다함수 부분에 (i, tag)는 map의 람다함수부분의 (i, value)라고 생각하면 된다.
--> 이 코드의 find함수의 요소를 보면 "p.copy a" 라는 식으로 되어 있는데, 이것은 jQuery형식으로 우리가 원하는 특정 태그에 접근해나가는 방식이다.
( 아래의 jQuery형식의 태그 접근 참고 )
(우리가 파일특정에 쓰는 절대주소처럼, 일종의 jQuery방식의 태그주소라고 보면된다.)
--> 그리고 코드 뒤쪽에 text(), attr("src"), attr("alt") 등이 있는데, 이것은 해당 태그에서 어느 부분을
가져올지 명시하는 함수들이다.
( 아래의 jQuery형식의 태그 접근 참고 )
우리가 절대주소를 사용하여 base/image/a.jpg와 같은 방식으로 파일을 특정하는 것처럼
jQuery는 자신의 방식으로 tag사이에서 특정 tag를 특정한다.
아래에 예시가 있다.
"p.copy a" --> <p>태그중에 copy라는 이름( class="copy"인 것 )을 가진 태그를 찾은 뒤, 그 태그 안에 <a>태그 라는 뜻이다.
"p.image a img" --> <p>태그중에 image라는 이름( class="image"인 것 )을 가진 태그를 찾은 뒤, 그 태그 안에 있는 <a>태그 안에 있는 <img>태그라는 뜻이다.
"p.price strong" --> <p>태그중에 price라는 이름( class="price"인 것 )을 가진 태그를 찾은 뒤, 그 태그 안에 <strong>태그 라는 뜻이다.
--> 이후 해당 태그를 find함수로 특정한 후ㅡ, text(), attr("src"), attr("alt")등등이 뒤에 사용되어 있는데, 이것은
우리가 특정한 태그에서 해당 부분을 가져오라는 뜻이다.
즉, text()는 해당 태그의 택스트 부분의 내용 가져오라는 뜻이다.
그리고 attr("src")는 해당 태그의 src속성( css영역 ) 부분의 내용을 가져오라는 뜻이다.
그리고 attr("alt")는 해당 태그의 alt속성( css영역 ) 부분의 내용을 가져오라는 뜻이다.
예를 들어)
아래 사진에서
1. 하이라이트된 <a> 태그를 특정한 뒤 .text()를 하면 --> "주린이가 가장 알고 싶은 최다질문 TOP77"을 반환한다.
2. 하이라이트된 <a> 태그를 특정한 뒤 .attr("href")를 하면 --> "/Product/Goods/96644794"를 반환한다.
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 });
}
});
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
})
}
if(desc && image && title && price){
--> 반복문을 돌리는 중에 이 데이터가 내가 원하는 데이터가 맞다면 해당 변수들에 모두 값이 할당 되었을 것
--> 변수에 값이 할당되지 못했다는 것은 해당 <li>태그에 데이터가 없다는 뜻이기 떄문이다.
위에서 데이터 크롤링한 것의 예시)
--> 데이터를 가져온 모습을 보면 안가져와도 되는 정보가 비어있는 태그의 데이터도 가지고 왔음을 확인할 수 있음
--> 따라서 위와 같은 확인이 필요한 것
price = price.slice(0,-1).replace(/(,)/g, "")
let date = new Date()
let goodsId = date.getTime()
--> 아래의 그림들을 보면,
크롤링해서 들어온 데이터는 제목(a), 이미지url(b), 설명(c), 가격(d)이다.
그리고 스키마에는 goodsId, name, thunbnailUrl, category, price가 있다.
여기서 각각
1. goodsId = 없음 ------> 문제(데이터가 없음)
2. name = 제목(a),
3. thunbnailUrl = 이미지url(b),
4. category = "도서" 로 고정,
5. price = 가격(d) ------> 문제(데이터의 형태)
으로 넣는다고 했을 때, 2가지를 가공해줘야한다.
price = price.slice(0,-1).replace(/(,)/g, "")
let date = new Date()
let goodsId = date.getTime()
아래의 그림은 크롤링한 데이터를 넣을 mongoDB의 스키마임
그리고 데이터는 아래와 같은 형식으로 크롤링 되어 들어왔음
await Goods.create({
goodsId:goodsId,
name:title,
thumbnailUrl:image,
category:"도서",
price:price
})