kcsc-mcp / src /utils /validator.py
nicefree19's picture
Upload 105 files
0ee3c92 verified
Raw
History Blame Contribute Delete
4.48 kB
# -*- coding: utf-8 -*-
import asyncio
import httpx
import json
import os
from typing import Dict, Any, List
async def check_url_live(client: httpx.AsyncClient, code: str, url: str, semaphore: asyncio.Semaphore) -> Dict[str, Any]:
"""๋‹จ์ผ URL์— ๋Œ€ํ•ด ์„ธ๋งˆํฌ์–ด ์ œ์–ด ํ•˜์— HEAD ์š”์ฒญ์„ ๋น„๋™๊ธฐ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค."""
async with semaphore:
try:
# HEAD ์š”์ฒญ์œผ๋กœ ๋Œ€์—ญํญ์„ ์ ˆ์•ฝํ•˜๊ณ  ์‹ ์†ํžˆ ์‘๋‹ต ์ฝ”๋“œ๋งŒ ๊ฒ€์‚ฌ
response = await client.head(url, timeout=5.0, follow_redirects=True)
status_code = response.status_code
is_live = (200 <= status_code < 400)
return {"code": code, "url": url, "is_live": is_live, "status": status_code}
except httpx.HTTPError as e:
return {"code": code, "url": url, "is_live": False, "status": "HTTPError", "error": str(e)}
except Exception as e:
return {"code": code, "url": url, "is_live": False, "status": "Error", "error": str(e)}
async def run_mappings_purification(mappings_filepath: str, output_filepath: str):
"""mappings.json์„ ๋กœ๋“œํ•˜๊ณ  ์ƒ์กด ์—ฌ๋ถ€๋ฅผ ์ „์ˆ˜ ๊ฒ€์‚ฌํ•˜์—ฌ ๊นจ์ง„ ๋งํฌ๊ฐ€ ์ œ๊ฑฐ๋œ ๋ฌด๊ฒฐ์„ฑ ๋งต์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค."""
if not os.path.exists(mappings_filepath):
print(f"์˜ค๋ฅ˜: ๋งคํ•‘ ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค. ({mappings_filepath})")
return
with open(mappings_filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
url_mapping = data.get("url_mapping", {})
if not url_mapping:
print("์ •ํ™”ํ•  url_mapping ํ•„๋“œ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
return
print(f"์ด {len(url_mapping)}๊ฐœ์˜ ์ถ”์ • URL ๋น„๋™๊ธฐ ์ƒ์กด ์—ฌ๋ถ€ ์ „์ˆ˜ ๊ฒ€์ฆ ์‹œ์ž‘...")
# ์œˆ๋„์šฐ ํ™˜๊ฒฝ ์†Œ์ผ“ ๊ณผ๋ถ€ํ•˜ ๋ฐ ์„œ๋ฒ„ ์ฐจ๋‹จ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ๋™์‹œ์„ฑ ์ˆ˜ ์ œ์–ด (Semaphore 20 ์ œํ•œ)
semaphore = asyncio.Semaphore(20)
# ๋ชจ๋ฐ”์ผ ํด๋ผ์ด์–ธํŠธ User-Agent๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋ด‡ ์ฐจ๋‹จ ์šฐํšŒ
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
cleaned_url_mapping = {}
verified_count = 0
dead_count = 0
async with httpx.AsyncClient(headers=headers, verify=False) as client:
tasks = []
for code, url in url_mapping.items():
tasks.append(check_url_live(client, code, url, semaphore))
# ๋น„๋™๊ธฐ ๋ณ‘๋ ฌ ๋Œ€๊ธฐ ๋ฐ ํ”„๋กœ๊ทธ๋ž˜์Šค ํŠธ๋ž˜ํ‚น
results = await asyncio.gather(*tasks)
for res in results:
code = res["code"]
url = res["url"]
if res["is_live"]:
cleaned_url_mapping[code] = url
verified_count += 1
else:
dead_count += 1
# [๊ฐœ์„ ์•ˆ] ๊นจ์ง„ ๋งํฌ๋Š” ๊ณต์‹ KCSC ๋ฉ”์ธ ๊ฒ€์ƒ‰ ํŽ˜์ด์ง€ ๋“ฑ์˜ ์•ˆ์ „ ์ง€๋Œ€๋กœ ํฌ์›Œ๋”ฉ ์žฌ๋งคํ•‘
cleaned_url_mapping[code] = "https://www.kcsc.re.kr/Standard/Portal"
# ์›๋ณธ ํŒŒ์ผ ๋ฐฑ์—… ๋ฐ ์ •ํ™”๋œ mappings_clean.json ์ €์žฅ
data["url_mapping"] = cleaned_url_mapping
# ํ†ต๊ณ„ ๋ฐ ๋ฆฌํŒฉํ† ๋ง ์ •๋ณด ์ ์žฌ ์‹œ ์˜ˆ์™ธ ๋ฐฉ์ง€ ์•ˆ์ „ ์กฐ์น˜
if "metadata" not in data:
data["metadata"] = {}
if "statistics" not in data["metadata"]:
data["metadata"]["statistics"] = {}
data["metadata"]["statistics"]["verified_urls"] = verified_count
data["metadata"]["statistics"]["estimated_urls"] = len(url_mapping) - verified_count
data["metadata"]["last_purified"] = ""
# ๋กœ์ปฌ ์‹œ๊ฐ„ ๋˜๋Š” timezone ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ฐ€๋น„์ง€ ์—†์ด ๋ฌด๋‚œํžˆ API๋กœ๋ถ€ํ„ฐ ์‹œ๊ฐ„์„ ๋ฐ›๊ฑฐ๋‚˜ ๋กœ์ปฌ ์‹œ๊ฐ„์œผ๋กœ ์ฒ˜๋ฆฌ
try:
response = httpx.get("http://worldtimeapi.org/api/ip", timeout=3.0)
data["metadata"]["last_purified"] = response.json().get("datetime", "")
except Exception:
import datetime
data["metadata"]["last_purified"] = datetime.datetime.now().isoformat()
with open(output_filepath, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"\n===== Mappings ์ •ํ™” ์™„๋ฃŒ ์š”์•ฝ =====")
print(f" * ๊ฒ€์ฆ ์ƒ์กดํ•œ ์œ ํšจ URL : {verified_count} ๊ฐœ")
print(f" * ๊นจ์ง„ ์ถ”์ • URL ์šฐํšŒ : {dead_count} ๊ฐœ (์•ˆ์ „ ์ฃผ์†Œ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์™„๋ฃŒ)")
print(f" * ์ •ํ™”๋œ ๋งคํ•‘ ์ €์žฅ ์™„๋ฃŒ : {output_filepath}")