AI

Bright Data와 Vercel AI SDK를 활용한 AI 뉴스 연구 보조 도구 구축

이 가이드는 Bright Data와 Vercel AI SDK를 활용하여 글로벌 뉴스를 수집하고, 유료 장벽을 우회하며, 편향을 감지하고, 스마트 분석을 제공하는 AI 뉴스 연구 보조 도구를 구축하는 방법을 안내합니다.
3 분 읽기
Bright Data with Vercel AI SDK blog image

이 시스템은 전 세계 소스의 뉴스를 검색하고, 전체 기사 내용을 추출하며(유료 장벽 우회), 보도 내용의 편향을 감지하고, 실제 뉴스 사건을 기반으로 지능형 분석을 생성합니다.

배울 내용:

  • 웹 스크래핑을 위한 Bright Data SDK를 활용한 뉴스 연구 도구 구축 방법
  • Vercel AI SDK를 활용한 지능형 뉴스 분석 및 대화 구현 방법
  • 유료화 및 봇 방지 기능을 우회하여 모든 뉴스 소스에 접근하는 방법
  • 다중 매체 보도 비교를 통한 편향성 탐지 방법
  • 뉴스 발견부터 팩트체킹까지 자동화된 파이프라인 구축 방법

시작해 보세요!

필수 조건

이 튜토리얼을 따라하려면 다음이 필요합니다:

  • React 및 Next.js에 대한 기본 지식
  • 로컬 개발 환경에 설치된Node.js 20.18.1 이상
  • API 접근 권한이 있는 Bright Data 계정 (무료 이용권 제공)
  • GPT-4 접근 권한이 있는 OpenAI API 키
  • TypeScript 및 현대적 JavaScript에 대한 이해
  • API 및 환경 변수 작업에 대한 기본적인 이해

기존 뉴스 소비 방식의 한계점

정보와 뉴스에 접근하는 전통적인 방식에는 몇 가지 주요 한계가 있습니다:

  • 정보 과부하: 매일 수백 개의 헤드라인을 접하게 됩니다. 이로 인해 무엇이 중요하거나 자신의 관심사와 관련되는지 파악하기 어렵습니다.
  • 편향성과 관점의 격차: 대다수 사람들은 소수의 출처에서 뉴스를 접합니다. 이로 인해 중요한 관점을 놓치거나 복잡한 이슈에 대한 일방적인 보도를 접하게 됩니다.
  • 유료화 장벽: 양질의 저널리즘은 종종 유료화되어 있습니다. 다양한 출처의 전체 기사를 찾아 철저한 연구를 진행하기 어렵습니다.
  • 사실 확인 부담: 주장을 검증하려면 여러 출처를 검색하고 정보를 교차 확인하며 출처의 신뢰성을 평가해야 합니다. 대부분의 사람들은 이를 위한 시간이 없습니다.
  • 맥락 부족: 속보는 종종 역사적 배경이나 관련 사건을 누락합니다. 진행 중인 뉴스의 전체적인 그림을 파악하기 어렵습니다.

NewsIQ는 이러한 과제를 해결합니다. AI 기반 분석과 기업용 웹 스크래핑을 결합한 이 시스템은 모든 뉴스 출처에 접근(봇 방지 기능 우회)하고, 여러 매체의 보도를 분석하며, 적절한 출처 표기와 함께 지능적인 통찰력을 제공합니다.

뉴스 리서치 어시스턴트 구축

Bright Data와 Vercel AI SDK를 활용해 완전한 AI 뉴스 연구 보조 도구인 NewsIQ를 구축합니다. 모든 출처의 뉴스를 처리하고 대화형 인터페이스를 통해 지능형 분석을 제공하는 솔루션을 만들 것입니다.

1단계: 프로젝트 설정

먼저 Next.js 개발 환경을 설정합니다. 프로젝트용 새 디렉터리를 생성하세요:

npx create-next-app@latest ai-news-assistant

프롬프트가 표시되면 다음 옵션을 선택하세요:

Selecting the usage of recommended settings

프로젝트 디렉터리로 이동하여 필요한 패키지를 설치합니다:

cd ai-news-assistant && 
npm install @brightdata/sdk ai zod @ai-sdk/openai

이 패키지들은 웹 스크래핑을 위한 Bright Data SDK, 지능형 분석을 위한 Vercel AI SDK, 타입 안전 스키마 검증을 위한 Zod, LLM 텍스트 생성을 위한 OpenAI 등 필요한 모든 기능을 제공합니다.

