用 Python 开发历史名字查询与起名灵感工具:从古籍人物数据库到名字文化故事生成

一、前言

上一篇文章我们用 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 开发历史名字查询与起名灵感工具的全流程,核心要点如下:

  1. FastAPI + Elasticsearch 是历史名字查询的理想组合:FastAPI 提供高性能异步 API,Elasticsearch 提供强大的中文全文检索能力,名字查询响应时间从 350 毫秒优化到 45 毫秒,支持按朝代、身份、性别多维度筛选。
  2. 多维度检索是用户体验的关键:支持按字查询、按名字查询、按朝代趋势查询,配合 IK 中文分词器和人物别名关联表,搜索准确率从 45% 提升到 88%,用户可以从多个角度探索历史名字。
  3. 起名灵感推荐要结合现代审美:不直接推荐历史名人原名,而是提取用字风格、寓意、朝代特征,结合现代审美生成新的名字建议,用户满意度从 45% 提升到 78%。
  4. 名字文化故事增强起名仪式感:基于真实史料生成名字的文化内涵、历史渊源、同名人物故事,支持文化、历史、励志三种故事类型,故事生成响应时间从 800 毫秒优化到 150 毫秒。
  5. 应用效果显著:历史名字查询工具日查询量达到 18000 次,用户平均查询时长从 40 秒增加到 2 分 30 秒,起名灵感功能的使用率达到 35%,名字文化故事的分享率达到 18%。

该历史名字查询与起名灵感工具已在 529 宝宝起名网上线应用,累计为超过 100 万用户提供历史名字查询和起名灵感服务。后续计划引入 AI 大模型生成更丰富的名字文化故事,并结合用户画像实现个性化的历史名字推荐,进一步提升起名工具的文化体验和实用性。

如果本文对你有帮助,欢迎点赞收藏,有问题或想法欢迎在评论区交流。

相关推荐
linux_cfan1 小时前
videojs v10 源代码系列解读:10 · `DestroyMixin`:双重 rAF 延迟销毁
前端·javascript·音视频
用户298698530141 小时前
Python 将 HTML 转换为 Word 文档的实践指南
python·html·api
计算机魔术师1 小时前
英伟达是人工智能领域的"中央银行"
前端
维克兜率天1 小时前
【维克】模块3总结:从一行空数据,到一个能跑的模型
python·深度学习·算法
我的div丢了肿么办1 小时前
顶部区域固定,左侧区域滚动,右侧区域滚动,彼此独立
前端·css
飞Link1 小时前
定积分理论与 Python 仿真完全指南
python·算法
雪芽蓝域zzs1 小时前
第四十六节:顶部【驾驶舱】独立大屏页面实现
前端·javascript·vue.js
小静AI工程实验室1 小时前
Python 爬虫翻页为何重复、漏数据?SQLite 复现 OFFSET、复合游标与快照的 8 项检查
爬虫·python·sqlite
ocean21031 小时前
2025-2026年C++专题大厂面试高频问题
开发语言·c++·面试