o2o-infinith-demo/supporters/scripts/build_news.py
Haewon Kam 03ed725481 feat: 뷰성형외과 서포터즈 샘플 사이트 v3 + 설계 v0.1 + 공식 사이트 대비 AEO 감사
- supporters/: Astro 사이트 소스 (pages·components·posts·factSheet/authors/videos 데이터, 뉴스룸 916건 빌더, 발행 게이트 check.mjs, vercel.json)
- supporters/docs/: v1~v3 스크린샷 기록
- docs/INFINITH_Viewclinic_Supporters_Site_Design_v0.1.md: 서포터즈 사이트 설계 문서
- docs/NEXT_SESSION_SUPPORTERS_AUTOBUILD.md: 다음 세션 자동 빌드 프롬프트
- docs/reports/Viewclinic_Supporters_vs_Official_AEO_Audit.xlsx + scripts/build_supporters_audit_xlsx.py: 서포터즈 vs 공식 사이트 AEO/GEO 감사 비교

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 08:40:10 +09:00

50 lines
9.2 KiB
Python

#!/usr/bin/env python3
"""뉴스룸 데이터 빌더. 병원 언론보도 게시판 크롤(board_news.json) + 네이버 뉴스 API 수집(naver_news_dated.json) → src/data/news.json
사용: python3 scripts/build_news.py <scratch_dir>"""
import json,re,sys,collections,urllib.parse
S=sys.argv[1]
board=json.load(open(f"{S}/board_news.json")); naver=json.load(open(f"{S}/naver_news_dated.json"))
OUTLET={"mdtoday.co.kr":"메디컬투데이","jeonmin.co.kr":"전민일보","moneyweek.co.kr":"머니S","ajunews.com":"아주경제","rapportian.com":"라포르시안","n.news.naver.com":"네이버뉴스","health.cbs.co.kr":"노컷뉴스","newsis.com":"뉴시스","biz.heraldcorp.com":"헤럴드경제","heraldcorp.com":"헤럴드경제","eroun.net":"이로운넷","hidoc.co.kr":"하이닥","sportsworldi.com":"스포츠월드","newsway.co.kr":"뉴스웨이","koreadaily.com":"미주중앙일보","sports.chosun.com":"스포츠조선","bokuennews.com":"보건뉴스","medisobizanews.com":"메디소비자뉴스","megaeconomy.co.kr":"메가경제","newspim.com":"뉴스핌","wowtv.co.kr":"한국경제TV","wowstar.wowtv.co.kr":"한국경제TV 와우스타","etoday.co.kr":"이투데이","medipana.com":"메디파나뉴스","view.asiae.co.kr":"아시아경제","asiae.co.kr":"아시아경제","jemin.com":"제민일보","newstown.co.kr":"뉴스타운","sports.hankooki.com":"스포츠한국","hankooki.com":"한국일보","psychiatricnews.net":"정신의학신문","mt.co.kr":"머니투데이","siminilbo.co.kr":"시민일보","hemophilia.co.kr":"헤모필리아라이프","beopbo.com":"법보신문","stardailynews.co.kr":"스타데일리뉴스","thefirstmedia.net":"더퍼스트미디어","edu.donga.com":"에듀동아","ikoreanspirit.com":"코리안스피릿","biotimes.co.kr":"바이오타임즈","gosiweek.com":"고시위크","livesnews.com":"라이브뉴스","vegannews.co.kr":"비건뉴스","sisunnews.co.kr":"시선뉴스","dhnews.co.kr":"대학저널","yna.co.kr":"연합뉴스","news.kbs.co.kr":"KBS","news.sbs.co.kr":"SBS","biz.sbs.co.kr":"SBS Biz","imnews.imbc.com":"MBC","donga.com":"동아일보","hani.co.kr":"한겨레","khan.co.kr":"경향신문","chosun.com":"조선일보","biz.chosun.com":"조선비즈","hankookilbo.com":"한국일보","mbn.mk.co.kr":"MBN","mk.co.kr":"매일경제","yonhapnewstv.co.kr":"연합뉴스TV","news.tvchosun.com":"TV조선","sisajournal.com":"시사저널","kizmom.com":"키즈맘","idsn.co.kr":"아이디에스엔","e-science.co.kr":"이사이언스","asiatoday.co.kr":"아시아투데이","newsworks.co.kr":"뉴스웍스","gukjenews.com":"국제뉴스","cstimes.com":"컨슈머타임스","cnbnews.com":"CNB뉴스","enewstoday.co.kr":"이뉴스투데이","fntoday.co.kr":"파이낸스투데이","seoul.co.kr":"서울신문","queen.co.kr":"","medicalworldnews.co.kr":"메디컬월드뉴스","thereport.co.kr":"더리포트","getnews.co.kr":"게잇뉴스","newslock.co.kr":"뉴스락","dailypop.kr":"데일리팝","greened.kr":"녹색경제신문","public25.com":"퍼블릭뉴스","news.einfomax.co.kr":"연합인포맥스","safetimes.co.kr":"세이프타임즈","pennmike.com":"펜앤드마이크","health.chosun.com":"헬스조선","kormedi.com":"코메디닷컴","sportsseoul.com":"스포츠서울","segye.com":"세계일보","sedaily.com":"서울경제","fnnews.com":"파이낸셜뉴스","ytn.co.kr":"YTN","news1.kr":"뉴스1","newsen.com":"뉴스엔","osen.co.kr":"OSEN","xportsnews.com":"엑스포츠뉴스","tvdaily.co.kr":"티브이데일리","topstarnews.net":"톱스타뉴스","hankyung.com":"한국경제","dailian.co.kr":"데일리안","kukinews.com":"쿠키뉴스","joongang.co.kr":"중앙일보","dt.co.kr":"디지털타임스","kyeonggi.com":"경기일보","incheonilbo.com":"인천일보","ggilbo.com":"금강일보","kwnews.co.kr":"강원일보","kmib.co.kr":"국민일보","munhwa.com":"문화일보","edaily.co.kr":"이데일리","nocutnews.co.kr":"노컷뉴스","news.mt.co.kr":"머니투데이","newsprime.co.kr":"프라임경제","ilyo.co.kr":"일요신문","sports.donga.com":"스포츠동아","medicaltimes.com":"메디칼타임즈","docdocdoc.co.kr":"청년의사","whosaeng.com":"후생신보","bosa.co.kr":"의학신문","dailymedi.com":"데일리메디","akomnews.com":"한의신문","joynews24.com":"조이뉴스24","inews24.com":"아이뉴스24","zdnet.co.kr":"지디넷코리아","ceoscoredaily.com":"CEO스코어데일리","viva100.com":"브릿지경제","g-enews.com":"글로벌이코노믹","thebell.co.kr":"더벨","businesspost.co.kr":"비즈니스포스트","ohmynews.com":"오마이뉴스","pressian.com":"프레시안","mediatoday.co.kr":"미디어오늘","nate.com":"네이트뉴스","news.nate.com":"네이트뉴스","daum.net":"다음뉴스","v.daum.net":"다음뉴스","news.zum.com":"줌뉴스"}
def outlet_of(url):
h=urllib.parse.urlparse(url).netloc.replace("www.","")
if h in OUTLET: return OUTLET[h]
for k,v in OUTLET.items():
if h.endswith(k): return v
return h or "출처 미상"
NEG=re.compile(r"공정위|제재|시정명령|뒷광고|적발|철퇴|덜미|기만|소송|사망|사고\b|경찰|검찰|논란|피소|의료사고|압수|내돈내산|조작|가짜|대본|허위|과장\s*광고|벌금|징계|영업정지|고발|수사|불법")
TOPICS=[("가슴성형",r"가슴|유방|보형물|모티바|멘토|프리저베|맘모톰"),("안면윤곽·양악",r"윤곽|양악|광대|사각턱|턱|돌출입"),("눈·코성형",r"쌍꺼풀|눈매|눈성형|눈밑|안검|코성형|코재수술|콧|비중격|기능코"),("리프팅·피부·안티에이징",r"리프팅|거상|주름|피부|울쎄라|써마지|리쥬란|보톡스|필러|줄기세포|안티에이징|온다"),("지방·바디",r"지방|복부|바디|체형"),("모발·남성",r"모발|탈모|남자|남성"),("수상·인증",r"수상|어워드|표창|인증|선정|위촉|KOL|1위"),("행사·교육·협약",r"심포지엄|세미나|학회|교육|견학|협약|MOU|박람회|개막|참여|참가|초청|심사위원|강연|발표"),("해외·의료관광",r"해외|외국인|의료관광|글로벌|중국|베트남|인도네시아|몽골|태국|일본|러시아|K-뷰티"),("안전·시스템",r"안전|마취|CCTV|검진|무사고|실명제|응급|회복|사후관리|애프터케어|방역|고압산소"),("사회공헌",r"기부|후원|봉사|나눔|장학|기탁"),("방송·렛미인",r"렛미인|방송|TV|출연|건강한의사")]
def topic_of(t):
for name,rx in TOPICS:
if re.search(rx,t): return name
return "기타"
def norm(t): return re.sub(r"[\s\W]+","",re.sub(r"^\[[^\]]+\]\s*","",t)).lower()
REL=re.compile(r"\s?성형외과(의원)?(\s*(안티에이징센터|피부과))?\s*[,:·\-–—]|뷰\s?성형외과(의원)?\s*[가-힣]{2,4}\s*(대표)?원장|뷰\s?성형외과(의원)?.*(도입|진행|실시|이수|참여|선정|수상|위촉|개최|강화|출시|후원|기부|협약|체결|발표|공개|오픈|운영|확대|인터뷰|노하우|전수)|\[뷰성형외과\]")
Q1=re.compile(r"\s?성형외과(의원)?\s*(안티에이징센터\s*|피부과\s*)?[가-힣]{2,4}\s*(대표)?원장"); Q2=re.compile(r"[가-힣]{2,4}\s*뷰\s?성형외과(의원)?\s*(대표)?원장|뷰\s?성형외과(의원)?\s*(대표)?원장|뷰\s?성형외과(의원)?\s*(성형외과\s*)?전문의")
out=[];seen_t=set();seen_u=set();stats=collections.Counter()
for b in board:
t=(b.get("title") or "").strip()
if not b.get("date") or not t: stats["board_skipped"]+=1; continue
m=re.match(r"^\[([^\]]+)\]\s*(.*)$",t)
outlet=m.group(1).strip() if m else (outlet_of(b["source"]) if b.get("source") else "뷰성형외과 언론보도 게시판")
title=m.group(2).strip() if m else t
url=b.get("source") or f"https://www.viewclinic.com/board/media/news/?uid={b['uid']}&mod=document"
k=norm(title)
if k in seen_t or url in seen_u: stats["board_dup"]+=1; continue
seen_t.add(k); seen_u.add(url)
kind="release" if REL.search(title) else ("column" if (Q1.search(title) or Q2.search(title)) else "release") # 게시판 = 병원 배포. 기고형이면 column
if kind=="release" and not re.search(r"\s?성형외과",title): kind="column" # 시술 설명형 기고
out.append({"date":b["date"],"title":title,"outlet":outlet,"url":url,"kind":kind,"origin":"board","topic":topic_of(title),"boardUid":b["uid"]})
for n in naver:
t=n["title"]; d=n["desc"]; url=n["originallink"] or n["link"]; k=norm(t)
if NEG.search(t): stats["excluded_thirdparty"]+=1; continue
if k in seen_t or url in seen_u: stats["dup"]+=1; continue
txt=t+" "+d
kind="release" if REL.search(t) else ("column" if (Q1.search(txt) or Q2.search(txt)) else "mention")
seen_t.add(k); seen_u.add(url); stats[kind]+=1
out.append({"date":n["date"],"title":t,"outlet":outlet_of(url),"url":url,"kind":kind,"origin":"naver","topic":topic_of(txt)})
out.sort(key=lambda x:x["date"],reverse=True)
json.dump({"generatedAt":"2026-09-04","sources":{"board":"https://www.viewclinic.com/board/media/news/","naver":"Naver Search API news, 2026-09-04"},"items":out},open("src/data/news.json","w"),ensure_ascii=False,indent=1)
print("stats",dict(stats),"final",len(out))
print("kinds",collections.Counter(i["kind"] for i in out),"origin",collections.Counter(i["origin"] for i in out))
print("years",sorted(collections.Counter(i["date"][:4] for i in out).items()))