파이썬 Selenium 다운로드 파일이 사라질 때는 저장 경로, 다운로드 완료 여부, driver.quit() 시점을 이 순서로 확인해야 합니다. 화면에서 다운로드 버튼을 눌렀다는 사실만으로 후속 Python 코드가 파일을 바로 읽을 수 있는 것은 아닙니다. 특히 헤드리스 Chrome이나 예약 실행에서는 현재 작업 디렉터리와 브라우저 종료 시점이 달라져 문제가 더 쉽게 드러납니다.
먼저 저장 폴더부터 고정합니다
Chrome 옵션의 download.default_directory는 다운로드 디렉터리를 지정하는 설정입니다. ChromeDriver 공식 문서는 이 값에 절대 경로를 사용하는 방식을 권장합니다. 예약 실행이나 서비스 실행처럼 현재 작업 디렉터리가 달라지는 환경에서 상대 경로를 사용하면, 개발 중에 보던 폴더와 실제 저장 폴더가 달라질 수 있기 때문입니다.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
download_dir = Path("/tmp/selenium-downloads").resolve()
download_dir.mkdir(parents=True, exist_ok=True)
options = Options()
options.add_argument("--headless=new")
options.add_experimental_option(
"prefs",
{
"download.default_directory": str(download_dir),
"download.prompt_for_download": False,
},
)
driver = webdriver.Chrome(options=options)
예제의 /tmp/selenium-downloads는 실행 환경에서 애플리케이션이 실제로 쓰는 전용 폴더로 바꾸어야 합니다. Chrome이 다운로드 디렉터리로 허용하지 않는 경로도 있으므로, 애플리케이션이 실제로 쓰고 읽을 수 있는 전용 폴더를 정하는 편이 안전합니다. 경로를 설정한 뒤에는 다운로드 직후 다른 폴더를 확인하지 말고, 코드가 지정한 download_dir에서 파일을 찾으십시오.
파일이 생겼는지보다 완료됐는지를 확인합니다
Selenium 공식 문서가 설명하듯 WebDriver는 브라우저 다운로드 진행률을 일반적인 API로 노출하지 않으며, 파일 목록도 즉시 스냅샷일 뿐 완료를 기다려 주지 않습니다. Chrome 다운로드는 진행 중인 파일과 완료된 파일을 구분합니다. 진행 중에는 임시 파일에 데이터가 기록되고, 완료되면 대상 파일명으로 바뀌는 흐름을 사용합니다.
따라서 다운로드 버튼을 누른 직후 glob() 결과가 비어 있지 않다는 이유만으로 다음 단계로 넘어가면 안 됩니다. 임시 확장자를 가진 항목을 완료 파일로 취급하지 않고, 대상 파일이 생긴 뒤 크기가 더 이상 변하지 않는지 확인하는 방식이 실무적으로 안전합니다. 제공된 공식 문서는 특정 대기 시간이나 안정성 확인 횟수를 정하지 않으므로, 서비스의 파일 크기와 허용 지연에 맞춰 제한 시간을 정해야 합니다.
import time
from pathlib import Path
def wait_for_download(folder: Path, timeout: float = 60.0) -> Path:
deadline = time.monotonic() + timeout
previous_size = None
stable_count = 0
while time.monotonic() < deadline:
temporary = list(folder.glob("*.crdownload"))
completed = [
path for path in folder.iterdir()
if path.is_file() and path not in temporary
]
if completed:
candidate = max(completed, key=lambda path: path.stat().st_mtime)
size = candidate.stat().st_size
if size == previous_size:
stable_count += 1
else:
previous_size = size
stable_count = 0
if stable_count >= 1 and not temporary:
return candidate
time.sleep(0.5)
raise TimeoutError("다운로드 완료 파일을 확인하지 못했습니다.")
이 코드는 파일이 존재하는지와 임시 파일이 남아 있는지를 함께 보고, 같은 크기가 다시 관찰된 뒤 반환합니다. 파일명 규칙을 알고 있다면 completed 후보를 그 규칙으로 더 좁히는 편이 좋습니다. 같은 폴더에 이전 실행 파일이 남아 있으면 새 다운로드와 혼동할 수 있으므로, 실행 전에 전용 폴더를 비우거나 시작 시점의 파일 목록을 기록해 새로 생긴 파일만 비교하는 방법도 고려하십시오.
driver.quit()은 확인 뒤에 호출합니다
ChromeDriver 공식 문서는 ChromeDriver가 다운로드 완료를 자동으로 기다리지 않는다고 설명합니다. 특히 ChromeDriver는 브라우저 다운로드가 끝날 때까지 자동으로 기다려 주지 않으므로, 파일이 생기기 전에 프로그램이 종료되면 후속 처리에서 파일이 없는 것처럼 보일 수 있습니다.
브라우저를 만든 함수가 다운로드를 시작하자마자 finally에서 driver.quit()을 호출하면, 다운로드가 끝나기 전에 브라우저가 닫힐 수 있습니다. 흐름은 다운로드 시작 → 완료 파일 확인 → pandas·업로드 등 후속 처리 → driver.quit() 순서로 두는 것이 기본입니다.
try:
driver.get(download_page)
download_button.click()
downloaded_file = wait_for_download(download_dir)
# 여기에서 downloaded_file을 읽거나 다음 처리로 전달합니다.
finally:
driver.quit()
download_page와 download_button은 실제 페이지에 맞게 준비해야 하는 값입니다. 핵심은 quit()을 없애는 것이 아니라, 다운로드 완료를 확인할 수 있는 지점을 quit()보다 앞에 두는 것입니다. 제한 시간 안에 완료되지 않으면 예외를 남기고 브라우저를 닫아야 다음 예약 실행이 같은 실패를 조용히 반복하지 않습니다.
Selenium을 계속 쓸지 HTTP 다운로드로 넘길지
브라우저가 필요한 이유가 로그인 화면 이동, 버튼 클릭, 링크 생성이라면 Selenium을 앞 단계에 사용할 수 있습니다. 반대로 다운로드 링크가 확인되고 필요한 인증 쿠키를 전달할 수 있다면, Selenium 공식 문서가 권하는 것처럼 링크와 쿠키를 얻은 뒤 requests나 curl 같은 HTTP 클라이언트로 파일을 받는 방식을 검토할 수 있습니다.
판단 기준은 간단합니다.
- 브라우저 동작 자체가 파일 URL을 만들거나 인증 상태를 준비한다면 Selenium 다운로드를 유지하되 완료 확인을 구현합니다.
- 파일 URL과 인증 정보를 안정적으로 확보할 수 있고 파일 전송만 필요하다면 HTTP 클라이언트로 분리하는 편이 진행률과 오류 처리를 다루기 쉽습니다.
- CAPTCHA 우회나 특정 사이트의 로그인 우회는 이 글의 범위가 아니며, 인증 정보를 코드에 직접 넣지 말고 실행 환경의 안전한 방식으로 전달해야 합니다.
점검 순서 요약
download.default_directory가 지정한 실제 폴더와 후속 코드가 읽는 폴더가 같은지 확인합니다.- 상대 경로 대신 실행 환경에서 해석이 변하지 않는 절대 경로를 사용합니다.
- 임시 파일이 남아 있는 동안 완료로 판단하지 않고, 대상 파일의 존재와 크기 안정성을 확인합니다.
- 완료 확인과 후속 처리가 끝난 뒤
driver.quit()을 호출합니다. - 링크와 필요한 쿠키를 얻을 수 있다면 HTTP 클라이언트 전환을 검토합니다.
이 순서로도 파일이 없다면 먼저 다운로드 폴더의 실제 목록과 파일명을 로그로 남기십시오. 그 다음 Chrome이 허용하지 않는 경로인지, 이전 실행 파일을 새 파일로 잘못 읽은 것은 아닌지, 제한 시간 안에 브라우저를 닫은 것은 아닌지를 분리해 확인하면 됩니다.