법제처 DRF API로 고시를 받으면 숫자표가 통째로 빠진다 — 첨부 HWPX를 직접 뜯은 기록
정부 고시의 숫자를 블로그 글에 인용할 때 요약본을 거치면 값이 틀어진다. 그래서 법제처 국가법령정보 Open API(DRF)로 원문을 직접 받아 쓰는데, 행정규칙(고시) 쪽에서는 법령과 다른 함정이 두 개 있었다.
1차 실패 — ID 파라미터가 두 종류다
검색은 문제없이 된다.
$ curl -s -G "https://www.law.go.kr/DRF/lawSearch.do" \
--data-urlencode "OC=test" --data-urlencode "target=admrul" \
--data-urlencode "type=JSON" \
--data-urlencode "query=주거급여 선정기준 및 최저보장수준"
응답에서 항목 하나를 꺼내 보면 이렇다.
{
"행정규칙명": "2026년 주거급여 선정기준 및 최저보장수준",
"행정규칙ID": "48362",
"행정규칙일련번호": "2100000264366",
"발령번호": "2025-506",
"시행일자": "20260101",
"행정규칙상세링크": "/DRF/lawService.do?OC=test&target=admrul&ID=2100000264366&type=HTML"
}
이름이 행정규칙ID이니 상세 조회의 ID에 넣는 게 자연스럽다. 그런데 이렇게 하면 실패한다.
$ curl -s "https://www.law.go.kr/DRF/lawService.do?OC=test&target=admrul&type=JSON&ID=48362"
{"Law": "일치하는 행정규칙이 없습니다. 행정규칙명을 확인하여 주십시오."}
메시지가 **“행정규칙명을 확인하라”**고 해서 이름 검색 문제로 오해하기 쉽다. 실제로는 식별자 종류가 틀렸을 뿐이다. 행정규칙일련번호를 넣으면 바로 열린다.
$ curl -s "https://www.law.go.kr/DRF/lawService.do?OC=test&target=admrul&type=JSON&ID=2100000264366"
{"AdmRulService": {"행정규칙기본정보": {"현행여부": "Y", "행정규칙명": "2026년 주거급여 선정기준 및 최저보장수준", ...
같은 응답 객체에 두 값이 나란히 들어 있는데 상세 조회가 받는 건 한쪽뿐이다. 헷갈리지 않는 방법은 간단하다 — 행정규칙상세링크 필드가 정답을 이미 포함하고 있으므로 거기서 ID를 꺼내 쓰면 된다.
2차 실패 — 본문에 숫자가 없다
식별자를 고쳐 200을 받아도 정작 필요한 값이 없다. 조문내용을 그대로 옮기면 이렇다.
1. 주거급여 선정기준
「주거급여법」 제5조제1항에 따라 주거급여 선정기준은 다음 값 이하로 한다.
* 8인 가구는 7인 가구 기준과 6인 가구 기준의 차이를 …
2. 주거급여 최저보장수준
가. 임차급여
「주거급여법」 제7조제3항 및 「주거급여 실시에 관한 고시」 제4조제1항에 따른
기준임대료는 다음과 같다.
나. 수선유지급여 …
“다음 값 이하로 한다” 다음에 값이 없다. “기준임대료는 다음과 같다” 다음에 표가 없다. 실제로 확인해 보면 응답 전체에 숫자가 한 건도 없다.
$ curl -s "https://www.law.go.kr/DRF/lawService.do?OC=test&target=admrul&type=JSON&ID=2100000264366" | grep -c "369,000"
0
DRF의 조문내용은 텍스트 문단만 담고 표로 조판된 부분은 통째로 빠진다. 고시는 내용의 핵심이 표인 경우가 많아서, 법령(target=law)에서 조문만 읽던 감각으로 접근하면 여기서 막힌다.
표는 첨부 HWPX에 있다
같은 응답의 첨부파일 필드가 출구다.
"첨부파일": {
"첨부파일링크": "http://law.go.kr/flDownload.do?flSeq=156379585",
"첨부파일명": "2026년 주거급여 선정기준 및 최저보장수준(전문).hwpx"
}
(전문)이라는 이름 그대로, 표를 포함한 완전한 문서다.
$ curl -sL "http://law.go.kr/flDownload.do?flSeq=156379585" -o gosi.hwpx
$ file -b gosi.hwpx
Zip data (MIME type "application/hwp+zip"?)
HWPX는 zip이다. 여기가 중요한 지점이다. 구형 HWP(.hwp)는 OLE 복합 문서라 olefile 같은 모듈로 스트림을 풀어야 하지만, HWPX는 OOXML과 같은 구조라 표준 라이브러리만으로 열린다.
의존성 없이 텍스트 뽑기
본문은 Contents/section*.xml에 있고, 텍스트 조각은 hp:t 태그 안에 들어 있다.
import zipfile, re, html
def hwpx_text(path):
z = zipfile.ZipFile(path)
out = []
for name in z.namelist():
if 'section' in name.lower() and name.endswith('.xml'):
xml = z.read(name).decode('utf-8', errors='replace')
for t in re.findall(r'<hp:t>(.*?)</hp:t>', xml, re.S):
out.append(html.unescape(re.sub(r'<[^>]+>', '', t)))
return out
print(' | '.join(t for t in hwpx_text('gosi.hwpx') if t.strip()))
hp:t 안에 서식 태그가 중첩되는 경우가 있어서 내부 태그를 한 번 더 지우고, XML 엔티티를 html.unescape로 풀어 준다.
결과에는 API 본문에 없던 숫자가 전부 들어 있다.
구 분 | 1인 가구 | 2인 가구 | 3인 가구 | 4인 가구 | …
금액 | (원/월) | 1,230,834 | 2,015,660 | 2,572,337 | 3,117,474 | …
구분 | 1급지 | (서울) | 2급지 | (경기·인천) | …
1인 | 369,000 | 300,000 | 247,000 | 212,000 |
2인 | 414,000 | 335,000 | 275,000 | 238,000 |
표 구조 복원
hp:t만 뽑으면 셀이 1차원으로 늘어선다. 그래도 대부분의 고시 표는 복원된다 — 헤더가 먼저 순서대로 나오고 값이 같은 순서로 이어지기 때문이다. 열 개수를 알면 잘라 쓰면 된다.
cells = [t for t in hwpx_text('gosi.hwpx') if t.strip()]
i = cells.index('1급지') # 표 시작 지점을 앵커로 잡는다
rows = cells[i:]
구조가 복잡한 표(병합 셀, 다단 헤더)라면 hp:tr(행)과 hp:tc(셀) 태그로 경계를 잡아야 한다. 그때도 zip과 정규식만으로 충분하다.
정리 — 고시를 다룰 때의 체크리스트
| 단계 | 함정 | 대응 |
|---|---|---|
| 검색 | target=admrul로 검색 | 응답에 식별자가 두 개 |
| 상세 | 행정규칙ID를 넣으면 “일치하는 행정규칙이 없습니다” | 행정규칙일련번호 또는 행정규칙상세링크의 ID |
| 본문 | 표의 숫자가 조문내용에 없다 | 첨부파일링크의 (전문) 파일 |
| 파싱 | .hwpx | zip + hp:t, 표준 라이브러리로 충분 |
| 파싱 | .hwp | OLE 복합 문서 — olefile 필요 |
법령(target=law)은 조문·항·호가 JSON으로 잘 정리돼 나와서 API만으로 끝난다. 행정규칙은 다르다. 고시의 실질은 대부분 표에 있고, 그 표는 API 본문에 없다. 숫자를 인용해야 한다면 첨부파일까지 내려가는 것이 원문 대조의 최소 조건이다.
자주 묻는 질문
행정규칙ID와 행정규칙일련번호는 어떻게 구분하나요?
lawSearch.do 응답의 각 항목에 둘 다 들어 있습니다. 행정규칙ID는 5자리 안팎의 짧은 숫자(예: 48362), 행정규칙일련번호는 2100000264366 같은 13자리입니다. lawService.do의 ID 파라미터는 후자를 받습니다. 응답에 함께 오는 '행정규칙상세링크' 필드가 실제로 어떤 값을 쓰는지 보여주므로, 그 링크의 ID 값을 그대로 쓰면 헷갈릴 일이 없습니다.
왜 조문내용에 표가 없나요?
법제처 DRF API의 조문내용은 텍스트 문단만 담습니다. 고시 본문에서 표로 조판된 부분은 이 필드에 포함되지 않아서, '기준임대료는 다음과 같다.' 다음에 바로 다음 항목이 이어집니다. 금액·비율처럼 표에만 있는 값이 필요하면 첨부파일을 받아야 합니다.
HWPX를 파싱하려면 별도 라이브러리가 필요한가요?
필요 없습니다. HWPX는 OOXML과 마찬가지로 zip 컨테이너이고 안에 XML이 들어 있어서, 파이썬 표준 라이브러리의 zipfile과 re만으로 텍스트를 뽑을 수 있습니다. 텍스트 조각은 hp:t 태그 안에 들어 있습니다. 다만 구형 HWP(.hwp)는 zip이 아니라 OLE 복합 문서라 olefile 같은 별도 모듈이 필요합니다.
표의 행·열 구조까지 복원되나요?
hp:t만 순서대로 뽑으면 셀 값이 1차원으로 늘어섭니다. 헤더가 '1인가구 2인가구 …' 순으로, 값이 그 다음에 같은 순서로 나오므로 가구원수처럼 열 개수를 아는 표는 순서만으로 복원됩니다. 구조가 복잡한 표라면 hp:tr과 hp:tc 태그로 행·셀 경계를 잡아야 합니다.