Skip to content

woals00/Struggle

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

409 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Struggle

https://struggle-psi.vercel.app/

필터 버블과 편향된 정보 소비 문제를 완화하기 위해, 하나의 이슈를 여러 시각에서 보여주는 뉴스 플랫폼

Struggle은 하나의 사회 이슈를 다양한 언론 관점에서 비교할 수 있도록 설계된 뉴스 플랫폼입니다. 자극적이거나 단일 관점의 정보 소비를 완화하고, 사용자가 스스로 판단할 수 있는 균형 잡힌 정보 탐색 경험을 제공하는 것을 목표로 합니다.

Struggle은 하나의 사회적 이슈를 둘러싼 다양한 언론사의 기사와 사설을 자동으로 수집·정리하여 서로 다른 관점을 한눈에 비교할 수 있도록 돕는 뉴스 플랫폼입니다.
기존 뉴스 소비가 개별 기사 중심으로 이루어지며, 추천시스템과 맞물려 특정 시각에 편향되기 쉬운 문제의식에서 출발해 이슈 단위로 콘텐츠를 묶고 언론사 성향을 함께 보여주는 구조를 설계했습니다.
이를 통해 사용자가 특정 주장에 머무르지 않고, 같은 사건을 바라보는 다양한 시각과 논점을 자연스럽게 탐색할 수 있도록 돕는 것을 목표로 합니다.

Why Struggle?

Struggle은 정치·사회 이슈에 대해 하나의 관점만 반복적으로 노출되는 기존 추천 시스템의 한계에서 출발한 프로젝트입니다. 기존 추천 시스템은 사용자 선호를 중심으로 콘텐츠를 제공해 편의성과 만족도를 높이지만, 정치·사회 영역에서 이러한 구조는 사용자가 이미 가지고 있는 관점을 강화하고, 다른 시각을 접할 기회를 점점 줄이는 방향으로 작동하기도 합니다. 특히 사람은 누구나 자신이 옳다고 믿는 생각과 반대되는 의견을 마주하게 되면 심리적으로 불편하며, 이를 일부러 찾아 나서는 과정은 그 자체로 쉽지 않습니다.

Struggle은 이러한 물리적·심리적 장벽을 사용자가 직접 넘도록 요구하기보다, 하나의 이슈에 대한 다양한 언론사의 기사와 사설을 한 화면에 정리해 제시하는 방식으로 접근했습니다. 사용자가 반대 입장을 적극적으로 선택하지 않더라도, 한곳에 모여 있는 다양한 시각을 자연스럽게 마주할 수 있도록 설계함으로써, 적어도 살펴보는 기회만큼은 제공하고자 했습니다. 서로 다른 관점을 이해하려는 이러한 시도 자체를 한 개인의 고군분투(Struggle) 로 인식했으며, 이를 통해 사회가 타인을 이해하는 문화로 나아가는 데 기여할 수 있기를 바라는 마음으로 이 프로젝트를 기획했습니다.

Key Features

  • 이슈 단위 기사·사설 자동 수집 및 클러스터링
  • 진보/중도/보수 관점별 기사 나란히 비교 UI
  • 이슈별 핵심 쟁점 요약 및 대표 이슈 제목 제공
  • 언론사 성향 및 기본 정보 제공 (보도 맥락 이해 지원)
  • 이슈 중심의 뉴스 탐색 구조로 사용자 주도적 정보 소비 지원

메인 페이지 (이슈 허브)

메인 페이지1

메인 페이지2

메인 페이지는 이슈를 한 번에 파악할 수 있도록 설계된 화면입니다.
상단에는 오늘의 주요 이슈 키워드가 노출되며, 클릭 시 해당 이슈 상세 페이지로 바로 이동합니다.
정렬은 기사 수/이슈 순위를 기준으로 전환할 수 있어, 기사량이 많은 이슈 또는 상위 랭킹 이슈를 빠르게 훑을 수 있습니다.
메인 카드 영역에서는 각 이슈의 대표 기사(진보/중도/보수)를 병렬로 배치해, 동일 사건을 서로 다른 시각으로 비교할 수 있게 했습니다.
또한 이슈 카드 내 기사 수·대표 이미지·언론사 정보를 함께 제공해 이슈의 규모와 맥락을 동시에 파악할 수 있습니다.

이슈 상세 페이지

이슈 상세 페이지

