콘텐츠로 이동

HTTP 요청으로 파일 다운로드 자동화

11/12(목)

한 줄 요약: 브라우저의 "다운로드" 버튼도 결국 서버로 가는 HTTP 요청 하나다. 코드가 같은 요청을 보내면 사람 없이 파일을 받을 수 있다. 다만 공식 API가 아니면 사이트 개편에 약하고, 로그인이 필요한 자료는 못 받는다.

📍 만난 상황

  • 2026-11-12 13:01 — 침수 시나리오용 재난 데이터를 정리하던 중이었다(→ 작업 2). 직전에 "dhub 파이프라인으로 원천 파일을 주기적으로 수집할 수 있고, 대부분 이미 파이프라인이 있다"는 답을 들었다. 파일은 브라우저에서 사람이 직접 받아야 할 텐데, 파이프라인은 어떤 원리로 가져오는지 궁금했다. → 그날 일지

🧩 쉽게 말하면

식당에서 메뉴판 사진을 손가락으로 가리키면 직원이 주방에 주문서를 넣는다. 주방은 손가락을 본 게 아니라 주문서를 받은 것이다.

그러니 주문서 양식만 알면 전화로 같은 주문서를 넣어도 음식이 나온다. 코드가 하는 일이 이 "전화 주문"이다.

이게 없으면 매달 사람이 수십 개 페이지를 돌며 버튼을 누르고, 어떤 파일이 바뀌었는지 눈으로 비교해야 한다.

📖 제대로 알기

HTTP 요청과 응답

  • HTTP: 브라우저(클라이언트)와 서버가 주고받는 약속. 요청 하나에 응답 하나가 온다.
  • 요청의 구성: 메서드(GET 가져오기, POST 보내며 요청), URL(주소), 쿼리 문자열(?id=3 같은 덧붙인 값), 헤더(부가 정보), 본문(보내는 데이터).
  • 응답의 구성: 상태 코드(200 성공, 404 없음, 403 거부), 헤더, 본문(HTML·JSON·파일 바이트).
  • Content-Disposition 헤더: attachment; filename="a.csv"가 오면 브라우저는 화면에 띄우지 않고 파일로 저장한다. 사람에게는 이게 "다운로드"로 보인다.

다운로드 버튼 뒤에 있는 세 가지 모양

모양 버튼이 하는 일 자동화 방법
고정 링크 정해진 주소로 바로 이동 그 주소를 그대로 요청
자바스크립트 함수 downloadFile('3')처럼 값을 붙여 요청 HTML에서 값을 뽑아 같은 요청
내부 API 화면이 JSON API를 불러 목록을 그림 그 API를 직접 호출

관련 용어

  • 공식 API(OpenAPI): 기관이 "이렇게 불러 쓰라"고 공개한 주소. 형식이 잘 안 바뀐다.
  • 화면용 내부 주소: 웹 화면이 쓰려고 만든 주소. 공개 약속이 아니라 예고 없이 바뀐다.
  • 스크래핑(scraping): HTML(웹 페이지 원문)에서 필요한 값을 뽑아내는 일.
  • manifest(수집 기록): 무엇을 언제 받았는지 남긴 목록. "바뀐 것만 받기"에 쓴다.
  • sha256: 파일 내용으로 만든 지문. 내용이 한 글자만 달라도 값이 바뀐다.

⚙️ 어떻게 동작하나

  1. 요청 찾기 — 브라우저 개발자 도구의 네트워크 탭을 켜고 버튼을 눌러, 실제로 나가는 요청을 본다.
  2. 필요한 값 얻기 — 상세 페이지 HTML이나 내부 API 응답에서 파일 키(순번·첨부 ID·파일명·수정일)를 뽑는다.
  3. 같은 요청 보내기 — 다운로드 주소에 그 값을 넣어 요청한다.
  4. 저장·기록 — 응답 본문을 원본 그대로 저장하고, 크기와 sha256을 manifest에 남긴다.
  5. 다음 실행 — 키가 manifest에 있으면 건너뛰고, 수정일·첨부 ID가 바뀌었으면 새로 받는다.

공공데이터포털 파일을 받는 흐름이다.

sequenceDiagram
  participant P as 파이프라인
  participant W as 상세 페이지
  participant A as 조회 주소
  participant D as 다운로드 주소
  P->>W: 상세 페이지 GET
  W-->>P: HTML (버튼 함수에 내부 키)
  P->>A: 내부 키로 조회
  A-->>P: 현재 첨부 ID와 파일명
  P->>D: 첨부 ID로 다운로드 요청
  D-->>P: 파일 본문
버튼이 하던 두 번의 요청을 코드가 그대로 따라 한다. 포털은 파일을 갱신하면 첨부 ID가 바뀌어서, 고정 주소를 저장하지 않고 매번 1단계부터 다시 읽는다.

