feat: add scripts for fetching and training educational corpus

- Implemented `fetch_corpus.py` to scrape Chinese educational content from Wikipedia, covering subjects like Chinese, Math, English, Chemistry, Politics, History, Geography, Physics, and Biology.
- Developed `fetch_corpus_incremental.py` for incremental fetching of missing entries, with automatic retries for rate limiting and support for resuming interrupted downloads.
- Created `train_chat.py` for training a dialogue model, including corpus cleaning, vocabulary management, and staged training with adaptive learning rates.
This commit is contained in:
2026-07-07 17:42:24 +08:00
parent d5affe4458
commit 81f6fd58e0
96 changed files with 53920 additions and 17 deletions

View File

@@ -0,0 +1,306 @@
#!/usr/bin/env python3
"""增量抓取九年义务教育全科目中文语料(维基百科 API
特性:
- 增量:已有词条跳过,只补缺失的
- 429 自动重试:等待 10 秒后重试,最多 5 次
- 长延时3秒防限流
- 每词条截取前 3000 字符
- 断点续传Ctrl+C 中断后再次运行会跳过已抓的
用法:
python scripts/fetch_corpus_incremental.py # 抓全部科目
python scripts/fetch_corpus_incremental.py 语文 数学 # 只抓指定科目
"""
import json
import urllib.request
import urllib.parse
import time
import re
import sys
from pathlib import Path
CORPUS_DIR = Path(__file__).parent.parent / 'corpus'
CORPUS_DIR.mkdir(parents=True, exist_ok=True)
# 九年义务教育各科目核心词条(覆盖小学到初中知识点)
SUBJECTS = {
'语文': [
# 古诗词与文言文(部编版教材重点篇目)
'静夜思', '春晓', '登鹳雀楼', '望庐山瀑布', '绝句', '春望',
'江雪', '游子吟', '清明', '枫桥夜泊', '寻隐者不遇',
'咏鹅', '回乡偶书', '咏柳', '凉州词', '出塞',
'芙蓉楼送辛渐', '送元二使安西', '九月九日忆山东兄弟',
'赠汪伦', '黄鹤楼送孟浩然之广陵', '望天门山',
'别董大', '早发白帝城', '赠花卿', '江南逢李龟年',
'春夜喜雨', '江畔独步寻花', '渔歌子', '塞下曲',
'望洞庭', '浪淘沙', '赋得古原草送别', '忆江南',
'悯农', '蚕妇', '陶者', '梅花', '元日',
'泊船瓜洲', '书湖阴先生壁', '六月二十七日望湖楼醉书',
'饮湖上初晴后雨', '题西林壁', '惠崇春江晚景',
'晓出净慈寺送林子方', '小池', '宿新市徐公店',
'春日', '观书有感', '题临安邸', '游园不值',
'乡村四月', '墨梅', '石灰吟', '竹石', '所见',
'村居', '己亥杂诗',
# 文言文
'论语', '孟子', '大学', '中庸', '诗经',
'世说新语', '聊斋志异', '西游记', '水浒传',
'三国演义', '红楼梦',
'岳阳楼记', '醉翁亭记', '爱莲说', '陋室铭',
'桃花源记', '小石潭记', '记承天寺夜游',
'送东阳马生序', '曹刿论战', '邹忌讽齐王纳谏',
'出师表', '陈涉世家',
# 作家
'李白', '杜甫', '白居易', '王维', '苏轼',
'李清照', '辛弃疾', '陆游', '鲁迅', '朱自清',
'老舍', '巴金', '冰心',
# 文学常识
'唐诗', '宋词', '元曲', '文言文', '成语',
],
'数学': [
'加法', '减法', '乘法', '除法', '四则运算',
'分数', '小数', '百分比', '比例', '方程',
'一元一次方程', '二元一次方程', '一元二次方程',
'不等式', '一元一次不等式',
'几何学', '三角形', '', '正方形', '长方形',
'平行四边形', '梯形', '多边形', '正方体', '长方体',
'圆柱', '圆锥', '', '扇形',
'面积', '体积', '周长', '角度', '',
'整数', '有理数', '无理数', '实数', '复数',
'代数', '函数', '一次函数', '二次函数', '反比例函数',
'勾股定理', '圆周率', '素数', '因数', '倍数',
'最大公约数', '最小公倍数', '绝对值', '平方根',
'统计', '概率', '平均数', '中位数', '众数',
'负数', '相反数', '倒数', '科学记数法',
'相似三角形', '全等三角形', '平行线',
'轴对称', '中心对称', '平移', '旋转',
'坐标', '直角坐标系', '象限',
],
'英语': [
'英文字母', '英语', '英语语法', '英语动词',
'英语名词', '英语形容词', '英语副词', '英语代词',
'英语介词', '英语连词', '英语冠词',
'英语时态', '一般现在时', '现在进行时',
'一般过去时', '过去进行时', '现在完成时',
'一般将来时', '过去完成时', '过去完成进行时',
'英语被动语态', '英语从句', '定语从句',
'名词性从句', '状语从句', '宾语从句',
'主语从句', '表语从句', '同位语从句',
'英语虚拟语气', '英语不定式', '英语动名词', '英语分词',
'英语单词', '英语发音', '英语拼写', '英语音标',
],
'化学': [
'化学', '化学元素', '元素周期表', '原子', '分子',
'离子', '化合物', '混合物', '纯净物', '单质',
'氧气', '氢气', '氮气', '二氧化碳', '一氧化碳',
'', '盐酸', '硫酸', '硝酸', '氢氧化钠',
'碳酸钙', '氯化钠', '碳酸氢钠', '氢氧化钙',
'', '', '', '氧化物', '过氧化物',
'化学反应', '化合反应', '分解反应', '置换反应',
'氧化还原反应', '复分解反应', '燃烧', '爆炸',
'溶液', '溶质', '溶剂', '溶解度', '浓度',
'金属', '合金', '', '', '', '', '',
'', '金刚石', '石墨', '有机化合物', '甲烷',
'乙醇', '乙酸', '蛋白质', '糖类', '脂肪',
'化学式', '化学方程式', '摩尔', '相对原子质量',
'原子结构', '电子', '质子', '中子', '同位素',
'化学键', '离子键', '共价键', '金属键',
],
'政治': [
'公民', '权利', '义务', '法律', '宪法',
'刑法', '民法', '消费者权益', '未成年人保护',
'人民代表大会制度', '全国人民代表大会',
'中国共产党', '社会主义', '社会主义市场经济',
'改革开放', '社会主义核心价值观',
'国家安全', '爱国主义', '集体主义',
'民主', '自由', '平等', '公正', '法治',
'民族团结', '一国两制', '和平共处五项原则',
'联合国', '世界贸易组织', '经济全球化',
'人民代表大会', '政治协商制度', '民族区域自治',
'基层群众自治', '依法治国', '以德治国',
],
'历史': [
'中国历史', '夏朝', '商朝', '西周', '东周',
'春秋时期', '战国时期', '秦朝', '西汉', '东汉',
'三国', '西晋', '东晋', '南北朝', '隋朝', '唐朝',
'宋朝', '辽朝', '金朝', '元朝', '明朝', '清朝',
'中华民国', '中华人民共和国',
'鸦片战争', '太平天国运动', '洋务运动', '甲午战争',
'戊戌变法', '义和团运动', '辛亥革命',
'新文化运动', '五四运动', '中国共产党成立',
'北伐战争', '土地革命', '长征',
'抗日战争', '解放战争', '新中国成立',
'丝绸之路', '四大发明', '郑和下西洋',
'孔子', '老子', '孟子', '庄子', '韩非子', '墨子',
'秦始皇', '汉武帝', '唐太宗', '宋太祖',
'成吉思汗', '朱元璋', '康熙帝',
'世界历史', '古埃及', '古希腊', '古罗马',
'文艺复兴', '工业革命', '第一次世界大战',
'第二次世界大战', '冷战', '欧洲联盟',
],
'地理': [
'地球', '地球仪', '经线', '纬线', '赤道',
'本初子午线', '时区', '国际日期变更线',
'大气层', '对流层', '水循环', '气候',
'热带', '温带', '寒带', '季风', '降水',
'地形', '平原', '高原', '山地', '盆地', '丘陵',
'河流', '湖泊', '海洋', '海峡', '岛屿', '半岛',
'中国地理', '中国行政区划', '省级行政区',
'长江', '黄河', '珠江', '淮河', '黑龙江',
'青藏高原', '内蒙古高原', '黄土高原', '云贵高原',
'塔里木盆地', '准噶尔盆地', '柴达木盆地', '四川盆地',
'华北平原', '东北平原', '长江中下游平原',
'喜马拉雅山脉', '昆仑山脉', '秦岭',
'北京', '上海', '广州', '深圳', '天津', '重庆',
'香港', '澳门', '台湾',
'世界地理', '亚洲', '欧洲', '非洲',
'北美洲', '南美洲', '大洋洲', '南极洲',
'太平洋', '大西洋', '印度洋', '北冰洋',
'撒哈拉沙漠', '亚马逊雨林', '尼罗河',
'人口', '城市', '农业', '工业', '交通运输',
],
'物理': [
'物理学', '', '运动', '速度', '加速度',
'牛顿运动定律', '牛顿第一定律', '牛顿第二定律',
'牛顿第三定律', '惯性', '质量', '密度',
'重力', '摩擦力', '弹力', '浮力', '压强',
'大气压强', '液体压强', '液压千斤顶',
'', '功率', '能量', '动能', '势能',
'机械能', '机械能守恒', '能量守恒定律',
'杠杆', '滑轮', '斜面', '轮轴',
'', '电荷', '电流', '电压', '电阻',
'欧姆定律', '串联', '并联', '电功率', '电能',
'', '磁场', '磁体', '电磁感应', '电磁铁',
'电动机', '发电机', '变压器',
'', '光的反射', '光的折射', '透镜',
'凸透镜', '凹透镜', '光的色散', '颜色', '光谱',
'声音', '声波', '音调', '响度', '音色',
'回声', '超声波', '次声波', '噪声',
'', '温度', '温度计', '熔化', '凝固',
'汽化', '液化', '升华', '凝华',
'比热容', '热传导', '热对流', '热辐射',
'物态变化', '沸腾', '蒸发', '内能',
'比热', '热量', '热机', '热机效率',
],
'生物': [
'生物学', '细胞', '细胞膜', '细胞核', '细胞质',
'细胞壁', '叶绿体', '线粒体', '染色体', 'DNA',
'基因', '遗传', '变异', '遗传学',
'光合作用', '呼吸作用', '新陈代谢',
'植物', '动物', '细菌', '病毒', '真菌',
'苔藓植物', '蕨类植物', '种子植物', '裸子植物', '被子植物',
'脊椎动物', '无脊椎动物', '哺乳动物', '鸟类',
'爬行动物', '两栖动物', '鱼类', '昆虫',
'生态系统', '食物链', '食物网', '生物多样性',
'生产者', '消费者', '分解者', '生物圈',
'人体', '心脏', '血管', '血液', '血液循环',
'', '呼吸', '肝脏', '', '',
'神经系统', '大脑', '脊髓', '神经元',
'消化系统', '吸收', '排泄', '泌尿系统',
'生殖', '发育', '青春期', '激素',
'群落', '种群', '生态系统稳定性',
'进化', '自然选择', '达尔文', '物种起源',
'组织', '器官', '系统', '细胞分裂', '细胞分化',
],
}
def fetch_extract(title: str, retries: int = 5) -> str:
"""从维基百科 API 获取词条纯文本摘要429 自动重试"""
api = "https://zh.wikipedia.org/w/api.php"
params = urllib.parse.urlencode({
'action': 'query',
'titles': title,
'prop': 'extracts',
'explaintext': '1',
'exsectionformat': 'plain',
'format': 'json',
'redirects': '1',
})
url = f"{api}?{params}"
for attempt in range(retries):
try:
req = urllib.request.Request(url, headers={
'User-Agent': 'JspaceAI/1.0 (educational corpus fetcher; contact@example.com)'
})
with urllib.request.urlopen(req, timeout=20) as resp:
data = json.loads(resp.read().decode('utf-8'))
pages = data.get('query', {}).get('pages', {})
for pid, page in pages.items():
if pid != '-1' and 'extract' in page:
return page['extract'][:3000] # 截取前 3000 字符
return '' # 词条不存在
except urllib.error.HTTPError as e:
if e.code == 429:
wait = 10 * (attempt + 1)
print(f" 429 限流,等待 {wait}s 后重试 ({attempt+1}/{retries})")
time.sleep(wait)
continue
else:
print(f" HTTP {e.code}: {title}")
return ''
except Exception as e:
print(f" 错误: {e}")
time.sleep(5)
return ''
def get_existing_titles(filepath: Path) -> set:
"""从已有文件读取已抓取的词条名"""
if not filepath.exists():
return set()
content = filepath.read_text(encoding='utf-8')
return set(re.findall(r'^## (.+)$', content, re.M))
def main():
subjects = sys.argv[1:] if len(sys.argv) > 1 else list(SUBJECTS.keys())
total_new = 0
total_skip = 0
for subject in subjects:
if subject not in SUBJECTS:
print(f"未知科目: {subject}")
continue
titles = SUBJECTS[subject]
out_file = CORPUS_DIR / f"{subject}.txt"
existing = get_existing_titles(out_file)
new_titles = [t for t in titles if t not in existing]
skip_count = len(existing)
print(f"\n{'='*60}")
print(f"[{subject}] 共 {len(titles)} 词条,已有 {skip_count},新增 {len(new_titles)}")
print(f"{'='*60}")
# 读取已有内容(追加模式)
if out_file.exists():
all_content = out_file.read_text(encoding='utf-8')
else:
all_content = f"=== {subject} ===\n"
for i, title in enumerate(new_titles):
print(f" [{i+1}/{len(new_titles)}] {title}...", end=' ', flush=True)
text = fetch_extract(title)
if text:
all_content += f"\n\n## {title}\n{text}\n"
print(f"{len(text)} 字符")
total_new += 1
else:
print("")
# 增量保存(每抓 5 条保存一次,防中断丢失)
if (i + 1) % 5 == 0:
out_file.write_text(all_content, encoding='utf-8')
time.sleep(3) # 长延时防限流
out_file.write_text(all_content, encoding='utf-8')
print(f"[{subject}] 完成,已保存到 {out_file.name}")
print(f"\n{'='*60}")
print(f"全部完成:新增 {total_new} 词条,跳过 {total_skip} 已有")
print(f"语料目录: {CORPUS_DIR}")
if __name__ == '__main__':
main()