이슈 상세 페이지는 특정 이슈를 깊게 이해할 수 있도록 관점별 기사 비교를 제공하는 화면입니다.
상단 영역에서는 이슈 제목을 제공합니다. 아이콘의 설명과 마찬가지로 이슈에서 모르는 단어나 맥락을 파악하기 위해 드래그 시 나무위키로 연결하는 동작을 구현해두었습니다. 하단 영역에는 진보/중도/보수 컬럼별로 관련 기사를 나열해, 동일 이슈에 대한 보도 프레이밍 차이를 한눈에 비교할 수 있습니다.

사설 페이지 (주제별/날짜별)

사설 페이지 주제별

사설 페이지 날짜별

사설 페이지는 동일 이슈를 다루는 사설을 주제별 클러스터로 묶어 보여주며,
좌측에는 주제 카드 리스트, 우측에는 선택된 주제의 성향별(진보/중도/보수) 사설을 컬럼으로 정렬해 제공합니다.
주제 카드에는 사설 건수와 시작일을 함께 표시해, 이슈가 얼마나 오래/넓게 다뤄졌는지를 빠르게 파악할 수 있습니다.
또한 “날짜별” 탭에서는 최신 날짜부터 선택해 하루 단위 사설 묶음을 확인할 수 있어, 특정 날짜의 여론 흐름을 집중적으로 살펴보는 데 적합합니다.

언론사 메인 페이지

언론사 메인 페이지

언론사 메인 페이지는 국내 주요 언론사를 진보/중도/보수 성향별로 분류해 탐색할 수 있도록 구성했습니다.
카드형 리스트로 제공되어 찾고자 하는 언론사를 빠르게 확인할 수 있으며,
언론사 상세 페이지로 연결되어 심층 정보 확인이 가능하게 합니다.

언론사 상세 페이지

언론사 상세 페이지 1

언론사 상세 페이지 2

언론사 상세 페이지는 한 매체의 정체성·맥락·보도 성향을 한 화면에서 이해할 수 있도록 설계되었습니다.
상단에는 공식 사이트 및 SNS 링크가 제공되어 즉시 외부 채널로 이동할 수 있고,
중앙에는 설립/본사/발행 형태 등 핵심 프로필이 정리되어 있습니다.
또한 소유구조·지배구조 시각화로 이해관계·구조적 이해를 돕고, 하단에는 최근 사설 목록을 제공해
해당 매체의 논조와 관심 이슈를 실제 기사로 검증할 수 있습니다.

How AI is Used

Struggle에서는 AI를 단순 기능이 아닌
문제 해결과 개발 전반을 함께하는 도구로 활용합니다.

개발 과정에서의 AI Agent 활용

  • ChatGPT(Codex CLI), Cursor를 활용
  • 설계 초안 검토
  • 반복적인 데이터 처리·스크립트 코드 자동 생성
  • 실험 코드 리팩토링 및 파이프라인 개선
  • MCP 연결하여 개발환경 효율화

서비스 기능 관점

  • 기사 임베딩 기반 이슈 클러스터링 실험
  • LLM 기반 이슈 요약 및 대표 문장 생성
  • src/scripts/grouping/grouping_title.py에서 기사 제목 임베딩(DBSCAN 클러스터링) 후, 각 클러스터의 대표 제목을 gpt-4o-mini로 생성해 representative_title로 업데이트

System Overview

  • 뉴스 데이터를 수집해 이슈 단위로 가공·구조화하는 파이프라인으로 구성
  • 크롤링, 이슈 생성, 기사 매핑, 요약 과정을 스크립트 기반으로 자동화
  • 가공된 데이터를 API로 제공하고, 프론트엔드에서는 이를 기반으로 비교 중심 UI 구성
  • 캐시 및 스케줄링을 통해 데이터 최신성과 서버 부하를 함께 고려한 갱신 구조 설계

Architecture Overview

Struggle은 수집 → 가공 → 저장 → 제공 → UI 흐름으로 동작합니다.

  1. 수집: 언론사/사설 데이터를 스크립트로 수집하고 URL·이미지·날짜를 정규화합니다.
  2. 가공: 이슈 단위로 기사들을 묶고, 사설은 제목 임베딩 기반 클러스터링으로 주제를 묶습니다.
  3. 저장: 가공 결과는 MongoDB에 보관됩니다.
  4. 제공: src/server/api.js의 Vercel 서버리스 API가 캐시와 함께 뷰에 필요한 데이터만 응답합니다.
  5. UI: React가 API를 호출해 관점 비교 중심 화면을 구성합니다.

Data Model (요약)

