카테고리 없음

[최종 프로젝트] 기획 단계(3)

myinfo7091 2025. 1. 3. 22:37
튜터님 피드백

- 룩북에 대한 게시글을 작성하면서 사용자가 직접 상품에 대한 구매처까지 등록하는 것은 번거로움을 유발할 수 있을 듯 합니다.
- 데이터가 필요하다면 크롤링을 시도해도 좋지만 기술적으로 구현이 어려울 수 있습니다.
- 오픈 api를 활용한 룩북 가상 시착 시스템 커뮤니티로서의 속성을 강화해서 유저 중심의 기능이 추가되면 좋을 것 같습니다.

 

 

[Next.js] Puppeteer로 CSR 페이지 크롤링하기

Chrome Headless 모드로 CSR 페이지 크롤링하기🤖

velog.io

Puppeteer란?

Puppeteer는 Chrome DevTools 프로토콜을 이용해 Chrome/Chromium을 제어할 수 있는 Node.js 라이브러리로, 기본적으로 헤드리스 모드(백그라운드에서 UI 없이 브라우저 실행)에서 동작한다.

npm install puppeteer-core
npm install @sparticuz/chromium-min

// 필요없으면 그냥 npm install puppeteer
NEXT_PUBLIC_BLOG_URL=https://velog.io/@kimbangul/posts
NEXT_PUBLIC_CDN_LINK=/* chromium 파일을 올린 링크 입력 */
NEXT_LOCAL_CHROME_PATH=Chrome 실행 경로
//src/app/api/crawl/route.ts
import { NextResponse } from "next/server";
import puppeteer from "puppeteer";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const url = searchParams.get("url");

  if (!url) {
    return NextResponse.json({ error: "URL is required" }, { status: 400 });
  }

  try {
    const browser = await puppeteer.launch({
      headless: true,
      args: ["--no-sandbox", "--disable-setuid-sandbox"],
    });
    const page = await browser.newPage();
    
    // User-Agent 설정 (크롤링 차단 우회)
    await page.setUserAgent(
      "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
    );
    
    // 페이지 이동 (타임아웃 60초)
    await page.goto(url, { waitUntil: "networkidle2", timeout: 60000 });

    // 페이지에서 상품 데이터 추출
    const products = await page.evaluate(() => {
      const productElements = document.querySelectorAll(
        ".sc-1xhqrcq-0.cMIGzr"
      );

      return Array.from(productElements).map((element) => {
        const titleElement = element.querySelector(
          ".sc-1xhqrcq-8.izVYxY.gtm-select-item p"
        ) as HTMLElement;
        const imageElement = element.querySelector(
          ".sc-1xhqrcq-3.dxvdoH"
        ) as HTMLImageElement;
        const priceElement = element.querySelector(
          ".sc-1xhqrcq-10.kmYwkG.text-black"
        ) as HTMLElement;
        const discountElement = element.querySelector(
          ".sc-1xhqrcq-10.kmYwkG.text-red"
        ) as HTMLElement;

        return {
          id: element.getAttribute("data-item-id") || "N/A",
          title: titleElement?.innerText || "No Title",
          image: imageElement?.getAttribute("src") || "",
          price: priceElement?.innerText || "N/A",
          discount: discountElement?.innerText || "0%",
          brand: element.getAttribute("data-item-brand") || "No Brand",
          originalPrice: element.getAttribute("data-original-price") || "N/A",
        };
      });
    });

    await browser.close();
    return NextResponse.json({ products });
  } catch (error: any) {
    console.error("Puppeteer Error:", error);
    return NextResponse.json(
      { error: error.message || "Internal Server Error" },
      { status: 500 }
    );
  }
}