このシステムは、世界中のソースからニュースを検索し、(ペイウォールをバイパスして)記事の全内容を抽出し、報道の偏りを検出し、実際のニュース・イベントに基づいたインテリジェントな分析を生成します。
学ぶ内容
- ウェブスクレイピング用の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はこのような課題を解決します。NewsIQは、AIを駆使した分析とエンタープライズ・グレードのウェブスクレイピングを組み合わせている。このシステムは、あらゆるニュースソースにアクセスし(ボット対策は回避)、複数の報道機関の報道を分析し、適切なソース帰属を伴うインテリジェントな洞察を提供します。
ニュース・リサーチ・アシスタントの構築
Bright DataとVercel AI SDKを使用して、完全なAIニュースリサーチアシスタントであるNewsIQを構築します。あらゆるソースからのニュースを処理し、会話型インターフェースを通じてインテリジェントな分析を提供するソリューションを構築します。
ステップ1:プロジェクトのセットアップ
まず、Next.jsの開発環境をセットアップします。プロジェクト用に新しいディレクトリを作成します:
npx create-next-app@latest ai-news-assistant
プロンプトが表示されたら、次のオプションを選択します:

プロジェクト・ディレクトリに移動し、必要なパッケージをインストールします:
プロジェクト・ディレクトリに移動し、必要なパッケージをインストールする。
npm install @brightdata/sdk ai zod @ai-sdk/openai
これらのパッケージは必要なものを全て提供してくれる:ウェブスクレイピングのためのBright Data SDK、インテリジェント分析のためのVercel AI SDK、タイプセーフなスキーマ検証のためのZod、LLMテキスト生成のためのOpenAI。

