DB_JOB/작업파일/완료/광역_사이트맵/전북특별자치도/12.전주시/_classify.py
hehihoho3 df16c98366 백업: DB수집 전체 스냅샷 (공공기관2 정리 전)
공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 18:15:40 +09:00

80 lines
3.5 KiB
Python

# -*- coding: utf-8 -*-
"""전주시 신규 leaf phase2-4 분류 + 본문탭 전수 스캔. 기존 매칭행은 값 보존."""
import requests, warnings, sys, re, json, time
from concurrent.futures import ThreadPoolExecutor
warnings.filterwarnings('ignore'); sys.stdout.reconfigure(encoding='utf-8', line_buffering=True)
from bs4 import BeautifulSoup
DIR=r'작업파일\광역_사이트맵\전북특별자치도\12.전주시'
BASE='https://www.jeonju.go.kr'
S=requests.Session(); S.headers['User-Agent']='Mozilla/5.0'
TAB_SELS=['.tab','ul.tab','.tab_menu','.tabmenu','.tab_wrap','.contab','.tab_con',
'.tab_list','.sub_tab','[role=tablist]','.tabContent','.tab_area']
def fetch(u):
r=S.get(u,timeout=25,verify=False); r.encoding='utf-8'
return BeautifulSoup(r.text,'html5lib')
def board_count(cu_):
try:
soup=fetch(BASE+'/index.9is?contentUid='+cu_)
if not soup.select_one('table.bbs_table'):
return None,soup
end=None
for a in soup.select('.paging a'):
if a.get_text(strip=True)=='': end=a.get('href')
if not end:
# single page: count numbered rows
tb=soup.select_one('table.bbs_table')
n=sum(1 for tr in tb.select('tbody tr') if (tr.find('td') and tr.find('td').get_text(strip=True).isdigit()))
return n,soup
m=re.search(r'page=(\d+)',end); last=int(m.group(1)) if m else 1
eu=BASE+end if end.startswith('/') else end
s2=fetch(eu); tb=s2.select_one('table.bbs_table')
lastn=sum(1 for tr in tb.select('tbody tr') if (tr.find('td') and tr.find('td').get_text(strip=True).isdigit())) if tb else 0
return (last-1)*10+lastn, soup
except Exception as e:
return -1,None
def classify(rec):
if 'match' in rec:
mt=rec['match']
rec.update(L=mt['L'],M=mt['M'],N=mt['N'],O=mt['O'],P=mt['P'],Q=mt['Q'],src='keep')
return rec
if rec['external']:
rec.update(L='사이트',M='',N='',O='',P='',Q='',src='ext'); return rec
cnt,soup=board_count(rec['cu'])
# tab scan
if soup is not None:
cont=soup.select_one('#content') or soup
tabs=[]
for sel in TAB_SELS:
for e in cont.select(sel):
txt=e.get_text(' ',strip=True)
if e.find('a') or e.find('li'): tabs.append((sel,txt[:60]))
rec['tabs']=tabs[:3]
if cnt is None:
rec.update(L='페이지',M=1,N='어문',O='미부착',P='',Q='',src='page')
elif cnt==-1:
rec.update(L='페이지',M=1,N='어문',O='미부착',P='',Q='',src='err')
else:
rec.update(L='게시판',M=cnt,N=('없음' if cnt==0 else '어문'),O='미부착',P='',Q='',src='board')
return rec
if __name__=='__main__':
rows=json.load(open(DIR+r'\_tree.json',encoding='utf-8'))
todo=[r for r in rows if 'match' not in r and not r['external']]
print('total leaves:',len(rows),'to-fetch:',len(todo))
done=0
with ThreadPoolExecutor(max_workers=12) as ex:
for r in ex.map(classify,rows):
done+=1
if done%50==0: print(' ..%d/%d'%(done,len(rows)))
json.dump(rows,open(DIR+r'\_rows.json','w',encoding='utf-8'),ensure_ascii=False,indent=1)
from collections import Counter
print('L분포:',Counter(r['L'] for r in rows))
tabbed=[r for r in rows if r.get('tabs')]
print('본문탭 발견 페이지:',len(tabbed))
for r in tabbed[:20]: print(' TAB',r['path'][-1],r['tabs'])
print('boards:',[(r['path'][-1],r['M']) for r in rows if r['L']=='게시판'][:40])