一、流程设计

起初,我想直接扫码后接入大模型接口,我想:

“既然有大模型,我能不能扫完条码直接问大模型,不建药品库了?”

答案是:

可以,但不能完全依赖大模型。

为什么不能完全依赖大模型?

假设用户扫到:

6907992101234

然后发给 Qwen:

这是一个药品条码:
6907992101234

请返回药品名称、成分、保质期、用法用量、副作用。

问题来了:

Qwen根本不知道这个条码是什么

因为:

  • 大模型不是实时联网数据库
  • 条码不是公开知识
  • 绝大多数药品条码没有进入训练数据

结果是:

因此我只能换个思路,将流程换成:

ML Kit扫码
↓
得到条码

↓
第三方条码API

↓
药品名称

接入条形码API,得到药品名称。但此时还无法得到药品详细数据。

例如(仅供举例):

6907992101234

返回:

布洛芬缓释胶囊

然后:

MongoDB
drug_info

查询:

findByName("布洛芬缓释胶囊")

如果存在:

返回药品信息

如果不存在:

请求扫描说明书
↓
OCR
↓
Qwen
↓
写入drug_info

我最后放弃了:

条码
↓
直接得到成分
直接得到副作用
直接得到保质期

因为没找到稳定免费的药品条码数据库能保证这些字段。实际上很多商业APP也是:

条码
↓
得到药名
↓
查自己的药品库

最后我的设计:

扫码
↓
第三方条码API

得到:
药品名称

↓
MongoDB

有药品
↓
直接返回

没药品
↓
OCR说明书
↓
Qwen提取
↓
存MongoDB

下面是爬取数据的过程:


一、起点:从一个药品页面开始

最初的目标很简单:拿到一个药品的说明书数据。

以 39 药品通为例:

https://ypk.39.net/572623/manual/

页面结构非常清晰:

  • 【药品名称】
  • 【成份】
  • 【适应症】
  • 【用法用量】
  • 【不良反应】

 第一版解析代码(核心结构)

items = soup.select("ul.drug-explain li")

for item in items:
    title = item.select_one(".drug-explain-tit")
    content = item.select_one(".drug-explain-txt")

    if title and content:
        key = title.get_text(strip=True)
        value = content.get_text(separator=" ", strip=True)
        drug_info[key] = value

这一阶段我解决了两个问题:

  • HTML结构解析(BeautifulSoup选择器)
  • 数据结构标准化(dict存储)

输出结果已经是结构化 JSON,而不是 HTML


二、关键转折:从“单页爬虫”到“搜索系统”

真正的难点不是解析,而是:

❓ 如何找到所有药品?

我尝试了一个错误入口:

https://ypk.39.net/weishoulu_p1

结果返回:

未收录信息

这一步让我意识到:

很多网页“存在URL ≠ 有数据结构”


正确方式:搜索页提取药品列表

通过搜索接口:

https://ypk.39.net/search/布洛芬-NULL-b0-ci0-c0-m0-bm0-otc0-fd0-p0

解析结果:

for a in soup.find_all("a"):
    text = a.get_text(strip=True)
    href = a.get("href")

    if "布洛芬" in text and href:
        print(text, href)

这一阶段的突破:

✔ 从“单页面解析” → “搜索结果发现系统”
✔ 能获取多个药品条目
✔ 拿到了关键ID(药品详情页入口)


三、核心升级:构建“药品数据管道”

当我拿到药品列表后,系统开始具备“数据流”结构:

关键词 → 搜索页 → 药品列表 → 详情页 → 说明书 → 结构化数据

提取药品详情页

drug_urls = set()

for a in soup.find_all("a"):
    text = a.get_text(strip=True)
    href = a.get("href")

    if "布洛芬" in text and "ypk.39.net" in href:
        drug_urls.add(href)

拼接说明书页(关键设计)

def get_manual_url(drug_url):
    return drug_url.rstrip("/") + "/manual/"

这里是整个系统的“数据扩展点”


四、系统化封装:模块化爬虫设计

当逻辑变复杂后,我把系统拆成三个模块:


1. 搜索模块

def search_drugs(keyword):
    url = f"https://ypk.39.net/search/{keyword}-NULL-b0-ci0-c0-m0-bm0-otc0-fd0-p0"

 2. 解析模块

def parse_manual(url):
    soup = BeautifulSoup(requests.get(url).text, "html.parser")

    data = {}

    for item in soup.select("ul.drug-explain li"):
        title = item.select_one(".drug-explain-tit")
        content = item.select_one(".drug-explain-txt")

        if title and content:
            data[title.text.strip()] = content.text.strip()

    return data

3. 数据流主控

for url in drug_urls:
    manual_url = url.rstrip("/") + "/manual/"
    data = parse_manual(manual_url)

五、从单药到批量系统(真正的转折)

当我把 keyword 写死时,系统是这样的:

keyword = "布洛芬"

但当我升级为文件输入后:

def load_keywords():
    with open("keywords.txt", encoding="utf-8") as f:
        return [line.strip() for line in f if line.strip()]

系统直接升级为:

多药品批量爬取系统

批量运行结构

for keyword in keywords:
    drug_urls = search_drugs(keyword)

    for url in drug_urls:
        data = parse_manual(get_manual_url(url))

六、数据落地:CSV结构化存储

最终我选择 CSV 作为第一阶段存储方案:

df = pd.DataFrame(all_data)
df.to_csv("drug_data.csv", index=False, encoding="utf-8-sig")

数据成果:

药品名称 成份 适应症 用法用量 来源关键词

七、这次项目让我真正理解的3件事

1.爬虫不是“抓网页”,而是构建数据流

从:

HTML → dict → CSV → 数据库

本质是 ETL 流程。


2.网页结构 ≠ 数据结构

例如:

weishoulu_p1 ❌(假入口)
search页面 ✔
manual页面 ✔

学会判断“可用数据入口”比写代码更重要


小系统也有架构设计

我的最终结构变成:

search()
   ↓
get_urls()
   ↓
parse_manual()
   ↓
store_csv()
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