batch_collect.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. #!/usr/bin/env python3
  2. """后台批量维基采集,从 drug_index.json 循环取药"""
  3. import json, time, httpx, os, sys
  4. from pathlib import Path
  5. from bs4 import BeautifulSoup
  6. from urllib.parse import quote
  7. PROJECT = Path(__file__).resolve().parent.parent.parent
  8. os.chdir(PROJECT)
  9. DATA_DIR = PROJECT / "data-pipeline" / "data"
  10. H = {"User-Agent": "PharmacopoeiaAI/1.0 (ResearchBot) Mozilla/5.0", "Accept-Language": "zh-CN,zh;q=0.9"}
  11. SECTION_MAP = {
  12. "性状": ["性状","外观","物理"],
  13. "适应症": ["适应","用途","主治","医疗用途","临床应用"],
  14. "用法与用量": ["用法","用量","剂量","给药","给药方式"],
  15. "禁忌": ["禁忌","禁用"],
  16. "不良反应": ["不良反应","副作用","不良事件"],
  17. "注意事项": ["注意","警告","谨慎"],
  18. "药理": ["药理","药效","作用机制","药物代谢","药物动力学","药代"],
  19. "药物相互作用": ["交互作用","药物相互"],
  20. "贮藏": ["贮藏","储存","保存"],
  21. }
  22. def parse_sections(html):
  23. soup = BeautifulSoup(html, "lxml")
  24. body = soup.find("div", class_="mw-parser-output")
  25. if not body: return {"概述": ""}
  26. overview_ps = []
  27. for elem in body.children:
  28. if elem.name == "div" and "mw-heading" in (elem.get("class") or []): break
  29. if elem.name == "p":
  30. t = elem.get_text(" ", strip=True)
  31. if len(t) > 15: overview_ps.append(t)
  32. sections = {"概述": " ".join(overview_ps)[:2000]}
  33. for h_div in body.find_all("div", class_=lambda c: c and "mw-heading" in c):
  34. span = h_div.find("span", class_="mw-headline")
  35. heading = (span.get_text() if span else h_div.get_text()).replace("[编辑]","").strip()
  36. if len(heading) < 2 or len(heading) > 60: continue
  37. if any(s in heading for s in ["参见","参考","外部","延伸","来源","引用","导航","链接","注释"]): continue
  38. matched_key = None
  39. for std_key, patterns in SECTION_MAP.items():
  40. if any(p in heading for p in patterns): matched_key = std_key; break
  41. texts = []; nxt = h_div.find_next_sibling()
  42. while nxt:
  43. if nxt.name == "div" and "mw-heading" in (nxt.get("class") or []): break
  44. if nxt.name in ("p","ul","ol"):
  45. t = nxt.get_text(" ", strip=True)
  46. if len(t) > 10: texts.append(t)
  47. nxt = nxt.find_next_sibling()
  48. text = " ".join(texts)[:3000]
  49. if text: sections[matched_key or heading] = text
  50. return sections
  51. # 读索引
  52. with open(DATA_DIR / "drug_index.json") as f:
  53. drugs = json.load(f)
  54. # 只取化学药+生物制品,跳过中药
  55. candidates = [d for d in drugs if d["volume"] in (2, 3)]
  56. out_dir = DATA_DIR / "wiki_drugs"
  57. out_dir.mkdir(parents=True, exist_ok=True)
  58. already = set(f.stem for f in out_dir.glob("*.json"))
  59. total = 0; ok = 0
  60. client = httpx.Client(timeout=20, follow_redirects=True)
  61. print(f"🚀 启动采集: {len(candidates)} 候选, 已采 {len(already)}, {len(candidates)-len(already)} 待采\n")
  62. for drug in candidates:
  63. name = drug["name"]
  64. safe = name.replace("/","_").replace(":","_")
  65. if safe in already: continue
  66. total += 1
  67. if total > 50: break # 首轮限量50个
  68. try:
  69. r = client.get(f"https://zh.wikipedia.org/wiki/{quote(name)}", headers=H)
  70. if r.status_code != 200:
  71. print(f" [{total}] ❌ {name} HTTP{r.status_code}")
  72. time.sleep(2); continue
  73. sections = parse_sections(r.text)
  74. if len(sections) <= 1:
  75. print(f" [{total}] ⚠️ {name} (空)")
  76. time.sleep(2); continue
  77. entry = {"drug_id":f"WIKI-{name}","name":name,"sections":sections,
  78. "source":{"version":"维基百科","volume":f"https://zh.wikipedia.org/wiki/{quote(name)}","page":"线上"}}
  79. with open(out_dir/f"{safe}.json","w") as f:
  80. json.dump(entry, f, ensure_ascii=False, indent=2)
  81. ok += 1
  82. print(f" [{total}] ✅ {name} ({len(sections)}段)")
  83. time.sleep(3)
  84. if ok % 15 == 0 and ok > 0:
  85. print(f" ⏸ 暂停45s...\n"); time.sleep(45)
  86. except Exception as e:
  87. print(f" [{total}] ❌ {name}: {e}")
  88. time.sleep(1)
  89. # 合并入库
  90. merged = []
  91. for f in out_dir.glob("*.json"):
  92. entry = json.load(open(f))
  93. if len(entry["sections"]) > 1:
  94. entry["category"] = "化学药"
  95. merged.append(entry)
  96. Path(DATA_DIR / "wiki_merged.json").write_text(json.dumps(merged, ensure_ascii=False, indent=2))
  97. print(f"\n✅ 完成: {ok} 新增 / {total} 处理, 合并 {len(merged)} 条 → wiki_merged.json")
  98. client.close()