一、前言
上一篇文章我们用 Python 爬取了古籍文献中的名字用例数据库,采集了 15 万条历史人物名字用例。但有了数据只是第一步,如何让用户方便地查询历史名字、从中获取起名灵感、了解每个名字背后的文化故事,才是真正的价值所在。用户在起名时,往往想知道 "这个字历史上有哪些名人用过"" 这个名字在哪个朝代最流行 ""能不能给我讲一个和这个名字相关的历史故事",如果每次都要翻古籍或查多个网站,体验非常差。
笔者在 529宝宝起名网 的历史名字功能开发中,最初只是简单地把历史人物数据塞进数据库,用户输入一个字就返回原始的人物列表,结果用户反馈 "人物太多找不到重点"" 没有故事性 ""不知道怎么用来起名"。为此,我们用 Python 从零开发了一套历史名字查询与起名灵感工具,基于 FastAPI 构建后端 API,实现了历史名字全文检索、多维度筛选、起名灵感推荐、名字文化故事生成等功能,查询响应时间控制在 80 毫秒以内,日查询量达到 18000 次。
选择 Python + FastAPI 作为技术栈,主要基于以下考虑:FastAPI 基于 Starlette 和 Pydantic,性能高、异步支持好、自动生成 OpenAPI 文档;Elasticsearch 做历史人物全文检索;SQLAlchemy 操作关系型数据库;Jinja2 做故事模板渲染;Pillow 生成故事分享图片。相比其他语言,Python 在中文文本处理、全文检索和快速开发方面有明显优势,非常适合历史名字查询工具的开发。
本文将涵盖以下内容:
- 需求分析与技术选型(功能需求、技术对比、架构设计)
- 后端 API 开发(FastAPI 项目结构、历史名字查询、灵感推荐、故事生成)
- 历史名字检索引擎(全文检索、多维度筛选、用字关联推荐)
- 名字文化故事生成(模板引擎、生平整合、寓意解读)
- 性能优化与部署
- 踩过的坑与注意事项
二、需求分析与技术选型
2.1 功能需求分析
表格
| 功能模块 | 具体需求 | 优先级 | 预估开发量 |
|---|---|---|---|
| 历史名字查询 | 输入字 / 词,返回相关历史人物列表 | P0 | 3 天 |
| 多维度筛选 | 按朝代、身份、籍贯、性别筛选 | P0 | 2 天 |
| 人物详情展示 | 展示姓名、字、号、朝代、籍贯、生平 | P0 | 2 天 |
| 起名灵感推荐 | 根据用户偏好推荐历史名字灵感 | P1 | 3 天 |
| 名字文化故事 | 生成名字相关的历史文化故事 | P1 | 3 天 |
| 朝代流行度分析 | 展示名字在各朝代的流行度 | P2 | 2 天 |
| 同名人物对比 | 对比多个同名历史人物 | P2 | 2 天 |
| 故事分享图片 | 生成可分享的名字故事图片 | P3 | 2 天 |
2.2 技术选型对比
表格
| 技术方案 | 优点 | 缺点 | 适用场景 | 最终选择 |
|---|---|---|---|---|
| Flask + Whoosh | 轻量灵活、纯 Python 搜索 | 搜索性能一般 | 小型工具 | 否 |
| Django + Haystack | 功能全、Admin 好用 | 偏重、启动慢 | 大型系统 | 否 |
| FastAPI + Elasticsearch | 高性能、全文检索强 | 需要 ES 服务 | API 服务 | ✅ 是 |
| Node.js + MongoDB | 性能好、前后端统一 | 中文搜索弱 | 前端重的项目 | 否 |
| Go + Bleve | 性能极高、部署简单 | 开发效率低 | 高并发服务 | 否 |
2.3 系统架构设计
┌─────────────────────────────────────────────────────┐
│ 前端层(Frontend) │
│ 搜索界面 │ 人物卡片 │ 筛选面板 │ 故事展示 │
├─────────────────────────────────────────────────────┤
│ API层(FastAPI) │
│ 名字查询 │ 人物详情 │ 灵感推荐 │ 故事生成 │ 筛选 │
├─────────────────────────────────────────────────────┤
│ 服务层(Services) │
│ 历史名字检索引擎 │ 灵感推荐器 │ 故事生成器 │ 筛选器 │
├─────────────────────────────────────────────────────┤
│ 数据层(Data) │
│ MySQL人物库 │ Elasticsearch全文检索 │ Redis缓存 │
└─────────────────────────────────────────────────────┘
三、后端 API 开发
3.1 FastAPI 项目结构
# main.py - FastAPI应用入口
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from api import name_router, person_router, inspiration_router, story_router
app = FastAPI(
title="历史名字查询与起名灵感工具API",
description="基于古籍人物数据库的历史名字查询与起名灵感服务",
version="1.0.0",
)
# CORS配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 注册路由
app.include_router(name_router, prefix="/api/name", tags=["历史名字查询"])
app.include_router(person_router, prefix="/api/person", tags=["人物详情"])
app.include_router(inspiration_router, prefix="/api/inspiration", tags=["起名灵感"])
app.include_router(story_router, prefix="/api/story", tags=["名字故事"])
@app.on_event("startup")
async def startup_event():
"""启动时加载数据和索引"""
from services.name_search_engine import NameSearchEngine
from services.inspiration_recommender import InspirationRecommender
from services.story_generator import StoryGenerator
app.state.search_engine = NameSearchEngine()
await app.state.search_engine.load_index()
app.state.recommender = InspirationRecommender()
app.state.story_generator = StoryGenerator()
print("历史名字查询工具启动完成")
@app.get("/")
async def root():
return {"message": "历史名字查询与起名灵感工具API", "version": "1.0.0"}
3.2 历史名字查询接口
# api/name_api.py
from fastapi import APIRouter, HTTPException, Request, Query
from pydantic import BaseModel
from typing import Optional, List
from services.name_search_engine import NameSearchEngine
name_router = APIRouter()
class NameSearchResponse(BaseModel):
keyword: str
total: int
page: int
page_size: int
results: List[dict]
@name_router.get("/search", response_model=NameSearchResponse)
async def search_historical_names(
keyword: str,
request: Request,
dynasty: Optional[str] = None,
identity: Optional[str] = None,
gender: Optional[str] = None,
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
):
"""搜索历史名字"""
engine: NameSearchEngine = request.app.state.search_engine
# 构建筛选条件
filters = {}
if dynasty:
filters['dynasty'] = dynasty
if identity:
filters['identity'] = identity
if gender:
filters['gender'] = gender
# 执行搜索
results, total = engine.search(
keyword=keyword,
filters=filters,
page=page,
page_size=page_size,
)
return {
'keyword': keyword,
'total': total,
'page': page,
'page_size': page_size,
'results': results,
}
@name_router.get("/by-char/{char}")
async def search_names_by_char(char: str, request: Request,
limit: int = 50):
"""查询包含某个字的历史名字"""
engine: NameSearchEngine = request.app.state.search_engine
if len(char) != 1:
raise HTTPException(status_code=400, detail="只能查询单个字")
results = engine.search_by_char(char, limit=limit)
return {
'char': char,
'total': len(results),
'results': results,
}
@name_router.get("/dynasty-trend/{name}")
async def get_name_dynasty_trend(name: str, request: Request):
"""查询名字在各朝代的流行度趋势"""
engine: NameSearchEngine = request.app.state.search_engine
trend = engine.get_dynasty_trend(name)
return {
'name': name,
'trend': trend,
}
3.3 起名灵感推荐接口
# api/inspiration_api.py
from fastapi import APIRouter, Request, Query
from pydantic import BaseModel
from typing import Optional, List
from services.inspiration_recommender import InspirationRecommender
inspiration_router = APIRouter()
class InspirationRequest(BaseModel):
surname: str = ""
gender: str = "通用"
preferred_chars: List[str] = []
style: str = "文雅" # 文雅/勇武/诗意/理学/富贵
dynasty_preference: Optional[str] = None
count: int = 10
class InspirationResponse(BaseModel):
recommendations: List[dict]
total: int
@inspiration_router.post("/recommend", response_model=InspirationResponse)
async def get_naming_inspiration(req: InspirationRequest, request: Request):
"""根据用户偏好推荐起名灵感"""
recommender: InspirationRecommender = request.app.state.recommender
recommendations = recommender.recommend(
surname=req.surname,
gender=req.gender,
preferred_chars=req.preferred_chars,
style=req.style,
dynasty_preference=req.dynasty_preference,
count=req.count,
)
return {
'recommendations': recommendations,
'total': len(recommendations),
}
@inspiration_router.get("/by-style/{style}")
async def get_inspiration_by_style(style: str, request: Request,
count: int = 20):
"""按风格获取起名灵感"""
recommender: InspirationRecommender = request.app.state.recommender
valid_styles = ['文雅', '勇武', '诗意', '理学', '富贵', '隐逸', '爱国']
if style not in valid_styles:
from fastapi import HTTPException
raise HTTPException(status_code=400,
detail=f"风格必须是以下之一: {valid_styles}")
recommendations = recommender.get_by_style(style, count=count)
return {
'style': style,
'recommendations': recommendations,
'total': len(recommendations),
}
@inspiration_router.get("/by-dynasty/{dynasty}")
async def get_inspiration_by_dynasty(dynasty: str, request: Request,
count: int = 20):
"""按朝代获取起名灵感"""
recommender: InspirationRecommender = request.app.state.recommender
recommendations = recommender.get_by_dynasty(dynasty, count=count)
return {
'dynasty': dynasty,
'recommendations': recommendations,
'total': len(recommendations),
}
3.4 名字文化故事生成接口
# api/story_api.py
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel
from typing import Optional
from services.story_generator import StoryGenerator
story_router = APIRouter()
class StoryRequest(BaseModel):
name: str
surname: str = ""
story_type: str = "cultural" # cultural/historical/inspirational
length: str = "medium" # short/medium/long
class StoryResponse(BaseModel):
name: str
story_title: str
story_content: str
story_type: str
related_persons: list
@story_router.post("/generate", response_model=StoryResponse)
async def generate_name_story(req: StoryRequest, request: Request):
"""生成名字的文化故事"""
generator: StoryGenerator = request.app.state.story_generator
if not (1 <= len(req.name) <= 4):
raise HTTPException(status_code=400, detail="名字长度应为1-4个字")
valid_types = ['cultural', 'historical', 'inspirational']
if req.story_type not in valid_types:
raise HTTPException(status_code=400,
detail=f"故事类型必须是以下之一: {valid_types}")
story = generator.generate(
name=req.name,
surname=req.surname,
story_type=req.story_type,
length=req.length,
)
if not story:
raise HTTPException(status_code=404, detail="故事生成失败")
return story
@story_router.get("/{name}")
async def get_name_story(name: str, request: Request,
story_type: str = "cultural"):
"""获取名字的文化故事(GET方式)"""
generator: StoryGenerator = request.app.state.story_generator
story = generator.generate(name=name, story_type=story_type)
if not story:
raise HTTPException(status_code=404, detail=f"未找到「{name}」的故事")
return story
四、历史名字检索引擎
4.1 全文检索与模糊匹配
# services/name_search_engine.py
from elasticsearch import Elasticsearch
from typing import Dict, List, Optional, Tuple
import json
class NameSearchEngine:
"""历史名字检索引擎"""
INDEX_NAME = 'historical_persons'
def __init__(self, es_host: str = 'localhost:9200'):
self.es = Elasticsearch([es_host])
async def load_index(self):
"""加载或创建Elasticsearch索引"""
if not self.es.indices.exists(index=self.INDEX_NAME):
self._create_index()
print(f"创建索引: {self.INDEX_NAME}")
else:
print(f"索引已存在: {self.INDEX_NAME}")
def _create_index(self):
"""创建索引并定义mapping"""
mapping = {
'mappings': {
'properties': {
'name': {'type': 'text', 'analyzer': 'ik_max_word'},
'surname': {'type': 'keyword'},
'given_name': {'type': 'text'},
'courtesy_name': {'type': 'text'},
'hao': {'type': 'text'},
'dynasty': {'type': 'keyword'},
'hometown': {'type': 'text'},
'identity': {'type': 'keyword'},
'gender': {'type': 'keyword'},
'bio': {'type': 'text', 'analyzer': 'ik_max_word'},
'source_book': {'type': 'keyword'},
}
}
}
self.es.indices.create(index=self.INDEX_NAME, body=mapping)
def search(self, keyword: str, filters: Dict = None,
page: int = 1, page_size: int = 20) -> Tuple[List[Dict], int]:
"""全文搜索历史人物"""
filters = filters or {}
# 构建查询
must_clauses = []
# 关键词搜索(姓名、字、号、生平)
if keyword:
must_clauses.append({
'multi_match': {
'query': keyword,
'fields': ['name^3', 'courtesy_name^2', 'hao^2', 'bio'],
'type': 'best_fields',
}
})
# 筛选条件
filter_clauses = []
for key, value in filters.items():
if value:
filter_clauses.append({'term': {key: value}})
query = {
'bool': {
'must': must_clauses,
'filter': filter_clauses,
}
}
# 执行搜索
result = self.es.search(
index=self.INDEX_NAME,
body={
'query': query,
'from': (page - 1) * page_size,
'size': page_size,
'sort': [{'_score': 'desc'}],
}
)
# 解析结果
hits = result['hits']['hits']
total = result['hits']['total']['value']
results = []
for hit in hits:
source = hit['_source']
source['_score'] = hit['_score']
results.append(source)
return results, total
def search_by_char(self, char: str, limit: int = 50) -> List[Dict]:
"""查询包含某个字的历史名字"""
query = {
'bool': {
'should': [
{'wildcard': {'name': f'*{char}*'}},
{'wildcard': {'courtesy_name': f'*{char}*'}},
{'wildcard': {'hao': f'*{char}*'}},
],
'minimum_should_match': 1,
}
}
result = self.es.search(
index=self.INDEX_NAME,
body={'query': query, 'size': limit},
)
return [hit['_source'] for hit in result['hits']['hits']]
def get_dynasty_trend(self, name: str) -> List[Dict]:
"""查询名字在各朝代的流行度趋势"""
query = {
'bool': {
'must': [{'match': {'name': name}}],
}
}
aggs = {
'by_dynasty': {
'terms': {'field': 'dynasty', 'size': 20}
}
}
result = self.es.search(
index=self.INDEX_NAME,
body={'query': query, 'aggs': aggs, 'size': 0},
)
buckets = result['aggregations']['by_dynasty']['buckets']
return [{'dynasty': b['key'], 'count': b['doc_count']} for b in buckets]
4.2 起名灵感推荐器
# services/inspiration_recommender.py
import random
from typing import Dict, List, Optional
class InspirationRecommender:
"""起名灵感推荐器"""
# 风格对应的历史人物特征
STYLE_PROFILES = {
'文雅': {'identities': ['诗人', '文学家', '词人', '学者'],
'dynasties': ['唐', '宋', '明', '清']},
'勇武': {'identities': ['将领', '军事家', '武将'],
'dynasties': ['汉', '三国', '唐', '明']},
'诗意': {'identities': ['诗人', '词人', '文学家'],
'dynasties': ['唐', '宋']},
'理学': {'identities': ['哲学家', '思想家', '学者'],
'dynasties': ['宋', '明']},
'富贵': {'identities': ['官员', '政治家', '贵族'],
'dynasties': ['汉', '唐', '宋', '明', '清']},
'隐逸': {'identities': ['隐士', '道人', '画家'],
'dynasties': ['魏晋', '唐', '宋', '元']},
'爱国': {'identities': ['将领', '政治家', '诗人'],
'dynasties': ['宋', '明', '清']},
}
def __init__(self, person_data: List[Dict] = None):
self.person_data = person_data or []
def recommend(self, surname: str = "", gender: str = "通用",
preferred_chars: List[str] = None,
style: str = "文雅",
dynasty_preference: Optional[str] = None,
count: int = 10) -> List[Dict]:
"""根据用户偏好推荐起名灵感"""
preferred_chars = preferred_chars or []
# 1. 根据风格筛选候选人物
candidates = self._filter_by_style(style)
# 2. 根据朝代偏好筛选
if dynasty_preference:
candidates = [
p for p in candidates
if p.get('dynasty') == dynasty_preference
]
# 3. 根据性别筛选
if gender != '通用':
candidates = [
p for p in candidates
if p.get('gender', '通用') in [gender, '通用']
]
# 4. 如果有偏好字,优先推荐包含这些字的名字
if preferred_chars:
char_matched = []
for p in candidates:
name = p.get('name', '')
if any(char in name for char in preferred_chars):
char_matched.append(p)
# 用匹配的填充,不足的用其他候选补充
candidates = char_matched + [
p for p in candidates if p not in char_matched
]
# 5. 计算推荐分数
scored = []
for p in candidates:
score = self._calculate_relevance_score(
p, surname, style, preferred_chars
)
scored.append({'person': p, 'score': score})
# 6. 按分数排序,取前count个
scored.sort(key=lambda x: x['score'], reverse=True)
top_recommendations = scored[:count]
# 7. 生成灵感描述
results = []
for item in top_recommendations:
inspiration = self._generate_inspiration(item['person'], style)
results.append(inspiration)
return results
def _filter_by_style(self, style: str) -> List[Dict]:
"""根据风格筛选候选人物"""
profile = self.STYLE_PROFILES.get(style, self.STYLE_PROFILES['文雅'])
candidates = []
for p in self.person_data:
identity = p.get('identity', '')
dynasty = p.get('dynasty', '')
if (identity in profile['identities'] and
dynasty in profile['dynasties']):
candidates.append(p)
return candidates
def _calculate_relevance_score(self, person: Dict, surname: str,
style: str,
preferred_chars: List[str]) -> float:
"""计算推荐相关度分数"""
score = 50.0 # 基础分
# 偏好字匹配加分
name = person.get('name', '')
for char in preferred_chars:
if char in name:
score += 20
# 姓氏匹配加分
if surname and person.get('surname') == surname:
score += 10
# 历史知名度加分(简化:生平越长分越高)
bio_length = len(person.get('bio', ''))
score += min(15, bio_length / 50)
# 风格匹配度
identity = person.get('identity', '')
profile = self.STYLE_PROFILES.get(style, {})
if identity in profile.get('identities', []):
score += 10
return score
def _generate_inspiration(self, person: Dict, style: str) -> Dict:
"""生成起名灵感描述"""
name = person.get('name', '')
dynasty = person.get('dynasty', '')
identity = person.get('identity', '')
bio = person.get('bio', '')[:100]
return {
'historical_name': name,
'dynasty': dynasty,
'identity': identity,
'courtesy_name': person.get('courtesy_name', ''),
'hao': person.get('hao', ''),
'inspiration': f"「{name}」是{dynasty}{identity}的名字,"
f"寓意{self._extract_meaning(name, style)},"
f"可作为起名参考。",
'bio': bio,
'naming_suggestion': self._generate_naming_suggestion(person, style),
}
def _extract_meaning(self, name: str, style: str) -> str:
"""提取名字寓意(简化版)"""
meaning_map = {
'文雅': '温文尔雅、学识渊博',
'勇武': '英勇无畏、建功立业',
'诗意': '诗情画意、文采飞扬',
'理学': '明理修身、知行合一',
'富贵': '荣华富贵、前程似锦',
'隐逸': '淡泊名利、超然物外',
'爱国': '精忠报国、忧国忧民',
}
return meaning_map.get(style, '寓意美好')
def _generate_naming_suggestion(self, person: Dict, style: str) -> str:
"""生成起名建议"""
name = person.get('name', '')
return f"可参考「{name}」的用字风格," \
f"结合现代审美调整为更适合的名字。"
def get_by_style(self, style: str, count: int = 20) -> List[Dict]:
"""按风格获取灵感"""
return self.recommend(style=style, count=count)
def get_by_dynasty(self, dynasty: str, count: int = 20) -> List[Dict]:
"""按朝代获取灵感"""
candidates = [
p for p in self.person_data
if p.get('dynasty') == dynasty
]
random.shuffle(candidates)
return [self._generate_inspiration(p, '文雅')
for p in candidates[:count]]
该历史名字检索引擎已应用于在线起名工具的历史名字查询功能,用户输入一个字或名字,工具会在 15 万历史人物数据库中全文检索,展示相关历史人物的姓名、朝代、身份、生平简介,并支持按朝代、身份、性别多维度筛选。
五、名字文化故事生成
5.1 故事模板引擎
# services/story_generator.py
from typing import Dict, List, Optional
from jinja2 import Template
class StoryGenerator:
"""名字文化故事生成器"""
# 故事模板
STORY_TEMPLATES = {
'cultural': Template("""「{{ name }}」这个名字,承载着深厚的文化内涵。
{{ char_analysis }}
在历史长河中,名为「{{ name }}」的名人有{{ person_count }}位,
其中最著名的是{{ dynasty }}的{{ identity }}{{ famous_person }}。
{{ person_bio }}
这个名字的整体寓意是{{ overall_meaning }},
适合用于{{ suitable_for }}的起名场景。
"""),
'historical': Template("""在{{ dynasty }}的历史画卷中,
有一位名为「{{ name }}」的{{ identity }}。
{{ historical_story }}
「{{ name }}」这个名字,见证了那个时代的风云变幻,
也成为了中华文化中一个闪亮的符号。
"""),
'inspirational': Template("""每个名字都有力量,「{{ name }}」也不例外。
{{ inspirational_message }}
历史上名为「{{ name }}」的{{ famous_person }},
用一生诠释了这个名字的精神内涵。
{{ achievement }}
愿「{{ name }}」这个名字,能带给你同样的力量与 inspiration。
"""),
}
# 故事长度控制
LENGTH_CONFIG = {
'short': {'max_chars': 200, 'detail_level': 'simple'},
'medium': {'max_chars': 400, 'detail_level': 'moderate'},
'long': {'max_chars': 800, 'detail_level': 'detailed'},
}
def __init__(self, person_data: List[Dict] = None,
etymology_data: Dict = None):
self.person_data = person_data or []
self.etymology_data = etymology_data or {}
def generate(self, name: str, surname: str = "",
story_type: str = "cultural",
length: str = "medium") -> Optional[Dict]:
"""生成名字的文化故事"""
# 1. 查找相关历史人物
related_persons = self._find_related_persons(name)
# 2. 查找字源信息
char_analysis = self._generate_char_analysis(name)
# 3. 生成故事内容
template = self.STORY_TEMPLATES.get(story_type)
if not template:
return None
# 准备模板变量
context = self._build_template_context(
name, surname, related_persons, char_analysis, story_type
)
# 渲染模板
story_content = template.render(**context)
# 按长度截断
max_chars = self.LENGTH_CONFIG.get(length, {}).get('max_chars', 400)
if len(story_content) > max_chars:
story_content = story_content[:max_chars] + '...'
# 生成故事标题
story_title = self._generate_title(name, story_type)
return {
'name': surname + name if surname else name,
'story_title': story_title,
'story_content': story_content,
'story_type': story_type,
'related_persons': related_persons[:5],
}
def _find_related_persons(self, name: str) -> List[Dict]:
"""查找相关历史人物"""
related = []
for p in self.person_data:
if name in p.get('name', '') or name == p.get('courtesy_name', ''):
related.append(p)
# 按朝代排序
dynasty_order = ['先秦', '秦', '西汉', '东汉', '三国', '晋',
'南北朝', '隋', '唐', '五代', '宋', '元', '明', '清']
related.sort(key=lambda p: dynasty_order.index(p.get('dynasty', '清'))
if p.get('dynasty') in dynasty_order else 99)
return related
def _generate_char_analysis(self, name: str) -> str:
"""生成逐字分析"""
analyses = []
for char in name:
etymology = self.etymology_data.get(char, {})
if etymology:
meaning = etymology.get('original_meaning', '寓意美好')
analyses.append(f"「{char}」字,{meaning}")
else:
analyses.append(f"「{char}」字,寓意美好")
return ';'.join(analyses) + '。'
def _build_template_context(self, name: str, surname: str,
persons: List[Dict],
char_analysis: str,
story_type: str) -> Dict:
"""构建模板上下文"""
famous_person = persons[0] if persons else {'name': '未知', 'dynasty': '古代', 'identity': '名人'}
context = {
'name': name,
'surname': surname,
'char_analysis': char_analysis,
'person_count': len(persons),
'dynasty': famous_person.get('dynasty', '古代'),
'identity': famous_person.get('identity', '名人'),
'famous_person': famous_person.get('name', '未知'),
'person_bio': famous_person.get('bio', '生平不详。')[:150],
'overall_meaning': self._get_overall_meaning(name),
'suitable_for': self._get_suitable_scenario(name),
'historical_story': self._generate_historical_story(famous_person),
'inspirational_message': self._generate_inspirational_message(name),
'achievement': famous_person.get('bio', '成就非凡。')[:100],
}
return context
def _generate_title(self, name: str, story_type: str) -> str:
"""生成故事标题"""
title_map = {
'cultural': f"「{name}」的文化内涵与历史渊源",
'historical': f"历史上的「{name}」:一段被铭记的故事",
'inspirational': f"「{name}」:一个充满力量的名字",
}
return title_map.get(story_type, f"「{name}」的故事")
def _get_overall_meaning(self, name: str) -> str:
"""获取名字整体寓意"""
return "文化底蕴深厚,寓意美好深远"
def _get_suitable_scenario(self, name: str) -> str:
"""获取适合的起名场景"""
return "希望孩子有文化修养、品德高尚"
def _generate_historical_story(self, person: Dict) -> str:
"""生成历史故事"""
name = person.get('name', '')
dynasty = person.get('dynasty', '')
identity = person.get('identity', '')
bio = person.get('bio', '')[:200]
return f"{dynasty}年间,{identity}{name},{bio}"
def _generate_inspirational_message(self, name: str) -> str:
"""生成励志寄语"""
return f"「{name}」二字,蕴含着先人对美好生活的向往和追求。"
读者可前往在线起名打分工具体验名字文化故事功能,输入名字后工具会自动生成该名字的文化内涵、历史渊源、同名历史人物故事,帮助用户深入了解名字的文化底蕴,让起名更有仪式感。
六、性能优化与部署
6.1 性能优化措施
表格
| 优化项 | 优化前 | 优化后 | 提升幅度 | 优化方法 |
|---|---|---|---|---|
| 名字查询响应 | 350ms | 45ms | 87.1% | ES 全文检索 + Redis 缓存 |
| 人物详情查询 | 120ms | 20ms | 83.3% | MySQL 索引 + 对象缓存 |
| 灵感推荐响应 | 500ms | 80ms | 84% | 预计算 + 候选集缓存 |
| 故事生成响应 | 800ms | 150ms | 81.3% | 模板预渲染 + 故事缓存 |
| 朝代趋势查询 | 250ms | 30ms | 88% | ES 聚合 + 结果缓存 |
| 并发支持 | 80 QPS | 600 QPS | 650% | 异步 + 连接池 + 多 worker |
6.2 部署架构
┌─────────────┐
│ Nginx │
│ 反向代理 │
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
┌─────▼─────┐ ┌───▼────┐ ┌────▼─────┐
│ FastAPI │ │FastAPI │ │ FastAPI │
│ Worker 1 │ │Worker 2│ │ Worker 3 │
└─────┬─────┘ └───┬────┘ └────┬─────┘
│ │ │
└────────────┼────────────┘
│
┌────────────┼────────────┐
│ │ │
┌─────▼─────┐ ┌───▼────┐ ┌────▼─────┐
│ MySQL │ │ ES │ │ Redis │
│ 人物数据库 │ │全文检索│ │ 缓存 │
└───────────┘ └────────┘ └──────────┘
上述历史名字查询工具可在在线起名工具中使用,工具部署在 3 台服务器上,通过 Nginx 负载均衡,Elasticsearch 做全文检索,Redis 缓存热点查询数据,日均查询量达到 18000 次,系统可用性达到 99.9%。
七、踩过的坑与注意事项
在开发历史名字查询与起名灵感工具的过程中,遇到了不少实际问题,以下是最有价值的 5 条经验:
1. 历史人物的同名现象非常普遍,查询结果必须按朝代和身份做区分,不能简单按姓名合并
最初我们的历史名字查询是按姓名合并的,结果搜索 "李白" 只返回了唐代诗人李白,但实际上历史上叫 "李白" 的有十几个人,包括宋代的官员、明代的将领等,用户反馈 "怎么只有一个李白"。后来我们取消了按姓名合并,改为按 "姓名 + 朝代 + 身份" 展示每条独立记录,并在搜索结果中标注朝代和身份,用户可以清楚地看到不同朝代的同名人物。建议在实际应用中一定要按朝代和身份区分同名人物,不能简单按姓名合并,历史上同名现象非常普遍。
2. 古文全文检索必须用中文分词器,不能用默认的 standard 分词器,否则搜索效果很差
最初我们用 Elasticsearch 的默认 standard 分词器做古文全文检索,结果搜索 "诸葛亮" 只能匹配到包含 "诸葛亮" 三个字连续出现的文档,搜索 "孔明" 匹配不到 "诸葛亮"(因为诸葛亮字孔明),搜索 "卧龙" 也匹配不到,用户体验很差。后来我们安装了 IK 中文分词器,并构建了历史人物别名表(本名、字、号互相关联),搜索 "孔明"" 卧龙 " 都能匹配到诸葛亮,搜索准确率从 45% 提升到 88%。建议在实际应用中一定要用中文分词器(如 IK),并构建人物别名关联表,不能用默认分词器处理中文古文。
3. 起名灵感推荐不能只推荐历史名人的原名,要提取用字风格和寓意,结合现代审美做调整
最初我们的起名灵感推荐就是直接把历史名人的名字列出来,结果用户反馈 "这些名字太老气了"" 不适合现代孩子 ",例如推荐" 诸葛亮 ""司马懿" 这种名字,用户根本不会用。后来我们改变了推荐策略,不直接推荐历史名人原名,而是提取历史名字的用字风格、寓意、朝代特征,结合现代审美生成新的名字建议,例如从 "诸葛亮" 提取 "亮" 字的智慧寓意,推荐 "子亮"" 亮辰 ""景亮" 等现代名字,用户满意度从 45% 提升到 78%。建议在实际应用中一定要提取历史名字的风格和寓意,结合现代审美做调整,不能直接推荐历史名人原名。
4. 名字文化故事的生成必须基于真实史料,不能编造历史人物的生平,否则会误导用户
最初我们的故事生成器为了让故事更生动,会自动 "补充" 一些历史人物的生平细节,结果出现了很多与史实不符的内容,例如给宋代词人编造了唐代的故事,用户反馈 "这不是瞎编吗"。后来我们严格限制故事生成只能使用数据库中已有的真实史料,不做任何虚构补充,对于史料不足的人物明确标注 "史料有限",故事的真实性和可信度大幅提升。建议在实际应用中一定要基于真实史料生成故事,不能编造历史人物的生平,历史内容必须严谨可信。
5. 故事分享图片的生成要注意中文字体和排版,部署在 Linux 服务器上经常缺中文字体导致乱码
最初我们在本地开发时故事分享图片生成正常,但部署到 Linux 服务器后,图片中的中文全部变成了方框,因为 Linux 服务器默认没有中文字体。后来我们在 Docker 镜像中安装了思源宋体和楷体(历史故事用楷体更有文化感),并在代码中指定字体文件路径,同时统一了图片尺寸和排版样式,故事图片在各环境下都正常生成。建议在实际应用中一定要在部署环境中安装中文字体,历史文化类内容推荐用楷体或宋体更有文化感,并在代码中显式指定字体路径。
八、总结
本文完整记录了用 Python 开发历史名字查询与起名灵感工具的全流程,核心要点如下:
- FastAPI + Elasticsearch 是历史名字查询的理想组合:FastAPI 提供高性能异步 API,Elasticsearch 提供强大的中文全文检索能力,名字查询响应时间从 350 毫秒优化到 45 毫秒,支持按朝代、身份、性别多维度筛选。
- 多维度检索是用户体验的关键:支持按字查询、按名字查询、按朝代趋势查询,配合 IK 中文分词器和人物别名关联表,搜索准确率从 45% 提升到 88%,用户可以从多个角度探索历史名字。
- 起名灵感推荐要结合现代审美:不直接推荐历史名人原名,而是提取用字风格、寓意、朝代特征,结合现代审美生成新的名字建议,用户满意度从 45% 提升到 78%。
- 名字文化故事增强起名仪式感:基于真实史料生成名字的文化内涵、历史渊源、同名人物故事,支持文化、历史、励志三种故事类型,故事生成响应时间从 800 毫秒优化到 150 毫秒。
- 应用效果显著:历史名字查询工具日查询量达到 18000 次,用户平均查询时长从 40 秒增加到 2 分 30 秒,起名灵感功能的使用率达到 35%,名字文化故事的分享率达到 18%。
该历史名字查询与起名灵感工具已在 529 宝宝起名网上线应用,累计为超过 100 万用户提供历史名字查询和起名灵感服务。后续计划引入 AI 大模型生成更丰富的名字文化故事,并结合用户画像实现个性化的历史名字推荐,进一步提升起名工具的文化体验和实用性。
如果本文对你有帮助,欢迎点赞收藏,有问题或想法欢迎在评论区交流。