크롤링 기능 구현

지헌·2025년 4월 18일

Banbok

목록 보기
3/8

개요

사용자에게 자신이 반복해서 푼 문제 횟수를 보여주는 건 어떤가? 라는 아이디어에서 출발해, 이를 구현하려면 사용자가 푼 문제의 제목과 사이트 정보를 수집해야 했습니다. 사이트 정보는 URL로부터 쉽게 알 수 있었지만, 문제 제목은 실제 페이지에 들어가서 HTML을 분석해야 했습니다.

이때, 문제 제목을 얻기 위해 HTML을 크롤링해야 하는 상황이 발생했습니다. 크롤링은 일반적으로 백엔드에서 수행하는 작업입니다. 왜냐하면 브라우저 환경(프론트엔드) 에서는 보안상의 이유로 CORS 정책에 의해 외부 HTML 접근에 제약이 있기 때문입니다.

하지만 저는 프론트엔드 개발자로서 백엔드 인프라를 전부 다루지 않기 때문에, 간단한 크롤링 기능을 구현할 수 있는 방법을 고민했습니다. 이번 크롤링 작업은 로그인이나 동적 렌더링이 필요한 복잡한 페이지가 아니었고, 정적인 HTML 구조에서 단순히 제목을 추출하는 수준이었기 때문에 프론트엔드에서 직접 구현해보기에 적절하다고 판단했습니다.

그래서 Next.js의 API Route 기능을 활용해 별도의 백엔드 서버 없이도 간단하고 가벼운 API 라우트를 구성하고, Node.js 환경에서 서버 기능을 수행할 수 있는 구조를 빠르게 만들 수 있었습니다.
이러한 구조 덕분에 프론트엔드 개발자로서도 Node.js 기반의 서버 사이드 코드에서 크롤링을 직접 구현할 수 있었고, 전체 흐름을 빠르게 구축할 수 있었습니다.

그리고 Next.js의 API Route에서 cheerio를 사용해 외부 문제 페이지의 HTML을 파싱하고, 필요한 문제 제목을 추출한 후, 이를 다시 Spring 기반의 백엔드 서버에 POST 요청하여 문제 데이터를 저장하는 구조를 만들었습니다.

이 구조는 마치 Next.js를 미니 서버처럼 활용한 마이크로 서비스 구성에 가깝고, 프론트엔드에서도 크롤링이나 데이터 가공을 처리할 수 있다는 유연성을 보여줍니다. 이를 통해 협업 중인 백엔드 서버와의 역할 분담도 깔끔하게 할 수 있었고, 기획 아이디어를 기술로 풀어내는 경험을 할 수 있었습니다.

import { NextRequest, NextResponse } from "next/server";
import * as cheerio from "cheerio";

export async function POST(req: NextRequest) {
  const { link } = await req.json();

  try {
    // const response = await fetch(link);
    const response = await fetch(link, {
      headers: {
        "User-Agent":
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
      },
    });

    const html = await response.text();
    const $ = cheerio.load(html);

    console.log("받은 HTML:", html);

    let title = "";
    let site = "";

    if (link.includes("acmicpc.net")) {
      title = $("#problem_title").text().trim();
      site = "Baekjoon";
    } else if (link.includes("school.programmers.co.kr")) {
      title = $(".challenge-title").text().trim();
      site = "Programmers";
    }

    console.log("추출된 제목:", title);

    return NextResponse.json({ title, link, site });
  } catch (error) {
    return NextResponse.json(
      { message: "크롤링 실패", error },
      { status: 500 }
    );
  }
}
  • app/api/scrape/route.ts에서 볼 수 있듯이, 별도의 백엔드 서버 없이도 API 라우트를 구현했습니다.
profile
차곡차곡 그만 쌓아올리고 취업해서 부딪쳐보고 싶은

0개의 댓글