데이터시트 생성: 주문형 및 일괄 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 배포

이 하이브리드 접근법은 최신 상태 유지(주문형)와 성능(일괄 사전 생성) 사이의 균형을 맞춥니다.


← 문서 색인으로 돌아가기