개발일기장
Node.JS로 웹 크롤링 하기. puppeteer, cheerio, 동적 크롤링 본문
1. 왜 puppeteer를 사용하는지
https://www.npmjs.com/package/puppeteer
일반적으로 axios나 fetch같은 단순히 HTTP GET method를 이용해서 해당 페이지를 긁어오는 방식이 있지만, 이것은 클라이언트 사이드에서 데이터를 로딩하는 방식의 웹페이지에서는 원하는 정보 모두를 가지고 올 수 없었음.
(내가 크롤링 하고 싶었던 내용은 내용은 기본적으로 서버사이드에서 로딩을 한 다음에 댓글부분은 사용자단에서 로딩이 되는것 같았음.)
puppeteer를 사용하는 경우 Chromium으로 원하는 링크를 연 다음에 페이지가 전부 랜더링 된 다름에 정보를 가지고 오는 방식이다.(인것 같다.. 정확한건 몰라)
-> axios, fetch는 정적크롤링, puppeteer 동적크롤링으로 검색하니깐 나왔음..
+ 추가
puppeteer에서는 Chromium으로 페이지를 연 다음에 거기서 클리이나 다른 작업을 코드로 할 수 있도록 기능을 제공한다.
2. 방법
1. puppeteer를 이용하여 데이터를 가지고 오자
1-1) browser를 실행한다(여기서 여러가지 옵션을 줄 수 있다. 브라우저(Chromium)를 직접 열어서 어떤 태그를 가지고 올지 확인할 수 있다.)
1-2) 새로운 페이지 시작
1-3) 원하는 링크를 연다
1-4) HTML 정보를 가지고 온다
1-5) 열었던 것들을 닫는다.
const puppeteer = require('puppeteer');
//1. 크로미움으로 브라우저를 연다.
const browser = await puppeteer.launch(); // -> 여기서 여러가지 옵션을 설정할 수 있다.
//2. 페이지 열기
const page = await browser.newPage();
//3. 링크 이동
await page.goto(`${href}`);
//4. HTML 정보 가지고 온다.
const content = await page.content();
//5. 페이지와 브라우저 종료
await page.close();
await browser.close();
이렇게 하면 content 변수에 우리가 원하는 데이터를 가지고 올 수 있다.
(이 경우 axios(fetch) GET을 이용해서 가지고 온 방식이랑 같다고 보면 된다.)
(크로미움으로 내용이 로딩 한 다음에 컨탠츠를 가지고 오는 것이기 때문에 시간이 조오오오금 걸린다.)
2. cheerio를 이용하여 원하는 데이터를 추출한다.
내가 가지고 오고 싶은 데이터는 일단
해당 게시판의 글 번호, 제목, 링크, 작성자, 작성 날짜를 가지고 오고 싶었다.
그리고 크롤링 코드를 재사용 하기 위해서 크롤링 정보만들 원하는 Object를 만들었음
const post_list_object = {
href: `${원하는 링크}`,
select_path: '#content-wrap > div > div.board-list > ul > li',
items: [
{ name: 'counter', path: 'span.count', text: true, href: false },
{ name: 'href', path: 'span.title > a', text: false, href: true },
{ name: 'title', path: 'span.title > a', text: true, href: false },
{ name: 'nick', path: 'span.global-nick', text: true, href: false },
{ name: 'date', path: 'span.date', text: true, href: false },
],
type: 'post',
};
이렇게 구성을 했다. items에서 text의 경우 그냥 html태그 내부의 데이터를 가지고 오기 위한 것이고, href는 <a>에서 링크값을 가지고 오고 싶었다.
이것을 crawl_obejct로 파라미터로 넘겨준다.
const cheerio = require('cheerio');
////////////////////////////
//0. crawl_object는 위에 있는 post_list_object임.
//1. 위에서 가지고온 데이터(content)를 cheerio에 넣는다.
const $ = cheerio.load(content);
const result = [];
//2. li를 로딩한다(각각의 데이터를 추출하기 위해서 each사용)
$(crawl_object.select_path).each(function (idx, element) {
//3. 각각의 li내부의 데이터를 cheerio에 넣은 다음
const $data = cheerio.load(element);
const return_data = {};
//4. 그 li내부의 이름, 날짜, 링크 등등 가지고 오고싶음
crawl_object.items.forEach((item) => {
//4-1.일반 태그 내부의 text데이터 가지고 오기
if (item.text === true) {
return_data[item.name] = $data(item.path).text();
}
//4-2.<a>에서 href속성을 가지고 오기위한것,
if (item.href === true) {
return_data[item.name] = $data(item.path).attr('href');
}
});
///////
"이 밑으로는 return_data를 이용한 로직이 있음."
"result에 push하기전에 좀 다듬을 게 있어서.."
///////
});
이렇게 하면 된다.
3. 전체 코드
const puppeteer = require('puppeteer');
const cheerio = require('cheerio');
const crawler = async (crawl_object) => {
try {
const browser = await puppeteer.launch(); .
const page = await browser.newPage();
await page.goto(crawl_object.href);
const content = await page.content();
await page.close();
await browser.close();
const $ = cheerio.load(content);
const result = [];
$(crawl_object.select_path).each(function (idx, element) {
const $data = cheerio.load(element);
const return_data = {};
crawl_object.items.forEach((item) => {
if (item.text === true) {
return_data[item.name] = $data(item.path).text();
}
if (item.href === true) {
return_data[item.name] = $data(item.path).attr('href');
}
});
// -> 여기부터는 내부 로직
});
//console.log(result);
return Promise.resolve(result);
} catch (err) {
return Promise.reject([]);
}
};
module.exports = {
crawler,
};
async await사용해서 문제 생길 수 있으니깐 try catch로 감싸주고 Promise로 반환하자.(더 써먹기 위해서)
포스트랑 내용이 좀 다르지만 있음.
https://github.com/tlqckd0/web-crawling/tree/main/crawl
'node.js' 카테고리의 다른 글
Node.js. Mysql2 Transaction작업 + template만들기 (jdbc방식) (0) | 2022.05.27 |
---|---|
Node.JS 웹 크롤링으로 Promise.all 성능 비교 (수정: 이거 다 틀렸음) (0) | 2022.05.18 |
Node.js에서 RabbitMQ(AMQP) 사용하기. Consume (3) (0) | 2021.07.28 |
Node.js에서 RabbitMQ(AMQP) 사용하기. Publish (2) (0) | 2021.07.28 |
Node.js에서 RabbitMQ(AMQP) 사용하기. 브라우저에서 설정 (1) (2) | 2021.07.28 |