처음 개발을 배우고 얼마 되지 않았을때 쯔음 백엔드 개발자와 협업해서 Vanilla.Js 로 Kream 사이트 Style 부분을 클론해 프로젝트를 진행한 경험이 있습니다. React를 학습하기 이전 자바스크립트로 0부터 1까지 Dom 조작을 해서 개발했던 경험이 아직까지 좋은 기억으로 남아 있어 그로부터 어느정도 시간이 흐른 지금 이 시점에, 연습삼아 Next.js 로 Kream 프로젝트를 다시 만들어 보기로 했습니다.
당시 백엔드 측에서 만든 api 를 사용해 crud 기능을 구현했기 때문에 데이터에 대한 큰 고민이 없었습니다.
게시글 리스트-> 상세보기-> 댓글 조회-> 댓글 등록-> 모달창에 데이터 연동 -> 화면 렌더링
등 프론트 기능을 집중적으로 구현했습니다. (Vanilla로 돔을 조작한다는 것이 생각보다 더 쉽지 않았습니다..🥺 제이쿼리를 쓰지 않고 구현하겠다고 난리를 피웠던 기억이 나네요. 이때도 바닐라 사랑은 여전했습니다.) 현재 저와 같이 협업할 백엔드 개발자가 없기 때문에 데이터와 모든 CRUD api 생성까지 고민해야 하는 상황에 이르렀습니다.
그래서 문제를 해결하기 위해 될 때까지 뭐라도 해보기로 합니다. 데이터를 직접 생성하고 Next.js 를 사용해 간단한 CRUD api 를 개발하기로 결심했습니다.
하지만 어떻게 데이터를 생성해야 할지 감이 잘 잡히지 않았습니다. 클라이언트 측에서 Mock 데이터로 반복문을 사용해 단순히 의미 없는 데이터를 db에 하나하나 손수 직접 삽입하고 싶지 않았습니다. 좀 더 실제 사이트에 가까운 프로젝트를 구현하고 싶은 욕심이 있었기 때문입니다.
이러한 고민을 하던 와중 Javascript 로 크롤링을 하는 방법이 있다는 것이 생각났습니다. 고민보다는 Go, 바로 크롤링을 시도해봅니다.
참고로 크롤링으로 모은 데이터는 상업적 목적이 절대 아니기 때문에 제가 철컹철컹 하는 일이 없었으면 합니다.. 조금 무서워요.. 😩
제가 사용한 크롤링 라이브러리는 Puppeteer 입니다. 브라우저가 페이지를 접근한 후에 결과값에 접근하고자 해당 라이브러리를 사용했습니다.
전체적인 크롤링 로직은 공개할 수 없지만 크롤링을 해서 데이터를 모으는 과정이 순탄하지만은 않았습니다.
크롤링의 전체적인 절차는
입니다.
async function init() {
const browser = await puppeteer.launch({
headless: false, // headless 모드 비활성화 (테스트 중에는 활성화해도 무방)
});
const page = await browser.newPage();
return { browser, page };
}
module.exports = {
init,
};
저는 페이지를 여는 함수 로직을 init 화 해서 따로 모듈로 분리 작업을 진행했습니다. 사실 모듈화는 귀찮은 작업이 아니라 필수 작업이라고 해도 무방합니다. 미래의 나를 위해서라도요....🥳
제가 크롤링하고 싶은 데이터가 있는 Style 페이지입니다. insepect 를 하면 Elements 탭에서 돔 구조를 볼 수 있는데 해당 돔 구조를 통해 직접 작성하고 반복문을 돌려 데이터를 추출하는 과정을 거칩니다. 메인페이지만 크롤링 한다면 굉장히 단순한 작업이 될 수도 있겠죠.
하지만 인생은(?) 그렇게 쉽게만 흘러가지 않더라구요..! 😬
유저 info (아이디/ 유저 아바타 이미지)
게시글이 작성된 날짜
좋아요 수
게시글 content [ text ]
=> 더보기 버튼이 있으면 click 트리거를 발생시켜 모든 내용을 보여줘야 한다.
=> click 이벤트 또한 코드로 구현해야 한다. Ex : page.click('.class')
=> 해시태그는 따로 추출해 배열에 담아야한다.
게시글 content [ img ]
=> 이미지는 1개 일수도, 여러개일 수도 있다.
=> 여러개일 수도 있기 때문에 반복문을 돌려 배열에 img를 담아야한다.
게시글에 달린 댓글
=> 로그인을 해야만 댓글을 볼 수 있다.
=> 로그인 또한 코드로 구현해야 한다.
=> [ 코드 구현 ] 로그인 페이지 이동=> id, pw 입력하는 dom을 찾아 타이핑=> 로그인 버튼 클릭
상세보기 페이지로 이동합니다. 예를들면 게시글에 달린 댓글이 총 3개가 있습니다. 댓글을 더 보고 싶다면 댓글 더 보기를 클릭합니다. Kream 사이트는 로그인을 하지 않으면 댓글을 볼 수 없습니다. 로그인하지 않은 사용자는 바로 로그인 창으로 리다이렉트 시켜버립니다.
짜잔 ! 이렇게 오른쪽 모달창에 게시글 정보와 함께 댓글이 연동이 됩니다. 제가 구현하면서 가장 재밌었(어렵다😬..) 다고 생각한 부분입니다.
상세보기로 이동 후 댓글 더 보기 버튼을 클릭해 오른쪽 모달 dom 을 찾는 로직을 작성했는데, 애초에 dom 이 계속 null로 찍히는 현상이 있었습니다. 그렇기 때문에 댓글 정보가 가져와지지 않아 몇시간을 꼬박 헤맸습니다.
고민은 전혀 다른 방향으로 흘러갔습니다. 차근차근 왜 dom 이 null이 찍히는 가에 대해서 다시 한 번 고민해봅니다.
'puppeteer login' 키워드를 구글에 검색해보니 정말로 코드로 로그인까지 구현이 가능했습니다. 단순하게도 저는 로그인 동작은 제가 직접 해야 한다고 생각했는데, 코드 몇줄만으로 로그인 test 까지 자동화 할 수 있다고 생각하니 다시 생각해도 놀랍네요!
https://stackoverflow.com/questions/50074799/how-to-login-in-puppeteer
로그인 로직을 작성하니 댓글 모달창의 dom 을 가져올 수 있게 되었습니다.
해당 절차를 통해 모든 main Page 의 상세보기 데이터를 얻는데 성공했습니다. 간단하게 축약했지만 사실 저 스텝 하나하나에 저의 수 많은 삽질과 노고가 담겨있습니다. 🥳
TEST1 데이터를 추출하며 수 많은 시행착오를 겪고 유저의 모든 게시글을 추출하는 과정은 수월하게 진행이 되었습니다. 데이터를 추출해
{ 'seorim' : [{작성한게시글..}, {작성한게시글..} ]}
형태로 만들어 주었습니다.
Kream 사이트는 무한 스크롤 형태로 데이터를 get 합니다. 스크롤도 직접 내려서 데이터를 수집하나요 ? 아닙니다! 스크롤을 내리는 코드 또한 직접 작성해야 합니다.
해당 로직은 구글에 검색을 해서 페이지의 스크롤을 끝까지 내리는 로직을 참고했습니다. 다만 모든 데이터를 수집하고 싶었는데 너무 오래 걸리기도 하고 데이터량이 어마무시하게 많아져 임의로maxScrollHeight 변수를 추가해 페이지의 스크롤 높이를 지정해주어 적당히 필요한 갯수의 데이터만을 모았습니다.
const maxScrollHeight = 3500; // 원하는 스크롤 높이 상한을 설정
let lastHeight = await page.evaluate("document.body.scrollHeight");
while (true) {
await page.evaluate("window.scrollTo(0, document.body.scrollHeight)");
await page.waitForTimeout(2000); // 스크롤 후 대기 시간
let newHeight = await page.evaluate("document.body.scrollHeight");
let scrollHeightIncrease = newHeight - lastHeight;
if (scrollHeightIncrease < maxScrollHeight) {
lastHeight = newHeight;
} else {
break; // 스크롤 높이가 상한에 도달하면 멈춥니다.
}
}
mongo db를 사용해 모은 데이터를 db 에 삽입하기로 합니다. Atlas 계정을 생성하고 시각화 툴인 compass 를 사용해 db 데이터들을 편하게 볼 수 있습니다.
db 폴더를 만들고 connection 로직, model 생성 로직, 스키마 타입 작성 로직을 모두 분류해주었습니다. 저는 백엔드 개발자가 아니기 때문에 db 로직음 처음 모듈화를 해봤는데 더 좋은 분류 방법이 있을 수도 있겠다는 생각이 드네요.
const mongoose = require("mongoose");
// 2. testDB 세팅
mongoose.connect(
"mongodb+srv://내 db 주소 / 컬렉션 명"
);
// 3. 연결된 testDB 사용
const db = mongoose.connection;
// 4. 연결 실패
db.on("error", function () {
console.log("Connection Failed!");
});
// 5. 연결 성공
db.once("open", function () {
console.log("Connected!");
});
module.exports = {
mongoose,
};
console.log("Connected!") 가 떴을 때 기분이 정말 좋았습니다!
db 연결 성공 후, 크롤링 데이터를 추출하는 로직들은 모두 따로 함수로 빼서 다른 폴더에 모듈화 작업을 진행했습니다. 미처 신경쓰지 못했던 예외처리와 에러 처리, 변수명 수정 , 추출 로직 함수 통일성 지키기 ( 예 : evaluate , eval 중 하나만 사용하기) 같은 작업을 진행했습니다.
🔊 TEST를 위한 임시 로직을 작성했습니다. 코드는 더 예뻐질 것입니다...!!
크롤링, Mongo DB, Next.js , React Query 전부 생전 처음 사용해보는 기술 스택들이라 작업을 하며 낯설고 생소한 부분이 많았는데 (특히 db 연결과 크롤링..😭) 결국 Javascipt 를 잘 이해하고 있다면 적용하는데 크게 어려운 부분이 없었던 것 같습니다.
마지막으로 제가 아~~ 주 예전에 React도 모르는 시절에 바닐라로 프론트 개발을 담당했던 프로젝트입니다. (추억돋..😬) 그때 당시 forEach도 제대로 몰라 for 문으로 손수 반복문을 돌렸고, 기능을 구현 하기만 급급했었는데 그래도 지금 돌아보니 꽤나 기특하다는 생각이 드네요.
현재 다시 진행하고 있는 프로젝트는 단순히 기능을 완성 하는 것이 목적이 아닌 자기 발전이 목적인 프로젝트 입니다. 구현에만 급급해 하지 않고 기술을 도입할 때 이 기술을 왜 사용하는지에 대한 깊은 고민을 해보며 차근차근 프로젝트를 진행해보고자 합니다.
프로젝트 현황은 계속 블로그에 작성해보려 노력하겠습니다.
멋지심니다