본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 06. 10. 13:20

【Python】『뉴스 요약 사이트』를 만들어 보았다 (실전편) ⑲

요약

Windsurf를 활용하여 Python Flask 기반의 뉴스 요약 웹 앱에 Web Speech API 읽어주기 기능을 구현하는 과정을 다룹니다. Cursor에서 Windsurf로 프로젝트를 인계하며 에이전트의 프로젝트 분석 및 단계별 구현 계획 수립 능력을 확인합니다.

핵심 포인트

  • Windsurf를 이용한 기존 Cursor 프로젝트의 연속 개발
  • Gemini API와 Structured Outputs를 활용한 뉴스 요약 자동화
  • Web Speech API를 이용한 뉴스 읽어주기 기능 구현 계획
  • AI 에이전트의 프로젝트 구조 파악 및 단계별 실행 능력

이번에는 드디어 Windsurf를 사용하여 step 5를 진행해 보겠습니다!

입력할 프롬프트는 다음과 같이 작성했습니다.

빠진 부분이나 누락된 내용이 있을까 걱정되지만, 가능한 한 지금까지의 흐름을 Windsurf 선배님이 읽어낼 수 있도록 기재했습니다.

*********************

이 프로젝트는 Python (Flask)로 제작된 뉴스 다이제스트 Web 앱입니다.

Cursor로 개발하던 것을 Windsurf로 인계하여 다음 단계를 진행하고 싶습니다.

【프로젝트 개요】

매일 아침 6시에 자동으로 AI 뉴스를 취득·요약하는 웹사이트.

RSS 피드 (ITmedia・Yahoo! 뉴스)에서 기사를 취득하고,

Gemini API (gemini-2.5-flash)를 사용하여 summary와 terms를 Structured Outputs로 생성.

날짜별로 JSON 파일로 저장하는 구성.

【현재 완료 상황】

  • Step 1 완료: Flask로 메인 페이지 표시 (더미 JSON으로 레이아웃 확인 완료)
  • Step 2 완료: feedparser로 RSS 취득·필터링·JSON 저장·90일 자동 삭제
  • Step 3 완료: Gemini + Structured Outputs로 summary와 terms를 JSON 생성
  • Step 4 완료: 로그가 포함된 일괄 실행 스크립트·Windows 작업 스케줄러로 매일 아침 6시 자동 실행

【환경】

  • Python 3.14.2
  • 가상 환경은 사용하지 않음 (글로벌 환경)
  • OS: Windows
  • 실행은 python run.py
  • 배치(Batch)와 웹(Web)은 분리됨 (배치에서 JSON 생성, Flask에서 데이터 표시)

【개발 방침】

  • MVP를 우선함. 우선 작동하는 것을 만들고, 잘 되면 확장한다
  • 과도한 구현·앞서 나가는 설계는 불필요
  • 주석은 일본어로 작성
  • Gemini API는 무료 범위이므로 요청 수는 최소한으로 억제

【이번에 하고 싶은 것: 읽어주기 기능 구현】

static/js/speech.js에 Web Speech API를 사용한 읽어주기 기능을 구현하고 싶다.

  • 「읽어주기」 버튼과 「멈춤」 버튼을 UI에 추가
  • 읽기 대상은 summary 텍스트
  • 언어는 ja-JP, 속도는 기본값 (1.0)

먼저 현재의 프로젝트 구성을 확인한 후, 구현 계획을 제시해 주세요.

갑자기 코드를 작성하지 말고, 계획을 먼저 보여주세요.

*********************

먼저, static/js/ (읽어주기 기능의 JavaScript를 둘 장소)가 존재하지 않는다는 것을 자동으로 파악한 것 같습니다.

Cursor에서도 비슷한 일을 할 수 있지만, Windsurf는 아무것도 지정하지 않아도 처음에 프로젝트 전체를 읽으러 가는 것이 기본 동작이라고 합니다.

확인 완료 후, 다음과 같은 구현 계획이 제시되었습니다.

【Step 1: 디렉토리·파일 생성】

  • static/js/ 디렉토리 생성
  • static/js/speech.js를 신규 생성

【Step 2: speech.js의 구현 내용】

  • speechSynthesis API 사용
  • 읽기 시작·정지 함수를 구현 (ja-JP・속도 기본값 1.0)
  • 중복 읽기 방지를 위한 상태 관리

【Step 3: HTML 템플릿 수정】

  • templates/index.html의 각 기사 카드에 「읽어주기」 「멈춤」 버튼을 추가
  • data-summary 속성으로 summary 텍스트를 전달하고, 클릭 이벤트로 함수를 호출

【Step 4: base.html 수정】

  • </body> 앞에 speech.js 로드 태그를 추가

