| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- #!/usr/bin/env python3
- """后台批量维基采集,从 drug_index.json 循环取药"""
- import json, time, httpx, os, sys
- from pathlib import Path
- from bs4 import BeautifulSoup
- from urllib.parse import quote
- PROJECT = Path(__file__).resolve().parent.parent.parent
- os.chdir(PROJECT)
- DATA_DIR = PROJECT / "data-pipeline" / "data"
- H = {"User-Agent": "PharmacopoeiaAI/1.0 (ResearchBot) Mozilla/5.0", "Accept-Language": "zh-CN,zh;q=0.9"}
- SECTION_MAP = {
- "性状": ["性状","外观","物理"],
- "适应症": ["适应","用途","主治","医疗用途","临床应用"],
- "用法与用量": ["用法","用量","剂量","给药","给药方式"],
- "禁忌": ["禁忌","禁用"],
- "不良反应": ["不良反应","副作用","不良事件"],
- "注意事项": ["注意","警告","谨慎"],
- "药理": ["药理","药效","作用机制","药物代谢","药物动力学","药代"],
- "药物相互作用": ["交互作用","药物相互"],
- "贮藏": ["贮藏","储存","保存"],
- }
- def parse_sections(html):
- soup = BeautifulSoup(html, "lxml")
- body = soup.find("div", class_="mw-parser-output")
- if not body: return {"概述": ""}
- overview_ps = []
- for elem in body.children:
- if elem.name == "div" and "mw-heading" in (elem.get("class") or []): break
- if elem.name == "p":
- t = elem.get_text(" ", strip=True)
- if len(t) > 15: overview_ps.append(t)
- sections = {"概述": " ".join(overview_ps)[:2000]}
- for h_div in body.find_all("div", class_=lambda c: c and "mw-heading" in c):
- span = h_div.find("span", class_="mw-headline")
- heading = (span.get_text() if span else h_div.get_text()).replace("[编辑]","").strip()
- if len(heading) < 2 or len(heading) > 60: continue
- if any(s in heading for s in ["参见","参考","外部","延伸","来源","引用","导航","链接","注释"]): continue
- matched_key = None
- for std_key, patterns in SECTION_MAP.items():
- if any(p in heading for p in patterns): matched_key = std_key; break
- texts = []; nxt = h_div.find_next_sibling()
- while nxt:
- if nxt.name == "div" and "mw-heading" in (nxt.get("class") or []): break
- if nxt.name in ("p","ul","ol"):
- t = nxt.get_text(" ", strip=True)
- if len(t) > 10: texts.append(t)
- nxt = nxt.find_next_sibling()
- text = " ".join(texts)[:3000]
- if text: sections[matched_key or heading] = text
- return sections
- # 读索引
- with open(DATA_DIR / "drug_index.json") as f:
- drugs = json.load(f)
- # 只取化学药+生物制品,跳过中药
- candidates = [d for d in drugs if d["volume"] in (2, 3)]
- out_dir = DATA_DIR / "wiki_drugs"
- out_dir.mkdir(parents=True, exist_ok=True)
- already = set(f.stem for f in out_dir.glob("*.json"))
- total = 0; ok = 0
- client = httpx.Client(timeout=20, follow_redirects=True)
- print(f"🚀 启动采集: {len(candidates)} 候选, 已采 {len(already)}, {len(candidates)-len(already)} 待采\n")
- for drug in candidates:
- name = drug["name"]
- safe = name.replace("/","_").replace(":","_")
- if safe in already: continue
- total += 1
- if total > 50: break # 首轮限量50个
- try:
- r = client.get(f"https://zh.wikipedia.org/wiki/{quote(name)}", headers=H)
- if r.status_code != 200:
- print(f" [{total}] ❌ {name} HTTP{r.status_code}")
- time.sleep(2); continue
- sections = parse_sections(r.text)
- if len(sections) <= 1:
- print(f" [{total}] ⚠️ {name} (空)")
- time.sleep(2); continue
- entry = {"drug_id":f"WIKI-{name}","name":name,"sections":sections,
- "source":{"version":"维基百科","volume":f"https://zh.wikipedia.org/wiki/{quote(name)}","page":"线上"}}
- with open(out_dir/f"{safe}.json","w") as f:
- json.dump(entry, f, ensure_ascii=False, indent=2)
- ok += 1
- print(f" [{total}] ✅ {name} ({len(sections)}段)")
- time.sleep(3)
- if ok % 15 == 0 and ok > 0:
- print(f" ⏸ 暂停45s...\n"); time.sleep(45)
- except Exception as e:
- print(f" [{total}] ❌ {name}: {e}")
- time.sleep(1)
- # 合并入库
- merged = []
- for f in out_dir.glob("*.json"):
- entry = json.load(open(f))
- if len(entry["sections"]) > 1:
- entry["category"] = "化学药"
- merged.append(entry)
- Path(DATA_DIR / "wiki_merged.json").write_text(json.dumps(merged, ensure_ascii=False, indent=2))
- print(f"\n✅ 完成: {ok} 新增 / {total} 处理, 合并 {len(merged)} 条 → wiki_merged.json")
- client.close()
|