본문 바로가기

Google Sheet

[스프레드시트] python으로 구글 시트영역 pdf, png 이미지로 변환하기

오늘은 구글 스프레드시트에 있는 표를 자동으로 이미지화해서 전송하는 방법을 정리해 보았습니다.

 

보통 구글 시트 자동화를 할 때 가장 난관이 "이미지 추출"입니다. 구글이 시트 영역을 PDF로 바꿔주는 기능(URL)은 지원하지만, 이미지 파일로 바로 바꿔주는 기능은 없기 때문이죠.

 

그래서 저는 PDF로 먼저 데이터를 받은 다음, "Poppler"라는 외부 프로그램을 사용해 이를 다시 깔끔한 이미지로 변환하는 우회 방법을 사용했습니다. 그 과정을 코드로 소개해 드릴게요.

 

Poppler 다운(pdf => png 필요시)

https://poppler.freedesktop.org/

필수 라이브러리 설치

pip install gspread oauth2client requests pdf2image pillow

 

파이썬 코드

import os
import requests
import gspread
from oauth2client.service_account import ServiceAccountCredentials
from pdf2image import convert_from_bytes
from PIL import Image, ImageOps 

# ================= 설정 영역 =================
# 구글 시트 및 인증 설정
SPREADSHEET_ID = "" # 시트 ID
SHEET_NAME = ""                                           # 시트 이름
CREDENTIALS_FILE = "credentials.json"                          # 인증 키 파일
POPPLER_PATH = r"C:\Program Files\poppler-25.12.0\Library\bin" # Poppler 경로
# ===========================================

def get_worksheet_object():
    """구글 시트 워크시트 객체와 인증 토큰을 반환"""
    scope = ['https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive']
    creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope)
    client = gspread.authorize(creds)
    sh = client.open_by_key(SPREADSHEET_ID)
    worksheet = sh.worksheet(SHEET_NAME)
    
    # PDF 다운로드 API 호출 시 필요한 Access Token과 Sheet ID 반환
    return worksheet, creds.get_access_token().access_token, sh.id

def calculate_dynamic_range(worksheet):
    """B열의 데이터 길이를 기준으로 인쇄할 범위를 동적으로 계산"""
    col_b_values = worksheet.col_values(2) 
    last_row = len(col_b_values)

    if last_row < 5:
        last_row = 5
    
    # 예: B5부터 L열의 마지막 데이터까지
    return f"B5:L{last_row}"

def download_range_pdf(access_token, gid, range_str):
    """구글 시트의 특정 범위(range_str)를 PDF 바이너리로 다운로드"""
    url = f"https://docs.google.com/spreadsheets/d/{SPREADSHEET_ID}/export"
    params = {
        'format': 'pdf', 
        'gid': gid, 
        'range': range_str,
        'size': 'A4', 
        'portrait': 'true', 
        'fitw': 'true', 
        'gridlines': 'false'
    }
    headers = {'Authorization': f'Bearer {access_token}'}
    res = requests.get(url, headers=headers, params=params)
    
    if res.status_code != 200:
        raise Exception(f"PDF 다운로드 실패: {res.status_code}")
    
    return res.content

def crop_whitespace(image, padding=20):
    """이미지의 흰색 여백을 자동으로 인식하여 제거 (Cropping)"""
    try:
        image = image.convert("RGB")
        # 색상을 반전시켜 내용이 있는 부분(검은색)의 좌표(BBox)를 찾음
        invert_im = ImageOps.invert(image)
        bbox = invert_im.getbbox()
        
        if bbox:
            left, upper, right, lower = bbox
            width, height = image.size
            
            # 패딩을 추가하여 너무 타이트하지 않게 자름
            left = max(0, left - padding)
            upper = max(0, upper - padding)
            right = min(width, right + padding)
            lower = min(height, lower + padding)
            
            return image.crop((left, upper, right, lower))
            
    except Exception as e:
        print(f"   [경고] 이미지 자르기 실패 (원본 반환): {e}")
    
    return image 

# ================= 메인 실행 로직 =================
if __name__ == "__main__":
    try:
        print("1. 구글 시트 연결 및 설정 로드...")
        worksheet, token, spreadsheet_id = get_worksheet_object()
        
        # 2. 다운로드할 범위 계산 (예: 데이터가 있는 마지막 행까지)
        dynamic_range = calculate_dynamic_range(worksheet)
        print(f"   >> 다운로드 범위: {dynamic_range}")

        # 3. PDF 다운로드 (메모리 상에서 처리)
        print("2. PDF 다운로드 및 변환 중...")
        pdf_bytes = download_range_pdf(token, worksheet.id, dynamic_range)
        
        # 4. PDF를 이미지 리스트로 변환 (pdf2image 라이브러리)
        images = convert_from_bytes(pdf_bytes, poppler_path=POPPLER_PATH)
        
        # 5. 이미지 여백 제거 및 파일 저장
        for i, image in enumerate(images):
            # 여백 자르기 함수 호출
            cropped_image = crop_whitespace(image, padding=30)
            
            save_filename = f"result_image_{i+1}.png"
            cropped_image.save(save_filename, "PNG")
            
            print(f"   >> 저장 완료: {save_filename}")

        print("\n🎉 변환 작업이 성공적으로 완료되었습니다.")

    except Exception as e:
        print(f"에러 발생: {e}")