법제처가 공짜로 열어준 API 사용해서 내 개인 변호사 만드는 방법!
내가 직면한 상황에 해당 되는 법만 감시하는 도구를 만들어 봤습니다.
시작은 아찔한 실수였습니다
집을 사려고 지역별 시세를 정리하다가, 어떤 동네가 규제지역인지 확인할 일이 생겼습니다. 검색해서 나온 블로그 글을 보고 "여기는 규제 안 걸렸네" 하고 계획을 짰죠.
며칠 뒤 원문을 다시 보니 규제지역이 12곳이었습니다. 제가 본 글에는 3곳만 적혀 있었고요. 제가 안전하다고 믿었던 동네가 그 12곳 안에 있었습니다. 규제지역이면 전세 끼고 사는 게 아예 안 되니까, 전략 자체가 성립하지 않는 상황이었습니다.
다행히 실행 전이라 계획만 고치면 됐지만, 여기서 배운 게 있습니다.
챗봇 말고 감시기를 만드세요
"AI 변호사"라고 하면 보통 질문하면 답해주는 챗봇을 떠올립니다. 그런데 곰곰이 생각해 보면, 조문 찾아 읽는 건 이미 AI한테 물어보면 웬만큼 됩니다. 진짜 문제는 다른 데 있었어요.
| 상황 | 챗봇 | 감시기 |
|---|---|---|
| "양도세 비과세 요건이 뭐야?" | 잘 답함 | 해당 없음 |
| 어제 그 요건이 바뀌었다 | 물어보지 않으면 모름 | 아침에 알려줌 |
몰라서 사고 나는 게 아닙니다. 안다고 믿었던 게 바뀌어서 사고가 납니다. 그래서 저는 챗봇 대신 감시기를 만들었습니다. 내 계획에 직접 걸린 법만 골라서, 개정되면 알려주는 물건이요.
✨법제처가 API를 공짜로 엽니다
의외로 잘 안 알려져 있는데, 법제처 국가법령정보센터가 191개 OPEN API를 무료로 제공합니다.
- 현행 법령 전문, 연혁, 영문 법령
- 대법원 판례
- 헌법재판소 결정례
- 법령해석례 (정부 부처 유권해석)
- 조세심판원 결정례
- 지자체 조례까지
open.law.go.kr 에서 신청하면 1~2일 안에 승인됩니다. 신청하면 OC 값(가입 이메일의 앞부분)을 받는데, 이걸 파라미터로 넣으면 끝입니다.
신청 전에 5초 만에 확인해 보기
테스트용 키로 바로 찔러볼 수 있습니다. 터미널에 이거 한 줄 넣어보세요.
curl "https://www.law.go.kr/DRF/lawSearch.do?OC=test&target=law&type=XML&query=소득세법&display=3"
XML이 쏟아지면 성공입니다. 법령명, 시행일자, 공포일자, 소관부처가 다 들어 있습니다.
엔드포인트는 딱 두 개입니다
| 용도 | 주소 |
|---|---|
| 검색 | /DRF/lawSearch.do |
| 본문 | /DRF/lawService.do |
target 파라미터만 바꾸면 종류가 달라집니다.
law 법령
prec 판례
expc 법령해석례
detc 조세심판원 결정례
admrul 행정규칙
검색 코드
import re
import requests
SEARCH_URL = "https://www.law.go.kr/DRF/lawSearch.do"
OC = "test" # 신청하면 받는 본인 ID
def _text(node, tag):
m = re.search(rf"<{tag}>(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?</{tag}>", node, re.S)
return m.group(1).strip() if m else ""
def search_law(query, display=5):
r = requests.get(SEARCH_URL, params={
"OC": OC, "target": "law", "type": "XML",
"query": query, "display": display,
}, timeout=20)
r.raise_for_status()
out = []
for m in re.finditer(r"<law id=\"\d+\">(.*?)</law>", r.text, re.S):
b = m.group(1)
out.append({
"name": _text(b, "법령명한글"),
"effective": _text(b, "시행일자"), # 20260701
"promulgated": _text(b, "공포일자"),
"mst": _text(b, "법령일련번호"), # 본문 조회용 키
"status": _text(b, "현행연혁코드"), # 현행 / 연혁
"ministry": _text(b, "소관부처명"),
})
return out
for row in search_law("소득세법"):
print(row["name"], row["effective"], row["mst"])
결과는 이렇게 나옵니다.
소득세법 20260701 280405
소득세법 시행령 20260701 286211
소득세법 시행규칙 20260701 286379
여기서 한 번 크게 헤맸습니다
본문을 가져오는 건 쉽습니다. 아까 받은 mst 값을 넣으면 됩니다.
r = requests.get("https://www.law.go.kr/DRF/lawService.do", params={
"OC": OC, "target": "law", "type": "XML", "MST": "280405",
}, timeout=30)
print(len(r.text)) # 611660
소득세법 전문이 61만 자입니다. 통째로는 못 쓰니까 필요한 조문만 골라야 하는데, 저는 처음에 이렇게 했습니다.
# 이렇게 하면 안 됩니다
arts = re.findall(r"<조문내용>(.*?)</조문내용>", r.text, re.S)
found = [a for a in arts if "1세대 1주택" in a]
print(len(found)) # 0 ... ???
0건이 나옵니다. 소득세법에 1세대 1주택 얘기가 없을 리가 없죠. XML을 직접 뜯어보니 이유가 나왔습니다.
<조문단위>
<조문번호>89</조문번호>
<조문내용>제89조(비과세 양도소득)</조문내용> ← 제목만 있음
<항>
<항내용>① 다음 각 호의 소득에 대해서는...</항내용> ← 본문은 여기
<호><호내용>1. 파산선고에 의한 처분으로...</호내용></호>
<호><호내용>2. 농지의 교환 또는 분합으로...</호내용></호>
</항>
</조문단위>
조문단위를 통째로 잡아서 안에 있는 걸 전부 이어붙여야 합니다.CDATA = re.compile(r"<!\[CDATA\[(.*?)\]\]>", re.S)
def article_text(unit):
"""조문단위 하나를 사람이 읽는 텍스트로"""
parts = []
for m in re.finditer(r"<(조문내용|항내용|호내용|목내용)>(.*?)</\1>", unit, re.S):
raw = m.group(2)
c = CDATA.search(raw)
text = (c.group(1) if c else raw).strip()
if text:
parts.append(text)
return "\n".join(parts)
def get_article(mst, keyword=None):
r = requests.get("https://www.law.go.kr/DRF/lawService.do", params={
"OC": OC, "target": "law", "type": "XML", "MST": mst,
}, timeout=30)
units = re.findall(r"<조문단위[^>]*>(.*?)</조문단위>", r.text, re.S)
arts = [t for t in (article_text(u) for u in units) if t]
if keyword:
arts = [a for a in arts if keyword in a]
return arts
for a in get_article("280405", "1세대 1주택")[:1]:
print(a[:200])
이제 제대로 나옵니다.
제95조(양도소득금액과 장기보유 특별공제액)
① 양도소득금액은 양도차익에서 장기보유 특별공제액을 공제한 금액으로 한다.
② 제1항에서 "장기보유 특별공제액"이란 ... 대통령령으로 정하는 1세대 1주택
(이에 딸린 토지를 포함한다)에 해당하는 자산의 경우에는 ...
AI한테 물어볼 때 이 원문을 같이 던져주면 답의 질이 확 올라갑니다. 요약본을 요약한 걸 다시 요약하는 게 아니라, 진짜 법 조문을 읽고 답하는 거니까요.
본론: 감시기 만들기
핵심 아이디어는 단순합니다. 시행일자와 공포일자를 저장해 두고, 매일 조회해서 달라지면 알린다.
import json
from pathlib import Path
CACHE = Path("law_snapshot.json")
# 내 상황에 직접 걸린 법만 고릅니다. 많으면 소음이 됩니다.
WATCH = [
{"name": "소득세법",
"affects": "집 팔 때 양도세 비과세 요건, 급여 과세"},
{"name": "지방세특례제한법",
"affects": "생애최초 취득세 감면 일몰 여부"},
{"name": "부동산 거래신고 등에 관한 법률",
"affects": "토지거래허가구역 지정 여부"},
{"name": "주택임대차보호법",
"affects": "세입자 계약갱신청구권"},
]
def check():
prev = {}
if CACHE.exists():
prev = {x["name"]: x for x in json.loads(CACHE.read_text())}
rows, changes = [], []
for item in WATCH:
hits = [h for h in search_law(item["name"])
if h["status"] == "현행" and h["name"] == item["name"]]
if not hits:
continue
row = {**hits[0], "affects": item["affects"]}
old = prev.get(item["name"])
if old and old["effective"] != row["effective"]:
changes.append({**row, "before": old["effective"]})
rows.append(row)
CACHE.write_text(json.dumps(rows, ensure_ascii=False, indent=1))
return changes
for c in check():
print(f"[개정] {c['name']}")
print(f" 시행 {c['before']} -> {c['effective']}")
print(f" 영향: {c['affects']}")
여기서 중요한 건 affects 필드입니다. "소득세법이 개정됐습니다"만 오면 그래서 뭐 어쩌라고 싶거든요. "이게 내 어떤 계획에 걸리는지"를 같이 적어두면 알림을 받았을 때 바로 판단이 섭니다.
매일 아침 돌리기
서버가 있으면 cron에 걸고, 없으면 노트북 켤 때 돌아가게 해도 됩니다. 저는 알림을 텔레그램 봇으로 받고 있습니다.
# crontab -e
30 7 * * * /path/to/python /path/to/law_check.py
텔레그램 봇은 @BotFather 에서 5분이면 만들고, 메시지 보내는 건 이게 전부입니다.
requests.post(
f"https://api.telegram.org/bot{TOKEN}/sendMessage",
json={"chat_id": CHAT_ID, "text": message},
)
판례도 같이 봅니다
법 조문만으로 답이 안 나오는 경우가 많습니다. 그럴 때 판례를 붙이면 훨씬 선명해집니다. target만 바꾸면 됩니다.
def search_prec(query, display=5):
r = requests.get(SEARCH_URL, params={
"OC": OC, "target": "prec", "type": "XML",
"query": query, "display": display,
}, timeout=20)
out = []
for m in re.finditer(r"<prec id=\"\d+\">(.*?)</prec>", r.text, re.S):
b = m.group(1)
out.append({
"title": _text(b, "사건명"),
"case_no": _text(b, "사건번호"),
"date": _text(b, "선고일자"),
"court": _text(b, "법원명"),
})
return out
for p in search_prec("양도소득세", 3):
print(p["date"], p["case_no"])
print(" ", p["title"][:60])
"양도소득세"로 검색하면 5,382건이 나옵니다. 최근 것부터 몇 개만 봐도 요즘 어떤 게 다투어지는지 감이 옵니다.
쓰면서 느낀 것
검색 능력이 아니라 갱신 능력이 핵심입니다. 모르는 걸 찾는 건 이미 잘 됩니다. 문제는 내가 안다고 믿는 게 낡았을 때인데, 그건 물어볼 생각조차 안 하니까 영영 모릅니다. 감시기는 그 구멍을 메웁니다.
감시 목록은 적을수록 좋습니다. 처음엔 스무 개쯤 넣고 싶어지는데, 그러면 알림이 소음이 되고 결국 안 보게 됩니다. 저는 7개로 시작했습니다. 내 돈이 실제로 걸린 것만요.
원문을 던져주면 AI 답이 달라집니다. 같은 질문이라도 "법 조문 원문 + 판례 3건"을 같이 주면 훨씬 정확해집니다. 애매하면 애매하다고 말해주고요.
정리
- open.law.go.kr 에서 API 신청 (무료, 1~2일)
- 엔드포인트는
lawSearch.do(검색),lawService.do(본문) 두 개 - 본문 파싱은 조문단위로 묶어서 (조문내용에는 제목만 있음)
- 내 상황에 걸린 법만 5~7개 골라 시행일 저장
- 매일 아침 대조해서 바뀌면 알림
주말 반나절이면 만듭니다. 저는 이걸로 세금 관리해주는 "AI 세무사"도 하나 더 만들었는데, 그건 아래 글에서 확인해주세요 :)
'AI' 카테고리의 다른 글
| AI 개인 세무사 만드는 방법 - AI 세무사 (법제처 API) (0) | 2026.09.09 |
|---|---|
| AI 토큰 ? 토큰이 무엇인가, 토큰 효율적으로 쓰는 방법 (쉬운 설명) (0) | 2026.09.09 |
| 비개발자 수십 명에게 바이브코딩을 시킨 회사의 Starter Kit 전부 공개 (2) | 2026.07.01 |
| AX 담당자는 실제로 무슨 일을 할까 - 'AX팀이 하는 일' (3) | 2026.07.01 |
| Claude Code 세션 여러개 동시에 굴리는 법 (5개 병렬 멀티 세션) (3) | 2026.06.30 |
댓글