All packages added successfully and the project was created

다음으로 API 자격 증명을 저장할 .env.local 파일을 생성하세요:

BRIGHTDATA_API_KEY=your_brightdata_api_key_here
OPENAI_API_KEY=your_openai_api_key_here

필요한 항목:

  • Bright Data API 토큰: Bright Data 대시보드에서 생성
  • OpenAI API 키: LLM 텍스트 생성에 사용

2단계: 뉴스 리서치 도구 정의

Bright Data의 웹 스크래핑을 활용하기 위해 세 가지 도구를 정의하여 핵심 뉴스 연구 기능을 생성합니다. 프로젝트 디렉터리에 lib/brightdata-tools.ts라는 새 파일을 생성하세요:

import { tool, type Tool } from "ai";
import { z } from "zod";
import { bdclient } from "@brightdata/sdk";

type NewsTools = "searchNews" | "scrapeArticle" | "searchWeb";

interface NewsToolsConfig {
  apiKey: string;
  excludeTools?: NewsTools[];
}

export const newsTools = (
  config: NewsToolsConfig
): Partial<Record<NewsTools, Tool>> => {
  const client = new bdclient({
    apiKey: config.apiKey,
    autoCreateZones: true,
  });

  const tools: Partial<Record<NewsTools, Tool>> = {
    searchNews: tool({
      description:
        "Google 뉴스를 사용하여 모든 주제의 뉴스 기사를 검색합니다. 제목, 요약문, 출처 및 발행일이 포함된 최근 뉴스 기사를 반환합니다. 특정 주제에 대한 최신 뉴스 보도를 찾기 위해 사용하세요.",
      inputSchema: z.object({
        query: z
          .string()
          .describe(
            '뉴스 검색 쿼리 (예: "인공 지능", "기후 변화 정책", "기술 기업 실적")'
          ),
        country: z
          .string()
          .length(2)
          .optional()
          .describe(
            '지역화 뉴스를 위한 2자리 국가 코드 (예: "us", "gb", "de", "fr", "jp")'
          ),
      }),
      execute: async ({
        query,
        country,
      }: {
        query: string;
        country?: string;
      }) => {
        try {
          const newsQuery = `${query} news`;
          const result = await client.search(newsQuery, {
            searchEngine: "google",
            dataFormat: "markdown",
            format: "raw",
            country: country?.toLowerCase() || "us",
          });
          return result;
        } catch (error) {
          return `${query} 관련 뉴스 검색 중 오류 발생: ${String(error)}`;
        }
      },
    }),

    scrapeArticle: tool({
      description:
        "모든 URL에서 뉴스 기사의 전체 내용을 스크랩합니다. 유료화 및 봇 방지 기능을 우회하여 깔끔한 마크다운 형식의 완전한 기사 텍스트를 반환합니다. searchNews로 찾은 기사 전체를 읽을 때 사용하세요.",
      inputSchema: z.object({
        url: z.string().url().describe("스크래핑할 뉴스 기사의 URL"),
        country: z
          .string()
          .length(2)
          .optional()
          .describe("프록시 위치에 대한 두 글자 국가 코드"),
      }),
      execute: async ({ url, country }: { url: string; country?: string }) => {
        try {
          const result = await client.scrape(url, {
            dataFormat: "markdown",
            format: "raw",
            country: country?.toLowerCase(),
          });
          return result;
        } catch (error) {
          return `${url} 기사 스크래핑 중 오류: ${String(error)}`;
        }
      },
    }),

    searchWeb: tool({
      description:
        "Google, Bing 또는 Yandex를 사용한 일반 웹 검색. 배경 조사, 사실 확인 또는 뉴스 기사 이상의 추가 맥락 찾기에 사용하세요.",
      inputSchema: z.object({
        query: z
          .string()
          .describe(
            "배경 정보 또는 사실 확인을 위한 검색어"
          ),
        searchEngine: z
          .enum(["google", "bing", "yandex"])
          .optional()
          .default("google")
          .describe("사용할 검색 엔진"),
        country: z
          .string()
          .length(2)
          .optional()
          .describe("지역화된 결과를 위한 두 글자 국가 코드"),
      }),
      execute: async ({
        query,
        searchEngine = "google",
        country,
      }: {
        query: string;
        searchEngine?: "google" | "bing" | "yandex";
        country?: string;
      }) => {
        try {
          const result = await client.search(query, {
            searchEngine,
            dataFormat: "markdown",
            format: "raw",
            country: country?.toLowerCase(),
          });
          return result;
        } catch (error) {
          return `"${query}"에 대한 웹 검색 중 오류 발생: ${String(error)}`;
        }
      },
    }),
  };

  for (const toolName in tools) {
    if (config.excludeTools?.includes(toolName as NewsTools)) {
      delete tools[toolName as NewsTools];
    }
  }

  return tools;
};

