download_data.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. #!/usr/bin/env python
  2. """
  3. 数据集下载脚本 - 下载各领域语料并保存到 database/corpus
  4. 用法:
  5. # 下载单个领域(测试链路)
  6. python download_data.py news_en --sample-num 10
  7. # 下载所有领域
  8. python download_data.py all --sample-num 50
  9. 领域列表:
  10. - news_en: 英文新闻 (CNN/DailyMail)
  11. - news_zh: 中文新闻 (CLUE news2016zh)
  12. - academic: 学术论文 (arXiv)
  13. - code: 代码 (GitHub Python)
  14. - dialogue: 对话 (OpenAssistant)
  15. - literature: 文学作品 (Gutenberg)
  16. """
  17. import os
  18. import re
  19. import json
  20. import random
  21. import argparse
  22. from pathlib import Path
  23. from typing import List, Optional, Dict
  24. # 设置 HuggingFace 镜像(国内加速)
  25. os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
  26. from datasets import load_dataset
  27. def preprocess_text(text: str, lang: str = "en", min_len: int = 100, max_len: int = 200) -> Optional[str]:
  28. """
  29. 统一预处理文本:去特殊符号、控制长度
  30. Args:
  31. text: 原始文本
  32. lang: 语言(en/zh)
  33. min_len: 最小长度(英文=词数,中文=字符数)
  34. max_len: 最大长度
  35. Returns:
  36. 预处理后的文本,None 表示不符合长度被过滤
  37. """
  38. text = text.strip()
  39. if not text:
  40. return None
  41. if lang == "en":
  42. # 英文:保留字母、数字、标点、空格
  43. text = re.sub(r'[^\w\s.,!?;:()"\']', '', text)
  44. words = text.split()
  45. if len(words) < min_len or len(words) > max_len:
  46. return None
  47. text = ' '.join(words[:max_len]).strip()
  48. else:
  49. # 中文:保留汉字、数字、标点
  50. text = re.sub(r'[^\u4e00-\u9fff0-9.,!?;:()""\']', '', text)
  51. chars = list(text)
  52. if len(chars) < min_len or len(chars) > max_len:
  53. return None
  54. text = ''.join(chars[:max_len]).strip()
  55. return text if text else None
  56. def load_news_en(sample_num: int = 50) -> List[str]:
  57. """加载英文新闻语料 (CNN/DailyMail)"""
  58. corpus = []
  59. print("加载 CNN/DailyMail 数据集...")
  60. try:
  61. ds = load_dataset("cnn_dailymail", "3.0.0", split="train")
  62. texts = [item["article"] for item in random.sample(list(ds), min(200, len(ds)))]
  63. for text in texts:
  64. processed = preprocess_text(text, lang="en")
  65. if processed and processed not in corpus:
  66. corpus.append(processed)
  67. if len(corpus) >= sample_num:
  68. break
  69. print(f" 成功提取 {len(corpus)} 条英文新闻")
  70. except Exception as e:
  71. print(f" 加载失败:{e}")
  72. return corpus
  73. def load_news_zh(sample_num: int = 50) -> List[str]:
  74. """加载中文新闻语料 (CLUE news2016zh)"""
  75. corpus = []
  76. print("加载 CLUE news2016zh 数据集...")
  77. try:
  78. ds = load_dataset("clue", "news2016zh", split="train")
  79. texts = [item["text"] for item in random.sample(list(ds), min(200, len(ds)))]
  80. for text in texts:
  81. processed = preprocess_text(text, lang="zh")
  82. if processed and processed not in corpus:
  83. corpus.append(processed)
  84. if len(corpus) >= sample_num:
  85. break
  86. print(f" 成功提取 {len(corpus)} 条中文新闻")
  87. except Exception as e:
  88. print(f" 加载失败:{e}")
  89. return corpus
  90. def load_academic(sample_num: int = 50) -> List[str]:
  91. """加载学术论文语料 (arXiv)"""
  92. corpus = []
  93. print("加载 arXiv 数据集...")
  94. try:
  95. ds = load_dataset("arxiv_dataset", split="train")
  96. texts = []
  97. for item in random.sample(list(ds), min(300, len(ds))):
  98. categories = item.get("categories", "")
  99. update_date = item.get("update_date", "")
  100. year = update_date.split("-")[0] if update_date else "0"
  101. if categories in ["cs.CL", "cs.LG", "stat.ML"] and year >= "2023":
  102. texts.append(item.get("abstract", ""))
  103. for text in texts:
  104. processed = preprocess_text(text, lang="en")
  105. if processed and processed not in corpus:
  106. corpus.append(processed)
  107. if len(corpus) >= sample_num:
  108. break
  109. print(f" 成功提取 {len(corpus)} 篇论文摘要")
  110. except Exception as e:
  111. print(f" 加载失败:{e}")
  112. return corpus
  113. def load_code(sample_num: int = 50) -> List[str]:
  114. """加载代码语料 (GitHub Python)"""
  115. corpus = []
  116. print("加载 codeparrot/github-code 数据集...")
  117. try:
  118. ds = load_dataset("codeparrot/github-code", streaming=True, split="train")
  119. count = 0
  120. for item in ds:
  121. if count >= 300:
  122. break
  123. count += 1
  124. lang = item.get("language", "")
  125. path = item.get("path", "").lower()
  126. if lang == "Python" and ".py" in path:
  127. code = item.get("code", "")
  128. processed = preprocess_text(code, lang="en")
  129. if processed and processed not in corpus:
  130. corpus.append(processed)
  131. if len(corpus) >= sample_num:
  132. break
  133. print(f" 成功提取 {len(corpus)} 段代码")
  134. except Exception as e:
  135. print(f" 加载失败:{e}")
  136. return corpus
  137. def load_dialogue(sample_num: int = 50) -> List[str]:
  138. """加载对话语料 (OpenAssistant)"""
  139. corpus = []
  140. print("加载 OpenAssistant/oasst1 数据集...")
  141. try:
  142. ds = load_dataset("OpenAssistant/oasst1", split="train")
  143. # 收集对话
  144. conversations = {}
  145. for item in random.sample(list(ds), min(200, len(ds))):
  146. msg_id = item.get("message_id", "")
  147. parent_id = item.get("parent_id", "")
  148. text = item.get("text", "")
  149. role = item.get("role", "")
  150. if parent_id and parent_id in conversations:
  151. conversations[parent_id].append(f"{role}: {text}")
  152. else:
  153. conversations[msg_id] = [f"{role}: {text}"]
  154. # 合并对话
  155. for msgs in conversations.values():
  156. if len(msgs) >= 2:
  157. text = " ".join(msgs[:4]) # 最多 4 轮
  158. processed = preprocess_text(text, lang="en")
  159. if processed and processed not in corpus:
  160. corpus.append(processed)
  161. if len(corpus) >= sample_num:
  162. break
  163. print(f" 成功提取 {len(corpus)} 段对话")
  164. except Exception as e:
  165. print(f" 加载失败:{e}")
  166. return corpus
  167. def load_literature(sample_num: int = 50) -> List[str]:
  168. """加载文学语料 (Gutenberg)"""
  169. corpus = []
  170. print("加载 gutenberg_english 数据集...")
  171. try:
  172. ds = load_dataset("gutenberg_english", split="train")
  173. texts = [item["text"] for item in random.sample(list(ds), min(200, len(ds)))]
  174. for text in texts:
  175. processed = preprocess_text(text, lang="en")
  176. if processed and processed not in corpus:
  177. corpus.append(processed)
  178. if len(corpus) >= sample_num:
  179. break
  180. print(f" 成功提取 {len(corpus)} 段文学文本")
  181. except Exception as e:
  182. print(f" 加载失败:{e}")
  183. return corpus
  184. # 领域加载器映射
  185. DOMAIN_LOADERS = {
  186. "news_en": load_news_en,
  187. "news_zh": load_news_zh,
  188. "academic": load_academic,
  189. "code": load_code,
  190. "dialogue": load_dialogue,
  191. "literature": load_literature,
  192. }
  193. def save_corpus(texts: List[str], domain: str, output_dir: str = "database/corpus") -> None:
  194. """保存语料到 JSONL 文件"""
  195. output_path = Path(output_dir) / domain / "texts.jsonl"
  196. output_path.parent.mkdir(parents=True, exist_ok=True)
  197. with open(output_path, "w", encoding="utf-8") as f:
  198. for i, text in enumerate(texts):
  199. record = {
  200. "text": text,
  201. "metadata": {
  202. "index": i,
  203. "domain": domain,
  204. }
  205. }
  206. f.write(json.dumps(record, ensure_ascii=False) + "\n")
  207. print(f" 已保存至:{output_path}")
  208. def main():
  209. parser = argparse.ArgumentParser(description="下载语料数据集")
  210. parser.add_argument(
  211. "domain",
  212. type=str,
  213. choices=list(DOMAIN_LOADERS.keys()) + ["all"],
  214. help="领域标识,或 'all' 下载所有"
  215. )
  216. parser.add_argument(
  217. "--sample-num",
  218. type=int,
  219. default=10,
  220. help="每个领域采样数量 (默认:10)"
  221. )
  222. parser.add_argument(
  223. "--output-dir",
  224. type=str,
  225. default="database/corpus",
  226. help="输出目录 (默认:database/corpus)"
  227. )
  228. args = parser.parse_args()
  229. print("=" * 50)
  230. print("语料数据集下载")
  231. print("=" * 50)
  232. print(f"目标领域:{args.domain}")
  233. print(f"采样数量:{args.sample_num}")
  234. print(f"输出目录:{args.output_dir}")
  235. print()
  236. if args.domain == "all":
  237. domains = list(DOMAIN_LOADERS.keys())
  238. else:
  239. domains = [args.domain]
  240. for domain in domains:
  241. print(f"\n[{domain}]")
  242. loader = DOMAIN_LOADERS[domain]
  243. texts = loader(args.sample_num)
  244. if texts:
  245. save_corpus(texts, domain, args.output_dir)
  246. else:
  247. print(f" 警告:未能获取有效语料")
  248. print("\n" + "=" * 50)
  249. print("下载完成!")
  250. print("=" * 50)
  251. if __name__ == "__main__":
  252. main()