用 Python 爬取古籍文献中的名字用例数据库:从二十四史到诗词文集的历史名字采集与分析

一、前言

名字是文化的载体,每个时代的名字都带有鲜明的时代烙印。汉代名字多单字、崇尚勇武,唐代名字多双字、追求文雅,宋代名字偏好理学色彩,明清名字讲究字辈排行。了解历史上的名字用例,不仅能让起名更有文化底蕴,还能避免选用历史上有负面联想的名字。然而,大多数起名系统只提供现代名字的统计,缺乏历史名字用例数据,用户想知道 "这个字在历史上有哪些名人用过"" 这个名字在哪个朝代最流行 ",都无法回答。

笔者在 529宝宝起名网 的历史名字功能开发中,最初的名字库只有现代名字数据,用户经常问 "诸葛亮的 ' 亮' 字还有哪些历史名人用"" 苏轼的 ' 轼' 字是什么意思 ""这个名字在古代有没有人用过",我们都无法回答。为此,我们用 Python 从零搭建了一套古籍文献名字用例数据库采集系统,从二十四史、诗词文集、地方志、家谱等多个数据源,全量采集了 15 万条历史人物名字用例,覆盖从先秦到清末的各个朝代,包括人物姓名、字、号、朝代、籍贯、生平简介等信息,为起名提供了深厚的历史文化支撑。

选择 Python 作为采集和处理工具,主要基于以下考虑:requests 和 BeautifulSoup 采集各古籍网站高效稳定,Scrapy 适合大规模分布式采集,lxml 解析 HTML 和 XML 格式的古籍数据,jieba 和 HanLP 做中文命名实体识别(NER),pandas 管理名字用例数据,re 做古文文本清洗和正则提取。相比其他语言,Python 在网页爬虫和中文 NLP 处理领域有最完善的生态,非常适合古籍名字用例的采集和结构化。

本文将涵盖以下内容:

  • 数据源与采集方案(数据源对比、架构设计、反爬处理)
  • 多源古籍数据采集实现(二十四史、诗词文集、OCR 清洗)
  • 名字实体识别与提取(规则提取、NER 模型、朝代标注)
  • 名字用例数据库构建(表结构、起名关联、热度统计)
  • 数据质量验证与应用
  • 踩过的坑与注意事项

二、数据源与采集方案

2.1 古籍数据源对比

表格

数据源 文献量 名字用例 数据格式 采集难度 数据质量
中国哲学书电子化 500 + 部 8 万 + HTML/XML 极高(公版)
国学大师 3000 + 部 12 万 + HTML 极高(有校注)
汉典古籍 1000 + 部 5 万 + HTML
古诗文网 20 万 + 篇 3 万 + HTML 高(诗词为主)
维基文库 1000 + 部 4 万 + HTML/XML 高(公版)
开源古籍库(GitHub) 500 + 部 6 万 + JSON/CSV 中(需清洗)

2.2 采集架构设计

复制代码
┌─────────────────────────────────────────────────────┐
│                   调度层(Scheduler)                 │
│         任务分配、进度监控、失败重试、去重管理          │
├─────────────────────────────────────────────────────┤
│                   采集层(Crawlers)                  │
│  二十四史爬虫 │ 诗词文集爬虫 │ 地方志爬虫 │ 开源导入   │
├─────────────────────────────────────────────────────┤
│                   处理层(Processors)                │
│  文本清洗 │ OCR识别 │ 名字NER │ 朝代标注 │ 人物关联   │
├─────────────────────────────────────────────────────┤
│                   存储层(Storage)                   │
│  原始文本MongoDB │ 结构化数据MySQL │ 全文检索ES       │
└─────────────────────────────────────────────────────┘

2.3 反爬与版权处理策略

表格

问题 应对方案 效果
IP 频率限制 代理 IP 池(50 个 IP 轮换)+ 随机延迟 5-10 秒 封禁率从 45% 降到 6%
User-Agent 检测 随机 UA 池(200 个真实浏览器 UA) 拦截率从 30% 降到 4%
动态渲染 Selenium+Headless Chrome 覆盖率 100%
图片版古籍 OCR 识别(PaddleOCR) 识别率 92%
版权问题 仅采集公版古籍(作者去世超 50 年) 100% 合规
数据去重 文本相似度 + 人物姓名双重去重 去重率 65%

三、多源古籍数据采集实现

3.1 二十四史人物传记采集

复制代码
# crawlers/history_book_crawler.py
import requests
from bs4 import BeautifulSoup
import time
import random
import json
import re
from typing import Dict, List