【Step 5: CSS 조정 (필요에 따라)】

  • 기존 디자인에 맞춘 버튼 스타일 조정

『이 계획대로 진행해도 될까요?』라고 물어왔기에 「OK!」라고 답했더니, 한 번 Permission denied 에러가 발생했습니다.

Windsurf가 폴더를 자동으로 만들려고 하다가 권한 문제로 거부당한 형태입니다.

터미널에서 수동으로 static/js/ 폴더를 만듭니다.

cd C:\dev\news-digest
mkdir static\js

그 후 「계속해 주세요」라고 말했더니, 다음 3개의 파일을 한꺼번에 완성해 주었습니다.

  • static/js/speech.js

:Web Speech API를 이용한 읽기, 정지 및 상태 관리 -
templates/index.html

:각 기사 카드에 「읽기」 「정지」 버튼 추가 -
templates/base.html

:speech.js 로드 추가

이제 python run.py

로 실행하여 동작을 확인하기만 하면 됩니다.

실제로 동작 확인을 실시해 본 결과…….

확실히 읽기 버튼이 만들어졌네요!!

하지만, 각 기사 카드의 『읽기』와 『정지』는 미묘할지도 모르겠습니다.

바쁠 때는 버튼 하나로 그날의 뉴스를 전부 읽어주었으면 좋겠고.

그리고, 실제로 제대로 읽어주기는 했지만, 속도가 너무 느렸습니다.

이를 개선 사항으로 알립니다.

【입력한 프롬프트】

*****************************

2가지 수정을 부탁드립니다.

① 읽기 버튼의 사양 변경

현재는 각 기사 카드에 「읽기」 「정지」 버튼이 달려 있습니다만, 페이지 상단에 버튼을 하나만 배치하여, 그날의 모든 기사 요약(summary)을 위에서부터 순서대로 이어서 읽어주는 형태로 바꿔 주세요.

② 읽기 속도 변경

현재 rate = 1.0입니다만, 1.5로 변경해 주세요.

*****************************

눈 깜짝할 사이에 변경해 주었습니다.

변경 내용은 다음과 같습니다.

  • 각 기사 카드에서 버튼 삭제
  • 페이지 상단에 「모든 기사 읽기」 「정지」 버튼 추가
  • 모든 기사의 요약(summary)을 위에서부터 순서대로 연속 읽기하는 기능 구현
  • rate를 1.5로 변경

참고로, 린트 에러(Lint error)가 발생했습니다만, Jinja2 템플릿 구문을 JavaScript 린터(Linter)가 오해한 것일 뿐, Flask 앱으로서는 올바르게 동작한다고 합니다.

【린트 에러란】

프로그램을 실행하기 전에 "이 코드에 이상한 작성 방식은 없는가?"를 자동으로 체크해 주는 기능으로, Windsurf가 백그라운드에서 항상 작동시키고 있습니다.

이번 경우, JavaScript 체크 도구(린터)가 {{ article.summary }}라는 Flask 특유의 작성 방식을 보고 "이것은 JavaScript로서 이상하다"라고 경고를 내보내고 있었습니다.

하지만, 『모든 기사 읽기』를 클릭해도 읽어주지 않게 되어 버렸다…….

그래서 그것을 그대로 전달해 보았습니다.

그러자 다음과 같이 결과가 돌아왔고, 무사히 해결.

원인은 Jinja2 템플릿에서 JavaScript로 데이터를 전달하는 방식에 있었던 것 같습니다.

수정 내용은 다음과 같습니다.

- onclick 속성 대신 addEventListener 사용
- JavaScript 배열을 직접 생성하여 summary 데이터를 전달하는 방식으로 변경
- 기사가 있는 경우에만 버튼을 표시하도록 수정

【메모】

onclick은 HTML 태그 안에 직접 "클릭하면 이것을 실행해"라고 쓰는 오래된 방식.
addEventListener는 JavaScript 파일 쪽에서 "이 버튼이 클릭되면 ~"라고 별도로 쓰는 요즘 방식.

HTML과 JavaScript를 분리할 수 있으므로, Jinja2의 {{ }}가 섞이지 않게 된다.

Flask의 HTML 템플릿 안에 {{ article.summary }}라고 쓰면, JavaScript 린터가 "이게 뭐야?"라며 혼란스러워할 수 있습니다. 그래서 HTML이 로드될 때 Python의 데이터를 JavaScript 배열로서 미리 변환해 두는 방식으로 바꾸어 준 것 같습니다.

python run.py로 재시작하고 브라우저를 새로고침했더니, 무사히 읽기 기능이 작동했습니다!!

마무리를 하여, MVP를 완성하겠습니다!!

AI 자동 생성 콘텐츠

본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0