[React] 무비앱 #2 - 네이버 API + 크롤링으로 검색 기능과 실시간 랭킹 구현
👉 [React] 무비앱 #1 - 네이버 API + 크롤링으로 검색 기능과 실시간 랭킹 구현
3. 영화 랭킹 구현
영화진흥위원회 API를 사용하면 박스 오피스 데이터를 쉽게 가져올 수 있지만, 네이버 영화 웹 페이지를 크롤링하는 방식으로 구현해보겠다.
3-1. Cheerio 세팅
크롤링 작업에는 axios와 cheerio를 사용할 것이다. axios는 이미 설치했으니 cheerio만 설치해준다.
npm install cheerio --save
- axios: 웹 페이지의 HTML을 가져옴
- cheerio: 가져온 HTML에서 필요한 정보를 추출(파싱)
3-2. HTML 가져오기
server 폴더 아래에 fetching.js 파일을 생성한다.
cheerio와 axios 모듈을 가져오고, HTML을 가져오는 함수를 작성한다. async와 try-catch문을 사용할 것이다.
(fetching.js)
// 설치한 axios와 cheerio 모듈을 가져온다.
const cheerio = require("cheerio");
const axios = require("axios");
// axios로 HTML을 가져오는 함수
const getHTML = async () => {
try {
return await axios.get('https://movie.naver.com/movie/sdb/rank/rmovie.naver')
} catch (error) {
console.log(error);
}
}
네이버 영화 랭킹 페이지의 전체 HTML을 긁어오는 코드를 작성했다.
이제 추출(파싱)까지 해보자. parsing 함수를 생성하고, 함수 안에서 axios로 추출한 HTML을 변수에 담는다.
(fetching.js)
// 설치한 axios와 cheerio 모듈을 가져온다.
const cheerio = require("cheerio");
const axios = require("axios");
// axios로 HTML을 가져오는 함수
const getHTML = async () => {
try {
return await axios.get('https://movie.naver.com/movie/sdb/rank/rmovie.naver')
} catch (error) {
console.log(error);
}
}
// 파싱
const parsing = async () => {
// 위에서 추출한 HTML 전체 가져오기
const html = await getHTML();
console.log(html)
}
parsing()
루트 경로에서 node server/fetching.js
명령어를 입력해보자. axios로 긁어온 엄청난 양의 데이터가 터미널에 출력된다.
3-3. 데이터 추출 (파싱)
Cheerio를 통해 저 데이터에서 1위부터 10위까지 영화의 타이틀과 링크를 추출할 것이다. Cheerio는 JQuery 문법을 사용한다.
(fetching.js)
const cheerio = require("cheerio");
const axios = require("axios");
// HTML 가져오기
const getHTML = async () => {
...
}
// 파싱
const parsing = async () => {
// 위에서 추출한 HTML 전체 가져오기
const html = await getHTML();
// JQuery처럼 사용하기 위해 '$'에 cheerio를 로드한다.
const $ = cheerio.load(html.data);
// 개발자 모드에서 확인해보면
// .list_ranking 아래 tr들이 있고 그 안에 하나씩 타이틀 존재
// 반복문을 돌릴 수 있어야 하니 병렬로 있는 요소까지만 찾는다.
const $trs = $("#old_content > .list_ranking > tbody > tr");
// 파싱한 데이터를 담을 배열
let dataArr = [];
// 찾은 tr 개수 만큼 반복문을 돌린다.
$trs.each((idx, node) => {
const title = $(node).find(".tit3 a").text();
const link = $(node).find(".tit3 a").attr("href");
// 빈 값 리턴
if (title === "") {
return;
}
// 오브젝트 형식으로 배열에 담기
dataArr.push({
title: title,
link: link
});
});
console.log(dataArr)
}
parsing()
서버를 껐다가 다시 켜준다. 아래와 같이 출력되면 성공이다.
3-4. fetching.js 모듈화
이제 fetching.js를 모듈화해서 server/index.js로 넘겨보자.
(fetching.js)
const cheerio = require("cheerio");
const axios = require("axios");
// HTML 가져오기
const getHTML = async () => {
...
};
// 파싱
const parsing = async () => {
...
};
// parsing 함수를 모듈화해서 내보내기
module.exports = parsing;
(server/index.js)
const express = require("express");
const app = express();
const port = process.env.PORT || 5000;
const parsing = require('./fetching.js');
...
// parsing 모듈 import
const parsing = require('./fetching.js');
...
// api/rank로 get 요청이 들어오면
// parsing() 실행해서 요청 결괏값 client로 내보내기
app.get('/api/rank', (req, res) => {
parsing().then(response => res.send(response))
})
...
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
3-5. 클라이언트로 내보내기
이제 이렇게 받은 랭킹 타이틀을 client에서 받을 수 있게 설정해보자.
URL이 /api
로 시작되는 요청은 5000번 포트로 서버가 실행되도록 하는 프록시 작업은 이 전에 이미 해놨기 때문에 패스.
(setupProxy.js)
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use (
createProxyMiddleware( '/api', {
target: 'http://localhost:5000',
changeOrigin: true
})
)
}
(ranking.tsx)
import axios from 'axios'
import React, { useEffect, useState } from 'react'
const Ranking = () => {
// 랭킹 데이터를 담을 State
const [Data, setData] = useState([])
// 랭킹 데이터 가져오는 함수
const fetchRank = async () => {
try {
const { data } = await axios.get('/api/rank')
// state에 담기
setData(data)
} catch (error) {
let message = 'Unknown Error'
if (error instanceof Error) message = error.message
console.log(message);
}
}
// 마운트 시 데이터 가져오는 함수 실행
useEffect(() => {
fetchRank()
}, [])
return (
<div className="ranking">
<div className="ranking__inner">
<h2>영화 랭킹</h2>
<div className="ranking__list">
<div className="ranking__list__inner">
<ul>
{
Data &&
Data.map((data: { title: string, link: string }, idx) => (
<li key={ idx } >
<span className="num">{ idx+1 }</span>
<a href={ `https://movie.naver.com/${data.link}` } target="_blank">
<span className="title">{ data.title }</span>
</a>
</li>
))
}
</ul>
</div>
</div>
</div>
</div>
)
}
export default Ranking
4. 마무리
CSS
CSS 한스푼 넣으면 완성이다.
News Picker
썸네일처럼 랭킹 영역을 실시간 검색어 형식(News picker)으로 구현하고 싶다면 아래 코드를 참고해보자.
Lazy-loading
컴포넌트의 불필요한 렌더링을 줄이기 위해 Lazy-loading을 세팅해준다. router v6부터 Lazy-loading을 적용하는 방법은 아래 페이지를 참고하면 된다.
📎 [React] Router v6 - Lazy-loading
마무리까지 완료된 페이지 구경하기
📎 Demo
배포
헤로쿠에 배포하는 방법은 아래 링크에 정리해두었으니 참고하면 되겠다.
Author And Source
이 문제에 관하여([React] 무비앱 #2 - 네이버 API + 크롤링으로 검색 기능과 실시간 랭킹 구현), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@nemo/movie-search-app-2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)