class HistoryBookCrawler:
    """二十四史人物传记采集器"""
    
    BASE_URL = 'https://www.guoxuedashi.com'
    HEADERS = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
    }
    
    # 二十四史列表
    TWENTY_FOUR_HISTORIES = [
        ('史记', '西汉', '司马迁'),
        ('汉书', '东汉', '班固'),
        ('后汉书', '南朝宋', '范晔'),
        ('三国志', '西晋', '陈寿'),
        ('晋书', '唐', '房玄龄等'),
        ('宋书', '南朝梁', '沈约'),
        ('南齐书', '南朝梁', '萧子显'),
        ('梁书', '唐', '姚思廉'),
        ('陈书', '唐', '姚思廉'),
        ('魏书', '北齐', '魏收'),
        ('北齐书', '唐', '李百药'),
        ('周书', '唐', '令狐德棻等'),
        ('隋书', '唐', '魏征等'),
        ('南史', '唐', '李延寿'),
        ('北史', '唐', '李延寿'),
        ('旧唐书', '五代后晋', '刘昫等'),
        ('新唐书', '北宋', '欧阳修等'),
        ('旧五代史', '北宋', '薛居正等'),
        ('新五代史', '北宋', '欧阳修'),
        ('宋史', '元', '脱脱等'),
        ('辽史', '元', '脱脱等'),
        ('金史', '元', '脱脱等'),
        ('元史', '明', '宋濂等'),
        ('明史', '清', '张廷玉等'),
    ]
    
    def __init__(self, proxy_pool: List[str] = None):
        self.session = requests.Session()
        self.session.headers.update(self.HEADERS)
        self.proxy_pool = proxy_pool or []
        self.results = []
    
    def _get_proxy(self) -> Dict:
        if not self.proxy_pool:
            return {}
        proxy = random.choice(self.proxy_pool)
        return {'http': proxy, 'https': proxy}
    
    def _request(self, url: str, retries: int = 3) -> str:
        for attempt in range(retries):
            try:
                time.sleep(random.uniform(5, 10))
                response = self.session.get(
                    url, proxies=self._get_proxy(), timeout=30
                )
                response.raise_for_status()
                response.encoding = 'utf-8'
                return response.text
            except Exception as e:
                print(f"请求失败(第{attempt+1}次): {e}")
                time.sleep(8 * (attempt + 1))
        return None
    
    def crawl_book_index(self, book_name: str) -> List[Dict]:
        """采集单部史书的目录"""
        print(f"采集《{book_name}》目录...")
        # 构造搜索URL
        search_url = f'{self.BASE_URL}/search/?kw={book_name}'
        html = self._request(search_url)
        if not html:
            return []
        
        soup = BeautifulSoup(html, 'html.parser')
        chapter_links = soup.select('.chapter-list a')
        
        chapters = []
        for link in chapter_links:
            title = link.get_text(strip=True)
            href = link.get('href', '')
            # 只采集列传(人物传记)
            if '传' in title and href:
                chapters.append({
                    'title': title,
                    'url': href if href.startswith('http') else self.BASE_URL + href,
                    'book': book_name,
                })
        
        print(f"  找到 {len(chapters)} 篇列传")
        return chapters
    
    def crawl_biography(self, chapter_url: str, book_name: str, 
                         dynasty: str) -> List[Dict]:
        """采集单篇列传中的人物"""
        html = self._request(chapter_url)
        if not html:
            return []
        
        soup = BeautifulSoup(html, 'html.parser')
        
        # 提取正文
        content_elem = soup.select_one('.content')
        if not content_elem:
            return []
        
        content = content_elem.get_text()
        
        # 提取人物信息
        persons = self._extract_persons(content, book_name, dynasty)
        
        return persons
    
    def _extract_persons(self, content: str, book_name: str, 
                          dynasty: str) -> List[Dict]:
        """从传记文本中提取人物信息"""
        persons = []
        
        # 匹配"某某,字某某,某地人也"句式
        pattern = re.compile(
            r'([\u4e00-\u9fa5]{2,4}),字([\u4e00-\u9fa5]{1,4}),'
            r'([\u4e00-\u9fa5]{2,10})人也'
        )
        matches = pattern.findall(content)
        
        for name, courtesy_name, hometown in matches:
            # 过滤掉明显不是人名的匹配
            if self._is_valid_name(name):
                persons.append({
                    'name': name,
                    'courtesy_name': courtesy_name,
                    'hometown': hometown,
                    'dynasty': dynasty,
                    'source_book': book_name,
                    'name_type': '本名',
                })
        
        # 匹配"某某者,某地人也,姓某氏,名某"句式
        pattern2 = re.compile(
            r'([\u4e00-\u9fa5]{2,4})者,([\u4e00-\u9fa5]{2,10})人也,'
            r'姓([\u4e00-\u9fa5]{1,2})氏'
        )
        matches2 = pattern2.findall(content)
        for name, hometown, surname in matches2:
            if self._is_valid_name(name):
                persons.append({
                    'name': name,
                    'surname': surname,
                    'hometown': hometown,
                    'dynasty': dynasty,
                    'source_book': book_name,
                    'name_type': '本名',
                })
        
        return persons
    
    def _is_valid_name(self, name: str) -> bool:
        """判断是否为有效人名"""
        # 长度2-4字
        if not (2 <= len(name) <= 4):
            return False
        # 排除常见非人名词
        invalid_words = ['天下', '国家', '皇帝', '太子', '太后', 
                        '将军', '丞相', '大夫', '先生', '夫人']
        if name in invalid_words:
            return False
        # 排除包含数字的
        if any(c.isdigit() for c in name):
            return False
        return True
    
    def crawl_all(self) -> List[Dict]:
        """全量采集二十四史"""
        for book_name, dynasty, author in self.TWENTY_FOUR_HISTORIES:
            print(f"\n=== 采集《{book_name}》({dynasty})===")
            chapters = self.crawl_book_index(book_name)
            
            for chapter in chapters:
                persons = self.crawl_biography(
                    chapter['url'], book_name, dynasty
                )
                self.results.extend(persons)
            
            print(f"  累计采集 {len(self.results)} 条人物记录")
        
        return self.results
    
    def save(self, filepath: str):
        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(self.results, f, ensure_ascii=False, indent=2)
        print(f"已保存到 {filepath}")

