연세대 ibook 뷰어(https://ibook.yonsei.ac.kr)에서 책을 PDF로 다운로드합니다.
- Python 3
- img2pdf (
pip install img2pdf)
./download-ibook.py https://ibook.yonsei.ac.kr/Viewer/{bookId}현재 디렉토리에 {책제목}.pdf 파일이 생성됩니다.
연세대 ibook 뷰어(https://ibook.yonsei.ac.kr)에서 책을 PDF로 다운로드합니다.
pip install img2pdf)./download-ibook.py https://ibook.yonsei.ac.kr/Viewer/{bookId}현재 디렉토리에 {책제목}.pdf 파일이 생성됩니다.
| #!/usr/bin/env python3 | |
| """연세대 ibook 뷰어에서 책을 PDF로 다운로드합니다.""" | |
| import sys | |
| import json | |
| import re | |
| import ssl | |
| import urllib.request | |
| from concurrent.futures import ThreadPoolExecutor | |
| from pathlib import Path | |
| import img2pdf | |
| def fetch(url): | |
| ctx = ssl.create_default_context() | |
| ctx.check_hostname = False | |
| ctx.verify_mode = ssl.CERT_NONE | |
| with urllib.request.urlopen(url, context=ctx) as resp: | |
| return resp.read() | |
| def fetch_to_file(url, path): | |
| path.write_bytes(fetch(url)) | |
| def main(): | |
| if len(sys.argv) < 2: | |
| print(f"Usage: {sys.argv[0]} <ibook-url>") | |
| print(f"Example: {sys.argv[0]} https://ibook.yonsei.ac.kr/Viewer/GEFQ24RMWHC7") | |
| sys.exit(1) | |
| url = sys.argv[1] | |
| bookcode = url.rstrip("/").split("/")[-1] | |
| base = "https://ibook.yonsei.ac.kr" | |
| # 책 제목 | |
| html = fetch(f"{base}/Viewer/{bookcode}").decode() | |
| match = re.search(r"<title>(.+?)</title>", html) | |
| title = match.group(1) if match else bookcode | |
| print(f"Title: {title}") | |
| # 페이지 정보 | |
| book_json = json.loads(fetch(f"{base}/Viewer/getBookXML/{bookcode}")) | |
| page_count = len(book_json) | |
| print(f"Pages: {page_count}") | |
| # 최고 해상도 이미지 URL 목록 | |
| pages = [] | |
| for i, page in enumerate(book_json): | |
| src = "https:" + page["src"].replace("scale1", "scale3") | |
| pages.append((src, i)) | |
| # 다운로드 | |
| pages_dir = Path("pages") | |
| pages_dir.mkdir(exist_ok=True) | |
| paths = [pages_dir / f"{i:03d}.jpg" for i in range(page_count)] | |
| print(f"Downloading {page_count} pages...") | |
| with ThreadPoolExecutor(max_workers=10) as pool: | |
| futures = [pool.submit(fetch_to_file, src, paths[i]) for src, i in pages] | |
| for f in futures: | |
| f.result() | |
| # PDF 생성 (JPEG 원본을 그대로 삽입) | |
| print("Creating PDF...") | |
| output = Path(f"{title}.pdf") | |
| with open(output, "wb") as f: | |
| f.write(img2pdf.convert([str(p) for p in paths])) | |
| print(f"Done: {output} ({output.stat().st_size / 1024 / 1024:.1f}MB)") | |
| if __name__ == "__main__": | |
| main() |