빌릴 책 열 권, 어느 도서관에 가야 할까 — 공공 데이터로 하루 만에 만든 「책 동선」
읽고 싶은 책 목록을 넣으면 지금 대출 가능한 지점을 찾아 최소 동선을 짜 주는 도구를 만들었습니다. 공식 API가 막힌 자리에서 길을 찾고, 버튼이 눌리지 않던 하루를 통과한 기록입니다.

빌릴 책 열 권, 어느 도서관에 가야 할까
공공 데이터로 하루 만에 도구 하나 만들기 — 「책 동선」 제작기
세 권째에서 그만뒀습니다
읽고 싶은 책 목록이 열 권 생겼습니다. 한 권씩 도서관 홈페이지에서 검색했습니다. 어느 지점에 있는지 적고, 대출중인지 확인하고, 지도를 열어 거리를 쟀습니다.
세 권째에서 그만뒀습니다. 이건 사람이 할 일이 아니었습니다.
그래서 스마택트가 도구를 하나 만들었습니다. 책 제목을 한 줄에 하나씩 적으면, 지금 대출 가능한 지점을 찾아 가장 적게 움직이는 동선을 짜 줍니다. 만드는 데 하루가 걸렸고, 그 하루가 꽤 볼 만해서 기록으로 남깁니다.
공식 API는 열려 있지 않았습니다
먼저 공식 경로를 찾았습니다. 국립중앙도서관이 운영하는 도서관 정보나루라는 공개 API가 있습니다. 전국 공공도서관의 소장과 대출 정보를 제공합니다.
두 가지가 막혔습니다. 중계 서버가 응답하지 않았고, 정보나루는 공공도서관만 다루기 때문에 대학도서관은 애초에 들어 있지 않았습니다.
여기서 보통 멈춥니다. 대신 도서관 홈페이지를 열어 봤습니다.
두 도서관이 같은 시스템을 쓰고 있었습니다
시흥시도서관 홈페이지의 HTML에 ikc-pyxis-wrap이라는 표시가 있었습니다. 경인교대 도서관에도 똑같은 것이 있었습니다.
둘 다 Pyxis라는 같은 도서관 시스템이었습니다. 이런 시스템은 대개 자기 화면이 쓰는 조회 통로를 그대로 열어 둡니다. 열려 있었고, 인증키도 필요 없었습니다.
문제는 질의 형식이었습니다. 어느 문서에도 없었습니다. 사이트의 자바스크립트 파일을 받아 검색어를 만드는 부분을 찾을 때까지 뒤졌습니다. 파일 크기는 4.3MB였습니다.
all=1|k|a|{키워드}
문서에 없던 질의 형식. 번들 안에서 찾아냈습니다.
넣어 보니 서명, 저자, 지점명, 청구기호, 그리고 지금 대출중인지 아닌지가 쏟아져 나왔습니다.
브라우저에서는 부를 수 없었습니다
바로 브라우저에서 부르려 했더니 경인교대는 되고 시흥은 막혔습니다. 응답 헤더를 보니 이유가 있었습니다.
경인교대 Access-Control-Allow-Origin: *
시흥 Access-Control-Allow-Origin: https://www.instagram.com/*
시흥은 인스타그램 주소만 허용하고 있어 브라우저가 차단합니다. 그래서 서버를 한 겹 두었습니다. 이 도구가 정적 HTML 한 장으로는 만들어질 수 없는 이유입니다.
진짜 문제는 그다음이었습니다
데이터가 들어오자 원래 풀려던 문제가 남았습니다. 책 열 권이 지점 서른아홉 곳에 흩어져 있을 때, 어디를 돌아야 할까요.
이건 이름이 있는 문제입니다. 집합 덮개(set cover)라고 합니다. 가장 좋은 답을 정확히 구하려면 계산량이 감당이 안 됩니다. 다행히 지점 마흔 곳, 책 열 권 정도면 탐욕 알고리즘으로 충분합니다. 매번 "지금 가장 이득인 곳"을 고르는 방식입니다. 전체 최적을 보장하지는 않지만, 이 규모에서는 사람이 손으로 짠 동선과 거의 같은 답이 나옵니다.
점수 기준을 바꾸니 성격이 다른 답이 나와서, 두 가지를 모두 보여주기로 했습니다.
- 한 번에 끝내기 — 권수를 최대화합니다. 한 곳에서 다 빌리되 멀 수 있습니다
- 가까운 데서 나눠 받기 — 권수를 거리로 나눕니다. 두세 곳을 도는 대신 다 가깝습니다
실제로 돌려 보니 열 권 중 일곱 권이 2.7km 안에 있는 두 곳에서 끝났습니다.