🧪 예시

  • 예시 1 · 일상에 빗대어: 매달 약 40개 파일을 사람이 받는다고 하자. 페이지 열기·버튼·저장·이름 정리에 1개 2분이면 80분이다. 게다가 "지난달과 같은 파일인가"를 눈으로 비교해야 한다. 코드는 40개 페이지를 읽고, 바뀐 3개만 받고, 지문까지 남긴 뒤 끝난다. 사람은 실패 기록만 보면 된다.

  • 예시 2 · 실제로는: 서울 열린데이터광장 방식을 단순화한 코드다(주소·파라미터 이름은 예시).

    page = requests.get(VIEW_URL, timeout=30).text
    seqs = re.findall(r"downloadFile\('(\d+)'\)", page)
    for seq in seqs:
        key = f"{DATASET_ID}|{seq}|{file_name(page, seq)}|{modified(page, seq)}"
        if key in manifest:
            continue
        r = requests.post(DOWNLOAD_URL,
                          data={"datasetId": DATASET_ID, "seq": seq}, timeout=60)
        r.raise_for_status()
        save(r.content)
        manifest[key] = hashlib.sha256(r.content).hexdigest()
        time.sleep(0.6)
    
    ① requests.get(VIEW_URL): "파일" 탭 페이지 HTML을 받는다. 브라우저가 화면을 여는 것과 같다. ② re.findall(...): 버튼에 붙은 downloadFile('순번')에서 순번을 뽑는다. ③ key 비교: 데이터셋ID·순번·파일명·수정일이 같으면 이미 받은 파일이라 건너뛴다. ④ requests.post(DOWNLOAD_URL, ...): 버튼이 보내던 다운로드 요청을 그대로 보낸다. ⑤ raise_for_status(): 4xx·5xx면 바로 실패로 처리한다. ⑥ sha256: 받은 파일의 지문을 기록한다. 나중에 내용이 같은지 확인할 때 쓴다. ⑦ sleep(0.6): 파일 사이에 간격을 둔다. 홍수위험지도 화면도 이 정도 간격을 둔다.

⚖️ 비교 · 흔한 오해

방식 장점 약점
공식 API 형식이 안정적, 약관상 명확 없는 자료가 많음, 키 신청 필요
화면용 요청 따라 하기 공개 파일이면 대부분 가능 사이트 개편 때 예고 없이 깨짐
브라우저 자동 조종(헤드리스) 복잡한 화면도 가능 무겁고 느림, 역시 깨지기 쉬움
  • 오해 1: "파일은 사람이 브라우저에서 받아야 한다" → 버튼은 요청을 보내는 장치일 뿐이다. 서버는 누가 보냈는지가 아니라 요청 내용을 보고 응답한다.
  • 왜 그렇게 생각하기 쉽나: 화면에는 버튼·저장 창·다운로드 폴더처럼 사람의 동작만 보인다. 그 사이의 요청은 보이지 않는다.
  • 오해 2: "공식 API가 없으면 자동화가 안 된다" → 된다. 다만 약속이 없는 주소라 깨질 각오를 하고 실패를 기록해야 한다.
  • 오해 3: "코드로 하면 뭐든 받을 수 있다" → 로그인·신청 승인이 필요한 자료(예: VWorld 공간정보 다운로드, 국토정보플랫폼 DEM)는 이 방식으로 못 받는다. CAPTCHA(사람 확인 문제)도 막힌다.

🛠️ 실무에서는

  • 먼저 공식 API가 있는지 찾는다. 없을 때만 화면 요청을 따라 한다.
  • 이용약관을 확인하고, 요청 사이에 간격을 둔다. 서버에 부담을 주지 않는다.
  • 고정 주소를 저장하지 말고 매번 목록부터 다시 읽는다. 첨부 ID가 바뀌는 사이트가 많다.
  • 실패하면 manifest에 failed와 이유를 남긴다. 깨진 걸 바로 알 수 있다.
  • 원본은 그대로 저장하고, 변환(CSV 파싱 등)은 다음 단계로 분리한다.
  • 문제가 생기면 먼저 볼 것
  • 상태 코드: 403이면 차단·로그인, 404면 주소 변경.
  • 받은 "파일"이 사실 HTML 오류 페이지가 아닌지: 크기와 Content-Type을 본다.
  • 뽑은 키가 0개인지: 0개면 페이지 구조가 바뀐 것이다.

❓ 자주 묻는 질문

  • Q. 버튼이 보내는 요청은 어떻게 찾나? A. 개발자 도구(F12) → Network 탭 → 버튼 클릭. 나온 요청을 우클릭해 "Copy as cURL"로 복사하면 그대로 다시 보낼 수 있다.
  • Q. 로그인이 필요한 사이트는 절대 못 하나? A. 기술적으로는 로그인 쿠키를 써서 할 수 있지만, 계정 보안과 약관 문제가 있다. 공식 API 키·신청 경로를 쓰는 게 맞다.
  • Q. 매번 전부 다시 받으면 안 되나? A. 된다. 하지만 트래픽이 낭비되고, "언제 바뀌었는지" 이력이 남지 않는다.
  • Q. 화면이 자바스크립트로 그려져 HTML에 값이 없으면? A. 화면이 부르는 내부 API를 찾아 직접 부른다. 홍수위험지도 정보시스템이 이 경우다.

🔭 더 깊이 보기

📚 참고 자료

  • HTTP 개요 (MDN 한국어) — 요청·응답의 구성과 메서드 · 링크
  • Content-Disposition (MDN 한국어) — 브라우저가 "다운로드"로 처리하게 만드는 헤더 · 링크
  • 네트워크 활동 검사 (Chrome DevTools 한국어 문서) — 버튼이 보내는 요청을 찾는 법 · 링크

🗓️ 이력

  • 2026-11-12: 파일 원천을 파이프라인이 어떤 원리로 받는지 물음(버튼 요청 따라 하기, 바뀐 것만 받기, 한계) → 일지

🕸️ 연결된 주제