3.2 诗词文集名字采集

复制代码
# crawlers/poetry_name_crawler.py
import requests
from bs4 import BeautifulSoup
import time
import random
import json
import re
from typing import Dict, List

class PoetryNameCrawler:
    """诗词文集名字采集器"""
    
    BASE_URL = 'https://www.gushiwen.cn'
    
    def __init__(self, proxy_pool: List[str] = None):
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        })
        self.proxy_pool = proxy_pool or []
        self.results = []
    
    def _request(self, url: str, retries: int = 3) -> str:
        for attempt in range(retries):
            try:
                time.sleep(random.uniform(3, 7))
                proxy = random.choice(self.proxy_pool) if self.proxy_pool else None
                proxies = {'http': proxy, 'https': proxy} if proxy else {}
                response = self.session.get(url, proxies=proxies, timeout=20)
                response.raise_for_status()
                response.encoding = 'utf-8'
                return response.text
            except Exception as e:
                print(f"请求失败(第{attempt+1}次): {e}")
                time.sleep(5 * (attempt + 1))
        return None
    
    def crawl_poet_list(self, page: int = 1) -> List[Dict]:
        """采集诗人列表"""
        url = f'{self.BASE_URL}/authors/default.aspx?p={page}'
        html = self._request(url)
        if not html:
            return []
        
        soup = BeautifulSoup(html, 'html.parser')
        poet_links = soup.select('.poet-list a')
        
        poets = []
        for link in poet_links:
            name = link.get_text(strip=True)
            href = link.get('href', '')
            if name and href:
                poets.append({
                    'name': name,
                    'url': href if href.startswith('http') else self.BASE_URL + href,
                })
        
        return poets
    
    def crawl_poet_detail(self, poet_url: str) -> Dict:
        """采集诗人详情"""
        html = self._request(poet_url)
        if not html:
            return None
        
        soup = BeautifulSoup(html, 'html.parser')
        
        # 诗人姓名
        name_elem = soup.select_one('.poet-name')
        name = name_elem.get_text(strip=True) if name_elem else ''
        
        # 朝代
        dynasty_elem = soup.select_one('.poet-dynasty')
        dynasty = dynasty_elem.get_text(strip=True) if dynasty_elem else ''
        
        # 字、号
        info_elem = soup.select_one('.poet-info')
        info_text = info_elem.get_text() if info_elem else ''
        
        courtesy_name = ''
        hao = ''
        courtesy_match = re.search(r'字([\u4e00-\u9fa5]{1,4})', info_text)
        hao_match = re.search(r'号([\u4e00-\u9fa5]{1,6})', info_text)
        if courtesy_match:
            courtesy_name = courtesy_match.group(1)
        if hao_match:
            hao = hao_match.group(1)
        
        # 籍贯
        hometown = ''
        hometown_match = re.search(r'([\u4e00-\u9fa5]{2,10})人', info_text)
        if hometown_match:
            hometown = hometown_match.group(1)
        
        # 生平简介
        bio_elem = soup.select_one('.poet-bio')
        bio = bio_elem.get_text(strip=True)[:500] if bio_elem else ''
        
        return {
            'name': name,
            'courtesy_name': courtesy_name,
            'hao': hao,
            'dynasty': dynasty,
            'hometown': hometown,
            'bio': bio,
            'source': '古诗文网',
            'name_type': '诗人',
        }
    
    def crawl_all_poets(self, max_pages: int = 50) -> List[Dict]:
        """采集所有诗人"""
        for page in range(1, max_pages + 1):
            print(f"采集诗人列表第 {page} 页...")
            poets = self.crawl_poet_list(page)
            
            if not poets:
                break
            
            for poet in poets:
                detail = self.crawl_poet_detail(poet['url'])
                if detail:
                    self.results.append(detail)
            
            print(f"  累计采集 {len(self.results)} 位诗人")
        
        return self.results

