제 보고서의 숫자가 제 데이터랑 달랐습니다
“개발자 채용 시장이 얼어붙었다”는 말이 데이터로 사실인지 궁금했습니다. 공공 통계랑 채용 공고를 모아서 확인해보기로 했어요.
일주일쯤 지나서 1차 분석 보고서를 만들어놨는데, 여기서부터 이상했습니다. 정리해둔 코드와 분석은 hr-data-analysis-study에 올려뒀습니다.
1. 보고서에는 0.70, 파일에는 0.65였습니다
보고서에 이렇게 써놨습니다.
SW/AI 구인배율은 0.70 (구인 16,770건 / 구직 24,000명)
그런데 수집한 CSV를 열어보니 구인 12,450건, 구직 18,900명에 배율은 0.65였습니다. 숫자가 안 맞으니 어느 쪽이 맞는지 보려고 수집 스크립트를 열었어요.
worknet_sample = [
{"직종명": "소프트웨어 개발자 및 AI 엔지니어",
"월별구인건수": 12450, "월별구직건수": 18900,
"구인배율(구인/구직)": 0.65},
]
API 호출 코드 바로 아래에 이게 있었습니다. 호출이 실패해도 이 리터럴이 CSV로 저장됩니다. 그러니까 0.65는 제가 예시로 적어둔 값이었어요.
그럼 0.70은 어디서 왔을까요. 분석 스크립트를 봤습니다.
insight = """
- 구인배율은 0.70으로 ...
"""
print(insight)
위에서 실제 값을 계산해놓고, 출력은 하드코딩된 문자열로 하고 있었습니다. 계산 결과가 뭐가 나오든 화면에는 0.70이 찍혔어요.
두 숫자가 서로 달랐던 게 다행이었습니다. 같았으면 못 찾았을 겁니다.
2. 두 직군을 나눠 받았는데 같은 데이터였습니다
하드코딩을 걷어내고 API로 다시 받았습니다. AI/데이터 직군과 백엔드 직군을 따로 수집했는데, 결과가 두 직군에서 소수점까지 똑같이 나왔어요. AI 스택 5.0%, 전통 스택 11.0%.
job_id를 비교해봤더니 99/100이 같았습니다.
카테고리 필터 파라미터를 서버가 조용히 무시하고 있었습니다. HTTP 200에 정상 JSON이 왔고, 다만 필터가 안 걸린 전체 목록이었을 뿐이에요. 오류가 없으니 알아챌 방법이 없었습니다.
파라미터 이름을 몇 개 바꿔가며 결과가 달라지는 걸 찾았습니다. 그러다 하나 더 나왔는데, 제가 “AI/데이터 엔지니어”라고 라벨을 붙여둔 카테고리 ID가 실제로는 웹 개발자였어요. 해당 카테고리 페이지 제목을 확인하고 알았습니다.
즉 1차 보고서의 “AI 직무 vs 일반 직무 비교”는 같은 데이터를 두 번 센 것이었고, 라벨도 틀렸던 겁니다.
제대로 받고 나니 AI/데이터 44.5%, 백엔드 22.0%로 갈렸습니다.
3. 통계표는 받아졌는데 산업 구분이 없었습니다
KOSIS API 키를 발급받아서 돌렸더니 162행이 들어왔습니다. 성공 로그를 보고 넘어갈 뻔했는데, 컬럼을 열어봤어요.
“성별 경제활동인구 총괄 / 15세이상인구” 였습니다. 단위는 천명이고 분류는 계·남자·여자·농가·비농가. 산업 구분 자체가 없는 표였습니다.
정보통신업을 보려면 다른 표가 필요했습니다. 통계표 검색 API로 찾아서 DT_1DA9003S로 바꾸니 58 = J 정보통신업이 분리된 78개월 시계열이 나왔어요.
shape, unique(), isna().sum() 세 줄이면 걸렀을 일이었습니다.
4. 워크넷은 신청할 때 아무 말이 없었습니다
구인배율을 제대로 구하려고 워크넷 채용정보 API를 신청했습니다. 인증키도 정상 발급됐어요. 그런데 호출하니 이렇게 왔습니다.
<GO24><error>개인회원은 사용할 수 없는 OPEN-API입니다.</error></GO24>
신청 단계에서는 막지 않고 호출 시점에 차단합니다. 안내에 “OPEN-API는 고용24 기업회원 전용 서비스”라고 적혀 있긴 했는데, 신청 화면에서는 안 보였어요.
우회로를 찾다가 고용행정통계(EIS) API를 발견했습니다. 인증키가 아예 필요 없고, 신규구인인원수와 신규구직건수를 줍니다. 구인배율을 직접 계산할 수 있는 그 데이터예요.
문제는 시군구·성별·연령이 전부 필수인데 “전체” 코드가 없다는 거였습니다. 서울 25개 구 × 2성별 × 11연령 = 550회를 돌렸더니 91회에서 막혔습니다.
HITS_EXCEEDED : 해당 사용자의 IP에서 조회할 수 있는 OPEN API 조회 횟수가 초과됐습니다
IP당 일일 쿼터였습니다. 서울 한 달치가 6일, 1년치면 66일이 걸립니다.
그때까지 모인 데이터가 더 문제였어요. 25개 구 중 5개만, 그것도 불균등하게 들어와 있었습니다. 이대로 저장했으면 나중에 “서울 데이터”로 착각했을 겁니다. 그래서 지우고, 수집기에 분기를 넣었습니다.
if quota_hit:
# 부분 수집분은 커버리지가 들쭉날쭉해 통계로 쓸 수 없다.
# 편향된 데이터를 파일로 남기지 않는 것이 이 분기의 목적이다.
raise SystemExit("[중단] 조회 한도 초과. 부분 수집분은 저장하지 않습니다.")
결국 구인배율은 포기했습니다. 두 경로가 다 막혔고, EIS가 뚫렸어도 직종이 대분류 10개까지만이라 SW 개발자가 연구직 및 공학 기술직에 기계·화학·전기와 같이 묶여 나오더라고요.
비슷한 지표를 억지로 만드는 대신 “산출 불가”로 적어뒀습니다.
5. resultCode: 00인데 0건이었습니다
국민연금 API는 승인이 났는데 계속 totalCount=0이 나왔습니다. 응답 헤더는 00 NORMAL_CODE. 파라미터를 지역으로도 넣어보고 사업장명으로도 넣어봤는데 전부 0이었어요.
포털에서 승인 상태까지 확인했는데 정상이었습니다. 반영 대기라고 생각하고 하루를 기다렸습니다.
그러다 상세조회 오퍼레이션은 seq=1로 데이터가 나오는 걸 봤어요. 키도 데이터도 멀쩡하다는 뜻이었습니다. 목록 조회만 안 되는 거였죠.
파라미터 표기를 바꿔봤습니다.
| 요청 | 결과 |
|---|---|
wkpl_nm=대신정보통신 | resultCode 00 / totalCount=0 |
wkplNm=대신정보통신 | resultCode 00 / totalCount=26 |
camelCase였습니다. snake_case로 보내면 서버가 그 파라미터를 모르는 걸로 치고 무시합니다. 그런데 오류를 안 내고 조건 없는 빈 결과를 돌려주니, 화면상으로는 “승인이 안 났나”로 보였어요.
2번에서 겪은 것과 같은 종류였습니다. 200 OK도 resultCode: 00도 요청이 의도대로 해석됐다는 뜻이 아니었습니다.
6. 매칭률 100%가 나왔을 때 웃으면 안 됐습니다
채용 공고에 나온 기업명을 국민연금 사업장 데이터에 붙였습니다. 20개 중 20개, 100% 매칭됐어요.
결과를 열어봤습니다.
| 입력 | 매칭 결과 | 업종 |
|---|---|---|
| 글로벌 IT 기업 | 상호가 겹치는 건설사 | 유리 및 창호 공사업 |
| 식품 브랜드 | 같은 상호의 음식점 지점 | 일식 음식점업 |
검색 API가 부분 문자열로 매칭하는데, 후보 중 첫 번째를 고르는 폴백을 제가 넣어놨더라고요. 이름 앞부분만 같으면 업종이 뭐든 붙어버립니다.
정규화 후 완전 일치만 채택하도록 바꾸니 88.4%로 떨어졌습니다. 그게 맞는 숫자였어요. 놓치는 것보다 틀리게 붙이는 게 훨씬 나쁘니까요.
7. 정확히 0.0%가 두 개 나왔습니다
연차별로 나눠 보다가, 경력무관 구간의 AI 스택 언급률이 0.0%, 전통 스택도 0.0% 인 걸 봤습니다. 두 지표가 동시에 0인 건 이상했어요.
14건을 열어보니 전부 한 회사의 AI Tutor - {언어} 공고였습니다. 슬로바키아어, 루마니아어, 슬로베니아어… 개발 직군이 아니라 언어 데이터 라벨링이었고, 같은 템플릿으로 14건이 올라와 있었습니다.
김에 중복도 확인했습니다. 동일 본문이 19개 그룹 41건이었어요. 한 회사가 같은 JD를 5건까지 올려둔 것도 있었습니다.
408건 중 35건을 걷어내고 373건으로 다시 돌렸습니다. 직군 격차는 22.5%p에서 27.7%p로 오히려 벌어졌어요.
8. 한국과 해외가 정반대로 보였습니다
국내 데이터로는 시계열을 못 만들었습니다. 채용 공고는 과거 걸 받을 수 없고, 워크넷과 EIS는 위에 적은 대로 막혔어요.
그래서 해외를 봤습니다. Hacker News에 Ask HN: Who is hiring? 스레드가 2011년부터 매달 열려요. 형식이 같아서 월 단위 비교가 됩니다. Algolia API로 81개월치 39,831건을 받았습니다. 인증키도 필요 없고요.
받고 보니 국내 통계랑 방향이 반대였습니다.
| 한국 (KOSIS) | 글로벌 (HN) | |
|---|---|---|
| 물량 | 2026-06 역대 고점 (+39.7%) | 2021 대비 -77% |
처음엔 하나가 틀렸다고 생각했는데, 서로 다른 걸 재고 있었습니다.
KOSIS는 산업의 고용 스톡이에요. 정보통신업에 속한 사람 수고, 재직자가 유지되면 수치도 유지됩니다. HN은 신규 채용 플로우고요. 문이 몇 개 열려 있는지를 봅니다.
재직자는 늘어도 신규 진입문은 좁아질 수 있습니다. 국내 공고에서 신입 지원 가능한 게 12.9%(48/373건)뿐이었던 것과 같은 방향이었어요.
기술 스택은 훨씬 선명했습니다. AI 스택 언급률이 2020년 1.5%에서 2026년 19.0%로 올라갔습니다. 전통 백엔드 스택은 42.8%에서 35.9%로 내려갔고요. 국내 단면 데이터로는 못 하던 “스택이 이동했다”를 시간축 위에서 볼 수 있었습니다.
마지막으로 신입 진입문을 직접 비교해봤는데, 국내 12.9%, 해외 7.8%로 한국이 오히려 넓었습니다. 다만 국내 공고는 연차 필드가 필수라 “경력 무관”으로 열어두는 관행이 있고 HN은 스타트업·원격 중심이라, “한국이 관대하다”기보다 적히는 방식이 다르다로 보는 게 맞을 것 같습니다.
💡 팁 / 주의할 점
- 팁: 필터가 먹었는지 확인하는 건 한 줄이면 됩니다. 필터를 넣은 결과와 안 넣은 결과가 다른지 보면 돼요.
assert set(base) != set(filtered). - 팁: 수집 스크립트에 키가 없으면 중단하는 분기를 먼저 넣어두세요. 예시값을 하나 적어두면 그게 결국 보고서까지 갑니다.
- 주의할 점: 결과가 0건일 때 “데이터가 없다”로 읽지 마세요. 내 요청이 무시됐을 가능성이 먼저입니다. 특히 국내 공공 API는 모르는 파라미터를 오류 없이 버립니다.
- 주의할 점: 매칭률이나 정확도가 기대보다 좋으면 틀린 케이스부터 찾으세요. 100%는 대개 폴백이 숨어 있다는 신호였습니다.
- 주의할 점: 정확히 0% 또는 100%인 구간이 보이면 그 구간의 원본을 직접 열어보세요. 정상 데이터는 그렇게 깔끔하지 않습니다.
정리
한 달 중에 분석에 쓴 시간보다 “이 숫자 어디서 왔지”를 확인하는 데 쓴 시간이 훨씬 길었습니다.
결론 자체는 짧습니다. 채용 한파는 총량 기준으로는 사실이 아니었고, 대신 신입 진입문이 12.9%로 좁았고, 요구 스택은 AI 쪽으로 옮겨가는 중이었습니다.
그런데 이 결론보다, 처음에 제가 자신 있게 써놨던 0.70이 아무 데서도 안 나온 숫자였다는 게 더 오래 남네요. 발표 자리에서 “그 0.70 어디서 나온 거예요”를 들었으면 어땠을까 싶습니다.
그래서 수집 코드에 규칙을 박아뒀습니다. 결론 문자열에 숫자를 쓰지 않고, 키가 없으면 중단하고, 원본 응답을 남기고, 부분 수집분은 저장하지 않기로요. 다음에 제가 또 급해질 때를 위한 장치입니다.
태그: #데이터분석, #공공데이터, #KOSIS, #국민연금, #OpenAPI, #데이터검증, #파이썬, #pandas, #HackerNews, #채용시장