책별 소장 현황 · 지점별로 몇 권을 가져올 수 있는지 · 최종 동선
도서관 앱이라고 양피지를 깔 뻔했습니다
처음 만든 화면은 크림빛 배경에 따뜻한 세리프였습니다. 만들어 놓고 보니 "도서관 앱을 만들라"고 하면 누구나 내놓을 그림이었습니다.
다시 잡았습니다. 분위기를 한 문장으로 못 박고 시작했습니다.
밤 열한 시, 불 꺼진 서가 사이. 아래에서 올라오는 열람등 하나가 펼쳐진 종이만 비추고, 나무 책장과 공기는 어둠에 잠겨 있다.
이 문장이 색을 강제했습니다. 배경은 거의 검고, 화면에서 밝은 것은 펼쳐진 종이 한 장뿐입니다. 브랜드 색은 배경이 아니라 열람등이 가집니다.
그리고 책을 만들었습니다. 별도 라이브러리 없이 CSS만으로 표지가 책등을 축으로 열립니다. 중요한 것은 이것입니다. 펼쳐진 지면이 곧 입력 폼입니다. 왼쪽 지면에 출발지, 오른쪽 지면에 책 제목. 장식이 아니라 인터페이스입니다.


왼쪽은 첫 화면, 오른쪽은 책이 펼쳐진 상태입니다. 펼쳐진 지면이 그대로 입력 폼입니다.
그리고 버튼이 눌리지 않았습니다
여기서 하루의 절반이 갔습니다.
다 만들어 배포하고 눌러 봤는데 아무 일도 일어나지 않았습니다.
- 브라우저 콘솔에 오류가 하나도 없습니다
- 서버를 직접 호출하면 0.5초에 정상 응답합니다
- 확장 프로그램을 모두 끈 환경에서도 안 됩니다
- 그런데 자동 테스트는 매번 통과합니다
브라우저 캐시를 의심했다가, 확장 프로그램을 의심했다가, 옛 버전을 보고 있나 싶어 화면에 배포 번호까지 찍어 봤습니다. 전부 아니었습니다.
문제는 테스트하는 방법에 있었습니다.
코드로 버튼을 누르는 방식으로 확인하고 있었는데, 그건 자바스크립트가 처리 함수를 직접 부르는 것입니다. 실제 클릭이 어디에 닿는지 판정하는 과정을 통째로 건너뜁니다. 버튼 위에 무엇이 덮여 있든, 판정 영역이 어긋나 있든 무조건 성공합니다.
실제 좌표에 마우스 신호를 보내는 방식으로 바꾸자 1분 만에 잡혔습니다.
누를 때 → 버튼
뗄 때 → 버튼 바깥
클릭 판정 → 버튼이 아님
브라우저는 누른 곳과 뗀 곳이 다르면 둘을 함께 감싸는 바깥 요소에 클릭을 발생시킵니다. 그래서 버튼의 처리 함수는 끝내 불리지 않았습니다.
원인은 눌린 느낌을 주려고 넣은 1픽셀이었습니다. 이 버튼은 3차원 효과가 걸린 공간 안에 있는데, 누르는 순간 위치를 옮기는 효과가 붙으면서 클릭 판정 영역이 눈에 보이는 위치에서 어긋난 것입니다.
보이는 곳과 손이 닿는 곳이 달라진 셈입니다. 눌림 표현을 색과 그림자로 바꾸자 즉시 해결됐습니다.
이 하루가 남긴 것
하나. 공식 통로가 막혔다고 길이 없는 것은 아닙니다. 다만 그렇게 찾은 길에는 설명서가 없습니다. 도서관 홈페이지가 개편되면 어느 날 갑자기 멈춥니다. 그래서 스마택트는 이 도구를 "서비스"라고 부르지 않습니다.
둘. 잘 돌아간다고 확인하는 방법이 틀리면, 확신만 남고 물건은 돌아가지 않습니다. 여러 차례 "확인했습니다"라고 말한 뒤에야 실제 마우스로 눌러 봤습니다. 요즘처럼 AI와 함께 만드는 방식에서 특히 그렇습니다. AI는 자신 있게 틀릴 수 있고, 그것을 걸러내는 일은 결국 만드는 사람 몫으로 남습니다. 바이브코딩 수업에서 늘 강조하는 지점이기도 합니다.
지금은 시흥시 도서관 서른아홉 곳과 경인교대 두 캠퍼스를 조회합니다. 광명·부천·안산·안양·인천은 도서관 시스템이 제각각이라 정보나루 공식 API로 붙이는 중입니다.
무료로 열어 두었습니다. 책 제목을 한 줄에 하나씩 적으면 됩니다.
https://booktrip-sigma.vercel.app
※ 조회만 가능한 도구입니다. 예약과 대출 신청은 각 도서관 홈페이지에서 하셔야 합니다. 대출 상태는 도서관 시스템 반영이 늦을 수 있으니, 방문 전에 한 번 확인하시길 권합니다.