기웅정보통신
경력근거: 사이트 차단 대응, 운영 백오피스 개선 및 수집 플랫폼 정비
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고는 사이트 차단 대응과 운영 백오피스 개선을 주요업무에 두고, 자격요건으로 HTTP 프로토콜과 인증·세션 흐름의 깊은 이해를 요구하며, 도메인 경험으로 공동인증서 전자서명과 다단계 인증 처리를 듭니다. 이 포트폴리오는 그 세 가지를 골조로 씁니다. 응답 코드와 선택자 적중률로 차단과 구조 변경을 가르고, 인증 단계 기록으로 만료와 재시도를 가릅니다. 다만 이 포트폴리오는 로그 판정 규칙과 운영 화면 구현을 보여줍니다. 금융권 스크래핑 실무나 공동인증서 규격에 대한 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
주요업무에 사이트 차단 대응과 운영 백오피스 개선, 수집 플랫폼 정비가 나란히 적혀 있습니다. 수집기를 새로 짜는 일보다 이미 도는 수집이 왜 멈췄는지 가려내는 일이 반복됩니다.
자격요건이 HTTP 프로토콜과 인증·세션 흐름에 대한 깊은 이해와 문제 해결 역량을 요구하고, 도메인 경험으로 공동인증서 전자서명과 다단계 인증 처리를 듭니다. 실패 원인의 상당수가 인증 단계에서 생긴다는 뜻입니다.
AI Agent 와 LLM 을 써서 데이터팀의 개발·운영·수집 플랫폼을 AI Native 환경으로 전환할 엔지니어를 찾는다고 적습니다. 도구를 얹는 수준이 아니라 운영 흐름 자체를 바꾸는 자리입니다.
수집 담당자가 하루치 작업 로그를 올리면, 응답 코드와 선택자 적중률과 인증 단계 기록을 함께 읽어 실패 원인을 네 갈래로 나눠 보여줍니다. 원인 판정은 규칙이 정하고, 모델은 판정에 쓰인 응답 코드와 적중률을 담당자가 읽을 한 줄짜리 실패 사유로 요약합니다.
저는 금융기관 스크래핑 운영을 맡고 있습니다. 아침에 실패 목록을 보면 수백 건인데, 사이트가 우리를 막은 건지 화면이 바뀐 건지 인증서가 만료된 건지는 로그를 하나씩 열어 봐야 압니다. 원인이 같은 건들이 흩어져 있어 같은 확인을 몇 번씩 반복합니다.
실패 대응이 오래 걸리는 이유는 건수가 많아서가 아니라 원인이 섞인 채로 목록이 오기 때문입니다. 응답 코드와 선택자 적중률과 인증 단계를 조합해 원인을 먼저 나누면, 한 갈래를 고칠 때 같은 원인의 나머지도 함께 풀립니다.
수집 담당자가 쓰는 화면 하나가 돕니다. 작업 로그를 올리면 원인을 네 갈래로 나누고, 각 건의 판정 근거가 된 값을 같이 보여줍니다. 실제 수집기를 붙이는 일은 로그 파일을 올리는 것으로 대신하고 3주차로 미룹니다.
담당자가 사후에 확인한 실제 원인 120건을 넣어 규칙의 판정과 맞춰 보고, 어느 조합에서 갈리는지 표로 냅니다. 자주 틀리는 조합은 규칙을 나눠 다시 잡습니다.
수집기가 로그를 직접 보내오게 붙이고, 한 사이트에서 같은 원인이 기준을 넘으면 담당자에게 알립니다. 아침에 목록을 여는 대신 알림을 받고 들어옵니다.
수집 담당자가 하루치 로그를 올리면 실패가 원인별로 나뉘고, 각 건의 판정 근거 값이 같이 보입니다.
응답 코드가 비거나 선택자 적중률이 없거나 등록되지 않은 사이트 코드가 섞인 로그 30종을 올려도 화면이 죽지 않고, 판정할 수 없는 건은 미판정으로 남습니다.
정답 원인 120건으로 판정 일치율을 냅니다. 단, 정답과 데이터를 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 판정 정확도는 기웅정보통신의 실 수집 로그가 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 응답 코드 결측·적중률 경계값·등록되지 않은 사이트 코드를 더미에 일부러 섞어 쉬운 문제만 푸는 함정을 피합니다.
응답 코드가 200 이라고 수집이 성공한 것은 아닙니다. 차단 페이지도 200 으로 오므로 적중률을 함께 봐야 하며, 판정을 결론으로 읽지 않게 근거 값을 항상 같이 보여줍니다.
실제 도입에서는 사이트마다 인증 단계 수와 차단 방식이 다릅니다. 사이트별 임계값이 늘어나면 규칙을 코드에서 빼 설정으로 옮겨야 하며, 그것은 도입 단계 과제로 분리합니다.
실제 금융기관 접속과 인증서 처리는 계약과 사용자 동의가 있어야 하므로 이 포트폴리오의 범위 밖입니다. 여기서는 이미 남은 로그를 읽는 데까지만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
수집 워커가 남긴 하루치 작업 로그입니다. 판정의 입력이며 응답 코드 결측과 등록되지 않은 사이트 코드, 적중률 경계값을 의도적으로 포함합니다.
| log_no | ran_at | site_code_raw | site_id | http_status | selector_hit_ratio | auth_stage | auth_error | elapsed_ms | retry_count | rows_collected |
|---|---|---|---|---|---|---|---|---|---|---|
| LOG-2026W34-001 | 2026-08-18T06:02:11 | KB-BANK | site_kb | 200 | 1 | cert_login | — | 8,420 | 0 | 214 |
| LOG-2026W34-002 | 2026-08-18T06:04:39 | SH-BANK | site_sh | 200 | 0.08 | cert_login | — | 6,110 | 0 | 0 |
| LOG-2026W34-003 | 2026-08-18T06:07:52 | NH-BANK | site_nh | 403 | — | session_reuse | — | 1,180 | 3 | 0 |
| LOG-2026W34-004 | 2026-08-18T06:09:15 | WR-CARD | site_wr | 200 | 0.97 | otp | — | 15,230 | 1 | 96 |
| LOG-2026W34-005 | 2026-08-18T06:12:03 | IBK-BANK | site_ibk | 401 | — | cert_login | certificate expired | 2,240 | 2 | 0 |
아래 JSON Schema 에 맞는 수집 작업 로그 행을 더 만들어 주세요. {"type":"object","properties":{"log_no":{"type":"string"},"ran_at":{"type":"string"},"site_code_raw":{"type":"string"},"site_id":{"type":["string","null"]},"http_status":{"type":["integer","null"]},"selector_hit_ratio":{"type":["number","null"]},"auth_stage":{"type":"string","enum":["none","cert_login","otp","session_reuse"]},"auth_error":{"type":["string","null"]},"elapsed_ms":{"type":"integer"},"retry_count":{"type":"integer"},"rows_collected":{"type":"integer"}},"required":["log_no","ran_at","site_code_raw","auth_stage","elapsed_ms","retry_count","rows_collected"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) http_status 가 null 인 연결 끊김 행, (2) selector_hit_ratio 가 0.0 이나 1.0 인 경계 행, (3) site_code_raw 의 구분자와 대소문자가 흔들려 site_id 가 null 인 행, (4) 사이트 프로필에 등록되지 않은 site_code_raw. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"log_no":{"type":"string"},"ran_at":{"type":"string"},"site_code_raw":{"type":"string"},"site_id":{"type":["string","null"]},"http_status":{"type":["integer","null"]},"selector_hit_ratio":{"type":["number","null"]},"auth_stage":{"type":"string","enum":["none","cert_login","otp","session_reuse"]},"auth_error":{"type":["string","null"]},"elapsed_ms":{"type":"integer"},"retry_count":{"type":"integer"},"rows_collected":{"type":"integer"}},"required":["log_no","ran_at","site_code_raw","auth_stage","elapsed_ms","retry_count","rows_collected"]}| Python | 판정 규칙과 로그 적재를 맡는 언어입니다. 응답 코드·선택자 적중률·인증 단계를 조합해 원인을 정하는 규칙이 여기 있습니다. |
| Django | 운영 백오피스 화면과 로그 조회 API 를 맡습니다. 사이트별·원인별 필터가 여기 붙습니다. |
| API | 수집 워커가 작업 결과를 보내는 접수 창구입니다. 3주차에 파일 업로드 대신 이 경로로 받습니다. |
| DevOps | 워커와 백오피스를 컨테이너로 띄우고 로그 적재 주기를 관리합니다. |
| LLM | 원인 판정이 끝난 뒤 그 판정의 근거가 된 값을 한 줄짜리 실패 사유로 요약합니다. 원인은 규칙이 정합니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 판정이 실제 원인과 같았는지 맞춰 보는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 로그를 직접 받아 같은 원인이 늘면 알리는 단계로 확장합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
cause_code사유 라벨 · log_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
blocked차단layout_changed화면 구조 변경auth_expired인증 만료network_unstable연결 불안정undecidable미판정실패한 작업 한 건의 원인 판정입니다. 원인 코드와 판정 근거가 된 값을 함께 남깁니다.
{"type":"object","properties":{"finding_id":{"type":"string"},"log_no":{"type":"string"},"cause_code":{"type":"string","enum":["blocked","layout_changed","auth_expired","network_unstable","undecidable"]},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{"type":["string","number","null"]}},"required":["field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["finding_id","log_no","cause_code","evidence","summary"]}[
{
"finding_id": "fd_0001",
"log_no": "LOG-2026W34-002",
"cause_code": "layout_changed",
"evidence": [
{
"field": "http_status",
"agg": null,
"value": 200
},
{
"field": "selector_hit_ratio",
"agg": null,
"value": 0.08
}
],
"summary": "응답은 정상인데 선택자가 거의 걸리지 않아 화면 구조가 바뀐 것으로 봅니다.",
"hedge": null
},
{
"finding_id": "fd_0002",
"log_no": "LOG-2026W34-003",
"cause_code": "blocked",
"evidence": [
{
"field": "http_status",
"agg": null,
"value": 403
},
{
"field": "retry_count",
"agg": null,
"value": 3
}
],
"summary": "재시도 세 번이 모두 403 으로 막혀 차단으로 봅니다.",
"hedge": null
},
{
"finding_id": "fd_0003",
"log_no": "LOG-2026W34-005",
"cause_code": "auth_expired",
"evidence": [
{
"field": "auth_error",
"agg": null,
"value": "certificate expired"
},
{
"field": "http_status",
"agg": null,
"value": 401
}
],
"summary": "인증서 만료 메시지와 401 이 함께 나와 인증 단계에서 끊긴 것으로 봅니다.",
"hedge": null
},
{
"finding_id": "fd_0004",
"log_no": "LOG-2026W34-007",
"cause_code": "network_unstable",
"evidence": [
{
"field": "http_status",
"agg": null,
"value": null
},
{
"field": "elapsed_ms",
"agg": null,
"value": 30000
}
],
"summary": "응답 코드가 없고 제한 시간까지 걸려 연결이 끊긴 것으로 봅니다.",
"hedge": "합성 기준 판정이라 실제로는 상대 서버의 일시 지연일 수 있습니다."
},
{
"finding_id": "fd_0005",
"log_no": "LOG-2026W34-011",
"cause_code": "layout_changed",
"evidence": [
{
"field": "selector_hit_ratio",
"agg": null,
"value": 0
},
{
"field": "rows_collected",
"agg": null,
"value": 0
}
],
"summary": "선택자가 하나도 걸리지 않아 같은 사이트에서 구조 변경이 이어지는 것으로 봅니다.",
"hedge": null
},
{
"finding_id": "fd_0006",
"log_no": "LOG-2026W34-006",
"cause_code": "undecidable",
"evidence": [
{
"field": "site_code_raw",
"agg": null,
"value": "kb bank"
},
{
"field": "site_id",
"agg": null,
"value": null
}
],
"summary": "사이트 코드 표기가 흔들려 어느 사이트인지 붙지 않아 판정을 미룹니다.",
"hedge": null
},
{
"finding_id": "fd_0007",
"log_no": "LOG-2026W34-013",
"cause_code": "network_unstable",
"evidence": [
{
"field": "http_status",
"agg": null,
"value": 503
},
{
"field": "log_no",
"agg": "count",
"value": 2
}
],
"summary": "같은 사이트에서 5xx 응답이 그날 두 번 나와 상대 서버 쪽 문제로 봅니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. cause_code 가 enum 다섯 값 중 하나인지 확인합니다. 2. finding_id·log_no·cause_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. selector_hit_ratio 가 0 이상 1 이하인지 확인합니다. 4. evidence 의 field 이름이 작업 로그에 실재하는 컬럼인지 대조합니다. 5. agg 가 없는 evidence 의 value 가 그 log_no 행의 값과 같은지 확인합니다. 6. http_status 가 200 이면 cause_code 는 blocked 가 아닌지 확인합니다. 로그에는 200 이면서 선택자가 안 걸린 행과 200 이면서 정상 수집된 행이 모두 있습니다. 7. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 담당자가 사후에 확인한 실제 원인 120건과 규칙의 cause_code 를 비교해 일치율과 갈리는 조합을 냅니다. [수동 검증 · 사람이 봅니다] 1. 정상 수집된 행(LOG-2026W34-001·LOG-2026W34-012)은 실패가 아니라 판정 대상에서 빠집니다. 실패만 골라 왔는지 표본으로 확인합니다. 2. 재시도 끝에 성공한 건을 실패로 셀지 말지는 운영 기준을 따라 담당자가 정합니다.