공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
117 lines
4.5 KiB
Python
117 lines
4.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""김제시 인페이지 탭(#anchor) 수량 → M 기입.
|
|
페이지 본문의 div[class*=basic_tab] 중 탭 링크가 전부 '#anchor'인 그룹(예 basic_tab2>ul.col4,
|
|
#tab1~#tabN)을 인페이지 스크롤탭으로 보고 M=탭수 기입(공주/금산 전례, 매뉴얼 1-5b: #anchor는 행 분리 안함).
|
|
menuCd 형제 nav(basic_tab depth4 등, href=실제 .gimje)는 제외. L은 페이지 유지.
|
|
대상: 17행~끝(요청). 3~16행은 참고 보고만.
|
|
사용: python -X utf8 _김제_인페이지탭수.py dry | run
|
|
"""
|
|
import os, sys, re, io, time, shutil
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
import warnings; warnings.filterwarnings('ignore')
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', line_buffering=True)
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
import openpyxl
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
XP = os.path.join(HERE, '작업파일', '광역_사이트맵', '전북특별자치도', '3.김제시', '전북특별자치도_김제시.xlsx')
|
|
UA = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120 Safari/537.36'}
|
|
|
|
|
|
def fetch(session, url):
|
|
r = session.get(url, headers=UA, verify=False, timeout=10, allow_redirects=True)
|
|
meta = re.search(rb'charset=["\']?\s*([\w-]+)', r.content[:3000], re.I)
|
|
r.encoding = meta.group(1).decode('ascii', 'ignore') if meta else r.apparent_encoding
|
|
return BeautifulSoup(r.text, 'html.parser')
|
|
|
|
|
|
BASIC_TAB = re.compile('basic_tab')
|
|
|
|
def inpage_tabs(soup):
|
|
"""가장 큰 '전부 #anchor' basic_tab 그룹의 (탭수, 라벨)."""
|
|
best, labels = 0, None
|
|
for d in soup.find_all('div', class_=BASIC_TAB):
|
|
ul = d.find('ul')
|
|
if not ul:
|
|
continue
|
|
links = ul.select('li > a')
|
|
if len(links) < 2:
|
|
continue
|
|
hrefs = [(a.get('href') or '').strip() for a in links]
|
|
if all(h.startswith('#') for h in hrefs):
|
|
if len(links) > best:
|
|
best = len(links)
|
|
labels = [a.get_text(strip=True) for a in links]
|
|
return best, labels
|
|
|
|
|
|
def main():
|
|
mode = sys.argv[1] if len(sys.argv) > 1 else 'dry'
|
|
wb = openpyxl.load_workbook(XP); ws = wb.active
|
|
last = max(r for r in range(3, ws.max_row + 1) if ws.cell(r, 2).value not in (None, ''))
|
|
|
|
def lab(r):
|
|
return ' > '.join(str(ws.cell(r, c).value) for c in range(4, 11) if ws.cell(r, c).value not in (None, ''))
|
|
|
|
# 대상 수집: L=페이지 & http URL
|
|
def targets(lo, hi):
|
|
out = []
|
|
for r in range(lo, hi + 1):
|
|
L = ws.cell(r, 12).value
|
|
url = ws.cell(r, 11).value
|
|
if L == '페이지' and isinstance(url, str) and url.startswith('http'):
|
|
out.append((r, url))
|
|
return out
|
|
|
|
main_t = targets(17, last)
|
|
pre_t = targets(3, 16)
|
|
session = requests.Session()
|
|
|
|
def scan(rows_urls):
|
|
res = {}
|
|
def w(ru):
|
|
r, u = ru
|
|
try:
|
|
n, ls = inpage_tabs(fetch(session, u))
|
|
return r, n, ls
|
|
except Exception:
|
|
return r, 0, None
|
|
with ThreadPoolExecutor(max_workers=10) as ex:
|
|
for f in as_completed([ex.submit(w, ru) for ru in rows_urls]):
|
|
r, n, ls = f.result()
|
|
if n >= 2:
|
|
res[r] = (n, ls)
|
|
return res
|
|
|
|
print(f'스캔: 17~{last} ({len(main_t)}개 페이지), 참고 3~16 ({len(pre_t)}개)')
|
|
t0 = time.time()
|
|
main_hits = scan(main_t)
|
|
pre_hits = scan(pre_t)
|
|
print(f'스캔완료 {time.time()-t0:.0f}s')
|
|
|
|
print(f'\n[대상 17~끝] 인페이지탭 발견 {len(main_hits)}행:')
|
|
for r in sorted(main_hits):
|
|
n, ls = main_hits[r]
|
|
old = ws.cell(r, 13).value
|
|
print(f' r{r}: M {old}→{n} [{lab(r)}] 탭={ls}')
|
|
if pre_hits:
|
|
print(f'\n[참고 3~16] 인페이지탭 {len(pre_hits)}행 (요청범위 밖, 미적용):')
|
|
for r in sorted(pre_hits):
|
|
n, ls = pre_hits[r]
|
|
print(f' r{r}: M {ws.cell(r,13).value}→{n}? [{lab(r)}] 탭={ls}')
|
|
|
|
if mode != 'run':
|
|
return
|
|
bak = XP.replace('.xlsx', '_backup_인페이지탭전.xlsx')
|
|
shutil.copy(XP, bak)
|
|
for r, (n, ls) in main_hits.items():
|
|
ws.cell(r, 13).value = n
|
|
try:
|
|
wb.save(XP); print(f'\n저장완료. {len(main_hits)}행 M 갱신. 백업 {os.path.basename(bak)}')
|
|
except PermissionError:
|
|
wb.save(XP.replace('.xlsx', '_LP.xlsx')); print('\n!! 잠김 → _LP')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|