이 코드는 Vercel AI SDK의 도구 인터페이스를 사용하여 세 가지 핵심 도구를 정의합니다. searchNews 도구는 Google 뉴스에서 최신 기사를 검색합니다. scrapeArticle은 모든 뉴스 URL에서 전체 콘텐츠를 추출합니다(유료 장벽 우회). searchWeb은 사실 확인을 위한 일반 웹 검색을 제공합니다. 각 도구는 타입 안전 입력 검증을 위해 Zod 스키마를 사용하고 AI가 분석할 수 있는 구조화된 데이터를 반환합니다. Bright Data 클라이언트는 봇 방지 및 프록시 관리의 모든 복잡성을 자동으로 처리합니다.

3단계: AI 채팅 API 경로 생성

대화형 인터페이스를 구동할 API 엔드포인트를 구축합니다. app/api/chat/route.ts 파일을 생성하세요:

import { openai } from "@ai-sdk/openai";
import { streamText, convertToModelMessages, stepCountIs } from "ai";
import { newsTools } from "@/lib/brightdata-tools";

export const maxDuration = 60;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const modelMessages = convertToModelMessages(messages);

  const tools = newsTools({
    apiKey: process.env.BRIGHTDATA_API_KEY!,
  });


  const result = streamText({
    model: openai("gpt-4o"),
    messages: modelMessages,
    tools,
    stopWhen: stepCountIs(5),
    system: `당신은 고급 AI 뉴스 연구 보조 도구인 NewsIQ입니다. 사용자가 최신 정보를 파악하고, 뉴스 보도를 분석하며, 복잡한 시사 문제를 이해하도록 돕는 것이 당신의 역할입니다.

**핵심 기능:**
1. **뉴스 탐색**: searchNews를 사용하여 모든 주제의 최신 뉴스 검색
2. **심층 읽기**: scrapeArticle로 전체 기사 스크래핑하여 완전한 맥락 제공
3. **사실 확인**: searchWeb로 주장 검증 및 추가 출처 찾기
4. **편향 분석**: 다중 출처 보도 비교 및 잠재적 편향 식별
5. **트렌드 분석**: 새롭게 부상하는 스토리를 식별하고 주제별 변화 추이를 추적

**가이드라인:**
- 항상 출판물명과 발행일을 명시하여 출처를 인용
- 편향 분석 시 객관성을 유지하고 증거 제시
- 논란의 여지가 있는 주제는 다양한 관점 제시
- 사실과 분석을 명확히 구분
- 정보가 오래된 경우 발행일 표기
- 기사 스크래핑 시 분석 전 핵심 내용 요약
- 팩트체킹 시 여러 독립적 출처 활용

**응답 형식:**
- 명확하고 직접적인 답변으로 시작
- 맥락에 맞는 출처 인용 제공
- 여러 출처는 글머리 기호로 표시
- 간결한 분석 또는 통찰로 마무리
- 특정 측면에 대한 추가 탐색 제안

기억하세요: 목표는 사용자가 더 나은 정보에 기반한 비판적 사고자가 되도록 돕는 것입니다.
  });

  return result.toUIMessageStreamResponse();
}

이 API 경로는 뉴스 연구 도구와 OpenAI의 GPT-4를 연결하는 스트리밍 엔드포인트를 생성합니다. 포괄적인 시스템 프롬프트는 AI가 전문 뉴스 분석가처럼 행동하도록 안내합니다. 출처 인용, 객관성, 비판적 사고를 강조합니다. 스트리밍 응답은 생성되는 즉시 사용자에게 실시간 분석을 보여줌으로써 반응형 대화형 경험을 제공합니다.

Getting a response based on real-time data

4단계: 채팅 인터페이스 구축

NewsIQ와 상호작용할 사용자 인터페이스를 생성합니다. app/page.tsx 의 내용을 다음으로 대체하세요:

```typescript
"use client";

import { useChat } from "@ai-sdk/react";
import { useState } from "react";

export default function NewsResearchAssistant() {
  const { messages, sendMessage, status } = useChat();
  const [input, setInput] = useState("");

  const [exampleQueries] = useState([
    "🌍 기후 변화 정책의 최신 동향은 무엇인가요?",
    "💻 인공지능 규제 관련 뉴스를 검색해 주세요",
    "📊 다양한 매체에서 경제를 어떻게 다루고 있나요?",
    "⚡ 이번 주 인기 기술 뉴스는 무엇인가요?",
    "🔍 팩트체크: [특정 주장]이 정말 있었나요?",
  ]);

  return (
    <div className="flex flex-col h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50">
      {/* 헤더 */}
      <header className="bg-white shadow-md border-b border-gray-200">
        <div className="max-w-5xl mx-auto px-6 py-5">
          <div className="flex items-center gap-3">
            <div className="bg-gradient-to-br from-blue-600 to-indigo-600 w-12 h-12 rounded-xl flex items-center justify-center shadow-lg">
              <span className="text-2xl">📰</span>
            </div>
            <div>
              <h1 className="text-2xl font-bold text-gray-900">NewsIQ</h1>
              <p className="text-sm text-gray-600">
                AI 기반 뉴스 연구 및 분석
              </p>
            </div>
          </div>
        </div>
      </header>

      {/* 메인 채팅 영역 */}
      <div className="flex-1 overflow-hidden max-w-5xl w-full mx-auto px-6 py-6">
        <div className="h-full flex flex-col bg-white rounded-2xl shadow-xl border border-gray-200">
          {/* 메시지 컨테이너 */}
          <div className="flex-1 overflow-y-auto p-6 space-y-6">
            {messages.length === 0 ? (
              <div className="h-full flex flex-col items-center justify-center text-center px-4">
                {/* 환영 화면 */}
                <div className="bg-gradient-to-br from-blue-500 to-indigo-600 w-20 h-20 rounded-2xl flex items-center justify-center mb-6 shadow-lg">
                  <span className="text-4xl">📰</span>
                </div>
                <h2 className="text-3xl font-bold text-gray-900 mb-3">
                  NewsIQ에 오신 것을 환영합니다
                </h2>
                <p className="text-gray-600 mb-8 max-w-2xl text-lg">
                  뉴스 분석, 팩트체킹, 최신 정보 습득을 위한
인공지능 기반 연구 보조 도구입니다. 다양한 뉴스
출처 검색, 편향성 분석, 복잡한 기사 이해를
도울 수 있습니다.
</p>

                {/* 기능 요약 */}
                <div className="flex flex-wrap gap-3 justify-center mb-8">
                  <div className="px-4 py-2 bg-blue-100 text-blue-700 rounded-full text-sm font-medium">
                    🔍 다중 출처 연구
                  </div>
                  <div className="px-4 py-2 bg-purple-100 text-purple-700 rounded-full text-sm font-medium">
                    🎯 편향 감지
                  </div>
                  <div className="px-4 py-2 bg-green-100 text-green-700 rounded-full text-sm font-medium">
                    ✓ 팩트체킹
                  </div>
                  <div className="px-4 py-2 bg-orange-100 text-orange-700 rounded-full text-sm font-medium">
                    📊 트렌드 분석
                  </div>
                </div>

                {/* 예시 쿼리 */}
                <div className="w-full max-w-3xl">
                  <p className="text-sm font-semibold text-gray-700 mb-4">
                    다음과 같이 물어보세요:
                  </p>
                  <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
                    {exampleQueries.map((query, i) => (
                      <button
                        key={i}
                        onClick={() => {
                          setInput(query);
                        }}
                        className="p-4 text-left bg-gradient-to-br from-gray-50 to-gray-100 hover:from-blue-50 hover:to-indigo-50 rounded-xl border border-gray-200 hover:border-blue-300 transition-all duration-200 text-sm text-gray-700 hover:text-gray-900 shadow-sm hover:shadow-md"
                      >
                        {query}
                      </button>
                    ))}
                  </div>
                </div>
              </div>
            ) : (
              // 메시지 표시
              messages.map((m: any) => (
                <div
                  key={m.id}
                  className={`flex ${
                    m.role === "user" ? "justify-end" : "justify-start"
                  }`}
                >
                  <div
                    className={`max-w-[85%] rounded-2xl px-5 py-4 ${
                      m.role === "user"
                        ? "bg-gradient-to-br from-blue-600 to-indigo-600 text-white shadow-lg"
                        : "bg-gray-100 text-gray-900 border border-gray-200"
                    }`}
                  >
                    <div className="flex items-center gap-2 mb-2">
                      <span className="text-lg">
                        {m.role === "user" ? "👤" : "📰"}
                      </span>
                      <span className="text-xs font-semibold opacity-90">
                        {m.role === "user" ? "당신" : "NewsIQ"}
                      </span>
                    </div>
                    <div className="prose prose-sm max-w-none prose-headings:font-bold prose-h3:text-lg prose-h3:mt-4 prose-h3:mb-2 prose-p:my-2 prose-ul:my-2 prose-li:my-1 prose-a:text-blue-600 prose-a:underline prose-strong:font-semibold">
                      <div
                        className="whitespace-pre-wrap"
                        dangerouslySetInnerHTML={{
                          __html:
                            m.parts
                              ?.map((part: any) => {
                                if (part.type === "text") {
                                  let html = part.text
                                    // 헤더
                                    .replace(/### (.*?)$/gm, "<h3>$1</h3>")
                                    // 굵게
                                    .replace(
                                      /**(.*?)**/g,
                                      "<strong>$1</strong>"
                                    )
                                    // 링크
                                    .replace(
                                      /[(.*?)]((.*?))/g,
                                      '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
                                    );

                                  html = html.replace(
                                    /(^- .*$n?)+/gm,
                                    (match: string) => {
                                      const items = match
                                        .split("n")
                                        .filter((line: string) => line.trim())
                                        .map((line: string) =>
                                          line.replace(/^- /, "")
                                        )
                                        .map((item: any) => `<li>${item}</li>`)
                                        .join("");
                                      return `<ul>${items}</ul>`;
                                    }
                                  );

                                  // 단락
                                  html = html
                                    .split("nn")
                                    .map((para: string) => {
                                      if (
                                        para.trim() &&
                                        !para.startsWith("<")
                                      ) {
                                        return `<p>${para}</p>`;
                                      }
                                      return para;
                                    })
                                    .join("");

                                  return html;
                                }
                                return "";
                              })
                              .join("") || "",
                        }}
                      />
                    </div>
                  </div>
                </div>
              ))
            )}

            {/* 로딩 표시기 */}
            {(status === "submitted" || status === "streaming") && (
              <div className="flex justify-start">
                <div className="bg-gray-100 rounded-2xl px-5 py-4 border border-gray-200">
                  <div className="flex items-center gap-3">
                    <div className="flex space-x-2">
                      <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"></div>
                      <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce delay-100"></div>
                      <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce delay-200"></div>
                    </div>
                    <span className="text-sm text-gray-600">
                      뉴스 출처 조사 중...
                    </span>
                  </div>
                </div>
              </div>
            )}
          </div>

          {/* 입력 영역 */}
          <div className="border-t border-gray-200 p-5 bg-gray-50">
            <form
              onSubmit={(e) => {
                e.preventDefault();
                if (input.trim()) {
                  sendMessage({ text: input });
                  setInput("");
                }
              }}
              className="flex gap-3"
            >
              <input
                value={input}
                onChange={(e) => setInput(e.target.value)}
                placeholder="뉴스 주제 문의, 분석 요청, 주장 사실 확인..."
                className="flex-1 px-5 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white shadow-sm text-gray-900 placeholder-gray-600"
                disabled={status === "submitted" || status === "streaming"}
              />

              <button
                type="submit"
                disabled={
                  status === "submitted" ||
                  status === "streaming" ||
                  !input.trim()
                }
                className="px-8 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white rounded-xl hover:from-blue-700 hover:to-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 font-semibold shadow-lg hover:shadow-xl"
>
                {status === "submitted" || status === "streaming" ? (
                  <span className="flex items-center gap-2">
                    <svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
                      <circle
                        className="opacity-25"
                        cx="12"
                        cy="12"
                        r="10"
                        stroke="currentColor"
                        strokeWidth="4"
                        fill="none"
                      />
                      <path
                        className="opacity-75"
                        fill="currentColor"
                        d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
                      />
                    </svg>
                    분석 중
                  </span>
                ) : (
                  "연구"
                )}
              </button>
            </form>
            <div className="flex items-center justify-between mt-3">
              <p className="text-xs text-gray-500">
                Bright Data × Vercel AI SDK 제공
              </p>
              <div className="flex gap-2">
                <span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs font-medium">
                  ✓ 실시간
                </span>
                <span className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs font-medium">
                  🌐 글로벌 소스
                </span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

이 인터페이스는 Vercel AI SDK의 useChat 훅을 활용해 매력적인 대화형 경험을 제공합니다. 시작을 돕는 예시 쿼리가 포함된 환영 화면이 표시됩니다. 메인 채팅 영역은 스트리밍 지원 기능과 함께 메시지를 표시합니다. 그라데이션 배경과 부드러운 애니메이션으로 현대적이고 전문적인 디자인을 구현하기 위해 Tailwind CSS를 사용했습니다. 이 컴포넌트는 로딩 상태를 우아하게 처리하며 AI 처리 중 시각적 피드백을 제공합니다.

Screenshot of NewsIQ welcome screen with example queries

5단계: 루트 레이아웃 업데이트

적절한 메타데이터로 app/layout.tsx를 업데이트하여 애플리케이션 설정을 완료하세요:

import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'NewsIQ - AI 뉴스 연구 어시스턴트',
  description:
    'AI 기반 뉴스 연구, 분석 및 팩트체킹 도구. 다양한 출처에서 검색하고 편향을 감지하며 지능형 인사이트로 최신 정보를 확인하세요.',
  keywords: [
    'news',
    'AI',
    'research',
    'fact-checking',
    'bias detection',
    'news analysis',
  ],
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  )
}

이 레이아웃 구성은 적절한 SEO 메타데이터를 설정하고 애플리케이션 전반에 깔끔하고 전문적인 타이포그래피를 위해 Inter 글꼴을 로드합니다.

6단계: 애플리케이션 실행

애플리케이션을 실행하려면 다음 명령어를 사용하세요:

npm run dev

애플리케이션은 http://localhost에서 시작됩니다. NewsIQ의 기능을 테스트하려면 다음 예제 쿼리를 시도해 보세요:

사실 확인: 애플이 지난주에 신제품을 발표했나요?

AI는 쿼리에 따라 자동으로 적절한 도구를 사용합니다. 뉴스를 요청하면 Google 뉴스를 검색하고, 전체 기사를 요청하면 콘텐츠를 스크래핑합니다. 팩트체킹의 경우 여러 출처를 교차 참조합니다. AI가 정보를 처리하는 동안 결과가 실시간으로 스트리밍되는 것을 확인할 수 있습니다.

NewsIQ in action - searching, scraping, and analyzing news

7단계: Vercel에 배포하기

애플리케이션을 프로덕션에 배포하려면 먼저 코드를 GitHub에 푸시하세요:

git init
git add .
git commit -m "Initial commit: NewsIQ AI News Assistant"
git branch -M main
git remote add origin https://github.com/yourusername/ai-news-assistant.git
git push -u origin main

그런 다음 Vercel에 배포하세요:

  1. vercel. com으로 이동하여 GitHub로 로그인합니다
  2. “새 프로젝트 추가”를 클릭하고 저장소를 가져옵니다
  3. 환경 변수를 구성하세요:
  • BRIGHTDATA_API_KEY 추가
  • OPENAI_API_KEY 추가
  1. “배포”를 클릭하세요

애플리케이션은 2~3분 이내에 https://ai-news-assistant.vercel.app와 같은 URL에서 실행됩니다.

마무리

이 AI 뉴스 리서치 어시스턴트는 자동화가 뉴스 수집 및 분석을 어떻게 간소화하는지 보여줍니다. Bright Data를 AI 도구와 통합하는 다른 접근 방식에 관심이 있다면, MCP 서버를 사용한 웹 스크래핑 가이드를 살펴보세요. 뉴스 모니터링 워크플로우를 더욱 향상시키려면, 모든 뉴스 소스에 접근할 수 있는 웹 스크레이퍼 API와 같은 Bright Data 제품, 그리고 콘텐츠 집계 및 미디어 모니터링 팀을 위해 구축된 기타 데이터셋 및 자동화 도구를 고려해 보세요.

Bright Data 문서에서 더 많은 솔루션을 확인해 보세요.

무료 Bright Data 계정을 생성하여 자동화된 뉴스 리서치 워크플로우를 시작하세요.