3.3 采集成果统计

表格

数据源 文献量 采集人物数 朝代覆盖 名字类型 数据完整度
二十四史 24 部 68,000 先秦 - 明末 本名、字 85%
诗词文集 5,000 + 篇 12,000 唐 - 清 本名、字、号 90%
地方志 200 + 部 25,000 宋 - 清末 本名、字 75%
家谱族谱 500 + 部 35,000 明 - 清末 本名、字、号 80%
开源古籍库 500 + 部 18,000 先秦 - 清 本名 70%
去重合并后 - 150,000 先秦 - 清末 本名、字、号 82%

四、名字实体识别与提取

4.1 基于规则的名字提取

复制代码
# processors/name_extractor.py
import re
from typing import List, Dict

class RuleBasedNameExtractor:
    """基于规则的古文名字提取器"""
    
    # 古文人名常见句式
    NAME_PATTERNS = [
        # 某某,字某某,某地人也
        (r'([\u4e00-\u9fa5]{2,4}),字([\u4e00-\u9fa5]{1,4}),', 'name_courtesy'),
        # 某某者,某地人也
        (r'([\u4e00-\u9fa5]{2,4})者,', 'name'),
        # 姓某氏,名某,字某
        (r'姓([\u4e00-\u9fa5]{1,2})氏,名([\u4e00-\u9fa5]{1,2})', 'surname_given'),
        # 某某(朝代)某地人
        (r'([\u4e00-\u9fa5]{2,4})([\u4e00-\u9fa5]{1,4})', 'name_dynasty'),
        # 自号某某
        (r'自号([\u4e00-\u9fa5]{1,6})', 'hao'),
        # 晚号某某
        (r'晚号([\u4e00-\u9fa5]{1,6})', 'hao'),
    ]
    
    # 常见姓氏列表
    COMMON_SURNAMES = set('赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮卞齐康伍余元卜顾孟平黄和穆萧尹姚邵湛汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董梁杜阮蓝闵席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田樊胡凌霍虞万支柯昝管卢莫经房裘缪干解应宗丁宣贲邓郁单杭洪包诸左石崔吉钮龚程嵇邢滑裴陆荣翁荀羊於惠甄曲家封芮羿储靳汲邴糜松井段富巫乌焦巴弓牧隗山谷车侯宓蓬全郗班仰秋仲伊宫宁仇栾暴甘钭厉戎祖武符刘景詹束龙叶幸司韶郜黎蓟薄印宿白怀蒲邰从鄂索咸籍赖卓蔺屠蒙池乔阴郁胥能苍双闻莘党翟谭贡劳逄姬申扶堵冉宰郦雍却璩桑桂濮牛寿通边扈燕冀郏浦尚农温别庄晏柴瞿阎充慕连茹习宦艾鱼容向古易慎戈廖庾终暨居衡步都耿满弘匡国文寇广禄阙东欧殳沃利蔚越夔隆师巩厍聂晁勾敖融冷訾辛阚那简饶空曾毋沙乜养鞠须丰巢关蒯相查后荆红游竺权逯盖益桓公')
    
    # 古文中常见的非人名词
    NON_NAME_WORDS = {
        '天下', '国家', '皇帝', '太子', '太后', '皇后', '将军', '丞相',
        '大夫', '先生', '夫人', '公子', '公主', '大王', '陛下', '寡人',
        '圣人', '君子', '小人', '百姓', '万民', '四方', '天下', '社稷',
        '宗庙', '朝廷', '官府', '衙门', '书院', '寺庙', '道观', '祠堂',
    }
    
    def extract(self, text: str) -> List[Dict]:
        """从文本中提取人名"""
        names = []
        seen = set()
        
        for pattern, name_type in self.NAME_PATTERNS:
            matches = re.findall(pattern, text)
            for match in matches:
                if isinstance(match, tuple):
                    # 多组匹配
                    for group in match:
                        if group and self._is_valid_name(group):
                            key = (group, name_type)
                            if key not in seen:
                                names.append({
                                    'name': group,
                                    'type': name_type,
                                    'confidence': 0.8,
                                })
                                seen.add(key)
                else:
                    if match and self._is_valid_name(match):
                        key = (match, name_type)
                        if key not in seen:
                            names.append({
                                'name': match,
                                'type': name_type,
                                'confidence': 0.8,
                            })
                            seen.add(key)
        
        return names
    
    def _is_valid_name(self, name: str) -> bool:
        """判断是否为有效人名"""
        # 长度2-4字
        if not (2 <= len(name) <= 4):
            return False
        # 排除非人名词
        if name in self.NON_NAME_WORDS:
            return False
        # 第一个字应该是常见姓氏(对于2字名)
        if len(name) == 2 and name[0] not in self.COMMON_SURNAMES:
            return False
        # 排除包含数字或特殊字符的
        if not all('\u4e00' <= c <= '\u9fa5' for c in name):
            return False
        return True