실제 프로젝트에서 사용하는 컬렉션 기준으로 정리합니다.

  • Article (이슈 단위 문서)
    • keyword, category, key_date
    • articles[]: { title, outlet, url, source_url, image_url, date }
    • 메인/상세 페이지는 이 구조를 그대로 사용합니다.
  • Editorial
    • title, source_url, image_url, outlet, date
    • cluster_id, representative_title
    • 사설 페이지의 주제별/날짜별 뷰에 사용됩니다.
  • Outlet
    • id, name, type, url, bias.category, updatedAt
    • 언론사 메인/상세 페이지에 사용됩니다.
  • WeeklyIssuesByDate
    • date, byCategory
    • “오늘의 주요 이슈” 키워드 목록에 사용됩니다.

Pipeline / Jobs

  • 수집 파이프라인
    • src/scripts/ 하위 스크립트에서 기사/사설 데이터를 수집하고,
      URL/이미지/날짜 형식을 통일해 MongoDB에 저장합니다.
  • 사설 주제 클러스터링
    • src/scripts/grouping/grouping_title.py가 사설 제목 임베딩(DBSCAN)으로
      cluster_id를 생성하고, gpt-4o-minirepresentative_title을 업데이트합니다.
  • 프론트 신선도 판단
    • 메인/사설 화면은 10:00, 18:00 기준의 갱신 시각을 기준으로
      캐시된 데이터의 신선도를 판단합니다.

API 요약

프론트에서 실제 호출하는 엔드포인트만 정리합니다.

  • GET /api/issues/summary
    • 최근 이슈 요약 목록(대표 기사, 성향별 기사 수, 요약)을 반환합니다.
    • 최근 5일치 날짜 범위를 기준으로 제한됩니다.
  • GET /api/issues/:keyword
    • 특정 이슈의 상세 기사 목록과 분석 데이터를 반환합니다.
  • GET /api/daily-issues/by-category
    • 최신 날짜의 카테고리별 주요 이슈 키워드와 집계 값을 반환합니다.
  • GET /api/editorials/compact
    • 사설 요약 데이터(클러스터/대표 제목 포함)를 반환합니다.
  • GET /api/editorials
    • 사설 원문 메타(전체) 데이터를 반환합니다.
  • GET /api/outlets/compact
    • 언론사 기본 정보(목록용)를 반환합니다.

컴포넌트별 API 사용 상세

아래는 실제 컴포넌트에서 호출하는 API와 사용 목적을 정리한 목록입니다.

MainIssueList (src/components/MainIssueList.jsx)

  • GET /api/issues/summary
    • 메인 이슈 목록, 대표 기사, 성향별 기사 수를 구성합니다.
    • 응답을 cachedIssueSummaries에 저장하고 신선도 기준으로 재사용합니다.
  • GET /api/daily-issues/by-category
    • “오늘의 주요 이슈” 키워드 목록을 생성합니다.
    • 카테고리별 상위 이슈를 정렬해 노출합니다.
  • GET /api/outlets/compact
    • 대표 기사에 언론사 이름/성향을 매핑합니다.
    • cachedOutlets 캐시를 사용합니다.

IssueDetailView (src/components/IssueDetailView.jsx)

  • GET /api/issues/:keyword
    • 특정 이슈의 상세 기사 목록과 분석(요약/입장별 논리 등)을 불러옵니다.
    • 404인 경우 fallback 데이터를 구성해 화면을 유지합니다.
    • 응답은 cachedIssueDetail:* 키로 캐시합니다.

Editorial (src/components/Editorial.jsx)

  • GET /api/editorials/compact
    • 주제별/날짜별 사설 데이터를 불러옵니다.
    • 10:00/18:00 갱신 주기에 맞춘 신선도 체크로 캐시를 무효화합니다.
  • GET /api/outlets/compact
    • 사설의 언론사/성향 매핑에 사용합니다.
    • cachedOutlets 캐시를 사용합니다.

Outlet (src/components/Outlet.jsx)

  • GET /api/outlets/compact
    • 언론사 목록을 로드하고 defaultOutlets 기준으로 필터/정규화합니다.
    • 캐시(cachedOutlets)를 먼저 확인합니다.

OutletDetail (src/components/OutletDetail.jsx)

  • GET /api/outlets/compact
    • 상세 페이지의 최신 언론사 정보(성향/타입/URL)를 병합합니다.
    • 캐시(cachedOutlets)를 사용합니다.
  • GET /api/editorials
    • 해당 언론사의 최근 사설을 필터링해 노출합니다.
    • outlet 필드가 없을 때는 URL에서 outlet code를 추출해 매칭합니다.

Current Status & Next Steps

현재 Struggle은 개인 프로젝트 형태의 프로토타입으로 운영 중이며,
지속적으로 구조와 기능을 개선하고 있습니다. 저작권 문제로 상업적인 프로젝트 발전이 불가하기에 공익적인 목적의 프로젝트로 지속할 계획입니다.

Links