매일 뉴스 자동수집 및 활동지 변환 파이프라인
입시 뉴스 → 수업 활동지 자동화 파이프라인 구축기

개요
베리타스알파 입시 뉴스를 자동 수집·분류하고, AI가 교사용 요약을 생성한 뒤,
HTML + DOCX 형식의 수업 활동지로 변환하는 파이프라인을 구축했다.
이 글은 기술 스택, 구현 과정, 트러블슈팅을 기록한다.
1. 파이프라인 전체 흐름
[베리타스알파 크롤링]
↓
[링크 필터링 + 제목 추출]
↓
[Gemini API → 교사용 요약 생성]
↓
[결과 .txt 저장]
↓
[HTML 활동지 생성] + [DOCX 활동지 생성]
2. 크롤링 구현
requests + BeautifulSoup 조합으로 구현했다.
핵심 포인트는 링크 필터다. find_all('a')로 전부 긁으면
네비게이션, 광고 링크까지 포함된다.
사이트별로 실제 기사 URL 패턴을 지정해서 필터링했다.
target_sites = [
{
"name": "베리타스알파(입시뉴스)",
"url": "http://www.veritas-a.com/news/articleList.html?sc_section_code=S1N1",
"link_filter": "/news/articleView" # 기사 URL만 통과
},
{
"name": "교육부 블로그(정책/정보)",
"url": "https://if-blog.tistory.com/",
"link_filter": "/entry/" # 포스트 URL만 통과
}
]
링크 보정은 urllib.parse.urljoin을 사용한다.site['url'].rstrip('/') + link 방식은 쿼리스트링 포함 베이스 URL에서
오작동하기 때문이다.
from urllib.parse import urlparse, urljoin
def get_base_url(url):
parsed = urlparse(url)
return f"{parsed.scheme}://{parsed.netloc}"
link = urljoin(get_base_url(site['url']), raw_link)
3. Gemini API 연동 및 Rate Limit 처리
문제 상황
gemini-2.0-flash free tier 사용 시 첫 번째 요청에서 즉시 429 발생.
429 RESOURCE_EXHAUSTED
Quota exceeded: GenerateRequestsPerDayPerProjectPerModel-FreeTier
해결 전략: 모델 폴백 + 자동 재시도
MODELS = ['gemini-1.5-flash', 'gemini-1.5-flash-8b', 'gemini-1.0-pro']
def call_gemini_with_retry(prompt, max_retries=3):
for model in MODELS:
for attempt in range(max_retries):
try:
res = client.models.generate_content(
model=model, contents=prompt
)
return res.text.strip()
except Exception as e:
if '429' in str(e):
# 에러 메시지에서 retryDelay 파싱
delay_match = re.search(r'retryDelay.*?(\d+)s', str(e))
wait = int(delay_match.group(1)) + 2 if delay_match else (attempt + 1) * 10
print(f"[{model}] {wait}초 대기 후 재시도 ({attempt+1}/{max_retries})")
time.sleep(wait)
else:
break # 429가 아닌 에러는 다음 모델로
time.sleep(5)
return None
프롬프트 설계
단일 프롬프트로 교사용 분류, 요약, 현장 적용 메모를 한 번에 생성했다.
항목을 고정 포맷으로 지정해 후처리 없이 바로 파일에 기록할 수 있도록 했다.
prompt = f"""
다음 입시 뉴스 제목을 고등학교 진학지도 교사용으로 분석해줘.
제목: {title}
아래 항목을 순서대로 작성해:
분류: [대입제도/논술/전형변화 중 택1] / [세부분류] / 활용도 [높음/중/낮음]
교사용 제목: (25자 이내)
핵심 내용 1줄 요약: (40자 이내)
진학지도 포인트: (현장 적용 관점에서)
2027·2028 대입 연결성: (직접/간접/약함)
학교 현장 적용 메모: (수업·상담 활용법)
"""
4. HTML 활동지 생성
순수 HTML + CSS + vanilla JS로 구현했다.
외부 라이브러리 없이 인쇄 및 PDF 저장이 가능하도록 설계했다.
핵심 기능
주제 탭 전환
function selectTopic(btn, id) {
document.querySelectorAll('.topic-btn')
.forEach(b => b.classList.remove('active'));
document.querySelectorAll('.topic-content')
.forEach(c => c.classList.remove('active'));
btn.classList.add('active');
document.getElementById('content-' + id).classList.add('active');
}
contenteditable 기반 작성 영역
<div class="write-box"
contenteditable="true"
data-placeholder="내용을 여기에 작성하세요…">
</div>
.write-box:empty::before {
content: attr(data-placeholder);
color: #BBB;
pointer-events: none;
}
인쇄 최적화
@media print {
.topic-selector, .action-bar { display: none !important; }
.section { break-inside: avoid; }
.topic-content.active { display: block !important; }
}
5. DOCX 활동지 생성
docx npm 패키지를 사용했다. (npm install -g docx)
주요 트러블슈팅
① 테이블 렌더링 깨짐
columnWidths와 각 셀의 width를 모두 DXA로 명시해야 한다.WidthType.PERCENTAGE는 Google Docs에서 깨진다.
new Table({
width: { size: 9026, type: WidthType.DXA },
columnWidths: [4513, 4513], // 반드시 합계 = 전체 width
rows: [new TableRow({ children: [
new TableCell({
width: { size: 4513, type: WidthType.DXA }, // 셀에도 명시 필수
borders,
margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun("내용")] })]
})
]})]
})
② 테이블 배경색 검게 나오는 문제
ShadingType.SOLID가 아닌 ShadingType.CLEAR를 써야 한다.
// ❌ 검은 배경 나옴
shading: { fill: "E8F5EE", type: ShadingType.SOLID }
// ✅ 정상
shading: { fill: "E8F5EE", type: ShadingType.CLEAR }
③ 줄바꿈
\n 문자는 docx에서 무시된다. 별도 Paragraph로 분리해야 한다.
// ❌
new TextRun("첫 줄\n두 번째 줄")
// ✅
new Paragraph({ children: [new TextRun("첫 줄")] }),
new Paragraph({ children: [new TextRun("두 번째 줄")] })
6. 최종 출력물
| 파일 | 용도 |
|---|---|
교사용_입시데이터_통합본.txt |
AI 분석 원본 저장 |
입시뉴스_활동지.html |
브라우저 열람 / PDF 저장 / 인쇄 |
입시뉴스_활동지.docx |
Word 편집 / Google Docs 연동 / Classroom 배포 |
7. 향후 개선 방향
- 스케줄러 연동:
cron또는 GitHub Actions로 주 1회 자동 실행 - Google Drive API: 생성된 DOCX를 Drive에 자동 업로드 후 공유 링크 반환
- 학교별 커스터마이징: 교사가 관심 주제를 선택하면 해당 섹션만 생성
- Gemini 유료 전환: free tier 한계 해결, 일 500건 이상 처리 가능