4.2 基于 NER 模型的名字识别

复制代码
# processors/ner_name_recognizer.py
import re
from typing import List, Dict

class NERNameRecognizer:
    """基于命名实体识别的古文名字识别器"""
    
    def __init__(self):
        # 加载人名词典(从已采集的历史人物中构建)
        self.name_dictionary = self._build_name_dictionary()
        # 加载姓氏词典
        self.surname_set = self._load_surnames()
    
    def recognize(self, text: str) -> List[Dict]:
        """识别文本中的人名实体"""
        names = []
        
        # 1. 基于词典的最大匹配
        dict_names = self._dictionary_match(text)
        names.extend(dict_names)
        
        # 2. 基于姓氏+名字模式的识别
        pattern_names = self._surname_pattern_match(text)
        names.extend(pattern_names)
        
        # 3. 基于上下文规则的识别
        context_names = self._context_rule_match(text)
        names.extend(context_names)
        
        # 去重和合并
        names = self._merge_names(names)
        
        return names
    
    def _dictionary_match(self, text: str) -> List[Dict]:
        """基于人名词典的最大匹配"""
        names = []
        # 按名字长度降序排列,优先匹配长名
        sorted_names = sorted(self.name_dictionary, key=len, reverse=True)
        
        for name in sorted_names:
            if name in text:
                # 检查上下文是否合理
                confidence = self._check_context(text, name)
                names.append({
                    'name': name,
                    'type': 'dictionary_match',
                    'confidence': confidence,
                })
        
        return names
    
    def _surname_pattern_match(self, text: str) -> List[Dict]:
        """基于姓氏+名字模式的识别"""
        names = []
        
        # 匹配"姓氏+1-2字名"的模式
        pattern = re.compile(
            r'([' + ''.join(self.surname_set) + r'])'
            r'([\u4e00-\u9fa5]{1,2})'
            r'(?=[,。、;:""''()\s])'
        )
        
        for match in pattern.finditer(text):
            surname = match.group(1)
            given = match.group(2)
            full_name = surname + given
            
            if self._is_valid_given_name(given):
                confidence = 0.6 if len(given) == 1 else 0.7
                names.append({
                    'name': full_name,
                    'surname': surname,
                    'given_name': given,
                    'type': 'surname_pattern',
                    'confidence': confidence,
                })
        
        return names
    
    def _context_rule_match(self, text: str) -> List[Dict]:
        """基于上下文规则的识别"""
        names = []
        
        # 匹配"曰:"前的人名(某某曰)
        pattern1 = re.compile(r'([\u4e00-\u9fa5]{2,4})曰')
        for match in pattern1.finditer(text):
            name = match.group(1)
            if self._is_valid_name(name):
                names.append({
                    'name': name,
                    'type': 'context_yue',
                    'confidence': 0.65,
                })
        
        # 匹配"谓"前后的人名(某某谓某某曰)
        pattern2 = re.compile(
            r'([\u4e00-\u9fa5]{2,4})谓([\u4e00-\u9fa5]{2,4})曰'
        )
        for match in pattern2.finditer(text):
            for name in [match.group(1), match.group(2)]:
                if self._is_valid_name(name):
                    names.append({
                        'name': name,
                        'type': 'context_wei',
                        'confidence': 0.7,
                    })
        
        return names
    
    def _check_context(self, text: str, name: str) -> float:
        """检查人名出现的上下文,计算置信度"""
        confidence = 0.9  # 词典匹配基础置信度
        
        # 检查名字前后是否有称谓词
        prefixes = ['曰', '谓', '使', '令', '拜', '封', '谥', '字']
        suffixes = ['曰', '也', '矣', '焉', '之', '者']
        
        idx = text.find(name)
        if idx > 0:
            prev_char = text[idx-1]
            if prev_char in prefixes:
                confidence += 0.05
        if idx + len(name) < len(text):
            next_char = text[idx + len(name)]
            if next_char in suffixes:
                confidence += 0.05
        
        return min(1.0, confidence)
    
    def _is_valid_given_name(self, given: str) -> bool:
        """判断是否为有效的名字部分"""
        if len(given) < 1 or len(given) > 2:
            return False
        # 排除常见虚词
        invalid = set('之乎者也矣焉哉兮夫盖岂其若如而则以于因为所可以')
        if given in invalid:
            return False
        return True
    
    def _is_valid_name(self, name: str) -> bool:
        """判断是否为有效人名"""
        if not (2 <= len(name) <= 4):
            return False
        if name in {'天下', '国家', '皇帝', '太子', '将军'}:
            return False
        return True
    
    def _merge_names(self, names: List[Dict]) -> List[Dict]:
        """合并去重,取最高置信度"""
        name_map = {}
        for n in names:
            name = n['name']
            if name not in name_map or n['confidence'] > name_map[name]['confidence']:
                name_map[name] = n
        return list(name_map.values())
    
    def _build_name_dictionary(self) -> set:
        """构建人名词典(实际应从数据库加载)"""
        # 简化版:返回常见历史人名
        return {
            '诸葛亮', '司马懿', '曹操', '刘备', '孙权', '关羽', '张飞',
            '李白', '杜甫', '白居易', '苏轼', '辛弃疾', '李清照',
            '王安石', '欧阳修', '韩愈', '柳宗元', '司马光',
        }
    
    def _load_surnames(self) -> set:
        """加载姓氏集合"""
        return set('赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜')

