API 인증 장벽 없이 CQC 케어 레지스터를 스크랩한 방법
출처: Dev.to
API 인증 장벽
CQC는 API를 api.service.cqc.org.uk 로 이전하고 베어러‑토큰 인증을 추가했습니다. 개발자 포털에 등록하고 애플리케이션을 만든 뒤, 모든 요청에 Authorization: Bearer <token> 헤더를 포함해야 합니다. 이는 엔터프라이즈 사용 사례에 치명적인 문제는 아니지만, 사용자가 CQC에 먼저 등록해야 액터를 실행할 수 있기 때문에 데이터 제품에 마찰을 추가합니다.
이전 API 베이스(api.cqc.org.uk/public/v1)는 이제 HTTP 403을 반환합니다 – 완전히 차단된 상태입니다.
오픈‑데이터 파일 구조
CQC는 매월 HSCA_Active_Locations.ods 라는 오픈‑데이터 파일을 공개합니다. 이 파일은 영국(≈ 56 000) 내 모든 활성 규제 시설을 담은 23 MB 규모의 OpenDocument 스프레드시트입니다. 파일은 무료이며 인증이 필요 없고, Open Government Licence 하에 배포됩니다. URL은 날짜가 포함되어 매달 바뀌지만, 투명성 페이지에서는 항상 최신 버전으로 연결됩니다.
접근 방법
- 투명성 페이지를 스크랩해 현재 ODS URL을 찾는다.
- 파일을 다운로드한다.
- 파싱하고, 행을 필터링한 뒤 결과를 푸시한다.
API도 없고 인증 장벽도 없습니다.
ODS 파싱 과제
ODS 파일(.ods)은 XML을 담은 ZIP 아카이브입니다. Node.js에서 이를 파싱하기 위한 표준 도구는 SheetJS(xlsx 패키지, v0.18.5 – 마지막 Apache 2.0 릴리즈)입니다.
시트 선택
워크북에는 README, HSCA_Active_Locations, Dual_Registration_Locations 세 개의 시트가 들어 있습니다. SheetJS는 기본적으로 첫 번째 시트(README)를 사용하며, 이 시트는 34행밖에 없습니다. 아래 코드는 행 수가 가장 많은 시트를 선택합니다:
let bestSheet = null;
let bestCount = 0;
for (const name of workbook.SheetNames) {
const sheet = workbook.Sheets[name];
const probe = XLSX.utils.sheet_to_json(sheet, { header: 1 });
if (probe.length > bestCount) {
bestCount = probe.length;
bestSheet = sheet;
}
}
컬럼 이름 처리
컬럼 이름은 Title Case와 공백으로 구성됩니다(예: Location Local Authority, Location Latest Overall Rating). 서드파티 문서에 나와 있는 snake_case와 다릅니다. 여러 변형을 시도해 보는 헬퍼 함수를 사용하면 향후 이름이 바뀌어도 파서를 견고하게 유지할 수 있습니다:
function col(row, ...candidates) {
for (const name of candidates) {
if (row[name] !== undefined) return row[name];
}
return undefined;
}
날짜 처리
워크북을 { cellDates: true } 옵션으로 읽으면 날짜가 JavaScript Date 객체로 반환되고, 좌표는 문자열로 반환됩니다. 두 값 모두 처리 단계에서 정규화합니다.
const workbook = XLSX.read(buffer, { type: 'buffer', cellDates: true });
보너스: 서비스 유형 및 사용자 밴드 추출
ODS 파일에는 122개의 컬럼이 있으며, 그 중 약 60개는 특정 서비스 유형 및 사용자 밴드에 대한 불리언 플래그(Y/null)입니다(예: Service type - Care home service with nursing). 각 컬럼을 일일이 매핑하는 대신, 활성화된 컬럼을 배열로 추출합니다:
function extractTagged(row, prefix) {
return Object.entries(row)
.filter(([key, val]) => key.startsWith(prefix) && (val === 'Y' || val === true))
.map(([key]) => key.slice(prefix.length));
}
결과 출력
{
"serviceTypes": ["Care home service without nursing"],
"serviceUserBands": ["Dementia", "Older People"]
}
결과
이 액터는 두 가지 모드를 지원합니다:
searchLocations– 지역, 평점, 서비스 유형으로 필터링.lookupProviders– 특정 제공자 ID에 해당하는 모든 위치를 조회.
매 실행 시 23 MB ODS 파일을 로드하고 파싱하는데 약 10–15초가 소요되며, 이후 메모리 내에서 필터링합니다. 프록시, 브라우저 자동화, 안티‑봇 처리 없이 동작합니다.
Apify Store에서 이용 가능: CQC Care Register Scraper