|
| 1 | +import os |
| 2 | +import requests |
| 3 | +from bs4 import BeautifulSoup |
| 4 | +from urllib.parse import urljoin, urlparse |
| 5 | +import pdfkit |
| 6 | +from PyPDF2 import PdfMerger, PdfReader, PdfWriter |
| 7 | +import re |
| 8 | + |
| 9 | +def natural_key(s): |
| 10 | + """Turn a string into a list of ints and text, so 's10' > 's9'.""" |
| 11 | + return [int(text) if text.isdigit() else text.lower() |
| 12 | + for text in re.split(r'(\d+)', s)] |
| 13 | + |
| 14 | + |
| 15 | +def get_all_links(base_url): |
| 16 | + """Crawl all internal links from base_url.""" |
| 17 | + visited = set() |
| 18 | + to_visit = [base_url] |
| 19 | + urls = [] |
| 20 | + |
| 21 | + while to_visit: |
| 22 | + url = to_visit.pop() |
| 23 | + if url in visited: |
| 24 | + continue |
| 25 | + visited.add(url) |
| 26 | + |
| 27 | + try: |
| 28 | + r = requests.get(url) |
| 29 | + r.raise_for_status() |
| 30 | + except Exception as e: |
| 31 | + print(f"❌ Failed to fetch {url}: {e}") |
| 32 | + continue |
| 33 | + |
| 34 | + soup = BeautifulSoup(r.text, "html.parser") |
| 35 | + urls.append(url) |
| 36 | + |
| 37 | + for a in soup.find_all("a", href=True): |
| 38 | + href = urljoin(base_url, a["href"]) |
| 39 | + # Only keep pages within same domain |
| 40 | + if urlparse(href).netloc == urlparse(base_url).netloc: |
| 41 | + if href not in visited and href not in to_visit and "#" not in href: |
| 42 | + to_visit.append(href) |
| 43 | + return urls |
| 44 | + |
| 45 | + |
| 46 | +def save_pages_as_pdfs(urls, output_dir="pages_pdfs"): |
| 47 | + os.makedirs(output_dir, exist_ok=True) |
| 48 | + pdf_files = [] |
| 49 | + for i, url in enumerate(urls): |
| 50 | + out_file = os.path.join(output_dir, f"page_{i+1}.pdf") |
| 51 | + try: |
| 52 | + pdfkit.from_url(url, out_file) |
| 53 | + pdf_files.append(out_file) |
| 54 | + print(f"✅ Saved {url} → {out_file}") |
| 55 | + except Exception as e: |
| 56 | + print(f"❌ Failed to render {url}: {e}") |
| 57 | + return pdf_files |
| 58 | + |
| 59 | + |
| 60 | +def merge_pdfs(pdf_files, output_file="combined.pdf"): |
| 61 | + merger = PdfMerger() |
| 62 | + for pdf in pdf_files: |
| 63 | + merger.append(pdf) |
| 64 | + merger.write(output_file) |
| 65 | + merger.close() |
| 66 | + print(f"📚 Combined PDF saved as {output_file}") |
| 67 | + |
| 68 | + |
| 69 | +import subprocess |
| 70 | + |
| 71 | +def compress_pdf(input_file, output_file, quality="/ebook"): |
| 72 | + subprocess.run([ |
| 73 | + "gs", "-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.4", |
| 74 | + f"-dPDFSETTINGS={quality}", |
| 75 | + "-dNOPAUSE", "-dQUIET", "-dBATCH", |
| 76 | + f"-sOutputFile={output_file}", input_file |
| 77 | + ]) |
| 78 | + print(f"📉 Compressed {input_file} → {output_file}") |
| 79 | + |
| 80 | + |
| 81 | + |
| 82 | +def split_pdf(input_file, output_files): |
| 83 | + reader = PdfReader(input_file) |
| 84 | + total_pages = len(reader.pages) |
| 85 | + num_splits = len(output_files) |
| 86 | + pages_per_split = total_pages // num_splits |
| 87 | + remainder = total_pages % num_splits |
| 88 | + |
| 89 | + start = 0 |
| 90 | + for i, out_file in enumerate(output_files): |
| 91 | + # Distribute the remainder pages one by one to the first splits |
| 92 | + end = start + pages_per_split + (1 if i < remainder else 0) |
| 93 | + writer = PdfWriter() |
| 94 | + for page in reader.pages[start:end]: |
| 95 | + writer.add_page(page) |
| 96 | + with open(out_file, "wb") as f: |
| 97 | + writer.write(f) |
| 98 | + print(f"✂️ Split pages {start+1}-{end} → {out_file}") |
| 99 | + start = end |
| 100 | + |
| 101 | + |
| 102 | +if __name__ == "__main__": |
| 103 | + base_url = "https://skaftenicki.github.io/dtu_mlops/" |
| 104 | + urls = get_all_links(base_url) |
| 105 | + urls = sorted(urls, key=natural_key) # 👈 use natural sort |
| 106 | + print(f"Found {len(urls)} pages.") |
| 107 | + |
| 108 | + pdf_files = save_pages_as_pdfs(urls) |
| 109 | + merge_pdfs(pdf_files, "dtu_mlops_all.pdf") |
| 110 | + split_pdf("dtu_mlops_all.pdf", [ |
| 111 | + "dtu_mlops_part1.pdf", "dtu_mlops_part2.pdf", "dtu_mlops_part3.pdf", "dtu_mlops_part4.pdf" |
| 112 | + ]) |
| 113 | + compress_pdf("dtu_mlops_part1.pdf", "dtu_mlops_part1_small.pdf") |
| 114 | + compress_pdf("dtu_mlops_part2.pdf", "dtu_mlops_part2_small.pdf") |
| 115 | + compress_pdf("dtu_mlops_part3.pdf", "dtu_mlops_part3_small.pdf") |
| 116 | + compress_pdf("dtu_mlops_part4.pdf", "dtu_mlops_part4_small.pdf") |
0 commit comments