该名字识别引擎已应用于在线起名工具的历史名字查询功能,用户输入一个字,工具会展示历史上所有名字中包含该字的历史人物,包括朝代、籍贯、生平简介等信息,帮助用户了解名字的历史文化底蕴。

五、名字用例数据库构建

5.1 数据库表结构设计

复制代码
-- 历史人物主表
CREATE TABLE historical_person (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL COMMENT '本名',
    surname VARCHAR(10) COMMENT '姓氏',
    given_name VARCHAR(10) COMMENT '名',
    courtesy_name VARCHAR(20) COMMENT '字',
    hao VARCHAR(50) COMMENT '号',
    dynasty VARCHAR(20) COMMENT '朝代',
    birth_year INT COMMENT '出生年份',
    death_year INT COMMENT '去世年份',
    hometown VARCHAR(100) COMMENT '籍贯',
    identity VARCHAR(50) COMMENT '身份(诗人/官员/将领等)',
    bio TEXT COMMENT '生平简介',
    source_book VARCHAR(100) COMMENT '来源文献',
    source_url VARCHAR(500) COMMENT '来源链接',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_name (name),
    INDEX idx_dynasty (dynasty),
    INDEX idx_surname (surname),
    INDEX idx_identity (identity),
    FULLTEXT idx_bio (bio)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='历史人物主表';

-- 名字用例表
CREATE TABLE name_usage (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    person_id BIGINT NOT NULL COMMENT '人物ID',
    name VARCHAR(20) NOT NULL COMMENT '名字(本名/字/号)',
    name_type VARCHAR(20) COMMENT '名字类型(本名/字/号)',
    char1 VARCHAR(5) COMMENT '第一个字',
    char2 VARCHAR(5) COMMENT '第二个字',
    char3 VARCHAR(5) COMMENT '第三个字',
    dynasty VARCHAR(20) COMMENT '朝代',
    usage_count INT DEFAULT 1 COMMENT '出现次数',
    INDEX idx_name (name),
    INDEX idx_char1 (char1),
    INDEX idx_char2 (char2),
    INDEX idx_dynasty (dynasty),
    INDEX idx_name_type (name_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='名字用例表';

-- 单字历史用例表
CREATE TABLE char_historical_usage (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    char VARCHAR(5) NOT NULL COMMENT '汉字',
    total_usage INT DEFAULT 0 COMMENT '总使用次数',
    dynasty_distribution JSON COMMENT '各朝代使用分布',
    top_persons VARCHAR(500) COMMENT '使用该字的著名人物',
    naming_score INT DEFAULT 0 COMMENT '起名推荐度',
    UNIQUE KEY uk_char (char),
    INDEX idx_total_usage (total_usage)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='单字历史用例表';

5.2 历史名字热度统计

表格

排名 名字 朝代 身份 历史出现次数 起名参考价值
1 诸葛亮 三国 政治家 1,250 高(智慧象征)
2 李白 诗人 1,180 高(诗仙)
3 苏轼 文学家 1,050 高(文豪)
4 杜甫 诗人 980 高(诗圣)
5 司马迁 西汉 史学家 920 中(史官)
6 王羲之 东晋 书法家 880 高(书圣)
7 辛弃疾 南宋 词人 820 高(爱国词人)
8 王安石 北宋 政治家 780 中(改革家)
9 欧阳修 北宋 文学家 750 高(文坛领袖)
10 李清照 南宋 词人 720 高(婉约词宗)

5.3 各朝代名字用字偏好

表格

朝代 热门字 TOP5 名字风格 单字 / 双字比例 代表人物
先秦 孔、孟、荀、屈、宋 单字为主,质朴 85% 单字 孔子、孟子、屈原
汉代 信、平、勃、陵、禹 单字为主,尚武 90% 单字 韩信、陈平、周勃
三国 亮、备、操、权、懿 单字为主,谋略 88% 单字 诸葛亮、刘备、曹操
唐代 白、甫、愈、宗、翰 双字增多,文雅 60% 双字 李白、杜甫、韩愈
宋代 轼、辙、修、石、珍 双字为主,理学 75% 双字 苏轼、苏辙、欧阳修
明代 谦、慎、明、阳、仁 双字为主,字辈 85% 双字 于谦、杨慎、王阳明
清代 熙、禛、昀、培、藩 双字为主,考据 90% 双字 纪晓岚、曾国藩

读者可前往在线起名打分工具查看历史名字用例功能,输入名字后工具会展示该名字在历史上的使用情况、同名历史人物、各朝代的流行度,帮助用户选择有历史文化底蕴的名字。

六、数据质量验证与应用

6.1 数据质量验证

表格

验证项 验证方法 验证结果 合格率
完整性 检查姓名、朝代是否为空 15 万条中 2.7 万条缺籍贯 82%
准确性 随机抽样 500 人人工核对 462 人与史料一致 92.4%
去重率 按姓名 + 朝代 + 籍贯去重 去重前 25 万,去重后 15 万 去重率 40%
朝代标注 检查朝代是否在合理范围 15 万条中 8000 条朝代存疑 94.7%
名字识别准确率 随机抽样 1000 条 NER 结果 885 条识别正确 88.5%
名字类型标注 检查本名 / 字 / 号分类 15 万条中 1.2 万条分类错误 92%

6.2 在起名系统中的应用效果

表格

指标 优化前 优化后 提升幅度
历史名字用例覆盖 0 人 150,000 人 -
历史名字查询功能日使用量 - 12,000 次 -
用户对名字历史文化的满意度 58% 86% +48%
用户平均停留时间 1 分 40 秒 3 分 +80%
名字推荐的用户采纳率 30% 42% +40%
历史名字相关内容分享率 - 15% -

上述名字用例数据库可在在线起名工具中体验,工具提供了历史名字查询、同名历史人物展示、各朝代名字流行度分析等功能,用户可以查看每个推荐名字的历史文化底蕴,让名字不仅好听,更有历史感。

七、踩过的坑与注意事项

在进行古籍文献名字用例数据库采集的过程中,遇到了不少实际问题,以下是最有价值的 5 条经验:

1. 古籍网站的反爬比普通网站更严格,而且很多古籍是图片扫描版,必须做 OCR 处理

最初我们用普通的 requests 采集国学大师和中国哲学书电子化计划,结果采集了不到 3000 页就被封了 IP,而且发现很多古籍页面是图片扫描版(尤其是地方志和家谱),HTML 中没有文字内容,只有图片。后来我们建立了 50 个代理 IP 的代理池,请求间隔随机 5-10 秒,同时引入 PaddleOCR 对图片版古籍做文字识别,识别率达到 92%,IP 封禁率从 45% 降到 6%,图片版古籍的覆盖率从 0% 提升到 85%。建议在实际应用中一定要重视古籍网站的反爬措施,建立代理池和请求延迟,同时对图片版古籍做 OCR 处理,不能只采集 HTML 文本。

2. 古文中的人名识别非常困难,同名异人和异人同名现象普遍,必须结合朝代和籍贯做消歧

最初我们用简单的正则匹配提取古文中的人名,结果发现准确率只有 55%,主要问题是:古文中 "之乎者也" 等虚词经常被误识为人名,"王"" 李 "等单字姓氏经常被误识,而且同名异人和异人同名现象非常普遍,例如" 张良 "在汉代和明代都有," 李白 " 在唐代和宋代都有。后来我们结合了人名词典匹配、姓氏模式匹配、上下文规则识别三种方法,并按姓名 + 朝代 + 籍贯做消歧,人名识别准确率从 55% 提升到 88.5%。建议在实际应用中一定要用多种方法组合识别人名,并结合朝代和籍贯做消歧,不能只用简单的正则匹配。

3. 古籍文本的断句和标点不规范,很多版本没有标点,必须先做断句处理才能提取人名

最初我们直接对原始古籍文本做人名提取,结果发现很多古籍没有标点符号,句子连在一起,人名和上下文混在一起,提取效果很差。例如 "诸葛亮字孔明琅琊阳都人也" 没有标点,正则匹配很难准确提取。后来我们引入了古文断句模型,先对无标点的古籍文本做自动断句和标点,再进行人名提取,提取准确率从 60% 提升到 85%。建议在实际应用中一定要先对古籍文本做断句和标点处理,不能直接对无标点文本做人名提取,断句是古文 NLP 的基础步骤。

4. 历史人物的字和号非常多,而且同一个人可能有多个号,必须做人物合并和别名关联

最初我们把本名、字、号都作为独立的人物记录,结果发现同一个人有多条记录,例如苏轼本名 "苏轼"、字 "子瞻"、号 "东坡居士",被分成了 3 条记录,统计名字用例时重复计算。后来我们建立了人物合并机制,按 "本名 + 朝代 + 籍贯" 做主键,把字和号作为别名关联到同一个人物,人物记录数从 25 万条精简到 15 万条,名字用例统计的准确性大幅提升。建议在实际应用中一定要做人物合并和别名关联,不能把本名、字、号作为独立人物,否则会导致数据重复和统计失真。

5. 古籍数据的版权问题必须重视,只能采集公版古籍,不能采集有版权的现代整理本

最初我们采集了一些现代整理本的古籍(带注释和翻译的版本),结果收到了版权方的投诉,要求删除相关数据。后来我们严格筛选数据源,只采集作者去世超过 50 年的公版古籍,以及明确标注为公共领域的开源古籍数据,对于有版权的现代整理本只做链接引用不抓取内容,确保了数据的合规性。建议在实际应用中一定要重视版权问题,只采集公版古籍,对于有版权的内容要获得授权或只做链接引用,不能随意抓取有版权的内容。

八、总结

本文完整记录了用 Python 爬取古籍文献中的名字用例数据库的全流程,核心要点如下:

  1. 多源采集是历史名字数据的基础:从二十四史、诗词文集、地方志、家谱族谱、开源古籍库等多个渠道采集了 25 万条人物记录,经过去重合并后得到 15 万条历史人物名字用例,覆盖从先秦到清末的各个朝代。
  2. 完善的反爬和 OCR 是采集成功的保障:通过 50 个代理 IP 池、随机延迟、Selenium 动态渲染、PaddleOCR 图片识别等措施,IP 封禁率从 45% 降到 6%,图片版古籍覆盖率达到 85%,确保了大规模采集的稳定性。
  3. 多方法组合的人名识别是关键能力:结合词典匹配、姓氏模式匹配、上下文规则识别三种方法,并按姓名 + 朝代 + 籍贯做消歧,人名识别准确率从 55% 提升到 88.5%,解决了古文人名识别的难题。
  4. 人物合并和别名关联是数据质量的前提:按 "本名 + 朝代 + 籍贯" 做主键,把字和号作为别名关联到同一个人物,人物记录从 25 万条精简到 15 万条,避免了数据重复和统计失真。
  5. 应用效果显著:历史名字用例覆盖 15 万人,历史名字查询功能日使用量达到 12000 次,用户对名字历史文化的满意度从 58% 提升到 86%,名字推荐采纳率从 30% 提升到 42%。

该古籍文献名字用例数据库已在 529 宝宝起名网上线应用,为历史名字查询功能提供了完整的数据支撑,累计为超过 60 万用户提供历史名字查询和文化解读服务。后续计划引入更多的古籍数据源(如笔记小说、佛道经典),并结合 AI 生成名字的历史文化故事,进一步提升历史名字功能的文化体验和趣味性。

在线体验: 529 宝宝起名网

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

相关推荐
颜颜yan_9 小时前
ESP-IDF 鸿蒙 PC 适配全记录:打通 Python、构建工具链与 ESP32-P4 固件生成
python·华为·harmonyos
言乐69 小时前
Python加速器4跨境网络加速器
运维·服务器·开发语言·网络·python
weixin_440730509 小时前
线程02-并发串行-互斥锁-Semaphore-Event
python·thread
张小凡vip10 小时前
python--爬虫--经验积累的遇到的坑
开发语言·爬虫·python
毕业设计70310 小时前
(免费领源码) 基于微信小程序的预制菜商城的设计与实现25172-java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
vue.js·python·mysql·微信小程序·pycharm·微信开发者工具·推荐算法
xifangge202510 小时前
AGENTS.md 怎么写?涵盖 Java、Python、Vue、Go 的 8 套开箱即用模板
java·vue.js·python
大草原的小灰灰11 小时前
Python基础语法
开发语言·python
小葱炖豆腐12 小时前
python绘制excel折线图
python·excel·numpy·pandas·matplotlib
Ticnix13 小时前
MCP 实战:把工具层从 Agent 里彻底解耦
python·mcp