OG-зображення в Python
Цей посібник показує, як генерувати OG-зображення в Python за допомогою FastOG — Django, FastAPI або Flask — шляхом підпису HMAC-URL та динамічного рендерингу зображень Open Graph на льоту. Підписка з безкоштовним акаунтом (оберіть тарифний план Dynamic API; створіть ваш API-ключ у панелі керування); скористайтеся безкоштовним тестером OG-зображень, щоб переглянути шаблон перед інтеграцією.
Повний посібник із впровадження HMAC-підписаних URL для OG-зображень у Python.
Швидкий старт
1. Створіть модуль підписувача (signer)
# utils/fastog.py
import hmac
import hashlib
import os
from urllib.parse import quote, urlencode
def _enc(s):
return quote(str(s), safe='-._~') # RFC-3986: only unreserved chars
def generate_signed_og_url(params: dict, options: dict = None) -> str:
options = options or {}
base_url = options.get('base_url', os.environ.get('FASTOG_BASE_URL', 'https://fastog.com/api/v1/og'))
api_key = options.get('api_key', os.environ.get('FASTOG_API_KEY'))
hmac_secret = options.get('hmac_secret', os.environ.get('FASTOG_HMAC_SECRET'))
if not api_key or not hmac_secret:
raise ValueError("Missing FASTOG_API_KEY or FASTOG_HMAC_SECRET")
all_params = {**params, 'key': api_key}
pairs = []
for k, v in all_params.items():
for vv in sorted(v if isinstance(v, list) else [v]):
pairs.append(f"{_enc(k)}={_enc(vv)}")
canonical = '&'.join(sorted(pairs))
signature = hmac.new(
hmac_secret.encode(),
f"GET/api/v1/og\n{canonical}".encode(),
hashlib.sha256
).hexdigest()
return f"{base_url}?{urlencode({**all_params, 's': signature})}"
2. Змінні середовища
# .env
FASTOG_API_KEY=sk_your_full_api_key_here
FASTOG_HMAC_SECRET=your_64_char_hex_secret_here
pip install python-dotenv
# config.py
from dotenv import load_dotenv
load_dotenv()
Django
Сервіс підписувача
# services/fastog.py
from utils.fastog import generate_signed_og_url
class QuickogService:
@staticmethod
def get_og_url(template: str, **params) -> str:
return generate_signed_og_url({
'template': template,
**params,
})
Представлення
# views.py
from django.shortcuts import render
from services.fastog import QuickogService
def blog_post(request, slug):
post = get_post(slug)
og_image_url = QuickogService.get_og_url(
template='blog',
title=post.title,
subtitle=post.excerpt,
author=post.author.name,
)
return render(request, 'blog/post.html', {
'post': post,
'og_image_url': og_image_url,
})
Шаблон
{# templates/blog/post.html #}
<!DOCTYPE html>
<html>
<head>
<meta property="og:image" content="{{ og_image_url }}" />
<meta property="og:title" content="{{ post.title }}" />
<meta property="og:description" content="{{ post.excerpt }}" />
</head>
<body>
<h1>{{ post.title }}</h1>
<p>{{ post.excerpt }}</p>
</body>
</html>
Власний тег шаблону
# templatetags/fastog_tags.py
from django import template
from services.fastog import QuickogService
register = template.Library()
@register.simple_tag
def og_image_url(template_name, **params):
return QuickogService.get_og_url(template_name, **params)
{# In template #}
{% load fastog_tags %}
<meta property="og:image" content="{% og_image_url 'blog' title=post.title subtitle=post.excerpt %}" />
FastAPI
# main.py
from fastapi import FastAPI, Query
from pydantic import BaseModel
from utils.fastog import generate_signed_og_url
app = FastAPI()
class OgRequest(BaseModel):
title: str
template: str = "default"
subtitle: str | None = None
@app.get("/api/og")
def generate_og(title: str = Query(...), template: str = Query("default")):
og_url = generate_signed_og_url({
'template': template,
'title': title,
})
return {"ogImageUrl": og_url}
@app.get("/blog/{slug}")
async def blog_post(slug: str):
post = await get_post(slug)
og_url = generate_signed_og_url({
'template': 'blog',
'title': post.title,
'subtitle': post.excerpt,
})
return {"post": post, "ogImageUrl": og_url}
Flask
# app.py
from flask import Flask, render_template, request, jsonify
from utils.fastog import generate_signed_og_url
app = Flask(__name__)
@app.route("/blog/<slug>")
def blog_post(slug):
post = get_post(slug)
og_image_url = generate_signed_og_url({
'template': 'blog',
'title': post['title'],
'subtitle': post['excerpt'],
})
return render_template('blog/post.html', post=post, og_image_url=og_image_url)
@app.route("/api/og")
def api_og():
title = request.args.get('title')
template = request.args.get('template', 'default')
if not title:
return jsonify({'error': 'Missing title'}), 400
og_url = generate_signed_og_url({
'template': template,
'title': title,
})
return jsonify({'ogImageUrl': og_url})
Приклад електронної комерції
# views.py
def product_detail(request, product_id):
product = get_product(product_id)
og_image_url = generate_signed_og_url({
'template': 'ecommerce',
'title': product.name,
'price': product.formatted_price,
'image': product.image_url,
})
return render(request, 'products/detail.html', {
'product': product,
'og_image_url': og_image_url,
})
Приклад допису в блозі
def user_profile(request, username):
user = get_user(username)
og_image_url = generate_signed_og_url({
'template': 'blog',
'title': user.name,
'subtitle': user.bio,
'author': user.name,
})
return render(request, 'profile/show.html', {
'user': user,
'og_image_url': og_image_url,
})
Типові помилки
Підпис неправильного повідомлення
❌ Неправильно: f"GET/api/v1/og{canonical}" — пропущено розділювач нового рядка.
✅ Правильно: f"GET/api/v1/og\n{canonical}" — метод + шлях, новий рядок, а потім канонічний запит.
Виключення key із підпису
❌ Неправильно: підписуєте лише свої параметри вмісту та додаєте key до URL після цього.
✅ Правильно: key об'єднується з параметрами до побудови канонічного запиту та з'являється в кінцевому URL.
Забування URL-кодування ключів і значень
❌ Неправильно: побудова канонічного запиту з необробленими, незакодованими значеннями.
✅ Правильно: відсоткове кодування і ключів, і значень за RFC-3986 (safe='-._~') перед підписом — використовуйте quote, а не quote_plus (який видає + для пробілів і не збігатиметься із сервером).
401 missing_signature / invalid_signature
missing_signature (401) означає, що key або s відсутній у URL. invalid_signature (401) означає, що канонічний запит не збігся — перевірте новий рядок у підписаному повідомленні, однаковий hmac_secret з обох боків та quote(safe='-._~') (не quote_plus) для кожного ключа й значення.