뭉지(moonz)
작은 발자국들의 위대한 여정
뭉지(moonz)
  • All (200)
    • Test Code (4)
    • 백엔드 개발하며 작성한 (25)
      • Spring (16)
      • 데이터베이스 (6)
      • 기억할 내용 (3)
    • 언어 (53)
      • Java (25)
      • node.js (7)
      • Python (21)
    • 클라우드 (6)
    • Algorithm & Data Structure (51)
      • 개념 (15)
      • 문제 풀이 (36)
    • 유용한 모든 것 (16)
    • monologue (7)
      • 업무 노트 (1)
      • 관리 로그 (0)
      • 내 이야기 공책 (6)
    • Project (2)
    • TroubleShooting (8)
    • 지식 (18)
      • Machine Learning (6)
      • Review (7)
      • Web (5)
    • Computer Science (5)

블로그 메뉴

  • 홈
  • 태그

인기 글

최근 글

최근 댓글

전체 방문자
오늘
어제

티스토리

hELLO · Designed By 정상우.
뭉지(moonz)

작은 발자국들의 위대한 여정

Node.js를 이용한 크롤링
언어/node.js

Node.js를 이용한 크롤링

2021. 10. 12. 09:07
반응형

사담

저는 스파르타 코딩클럽 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
    '언어/node.js' 카테고리의 다른 글
    • Express로 웹서버 만들기 (Basic)
    • html의 유용한 기능
    • [npm] 필요한 모듈 한번에 설치하기
    • [개념] Routing
    뭉지(moonz)
    뭉지(moonz)
    제가 깨달은 것을 정리하는 공간입니다. 🧡

    티스토리툴바