검색어가 한글
일 경우 keyword를 그대로 입력하면 에러가 발생한다. 이러한 에러를 회피하기 위해 특수문자 등을 인코딩된 문자열로 치환하는 encodeURI()를 사용한다.
encodeURI() - mdn
이스케이프 제외:A–Z a–z 0–9 - _ . ! ~ * ' ( ) ; / ? : @ & = + $ , #
util.inspect(object, [options])
: [object object]로 표현되는 객체에 대하여 그 구조를 점검하고 string타입으로 표현 및 반환한다. fs.writeFile(파일명, 데이터, 콜백함수)
에서 데이터를 string으로 받기위해 사용했다.
$().each()
: jquery에 선택자를 넘기면 그 선택자를 for문(반복문)과 같이 사용한다.
참고 - https://webclub.tistory.com/455
- 일정하게 반복되는
div
에 대하여
상위 클래스course_card_item
을 아래와 같이 지정하고const $courseList = $(".course_card_item");
course_title
과 같은 하위 클래스는<div class="course_title">한 입 크기로 잘라먹는 타입스크립트</div>
제이쿼리로 반복문을 돌려 구할 수 있다.
$courseList.each((idx, node) => { const title = $(node).find(".course_title").text(); console.log(title); }
typescript를 검색했을 때 강의 목록을 크롤링하여
id, 강의 제목, 강사, 가격(할인가 또는 없으면 현재가), 별점, 이미지url
정보를 log.txt파일에 입력받도록 구현했다.
const fs = require("fs");
const axios = require("axios"); // 외부API로 html구조를 가져옴
const cheerio = require("cheerio"); // 가져온 html 구조를 쉽게 parsing하게 해줌
const util = require("util");
const getHTML = async (keyword) => {
try {
return await axios.get(
"https://www.inflearn.com/courses?s=" + encodeURI(keyword)
);
} catch (err) {
console.log(err);
}
};
const parsing = async (keyword) => {
const html = await getHTML(keyword);
const $ = cheerio.load(html.data); // jquery
const $courseList = $(".course_card_item");
let courses = [];
$courseList.each((idx, node) => {
courses.push({
id: idx,
title: $(node).find(".course_title:eq(0)").text(), // 2개의 노드를 가져오는데 첫번째 것만
instructor: $(node).find(".instructor").text(),
price:
$(node).find(".price > .pay_price").text() ||
$(node).find(".price").text(),
rating: $(node).find(".star_solid").css("width"),
img: $(node).find(".card-image > figure > img").attr("src"),
});
});
const htmlString = util.inspect(courses, {
showHidden: true,
depth: null,
colors: false,
});
fs.writeFile("log.txt", htmlString, (error) => {
if (error) {
console.error(error);
} else {
console.log("성공적으로 log.txt에 저장되었습니다.");
}
});
};
parsing("typescript");
log.txt
[
{
id: 0,
title: '한 입 크기로 잘라먹는 타입스크립트',
instructor: '이정환 Winterlood',
price: '₩38,500',
rating: '100%',
img: 'https://cdn.inflearn.com/public/courses/330452/cover/7f862bab-8519-4b31-afc6-26cb355833eb/330452-eng.png'
},
{
id: 1,
title: '타입스크립트 입문 - 기초부터 실전까지',
instructor: '장기효(캡틴판교)',
price: '₩41,250',
rating: '98.9413988657845%',
img: 'https://cdn.inflearn.com/public/courses/326082/cover/c6519e92-f334-46ac-8a31-6290db19b32a'
},
(...생략...)
{
id: 23,
title: '만들면서 배우는 프론트엔드 DO IT 코딩 (Next.js, Typescript)',
instructor: 'totuworld',
price: '₩99,000',
rating: '89.41176470588235%',
img: 'https://cdn.inflearn.com/public/courses/329080/cover/c7af9560-eced-48dd-af9e-66b2e8ee17bc/329080-eng.jpg'
},
[length]: 24
]
이제 내 velog의 시리즈 제목을 크롤링 해보자.
const fs = require("fs");
const axios = require("axios");
const cheerio = require("cheerio");
const util = require("util");
const getHTML = async (keyword) => {
try {
return await axios.get(
`https://velog.io/@${keyword}/series`
);
} catch (err) {
console.log(err);
}
};
const parsing = async (keyword) => {
const html = await getHTML(keyword);
const $ = cheerio.load(html.data);
console.log(html.data);
const $seriesList = $(".hpEiVB");
let series = [];
$seriesList.each((idx, node) => {
series.push({
id: idx,
title: $(node).find("h4 > .hiUmEs").text(),
});
});
};
parsing("scroll0908");
그런데 안된다?! 브라우저에서 확인한 각 시리즈 div의 클래스명은 .sc-kYHfwS hpEiVB
인데 아무것도 읽어오지 못한다 왜일까?
html.data
를 여러번 콘솔로 찍어본 결과, 클래스 명이 랜덤으로 계속 변하는 것이 확인됐다. 클래스명을 기준으로 크롤링하려고 했는데 클래스명이 동적이라니..
cheerioLoaded를 비교 시:
토이프로젝트는 기술적 한계로 중단됐지만
cheerio와 axios로 간단한 크롤링 방법을 익힐 수 있었다.
참조
- 개발자의품격 - Node.js 크롤링 - 인프런 사이트 크롤링하기
https://youtu.be/xbehh8lWy_A