엘리스 AI트랙 4기/프로젝트

express 웹서비스 프로젝트에 뉴스 크롤링 추가하기_2탄

남쪽마을밤송이 2022. 5. 12. 16:24

1탄에서 이어지는 내용입니다.

 (2) 개발자도구에서 추출할 elements 확인하기 

  • 크롬(이나 혹은 다른 브라우저 대부분)에서 F12를 누르면 개발자모드가 활성화되고 그 중 Elements 탭에서 페이지를 구성하는 요소들을 확인할 수 있다는 사실은 이 글을 보고 있는 대부분이 알 것이다.
  • 많은 요소들 중 필요한 데이터만을 추출하기 위해 Elements 탭 왼쪽의 선택 도구를 클릭하고 원하는 영역을 확인한다.
    • 나는 각 뉴스(사진에서 li 태그)들의 기사 제목, 썸네일, 링크가 필요했다.
  • 영역을 확인했으면 구분 가능한 id에서부터 시작해서 내가 필요한 태그까지 안쪽으로 타고타고 들어간다. 
    • 무슨 말이냐면 사진의 저 영역은 content라는 id 아래의 news-list라는 div 아래의 div 태그 아래의 ul 태그 아래의 li 태그이다.
    • 주의할 점은 ul 태그 아래의 li 태그들에 반복문을 사용할거라 일단 ul 태그까지만 들어갔는데, 내가 필요한 정보가 모든 li 태그들에 있다면 li 태그까지 들어가는게 맞았다. 대신 li 태그가 총 15개(한 페이지에 기사가 15개씩 페이지네이션되어 있었음)이기 때문에  toArray( )  를 사용해 배열 형태로 만들어준다.
    • 그리고 두 번째 div 처럼 div 태그가 여러개인 경우에는  . 으로 클래스 이름을 특정해주어야 한다. 이를 표현하면 다음과 같다.
#content > div.news-list > div > ul > li
// cheerio를 활용하여 body에서 데이터 추출
    const $ = cheerio.load(body);
    const list_news = $(
      "#content > div.news-list > div > ul > li"
    ).toArray(); // 한 페이지는 뉴스 15개

 (3) 추출할 데이터 디테일하게 접근 

  • 여기서 내가 결국 응답값으로 얻고 싶은 데이터의 형식을 생각해보면, 나는 배열 안에 각 기사들의 url, 썸네일, 제목이 객체값으로 들어가길 바랐다.
  • 따라서 result 라는 배열을 하나 만들어주고 list_news에 li 태그를 기준으로 forEach문을 사용했다.
  • 그리고 이제  find( ) 와  attr( ) 함수만 쓰면 모든 데이터에 접근할 수 있다!
    • a 태그에 접근하기 위해  const aTag = $(li).find("a") 
    • 그리고 그 안의 href 속성에 접근하기 위해  const path = aTag.attr("href") 
    • 이런식으로 필요한 데이터에 접근하고 변수에 할당해주면 된다.
  • 나는 이게 result 라는 배열 안에 객체값으로 들어가길 바랐으니까  result.push({ url, thumbnail, title }) 로 넣어주었다.
  • 그리고 나는 Promise 객체를 return하게 만들었기 때문에 res 값에 result를 넣어주었다. 그러면 전체 코드는 다음과 같다.
import request from "request";
import cheerio from "cheerio";

const OutsideApi = {
  // 온라인: "O" | PC: "P" | 비디오: "V" | 웹게임: "W" | 모바일: "M"
  getNews: async (category) => {
    return new Promise((res, rej) => {
      request(
        {
          url: `https://www.gamemeca.com/news.php?ca=${category}`,
          method: "GET",
        },
        (error, response, body) => {
          if (error) {
            console.error(error);
            rej(new Error("error"));
          }
          if (response.statusCode === 200) {
            // cheerio를 활용하여 body에서 데이터 추출
            const $ = cheerio.load(body);
            const list_news = $(
              "#content > div.news-list > div > ul > li"
            ).toArray(); // 한 페이지는 뉴스 15개

            const result = [];
            list_news.forEach((li) => {
              // result에 1. 뉴스글 url / 2. 뉴스 썸네일 / 3. 뉴스제목
              const aTag = $(li).find("a");
              const path = aTag.attr("href"); // 첫번째 <a> 태그 href
              const url = `https://www.gamemeca.com/${path}`; // 도메인을 붙인 url 주소

              const thumbnail = aTag.find("img").attr("src"); // 썸네일 url

              const divTag = $(li).find("div").first();
              const title = divTag.find("strong").find("a").text().trim();

              result.push({ url, thumbnail, title });
            });
            res(result);
          }
        }
      );
    });
  }
}

export { OutsideApi };

 

 [04 잘 응답하는지 확인 ] 

 (1) Postman 테스트 

  • Postman으로 요청을 할 수 있도록 router 계층에서의 코드도 완성해준다.
import { redisClient, DEFAULT_EXPIRATION } from "../db";
import { Router } from "express";
import { OutsideApi } from "../common/OutsideApi";

const OutsideApiRouter = Router();

OutsideApiRouter.get("/gameNews", async (req, res, next) => {
  try {
    const category = req.query.category;

    // redis 서버에서 캐시 확인
    const cache = await redisClient.GET(`gameNews?${category}`);
    if (cache) {
      // 캐시가 있으면
      res.status(200).json(JSON.parse(cache));
    } else {
      // 캐시가 없으면
      const gameNews = await OutsideApi.getNews(category);

      await redisClient.SETEX(
        `gameNews?${category}`,
        DEFAULT_EXPIRATION,
        JSON.stringify(gameNews)
      );
      res.status(200).json(gameNews);
    }
  } catch (error) {
    next(error);
  }
});
  • 코드를 완성하고 postman으로 요청을 보내보면 정상적으로 응답이 돌아오는 것을 확인할 수 있다.

  • 나는 시행착오를 겪은 과정을 뺐지만 태그가 많다면 당연히 중간 중간  console.log( ) 로 잘 접근 중인지 원하는 데이터가 맞는지 확인하며 진행하는 것을 추천한다.

 (2) Front에서 적용한 모습 

 

 [05 느낀점 ] 

  • 보통 크롤링을 파이썬으로 많이 하기 때문에 한 번은 해봐야지 했지만 막상 해 보지 않았었는데 프로젝트를 계기로 외뷰 뉴스를 내 웹서비스에 넣어보니 신기하고 재밌었다.
  • 그리고 이번건 실제로 했을 땐 별거 아니었는데 글로 작성하는게 더 어려웠다... 그래도 다음에 또 사용할 때를 대비해서 또 복습을 위해 기록해두기!