매일 아침 07:30에 FnGuide 리서치에서 어제자~오늘자 채권 리포트를 모두 내려받아
G:\내 드라이브\rawdata\Report\국내리포트\채권 에 저장한다.
Chrome 확장 FnGuide Report Downloader v4.5(C:\Users\USER\Downloads\fnguide-dl-v4.5)가
하던 일을 사람 손 없이 수행한다. 확장의 팝업 버튼을 마우스로 누르는 흉내를 내지 않고,
그 뒤에서 실제로 오가던 두 호출을 그대로 재현했다.
목록 검색은 확장과 동일하게 POST /Research/GetReports 를 로그인된 페이지 안에서
호출한다. 쿠키와 헤더가 브라우저가 보내는 것과 완전히 같아진다.
PDF 받기는 확장이 만들어진 2026년 2월과 방식이 달라졌다. 확장은 뷰어 페이지 HTML에서
documentPath(PDF 직접 주소)를 뽑아 그 주소를 GET 했는데, 지금 FnGuide는 Syncfusion
뷰어로 바뀌어 documentPath가 빈 문자열이다. 대신 이렇게 동작한다.
GET /Research/PdfViewer?rptId={RPT_ID}— 페이지에 한 번만 쓸 수 있는 토큰이 hidden input#documentData로 들어 있다.- 뷰어가 그 토큰을
POST /Research/GetPdfFile로 보낸다. - 응답은 JSON 한 덩어리이고 그 안에 PDF가 base64로 들어 있다.
{"dataSet": "data:application/pdf;base64,JVBERi0..."}
토큰은 페이지가 열리는 순간 뷰어가 소진하므로, 같은 토큰으로 우리가 다시 요청하면 400이
돌아온다. 헤더(X-Requested-With)나 Referer를 맞춰도 마찬가지다. 그래서 우리가 따로
요청하지 않고 뷰어가 스스로 받아온 응답을 Chrome 개발자도구 프로토콜(CDP)로 가로챈다.
start_browser() 가 성능 로그를 켜 두는 이유가 이것이다.
버튼 클릭 자동화가 아니므로 화면이 잠겨 있어도 동작한다 — 07:55 자금판 작업과 같은 조건이다.
일부 증권사(확인된 곳: 한국투자증권)는 자사 고객에게만 리포트를 공개한다. 이런 건은 뷰어에
본문 글자는 떠도 #documentData 토큰이 아예 없어서 누구도 PDF로 내려받을 수 없다.
확장 프로그램으로도 안 된다. 스크립트는 이걸 오류가 아니라 제공사 제한으로 세어 보고하고,
재시도하지 않으며, 다음 실행에서 또 붙잡지 않도록 처리 완료로 기록해 둔다.
| 항목 | 값 |
|---|---|
| 분류 | 채권 (menuCd=1050) |
| 소분류 | 전체 (소분류를 비우면 대분류 코드로 검색된다) |
| 기간 | 어제 ~ 오늘 |
| 검색어 | 없음 |
| 증권사 / 업종 | 전체 |
| 최소 페이지 | 0 (제한 없음) |
| 최신 갯수 | 0 (전체) |
| 간격 | 2초 |
git clone https://github.com/T19340/Daily_FN_Downloader.git
cd Daily_FN_Downloader
pip install selenium keyring
python set_credentials.pyChrome 브라우저가 있어야 한다. Selenium이 맞는 드라이버를 알아서 받아 온다.
기기마다 다른 것이 두 가지 있으니 확인한다.
| 무엇 | 어디 | 기본값 |
|---|---|---|
| 저장 폴더 | download_fnguide_bond.py 의 SAVE_DIR |
G:\내 드라이브\rawdata\Report\국내리포트\채권 |
| 로그인 정보 | Windows 자격 증명 관리자 | 기기마다 set_credentials.py 로 새로 넣는다 |
파이썬 경로는 예전에 손으로 적어 주어야 했으나 이제 run_fnguide_bond.ps1 이 스스로 찾는다.
py 런처, PATH, 흔한 설치 위치를 차례로 뒤지되 selenium 과 keyring 이 실제로 import 되는지
확인한 뒤 그 파이썬을 쓴다. 파이썬이 여러 개 깔린 기기에서 엉뚱한 것을 잡지 않기 위해서다.
못 찾으면 브라우저를 띄우지 않고 종료 코드 2로 멈추면서 그 사실을 로그에 남긴다.
로그인 세션(chrome-profile\)과 실행 기록(downloaded.json)은 기기마다 다르므로 저장소에
넣지 않는다. 첫 실행 때 자동으로 생긴다. 즉 새 기기에서는 과거 리포트를 다시 받는다 —
이미 있는 파일은 이름으로 건너뛰므로, 같은 저장 폴더를 공유한다면 중복은 생기지 않는다.
매일 07:30 예약 작업으로 등록하려면 (PowerShell):
$script = "$PWD\run_fnguide_bond.ps1"
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$script`""
$trigger = New-ScheduledTaskTrigger -Daily -At 7:30am
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERNAME" -LogonType Interactive -RunLevel Limited
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -WakeToRun -ExecutionTimeLimit (New-TimeSpan -Hours 1)
Register-ScheduledTask -TaskName 'FnGuideBond_0730' -Action $action -Trigger $trigger `
-Principal $principal -Settings $settings -Description 'FnGuide 채권 리포트 자동 다운로드'python set_credentials.py아이디와 비밀번호를 물어보고 Windows 자격 증명 관리자에 저장한다. 비밀번호는 입력하는
동안 화면에 보이지 않는다. 저장된 항목 이름은 fnguide-bond-downloader 이고,
제어판 > 자격 증명 관리자 > Windows 자격 증명 에서 확인·삭제할 수 있다.
설정 파일에 평문으로 적지 않는 이유가 두 가지다. 하나는 비밀번호를 디스크에 그대로 두지
않으려는 것이고, 다른 하나는 이 컴퓨터의 회사 DRM(MarkAny Document SAFER)이 메모장
저장을 가로채 파일을 암호화해 버리기 때문이다. 메모장으로 만든 설정 파일은 머리에
<DOCUMENT SAFER V2010 R2> 가 붙어 파이썬이 읽지 못한다.
로그인 정보가 없으면 스크립트는 브라우저를 띄우지 않고 즉시 멈춘다(종료 코드 2).
$env:PYTHONIOENCODING="utf-8"
python download_fnguide_bond.py # 어제 ~ 오늘 (예약 작업과 동일)
python download_fnguide_bond.py 2026-07-18 # 특정 하루
python download_fnguide_bond.py 2026-07-01 2026-07-18 # 기간 (양 끝 포함)
python download_fnguide_bond.py 2026-07-16 --out "D:\어딘가" # 저장 폴더를 그때만 바꿈날짜는 YYYY-MM-DD와 YYYYMMDD를 모두 받고, 두 날짜의 순서가 뒤바뀌어도 알아서 정렬한다.
260718_삼성증권_홍길동_제목.pdf 형태다. 앞이 여섯 자리 YYMMDD 다.
원래 확장은 파일명 앞부분을 202607처럼 연월까지만 넣어 같은 달 리포트의 날짜가
구분되지 않았다. 매일 도는 작업에서는 날짜가 중요하므로 연월일을 모두 넣는다.
여덟 자리(20260718)가 아니라 여섯 자리인 이유는 기존 아카이브가 그 규칙이기 때문이다.
이게 어긋나면 중복 방지가 헛돈다 — 같은 리포트가 아카이브에 260716_… 로 있는데
새로 받은 것이 20260716_… 이면 다른 파일로 보여 그대로 두 번 저장된다.
ANL_DT 는 "26.07.16" 처럼 연도가 두 자리로 오므로 그대로 쓰면 되고,
네 자리로 오는 경우에도 앞 두 자리를 떼어 같은 규칙으로 맞춘다.
한 번 받은 리포트는 downloaded.json 에 RPT_ID로 기록해 두고 다음 실행에서 건너뛴다.
저장 폴더에 같은 이름의 파일이 이미 있어도 건너뛴다. 어제~오늘로 기간이 겹치게 도는
이유가 이것이다 — 늦게 올라온 어제자 리포트를 놓치지 않으면서 같은 파일을 두 번 받지 않는다.
전부 다시 받고 싶으면 downloaded.json 을 지운다.
- G: 드라이브(구글 드라이브)가 연결돼 있어야 한다. 로그온 세션에서만 붙으므로 예약 작업도 로그인한 사용자 세션에서 실행하도록 등록돼 있다. 쓸 수 없으면 Chrome을 띄우기 전에 멈춘다.
- 비밀번호를 여러 번 틀리면 FnGuide 계정이 잠길 수 있다. 로그인 실패를 감지하면 재시도하지
않고 즉시 중단한다. 비밀번호를 바꾼 뒤에는
set_credentials.py를 다시 실행해야 한다. - 로그인 세션은
chrome-profile\에 남지만 하룻밤을 넘기지 못한다. 매일 아침 실행은 사실상 매번 새로 로그인한다고 보면 된다 — 아이디·비밀번호로 자동 복구된다. - 계정당 동시 접속은 하나뿐이다. 다른 기기나 브라우저(크롬 확장 포함)에 세션이 살아 있으면
로그인 버튼을 눌러도 주소가 바뀌지 않고
이미 로그인된 다른 기기가 있습니다확인 창에서 멈춘다. 무인 실행에는 눌러 줄 사람이 없으므로confirm_kick_other_device()가 [확인]을 눌러 기존 접속을 끊고 이 기기로 진행한다. 낮에 브라우저로 쓰던 FnGuide가 아침에 튕겨 있다면 그것이 이유다. - 로그인 실패가 났을 때는 걸린 시간이 원인을 가른다. 비밀번호가 틀리면 오류 문구가 떠서 즉시
끊기고, 30초를 꽉 채웠다면 위 확인 창처럼 화면에서 멈춘 것이다.
run_log.txt의 시작·종료 시각으로 구분한다. - 같은 프로필로 두 번 동시에 실행할 수 없다.
- 같은 프로필로 실행하다 중간에 강제로 끊기면 Chrome 프로세스가 남아 프로필이 잠긴다.
다음 실행이
session not created: DevToolsActivePort file doesn't exist로 죽으면, 명령줄에 이 폴더 경로가 들어 있는 chrome 프로세스만 골라 종료하면 된다. - FnGuide 사이트가 또 개편되면 위 호출들부터 다시 확인해야 한다.
API 응답 구조가 예상과 다릅니다(목록) 또는뷰어가 PDF를 불러오지 않음이 여러 건 연속(본문)이 그 신호다.
run_log.txt 에 매 실행의 시작·결과·종료 코드가 쌓인다. 1MB를 넘으면 최근 500줄만 남긴다.
종료 코드는 성공 0, 실패 있음 1, 설정 문제(로그인 정보 없음) 2, 로그인 실패 3이다.
제공사 제한은 실패로 세지 않으므로 그것만 있으면 종료 코드는 0이다.
2026-07-19에 2026-07-16자로 시험 실행:
- 25건을 검색해 24건을 정상 PDF로 저장(전부 머리가
%PDF). 나머지 1건은 한국투자증권7월 금통위, Open the door로, 위에 적은 제공사 제한 건이다. - 다시 실행하니 24건은 건너뛰고 제한 1건만 다르게 보고했다 — 중복 방지가 동작한다.
- 저장 폴더에 쓸 수 없으면 Chrome을 띄우기 전에 멈추는 것도 확인했다.