데이터시트 생성: 주문형 및 일괄 PDF 생성
이 문서에서는 제품 데이터시트를 PDF로 생성하는 방법을 설명합니다. 사용자가 요청할 때 생성하는 주문형 방식과 인기 제품을 사전 생성하는 일괄 방식 모두를 다룹니다.
문제점: 제품 데이터시트 생성
각 제품은 다음을 포함하는 전문적인 PDF 데이터시트가 필요합니다:
-
제품명 및 이미지
-
범주별로 그룹화된 기술 사양
-
회사 브랜딩 및 연락처 정보
-
최적화된 레이아웃 (단일 페이지, 균형 잡힌 열)
매 요청마다 PDF를 생성하는 것은 느립니다 (PDF당 약 2초). 65,000개의 SKU를 모두 사전 생성하는 것은 저장 공간과 시간을 낭비합니다.
해결책: 하이브리드 접근법
우리는 두 가지 생성 방법을 사용합니다:
주문형: 사용자가 요청할 때 PDF 생성 (최초 요청 시)
일괄: 인기 제품에 대한 PDF 사전 생성 (매일 밤)
주문형 생성
URL 구조
/ds/<sku>.pdf
/ds/<sku>
두 URL 모두 동일한 PDF를 생성합니다.
요청 흐름
@web.route("/ds/<sku>.pdf")
def datasheet(sku: str):
# SKU 검증
# ... (구현 세부사항 생략)
생성 프로세스
1단계: 제품 데이터 가져오기
# 제품명
name = expand_sku(sku)
# ... (구현 세부사항 생략)
2단계: 열 균형 맞추기
기능들은 줄 수를 균형 있게 배분하기 위해 3개의 열에 걸쳐 분배됩니다:
# 기능 그룹별 줄 수 계산
all_groups = []
for heading, f_items in features.items():
# ... (구현 세부사항 생략)
3단계: 이미지 높이 계산
# 메인 이미지 높이 (종횡비에 비례)
with Image.open(main_image_path) as img:
main_width, main_height = img.size
# ... (구현 세부사항 생략)
4단계: 간격 계산
페이지 하단으로 푸터를 밀어냅니다:
available_height = 25.0 # cm
content_height = total_image_height + text_height_cm
spacer_height = max(0, available_height - content_height)
5단계: HTML 렌더링
html = render_template(
"datasheet.html",
sku=sku,
# ... (구현 세부사항 생략)
6단계: PDF 생성
options = {
"page-size": "A4",
"enable-local-file-access": None,
"load-error-handling": "ignore",
"load-media-error-handling": "ignore",
"no-stop-slow-scripts": None
}
pdf_data = pdfkit.from_string(html, options=options)
7단계: 첫 페이지만 유지
reader = PdfReader(BytesIO(pdf_data))
if len(reader.pages) > 1:
writer = PdfWriter()
writer.add_page(reader.pages[0])
output = BytesIO()
writer.write(output)
pdf_data = output.getvalue()
성능
생성 시간: PDF당 약 2초
캐싱: 캐싱 없음 (항상 최신 상태)
이점: 최신 제품 데이터와 항상 동기화
일괄 생성
목적
주문형 부하를 줄이기 위해 인기 제품의 PDF를 사전 생성합니다.
스크립트 위치
scripts/utils/generate_datasheets.py
변경 사항 추적
각 SKU와 관련된 productdb.json 섹션의 변경 사항을 추적합니다:
def get_sku_productdb_hash(sku: str) -> str:
"""이 SKU와 관련된 productdb 섹션의 해시를 가져옵니다."""
with open(PRODUCTDB_PATH, "r") as f:
# ... (구현 세부사항 생략)
우선순위 지정
요청 빈도에 따라 SKU에 우선순위를 지정합니다:
def load_popular_skus() -> List[str]:
"""액세스 로그에서 인기 SKU를 로드합니다."""
try:
# ... (구현 세부사항 생략)
생성 전략
def batch_generate():
# 해시 캐시 로드
hash_cache = load_hash_cache()
# ... (구현 세부사항 생략)
스케줄링
일괄 생성은 cron을 통해 매일 밤 실행됩니다:
0 2 * * * cd /home/ubuntu/manage && python scripts/utils/generate_datasheets.py
저장소
로컬: /home/ubuntu/web-static/ds/<sku>.pdf
S3: s3://thinvent-web-static/ds/<sku>.pdf
CDN: CloudFront를 통해 제공
템플릿 구조
데이터시트 템플릿은 3열 레이아웃을 사용합니다:
<div class="container">
<!-- 로고와 제품명이 있는 헤더 -->
<div class="header">
<img src="logo.png">
<h1>{{ name }}</h1>
</div>
<!-- 메인 이미지 -->
<img src="{{ images[0][1] }}" style="width: 9.5cm">
<!-- 썸네일 (최대 4개) -->
<div class="thumbnails">
{% for name, url in images[1:5] %}
<img src="{{ url }}" style="width: 4.5cm">
{% endfor %}
</div>
<!-- 3열에 걸친 기능 -->
<div class="features">
<div class="column">
{% for heading, items in features1 %}
<h3>{{ heading }}</h3>
{% for name, value in items.items() %}
<div><strong>{{ name }}:</strong> {{ value }}</div>
{% endfor %}
{% endfor %}
</div>
<div class="column">
<!-- features2 -->
</div>
<div class="column">
<!-- features3 -->
</div>
</div>
<!-- 푸터를 하단으로 밀기 위한 간격 -->
<div style="height: {{ spacer_height }}cm"></div>
<!-- 연락처 정보가 있는 푸터 -->
<div class="footer">
<p>www.thinvent.in | sales@thinvent.in | +91-124-4343177</p>
</div>
</div>
구성
상수들이 레이아웃을 제어합니다:
DATASHEET_COLUMN_COUNT = 3
DATASHEET_HEADING_LINE_WEIGHT = 2 # 제목당 줄 수
DATASHEET_FEATURE_NAME_MAX_LEN = 30 # 줄 바꿈 전 최대 문자 수
DATASHEET_FEATURE_VALUE_CHARS_PER_LINE = 40 # 줄당 문자 수
PDF_PAGE_SIZE = "A4"
통합 포인트
제품 페이지
데이터시트 링크:
<a href="/ds/{{ sku }}.pdf" target="_blank">데이터시트 다운로드</a>
Google 쇼핑
제품 피드에 데이터시트 링크:
<g:product_detail>
<g:section_name>Datasheet</g:section_name>
<g:attribute_name>PDF</g:attribute_name>
<g:attribute_value>https://www.thinvent.in/ds/{{ sku }}.pdf</g:attribute_value>
</g:product_detail>
이메일 캠페인
견적 이메일에 데이터시트 첨부.
오류 처리
잘못된 SKU
if not check_sku(sku):
abort(422, description="Product not found.")
생성 실패
try:
pdf_data = generate_datasheet_pdf(sku)
if pdf_data is None:
abort(500, description="PDF generation failed.")
except Exception as e:
logger.error(f"PDF generation failed for {sku}: {e}")
abort(500, description="PDF generation failed.")
이미지 누락
try:
with Image.open(image_path) as img:
width, height = img.size
except Exception:
# 기본 치수 사용
width, height = 800, 600
참고 자료
라이브러리
관련 문서
요약
데이터시트 생성은 하이브리드 접근법을 사용합니다:
주문형:
-
✅ 사용자 요청 시 생성
-
✅ 항상 최신 상태
-
✅ 저장 공간 낭비 없음
-
✅ PDF당 약 2초
일괄:
-
✅ 인기 제품 사전 생성
-
✅ productdb 변경 사항 추적
-
✅ 요청 빈도에 따른 우선순위 지정
-
✅ 매일 밤 cron 작업
프로세스:
-
✅ 제품 데이터 가져오기 (이름, 이미지, 기능, 설명)
-
✅ 3열에 걸쳐 기능 균형 맞추기
-
✅ 이미지 높이 계산
-
✅ HTML 템플릿 렌더링
-
✅ pdfkit으로 PDF 생성
-
✅ 첫 페이지만 유지
저장소:
-
✅ 로컬:
/home/ubuntu/web-static/ds/ -
✅ S3:
s3://thinvent-web-static/ds/ -
✅ CDN: CloudFront 배포
이 하이브리드 접근법은 최신 상태 유지(주문형)와 성능(일괄 사전 생성) 사이의 균형을 맞춥니다.