OG Images trong Python
Hướng dẫn này chỉ cho bạn cách tạo OG images trong Python với FastOG — Django, FastAPI, hoặc Flask — bằng cách ký một URL HMAC và hiển thị các Open Graph images động ngay lập tức. Dịch vụ theo gói đăng ký với tài khoản miễn phí (chọn gói Dynamic API; tạo API key của bạn trong dashboard); truy cập Free OG Image Tester để xem mẫu trước khi tích hợp.
Hướng dẫn đầy đủ để triển khai các URL OG images có chữ ký HMAC trong Python.
Bắt đầu nhanh
1. Tạo mô-đun ký (signer module)
# 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. Biến môi trường
# .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
Dịch vụ ký (Signer Service)
# 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,
})
View
# 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,
})
Mẫu (Template)
{# 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>
Thẻ mẫu tùy chỉnh (Custom Template Tag)
# 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})
Ví dụ Thương mại điện tử
# 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,
})
Ví dụ Bài Đăng Blog
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,
})
Các Lỗi Thường Gặp
Ký Sai Nội Dung
❌ Sai: f"GET/api/v1/og{canonical}" — thiếu dấu phân cách xuống dòng.
✅ Đúng: f"GET/api/v1/og\n{canonical}" — phương thức + đường dẫn, một dấu xuống dòng, sau đó là chuỗi truy vấn chuẩn hóa.
Bỏ key Ra Khỏi Chữ Ký
❌ Sai: chỉ ký các tham số nội dung của bạn và thêm key vào URL sau đó.
✅ Đúng: key được hợp nhất vào các tham số trước khi chuỗi truy vấn chuẩn hóa được xây dựng và xuất hiện trong URL cuối cùng.
Quên Mã Hóa URL Các Khóa và Giá Trị
❌ Sai: xây dựng chuỗi truy vấn chuẩn hóa với các giá trị thô, chưa được mã hóa.
✅ Đúng: mã hóa phần trăm cả khóa và giá trị theo RFC-3986 (safe='-._~') trước khi ký — sử dụng quote, không dùng quote_plus (vì hàm này phát ra + cho khoảng trắng và sẽ không khớp với máy chủ).
Lỗi 401 missing_signature / invalid_signature
missing_signature (401) nghĩa là key hoặc s bị thiếu trong URL. invalid_signature (401) nghĩa là chuỗi chuẩn hóa không khớp — hãy xác nhận dấu xuống dòng trong nội dung đã ký, cùng một hmac_secret ở cả hai phía và quote(safe='-._~') (không phải quote_plus) cho mọi khóa và giá trị.