次に、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のウェブスクレイピングを活用するための3つのツールを定義することで、コアとなるニュースリサーチ機能を作成します。プロジェクトディレクトリに、lib/brightdata-tools.tsという新しいファイルを作成します:
import { tool, type Tool } from "AI";
import { z } from "zod";
import { bdclient } from "@brightdata/sdk";
タイプ 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>> = { {検索News: tool({NewsTools, Tool>)
searchNews: tool({
説明
"Googleニュースを使ってあらゆるトピックのニュース記事を検索。最近のニュース記事をタイトル、スニペット、ソース、公開日と共に返します。特定のトピックに関する現在のニュース報道を見つけるために使用する。"、
inputSchema: z.object({
query: z
.string()
.describe()
'ニュース検索クエリ(例えば、"人工知能"、"気候変動政策"、"技術収益")'
),
国: z
.string()
.length(2)
.optional()
.describe(
'ローカライズされたニュースのための2文字の国コード (例: "us"、"gb"、"de"、"fr"、"jp")'
),
}),
実行: 非同期 ({
query、
country、
}:{
query: string;
country?
}) => {
try {
const newsQuery = `${query} news`;
const result = await client.search(newsQuery,{検索エンジン: "google
searchEngine: "google"、
dataFormat:"markdown"、
format:"raw"、
country: country?.toLowerCase() || "us"、
});
結果を返します;
} catch (error) {
return `"${query}"のニュース検索エラー: ${String(error)}`;
}
},
}),
scrapeArticle: tool({
説明
"任意のURLからニュース記事の全内容をスクレイプする。ペイウォールやボット対策を回避し、クリーンなマークダウン形式で記事テキストを完全に返します。searchNewsで見つけた後、記事の全文を読むのに使用する。"、
inputSchema: z.object({
url:z.string().url().describe("スクレイピングするニュース記事のURL")、
country: z
.string()
.length(2)
.optional()
.describe("プロキシ位置の2文字の国コード")、
}),
execute: 非同期 ({ url, country }: { url: 文字列; country?: 文字列 }) => { 以下のようになります。
try {
const result = await client.scrape(url, {
dataFormat:"markdown"、
format:"raw"、
country: country?.toLowerCase()、
});
結果を返します;
} catch (error) {
return `${url}で記事のスクレイピングにエラーが発生しました:文字列(エラー)}`;
}
},
}),
searchWeb: tool({
説明:
"Google、Bing、Yandexを使った一般的なウェブ検索。背景調査、事実確認、ニュース記事以外の追加的な文脈を見つけるために使用する。"、
inputSchema: z.object({
query: z
.string()
.describe()
"背景情報や事実確認のための検索クエリ"
),
searchEngine: z
.enum(["google", "bing", "yandex"])
.optional()
.default("google")
.describe("使用する検索エンジン")、
国: z
.string()
.length(2)
.optional()
.describe("ローカライズされた結果のための2文字の国コード")、
}),
execute: 非同期 ({
query、
searchEngine = "google"、
country、
}:{
query: 文字列;
searchEngine: "google" | "bing" | "yandex";
country?
}) => {
try {
const result = await client.search(クエリ, { {検索エンジン?
searchEngine、
dataFormat:"markdown"、
format:"raw"、
country: country?.toLowerCase()、
});
結果を返します;
} catch (error) {
return `"${query}" のウェブ検索エラー: ${String(error)}`;
}
},
}),
};
for (tools 内の const toolName) {
if (config.excludeTools?.includes(toolNameasNewsTools)){。
tools[toolNameをNewsToolsとして]を削除する;
}
}
return tools;
};
このコードでは、Vercel AI SDKのツール・インターフェースを使用して、3つの重要なツールを定義している。searchNewsツールはGoogle Newsに最近の記事を問い合わせる。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: リクエスト) { { { messages } = await req: リクエスト。
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: `あなたはNewsIQ、高度なAIニュース研究アシスタントです。あなたの役割は、ユーザーの情報収集、ニュース報道の分析、複雑な時事問題の理解をサポートすることです。
**主な機能
1.**ニュース発見**:searchNewsを使用して、あらゆるトピックに関する最新のニュースを検索します。
2.**ディープ・リーディング完全なコンテキストを提供するためにscrapeArticleを使用して完全な記事をスクレイピング
3.**事実確認**:searchWebを使って主張を検証し、追加ソースを見つける。
4.**バイアス分析**:複数の情報源の報道を比較し、潜在的な偏りを特定する。
5.**トレンド分析**:新たなストーリーを特定し、トピックがどのように変化するかを追跡
**ガイドライン
- 出典は必ず出版社名と日付を明記すること。
- 偏りを分析する場合は、客観的であること、そして証拠を提示すること
- 物議を醸す話題については、複数の視点を提示すること
- 事実と分析を明確に区別する
- 情報が古い場合は、発行日を明記する。
- 記事をスクラップする場合は、分析の前に重要な点を要約する。
- ファクトチェックには、複数の独立した情報源を使用する。
**回答フォーマット:***
- 明確で直接的な回答から始める
- 文脈に沿って出典を引用する
- 複数のソースを箇条書きにする。
- 簡単な分析や洞察で終わる
- 特定の側面についてさらに調べるよう申し出る
覚えておいてください:あなたのゴールは、ユーザーがより良い情報を得た、批判的な考え方をする人になるのを助けることです、
});
return result.toUIMessageStreamResponse();
}
このAPIルートは、ニュースリサーチツールとOpenAIのGPT-4を接続するためのストリーミングエンドポイントを作成します。包括的なシステムプロンプトは、AIがプロのニュースアナリストとして行動するようガイドします。ソースの引用、客観性、批判的思考を強調しています。ストリーミング応答は、生成されたリアルタイムでユーザーに分析を示し、応答性の高い会話体験を作り出します。

ステップ4:チャットインターフェースの構築
NewsIQと対話するためのユーザーインターフェースを作成します。app/page.tsxの内容を以下のように置き換える:
タイプスクリプト
"use client";
import { useChat } from "@ai-sdk/react";
インポート { useState } from "react";
エクスポートデフォルト関数 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">とします。
{ヘッダー */}
<ヘッダー 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> </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-gray-200"> <div className="h-full flex-col bg-white rounded-2xl shadow-xl border-gray-200">
{メッセージコンテナ */}
<div className="flex-1 overflow-y-auto p-6 space-y-6">
{messages.length === 0 ?
<div className="h-full flex-col items-center justify-center text-center px-4"> <div className="h-full flex-col items-center justify-center text-center px-4"> {メッセージの長さ === 0 ?
{ようこそ画面 */}
<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"> </h2
NewsIQへようこそ
</h2>
<p className="text-gray-600 mb-8 max-w-2xl text-lg">
ニュース分析、ファクトチェック、情報収集のためのAIリサーチアシスタント、
ニュース分析、ファクトチェック、情報収集のためのAIリサーチアシスタントです。ニュース
偏りを分析し、複雑なストーリーを理解する手助けをします。
複雑なストーリーを理解する手助けをします。
</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> <div className="px-4 py-2 bg-purple-100 text-purple-700 rounded-full text-sm font-medium"> 🏶ソース調査 </div> </div
🎯 バイアス検出
</div>
<div className="px-4 py-2 bg-green-100 text-green-700 rounded-full text-sm font-medium"> ✓ ファクトチェック </div> </div
ファクトチェック
</div
<div className="px-4 py-2 bg-orange-100 text-orange-700 rounded-full text-sm font-medium"> ✓ 事実確認 </div
トレンド分析
</div>
</div>
{クエリ例 */}
<div className="w-full max-w-3xl">
<p className="text-sm font-semibold text-gray-700 mb-4">
と聞いてみてください:
</p>
<div className="グリッド 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-gray-200 hover:border-blue-300 transition-all duration-200 text-sm text-gray-700 hover:text-gray-900 shadow-sm hover:shadow-md" "
>
{クエリ}
</ボタン
))}
</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-lg">
<span className="text-xs font-semibold opacity-90">
{m.role === "user" ?"You" : "NewsIQ"}.
</span> </span
</div> </span
<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) => { もし (part.type === "text")
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.replace(/^- /, "")
)
.map((item: any) => `<li>${アイテム}</li>`)
.join("");
return `<ul>${items}</ul>`;
}
);
// パラグラフ
html = html
.split("n")
.map((para: string) => { { { .map((para: string) => { .map((para: string) => {
if (
para.trim() &&
!.para.startsWith("<")
){
return `<p>${para}</p>`;
}
return para;
})
.join("");
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-gray-200"> <div className="flex justify-start">となります。
<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> </span
</div> </span
</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="Ask about any news topic, request analysis, or fact-check a claim..."
className="flex-1 px-5 py-3 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={
ステータス === "送信" ||
ステータス === "ストリーミング" ||
!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">
<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> : (
) : (
"調査"
)}
</ボタン
</form
<div className="flex items-center justify-between mt-3">
<p className="text-xs text-gray-500">
Powered by ブライトデータ × 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
グローバルソース
</span
</div>
</div>
</div> </div
</div> </div
</div> </div
</div>
);
}
このインターフェイスは、Vercel AI SDKのuseChatフックを使って魅力的な会話体験を作り出します。ウェルカムスクリーンには、クエリの例が表示されます。メインチャットエリアは、ストリーミングサポートでメッセージを表示します。デザインは、グラデーションの背景とスムーズなアニメーションでモダンでプロフェッショナルな外観のために Tailwind CSS を使用しています。コンポーネントは、ロード状態を優雅に処理し、AI 処理中に視覚的なフィードバックを提供します。

ステップ5:ルートレイアウトの更新
適切なメタデータでapp/layout.tsxを更新することで、アプリケーションのセットアップを完了します:
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
インポート './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata:メタデータ = {
title: 'NewsIQ - AIニュースリサーチアシスタント'、
description:
AIを搭載したニュースリサーチ、分析、ファクトチェックツール。ソースを横断して検索し、バイアスを検出し、インテリジェントな洞察力で情報を提供します、
keywords: [
'news'、
'AI'、
'research'、
'fact-checking'、
偏見検出
ニュース分析
],
}
export default function RootLayout({
children、
}:{
children:React.ReactNode
}) {
return (
<html lang="ja"> <body
<body className={inter.className}>{children}</body>。
</html>
)
}
このレイアウト構成は、適切なSEOメタデータを設定し、アプリケーション全体を通してクリーンでプロフェッショナルなタイポグラフィのためにInterフォントをロードします。
ステップ6:アプリケーションの実行
アプリケーションを実行するには、次のコマンドを使用します:
npm run dev
アプリケーションはhttp://localhost で起動します。NewsIQの機能をテストするには、次のクエリー例を試してください:
ファクトチェックアップルは先週新製品を発表しましたか?
AIはクエリに基づいて適切なツールを自動的に使用する。ニュースを要求すると、グーグルニュースを検索する。記事の全文を要求すると、コンテンツをスクレイピングする。ファクトチェックでは、複数のソースを相互参照する。AIが情報を処理するにつれて、リアルタイムで結果がストリーミングされるのがわかります。

ステップ7:Vercelへのデプロイ
アプリケーションを本番環境にデプロイするには、まずコードをGitHubにプッシュする:
git init
git add .
git commit -m "初期コミット:NewsIQ AI ニュースアシスタント"
git branch -M main
git remote add origin https://github.com/yourusername/ai-news-assistant.git
git push -u origin main
次に Vercel にデプロイします:
- vercel.comにアクセスし、GitHubでサインインします。
- Add New Project “をクリックし、リポジトリをインポートします。
- 環境変数を設定します:
BRIGHTDATA_API_KEYを追加します。OPENAI_API_KEYを追加する。
- Deploy “をクリックします。
あなたのアプリケーションは、https://ai-news-assistant.vercel.app のようなURLで2-3分で公開されます。

最終的な感想
このAIニュース・リサーチ・アシスタントは、自動化がいかにニュース収集と分析を効率化するかを示している。Bright DataとAIツールを統合する別のアプローチに興味がある場合は、MCPサーバーを使ったウェブスクレイピングに関するガイドをご覧ください。ニュースモニタリングワークフローをさらに強化するには、あらゆるニュースソースにアクセスするためのウェブスクレイパーAPIや、コンテンツアグリゲーションやメディアモニタリングチーム向けに構築されたその他のデータセットや自動化ツールなど、Bright Data製品をご検討ください。
Bright Dataのドキュメントでより多くのソリューションをご覧ください。
無料の Bright Data アカウントを作成して、自動ニュースリサーチワークフローの使用を開始しましょう。