네이버 뉴스 크롤링 따라 하기
자바스크립트, 노드 공부를 목적으로 하는 크롤링 포스트입니다.
1. 프로젝트 세팅
크롤러를 만들기 위해 npm 프로젝트를 생성해 줍니다.
mkdir news-crawler
cd news-crawler
npm init
request, cheerio, iconv 패키지 설치
npm install request cheerio iconv
루트 폴더에 index.js 파일을 생성해 줍니다
touch index.js
코드 편집기 실행
code .
index.js 파일 상단에 필요한 패키지 3가지를 불러옵니다
const request = require("request");
const cheerio = require("cheerio");
const iconv = require("iconv-lite");
getNews() 함수를 작성하고 실행환경 테스트를 위해 콘솔출력을 한번 해줍니다.
const request = require("request");
const cheerio = require("cheerio");
const iconv = require("iconv-lite");
const getNews = () => {
console.log("getNews function");
};
getNews();
터미널에서 index 파일실행
node index.js
실행결과
콘솔출력이 잘 되었습니다.
2. 구현
크롤링 흐름은 다음과 같습니다.
- request 패키지로 html 가져오기
- iconv로 디코딩
- cheerio로 데이터 추출
2-1. request 패키지로 html 가져오기
우선 데이터를 추출할 네이버 뉴스 페이지 url을 확인합니다.
https://news.naver.com/
getNews() 함수에서 request 패키지를 사용합니다.
const getNews = () => {
request(
{
url: "https://news.naver.com/",
method: "GET"
},
(error, response, body) => {
if (error) {
console.error(error);
return;
}
if (response.statusCode === 200) {
console.log("response ok");
// cheerio를 활용하여 body에서 데이터 추출
}
}
);
};
옵션으로 url과 method를 전달해 주고 handler 함수에서 전달받은 응답을 처리해 줍니다.
중간 테스트
node index.js
실행결과
정상적으로 콘솔 출력 되고 있습니다.
에러 테스트
url을 임의로 변경하여 에러를 발생시켜 봅니다.
url: "https://news.naver.com1212121212121212/"
node index.js
실행결과
에러 발생되고, 에러 내용이 정상적으로 콘솔출력 되었습니다. url은 다시 수정해 줍니다.
크롤링할 부분은 우측의 '언론사별 가장 많이 본 뉴스' 탭입니다.
개발자 도구를 열고 목표 영역을 찾아봅니다.
DOM을 보면 id가 _rankingList0인 <ul> 태그를 찾으실 수 있습니다.
여기 하위에 있는 5개의 <li> 태그들이 타겟이 됩니다.
추출할 데이터는 <li> 태그 안에 있는
- 뉴스글 url
- 뉴스 제목
- 작성자(언론사)
2-2. iconv로 디코딩
response ok인 상태에서 body를 출력해 봅니다.
const getNews = () => {
request(
{
url: "https://news.naver.com/",
method: "GET"
},
(error, response, body) => {
if (error) {
console.error(error);
return;
}
if (response.statusCode === 200) {
console.log("response ok");
console.log(body);
}
}
);
};
실행결과 일부
출력되는 <html> 안에 깨진 글자들이 다수 보입니다. 타겟 페이지의 charset이 utf-8이 아닌 경우 위와 같은 현상이 발생합니다.
네이버 뉴스 페이지의 <head> 태그를 확인해 봅니다.
charset이 euc-kr임을 확인하였습니다. euc-kr를 디코딩하기 위해 iconv 패키지를 사용합니다.
아래와 같이 bodyDecoded 변수에 디코딩된 결과를 저장합니다.
const bodyDecoded = iconv.decode(body, "euc-kr");
const getNews = () => {
request(
{
url: "https://news.naver.com/",
method: "GET",
encoding: null,
},
(error, response, body) => {
if (error) {
console.error(error);
return;
}
if (response.statusCode === 200) {
console.log("response ok");
const bodyDecoded = iconv.decode(body, "euc-kr");
console.log(bodyDecoded);
}
}
);
};
디코딩된 bodyDecoded를 출력해 보면 한글이 정상적으로 출력됩니다.
2-3. cheerio로 데이터 추출
데이터에 접근하기 위해 bodyDecoded를 cheerio 모듈로 load 합니다.
const $ = cheerio.load(bodyDecoded);
네이버 뉴스로 돌아가셔서 사이트의 DOM을 보시면 class명이 list_text_inner인 div에 필요한 데이터가 모두 담겨있습니다.
(두 개의 <a> 태그)
ul#_rankingList > div.list_text_inner
ul#_rankingList0 안에는 div.list_text_inner가 총 5개가 있습니다. 이 5개의 div를 배열로 받아보겠습니다.
const list_text_inner_arr = $("#_rankingList0 > li > div > div > div").toArray();
list_text_inner_arr.length // 5
/*
<div class="list_text_inner">...</div>
<div class="list_text_inner">...</div>
<div class="list_text_inner">...</div>
<div class="list_text_inner">...</div>
<div class="list_text_inner">...</div>
*/
이 배열을 순회하면서 뉴스글 url, 뉴스제목, 작성자(언론사) 데이터를 추출해 봅니다.
순회 시마다 결괏값을 담을 빈 배열 result를 생성한 후 forEach문으로 순회하겠습니다.
if (response.statusCode === 200) {
console.log("response ok");
const bodyDecoded = iconv.decode(body, "euc-kr");
const $ = cheerio.load(bodyDecoded);
const list_text_inner_arr = $("#_rankingList0 > li > div > div > div").toArray();
const result = [];
list_text_inner_arr.forEach((div) => {
// result에 1. 뉴스글 url / 2. 뉴스제목 / 3. 작성자(언론사) 저장
});
}
첫 번째 <a> 태그에 접근(aFirst)
list_text_inner_arr.forEach((div) => {
const aFirst = $(div).find("a").first();
});
첫 번째 <a> 태그에서 url, title 추출
list_text_inner_arr.forEach((div) => {
const aFirst = $(div).find("a").first();
const path = aFirst.attr("href"); // 첫번째 <a> 태그 href
const url = `https://news.naver.com/${path}`; // 도메인을 붙인 url 주소
const title = aFirst.text().trim(); // trim으로 공백제거
console.log(url, title) // https://news.naver.com//main/ranking/read.nhn?mode=LSD&mid=shm&sid1=001&oid=437&aid=0000262268&rankingType=RANKING, "나발니, 못 걷는 수준…건강 급격히 악화"
});
두 번째 <a> 태그에 접근(aLast), author 추출
list_text_inner_arr.forEach((div) => {
const aFirst = $(div).find("a").first();
const path = aFirst.attr("href"); // 첫번째 <a> 태그 href
const url = `https://news.naver.com/${path}`; // 도메인을 붙인 url 주소
const title = aFirst.text().trim(); // trim으로 공백제거
console.log(url, title) // https://news.naver.com//main/ranking/read.nhn?mode=LSD&mid=shm&sid1=001&oid=437&aid=0000262268&rankingType=RANKING, "나발니, 못 걷는 수준…건강 급격히 악화"
const aLast = $(div).find("a").last();
const author = aLast.text().trim();
console.log(author); // JTBC
});
모든 데이터를 추출했으니 result 배열에 객체형태로 넣어주면 완성입니다.
const result = [];
list_text_inner_arr.forEach((div) => {
const aFirst = $(div).find("a").first(); // 첫번째 <a> 태그
const path = aFirst.attr("href"); // 첫번째 <a> 태그 url
const url = `https://news.naver.com/${path}`; // 도메인을 붙인 url 주소
const title = aFirst.text().trim();
const aLast = $(div).find("a").last(); // <두번째(마지막) <a>태그
const author = aLast.text().trim();
result.push({
url,
title,
author,
});
});
완성 코드
const request = require("request");
const cheerio = require("cheerio");
const iconv = require("iconv-lite");
const getNews = () => {
request(
{
url: "https://news.naver.com/",
method: "GET",
encoding: null,
},
(error, response, body) => {
if (error) {
console.error(error);
return;
}
if (response.statusCode === 200) {
console.log("response ok");
const bodyDecoded = iconv.decode(body, "euc-kr");
const $ = cheerio.load(bodyDecoded);
const list_text_inner_arr = $(
"#_rankingList0 > li > div > div > div"
).toArray();
const result = [];
list_text_inner_arr.forEach((div) => {
const aFirst = $(div).find("a").first(); // 첫번째 <a> 태그
const path = aFirst.attr("href"); // 첫번째 <a> 태그 url
const url = `https://news.naver.com/${path}`; // 도메인을 붙인 url 주소
const title = aFirst.text().trim();
const aLast = $(div).find("a").last(); // <두번째(마지막) <a>태그
const author = aLast.text().trim();
result.push({
url,
title,
author,
});
});
console.log(result);
}
});
};
getNews();
3. 최종 실행 결과
👏완성
수고하셨습니다!
'기타' 카테고리의 다른 글
[웹 개발 초보] 웹개발 독학 사이트 추천 (0) | 2023.12.28 |
---|---|
예쁜 색상 모음 사이트(파스텔톤) flatuicolors, colorhunt, paletadecolores (0) | 2019.08.06 |