날꿀닷컴의 약국 찾기와 병원 찾기는 지역과 기관 이름으로 연락처 후보를 좁히는 도구입니다. 이 글은 워드프레스 MU 플러그인의 현재 구현을 살펴보려는 개발자를 위한 기록입니다. 지역명을 API 코드로 바꾸는 방법, 검색 결과를 다시 검사하는 이유, 첫 페이지 조회의 한계를 설명합니다.
2026-10-22 구현 대조·정정: 기존 글의 잘못된 지역 코드, 현재 코드에 없는 대체 API 경로, 병원 서비스명과 검색 방식 설명을 바로잡았습니다. 확인할 수 없는 개인 개발 일화와 성능 수치는 제외했습니다. 아래 코드는 운영 파일의 발췌 또는 설명용 축약이며, 그대로 설치하는 완성 플러그인은 아닙니다.
현재 연결한 API와 지원 범위
데이터 제공기관은 건강보험심사평가원입니다. 날꿀닷컴은 원자료를 직접 조사한 기관이 아니며, API 응답의 지역·이름 조건을 검사하고 중복을 제거한 뒤 카드로 보여줍니다. 두 도구는 서로 다른 기본목록 경로를 사용합니다.
- 약국:
pharmacyInfoService/getParmacyBasisList— 운영 파일pharmacy-finder.php - 병원:
hospInfoServicev2/getHospBasisList— 운영 파일hospital-finder.php
두 경로 모두 https://apis.data.go.kr/B551182/ 아래에 있습니다. 약국 도구는 서울 25개 구, 부산 16개 구·군, 경기도의 등록된 10개 시를 지원합니다. 병원 도구에는 17개 시·도와 등록된 시·군·구 목록이 있습니다. 시·도 선택지가 있다고 모든 하위 지역·기관을 빠짐없이 제공하는 것은 아닙니다. 화면에 있는 지역과 현재 코드표를 기준으로 이용해야 합니다.
약국은 시·도와 시·군·구를 모두 선택해야 합니다. 병원은 시·도를 선택한 뒤 시·군·구, 기관 종류, 이름으로 조건을 좁힐 수 있습니다. 병원 종류에는 clCd를 사용하며, 이 도구는 진료과목 검색을 구현한 것이 아닙니다. 서비스 원문은 공공데이터포털 병원정보서비스, 지역코드는 보건의료데이터개방시스템의 코드 조회 안내와 대조할 수 있습니다.
지역명은 가나다순 번호가 아니라 명시적인 코드로 변환
약국 검색은 nalkkul_pharm_sido_codes()와 nalkkul_pharm_sggu_codes()에서 코드를 찾습니다. 예를 들어 서울 강북구와 강서구는 이름이 비슷해도 서로 다른 코드를 사용합니다. 다음 값은 2026-10-22 배포 코드와 실제 조회를 대조한 예입니다.
| 시·도 | 선택 지역 | sidoCd | sgguCd |
|---|---|---|---|
| 서울특별시 | 강북구 | 110000 | 110024 |
| 서울특별시 | 강서구 | 110000 | 110003 |
| 부산광역시 | 강서구 | 210000 | 210012 |
수원시처럼 여러 구로 나뉜 선택지는 하나의 코드가 아니라 배열로 저장합니다. 현재 약국 코드의 수원시는 310601, 310602, 310603, 310604를 순회합니다. 각각을 조회한 뒤 같은 기관을 합치기 때문에, 시 전체 선택을 임의의 단일 구 코드로 바꾸면 안 됩니다. 행정구역 변경 시에는 선택 목록과 코드 매핑을 함께 점검해야 합니다.
API 요청: 이름도 전달하고 값은 한 번만 인코딩
다음은 nalkkul_pharm_fetch()의 요청값 구성 부분입니다. $sido_cd와 $sggu_cd는 앞서 검증한 매핑 값이고, $name은 선택 입력입니다. 키의 실제 값은 서버 설정에만 두고 브라우저나 로그에 전달하지 않습니다.
$params = array(
'serviceKey' => NALKKUL_DATA_GO_KR_KEY,
'sidoCd' => $sido_cd,
'sgguCd' => $sggu_cd,
'pageNo' => '1',
'numOfRows' => '20',
'_type' => 'json',
);
if ( ! empty( $name ) ) {
$params['yadmNm'] = $name;
}
약국 이름은 API의 yadmNm 조건으로 전달하고, 돌아온 항목에도 PHP의 mb_stripos()로 이름 포함 여부를 다시 검사합니다. 브라우저가 전체 목록을 받아 이름만 걸러내는 구조가 아닙니다. 검색 대상은 API가 이번 요청에 반환한 첫 페이지이므로 이름 검색 결과도 전체 기관 목록이라고 해석해서는 안 됩니다.
현재 코드는 각 매개변수의 인코딩 전 값에 rawurlencode()를 적용해 URL을 조립합니다. 이는 http_build_query()가 사용할 수 없는 함수라는 뜻이 아닙니다. 인코딩 전 값을 http_build_query($params, '', '&', PHP_QUERY_RFC3986)에 넘겨도 RFC 3986 방식의 쿼리를 만들 수 있습니다. 이미 인코딩된 키에 다시 인코딩을 적용하지 않는 것이 중요합니다. 자세한 동작은 PHP 공식 문서에서 확인할 수 있습니다.
응답을 받았다고 지역·종류가 맞는 것은 아님
두 도구는 통신 오류, HTTP 상태, JSON 구조, API 결과코드를 차례로 확인합니다. HTTP 200만으로 성공 처리하지 않으며, 현재 허용하는 API 성공 코드는 00과 000입니다. 항목이 하나면 배열로 감싸 같은 순회 흐름으로 처리합니다.
그다음 응답 항목의 sidoCd와 sgguCd를 요청한 지역과 다시 대조합니다. 병원 종류를 지정했다면 clCd도 확인합니다. 기관 식별값 ykiho가 있으면 이를 중복 제거에 쓰고, 없으면 기관명과 주소 조합을 사용합니다. 이 검사는 지역 조건이 맞지 않는 데이터를 그대로 표시하지 않기 위한 처리이지, 제공기관 자료가 항상 정확하다는 보증은 아닙니다.
// 약국 응답 검사 중 지역 검증 부분만 발췌
if ( (string) ( $item['sidoCd'] ?? '' ) !== (string) $sido_cd ) {
continue;
}
if ( ! in_array( (string) ( $item['sgguCd'] ?? '' ), $district_codes, true ) ) {
continue;
}
약국 파서는 이름 yadmNm, 주소 addr, 전화번호 telno를 우선 읽습니다. 코드에 dutyName·dutyAddr·dutyTel1 호환 처리가 남아 있지만, 이것이 두 번째 API를 자동 호출한다는 의미는 아닙니다. 현재 조회는 위에 적은 단일 약국 경로를 사용하며, 연결 실패 때는 오류 안내를 반환합니다. 가짜 기관이나 성공한 것처럼 보이는 대체 목록은 만들지 않습니다.
시·군·구 선택, nonce와 캐시는 서로 다른 역할
약국 화면은 PHP의 지역 목록을 wp_json_encode()로 브라우저에 전달합니다. 시·도를 바꿀 때는 이 목록으로 옵션을 다시 만들고, 실제 검색 버튼을 눌렀을 때 WordPress AJAX로 요청합니다. 지원하지 않는 지역은 서버에서도 거절합니다.
약국 요청의 action은 nalkkul_pharm_search이며 입력 이름은 city, district, pharm_name입니다. 병원은 nalkkul_hosp_search에 hosp_type과 hosp_name을 추가합니다. 각 핸들러는 자기 nonce를 검사합니다.
다만 공개 검색용 nonce는 로그인 인증이나 호출 횟수 제한이 아닙니다. 공개 페이지에서 값을 얻을 수 있으며 nonce가 있다는 이유만으로 외부 반복 호출이나 API 한도 소진이 방지되지는 않습니다. WordPress도 공식 nonce 안내에서 인증·권한 확인을 대체하지 않는다고 설명합니다.
현재 조회 결과 캐시는 약국·병원 모두 3,600초(1시간)로 설정되어 있습니다. 약국 키에는 지역과 이름, 병원 키에는 지역·종류·이름이 포함됩니다. 같은 조건의 재조회는 캐시를 재사용하고, 통신/API 오류를 정상 결과처럼 저장하지 않습니다. 정상 응답을 필터링한 뒤 항목이 없으면 빈 결과로 캐시될 수 있습니다. 캐시 시간은 사이트의 재조회 정책이지 원자료가 갱신되는 주기나 영업정보의 유효기간이 아닙니다.
2026-10-22 서버 조회 점검과 재현 방법
약국 도구에서 이름 입력을 비우고 아래 지역을 각각 조회했습니다. 서울 강북구·서울 강서구·부산 강서구에서 각각 20건을 반환했고, 반환 항목의 주소에 선택한 구 이름이 포함됨을 확인했습니다. 세 조회의 영업시간 값은 모두 비어 있었습니다. 이는 해당 날짜의 서버 측 조회 점검이며 직접 방문이나 전화 확인 결과가 아닙니다. 결과 건수는 이후 달라질 수 있고, 20건이 지역 전체 약국 수를 뜻하지 않습니다.
- 약국 찾기에서 서울특별시 → 강북구를 선택하고 이름을 비운 채 검색합니다.
- 주소가 선택한 지역인지 확인한 뒤, 서울 강서구와 부산 강서구로 조건을 바꿔 비교합니다. 같은 ‘강서구’라도 시·도 코드가 달라야 합니다.
- 기관명을 입력하면 이름 조건을 추가한 별도 검색이 됩니다. 결과가 없다고 해당 지역에 기관이 전혀 없다고 판단하지 않습니다.
- 방문 후보를 찾았다면 카드의 연락처로 현재 운영 여부를 확인합니다. 이 글의 점검에서는 통화를 실행하지 않았습니다.
병원은 같은 날 서울특별시 → 강북구 → 의원(clCd=31) 조건으로 30건을 확인했고, 반환 주소의 강북구 포함 여부와 기관 종류의 의원 일치를 대조했습니다. 병원 찾기에서 같은 조건을 재현할 수 있습니다. 약국은 코드별 첫 20건, 병원은 요청별 첫 30건을 가져오며 자동 페이지 순회는 구현하지 않았습니다. 여러 구 코드가 연결된 도시에서는 코드별 결과를 합치므로 합계가 이 수보다 많을 수 있습니다. 필터링·중복 제거 후에는 더 적을 수도 있습니다. 표시 순서는 거리순이나 추천 순위가 아닙니다.
이 구현이 확인하지 못하는 것
- 지금 문을 열었는지: 약국 파서에는 요일별 시간 필드가 있을 때 표시하는 코드가 있지만 기본목록 조회에서 값이 항상 오는 것은 아닙니다. 시간 값이 있어도 임시 휴무까지 검증한 것은 아닙니다.
- 가장 가까운 기관인지: 현재 위치를 기준으로 전체 기관의 거리를 계산해 정렬하는 기능이 아닙니다.
- 지역의 모든 기관인지: 지원 지역 목록과 첫 페이지 조회 제한이 있습니다. 전체 수·누락 없는 목록·의료서비스 품질을 보증하지 않습니다.
- 빈 결과와 장애의 차이: 정상 응답에서 조건에 맞는 항목이 없으면 빈 결과를, 연결·권한·응답 오류이면 오류 메시지를 보여줍니다. 오류를 ‘기관이 없음’으로 바꿔 안내하지 않습니다.
이 도구의 목적은 연락처 후보를 찾는 것입니다. 주말·공휴일 방문 전 확인 절차는 병원·약국 방문 전 체크리스트에 정리했습니다. 다른 생활 도구는 도구 모음에서 찾을 수 있습니다.