# 药店采购比价系统 - 前端集成指南(爬虫平台功能)
> **⚠️ MVP 阶段说明**
> 本项目为**全新项目,尚未上线**,当前处于 **MVP(最小可行产品)阶段**。
> **核心业务**:药店采购比价系统,爬取各医药B2B平台药品价格,生成采购链接。
> **目标用户**:药店采购人员、药师,目标日活20万+。
>
> 📅 版本:v1.0-MVP | 📆 更新日期:2026-06-16 | ✅ 状态:开发中
---
## 📋 目录
- [功能概述](#功能概述)
- [快速开始](#快速开始)
- [接口详解](#接口详解)
- [前端实现示例](#前端实现示例)
- [数据流说明](#数据流说明)
- [注意事项](#注意事项)
- [常见问题](#常见问题)
---
## 🎯 功能概述
### 核心价值
本次更新为爬虫系统添加了**全渠道医药平台区分**功能,支持记录每次爬取的采购平台来源:
| 价值点 | 说明 | 收益 |
|--------|------|------|
| 🎯 最优价格采购 | 覆盖34个主流医药渠道 | 降低采购成本10%-30% |
| 🔗 一键跳转购买 | 生成各平台采购链接 | 提升转化率50%+ |
| 📊 跨平台对比 | 实时价格对比分析 | 提高决策效率 |
| 👥 邀请裂变 | 通过分享获得额外配额 | 提升用户注册率和日活 |
### 平台分类
```
🏭 B2B批发平台(14个)- 药店主要采购渠道
├─ 核心平台:药师帮、药京采、健之佳
└─ 扩展平台:1药网、康爱多、阿里健康等
🏪 B2C零售平台(8个)- 小批量采购/急单
├─ O2O即时配送:叮当快药、美团买药
└─ 连锁药房:老百姓、大参林、益丰等
🛒 综合电商医药频道(7个)- 价格参考
└─ 京东健康、天猫医药、拼多多医药等
👨⚕️ 垂直医药平台(4个)- 特色渠道
└─ 微医、平安好医生、春雨医生等
```
---
## 🚀 快速开始
### 1. MVP阶段核心流程
```javascript
// Step 1: 用户搜索药品
const drugName = '阿莫西林胶囊';
// Step 2: 选择比价平台(MVP推荐3个核心平台)
const platforms = ['yaoshibang', 'yaobangmang', 'yiyaocheng'];
// Step 3: 逐个平台爬取价格
for (const platform of platforms) {
// 消耗配额
await consumeQuota(platform);
// 爬取价格并生成采购链接
const priceData = await crawlPrice(drugName, platform);
}
// Step 4: 展示比价结果,点击跳转到对应平台购买
```
### 2. 关键代码片段
```javascript
// 消耗配额(必须传递platform参数)
const consumeQuota = async (platform) => {
const response = await fetch('/api/crawler/consume', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({
count: 1,
useCouponFirst: false,
platform: platform // ⚠️ 必填:平台代码
})
});
const result = await response.json();
if (result.code !== 200) {
throw new Error(result.message || '配额不足');
}
return result.data;
};
```
---
## 📡 接口详解
### 1. 消耗爬虫次数 ⭐⭐⭐
**接口信息:**
- **URL**: `POST /api/crawler/consume`
- **认证**: 需要 JWT Token
- **用途**: 用户点击"比价"按钮时调用,扣减配额并记录平台
**请求参数:**
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| count | Number | 是 | - | 消耗次数(通常传1) |
| useCouponFirst | Boolean | 否 | false | 是否优先使用优惠券 |
| platform | String | **是** | - | 平台代码(见下方枚举) |
**MVP核心平台枚举:**
```javascript
const MVP_PLATFORMS = {
YAOSHBANG: 'yaoshibang', // 药师帮 - 国内最大医药B2B ⭐
YAOBANGMANG: 'yaobangmang', // 药帮忙 - 西南地区领先 ⭐
YIYAOCHENG: 'yiyaocheng' // 1药城 - 华中地区知名 ⭐
};
```
**完整平台列表(Phase 2-6扩展):**
点击查看34个平台完整列表
```javascript
const ALL_PLATFORMS = {
// B2B批发平台(14个)
yaoshibang: '药师帮', yaojingcai: '药京采', jianzhijia: '健之佳',
'1yao': '1药网', kangaiduoduo: '康爱多', alihealth: '阿里健康',
sinopharm: '国药控股', jointown: '九州通', zhencheng: '珍诚医药',
yaoyitong: '药易通', hezong: '合纵药易购', yicaotang: '宜草堂',
yaodu: '药都在线', yaocaiying: '药材盈',
// B2C零售平台(8个)
dingdang: '叮当快药', quanyuantang: '泉源堂', dashenlin: '大参林',
laobaixing: '老百姓大药房', yixintang: '一心堂', yifeng: '益丰大药房',
haiwang: '海王星辰', guoda: '国大药房',
// 综合电商医药频道(7个)
jd_health: '京东健康', tmall_pharma: '天猫医药', pdd_pharma: '拼多多医药',
douyin_pharma: '抖音医药', kuaishou_pharma: '快手医药',
meituan_pharma: '美团买药', eleme_pharma: '饿了么医药',
// 垂直医药平台(4个)
weyi: '微医', pingan_good: '平安好医生', chunyu: '春雨医生', haodf: '好大夫在线'
};
```
**响应示例:**
```json
{
"code": 200,
"message": "success",
"data": true
}
```
**错误响应:**
```json
{
"code": 400,
"message": "每日配额已用完,请升级会员或等待明天",
"data": null
}
```
**前端调用示例:**
```javascript
// ✅ 正确用法
async function startPriceComparison(drugName, platform) {
try {
// 1. 消耗配额
await fetch('/api/crawler/consume', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
count: 1,
platform: platform // 必须传递
})
});
// 2. 爬取价格(调用后端爬虫服务)
const priceData = await crawlDrugPrice(drugName, platform);
return priceData;
} catch (error) {
console.error('比价失败:', error);
throw error;
}
}
// ❌ 错误用法:不传platform参数
await fetch('/api/crawler/consume', {
body: JSON.stringify({ count: 1 }) // 缺少platform字段!
});
```
---
### 2. 获取爬虫使用记录
**接口信息:**
- **URL**: `GET /api/crawler/logs?days=7`
- **认证**: 需要 JWT Token
- **用途**: 展示用户的历史爬取记录
**查询参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| days | Number | 否 | 7 | 查询最近N天的记录 |
**响应示例:**
```json
{
"code": 200,
"message": "success",
"data": [
{
"id": 1,
"userId": 1,
"usageDate": "2026-06-16",
"usageCount": 3,
"platform": "yaoshibang", // ✅ 平台代码
"source": "QUOTA", // QUOTA:配额 COUPON:优惠券
"remark": null,
"createTime": "2026-06-16T10:30:00"
}
]
}
```
**前端调用示例:**
```javascript
async function fetchUsageLogs(days = 7) {
const response = await fetch(`/api/crawler/logs?days=${days}`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
const result = await response.json();
return result.data || [];
}
```
---
### 3. 查看用户爬虫详情(运营后台专用)
**接口信息:**
- **URL**: `GET /api/admin/crawler/user-detail?userId=1`
- **认证**: 需要管理员 JWT Token
- **用途**: 运营后台查看用户的平台使用分布统计
**查询参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| userId | Long | 是 | 用户ID |
**响应示例:**
```json
{
"code": 200,
"message": "success",
"data": {
"userId": 1,
"nickname": "测试用户",
"phone": "138****8000",
"todayTotalUsage": 8,
"monthlyTotalUsage": 45,
"platformStats": [
{
"platformCode": "yaoshibang",
"platformName": "药师帮",
"todayUsage": 3,
"monthlyUsage": 15,
"totalUsage": 50
},
{
"platformCode": "yaobangmang",
"platformName": "药帮忙",
"todayUsage": 2,
"monthlyUsage": 10,
"totalUsage": 30
}
]
}
}
```
---
## 💻 前端实现示例
### React组件:药品比价功能(MVP简化版)
```jsx
import React, { useState } from 'react';
// MVP核心平台配置(3个)
const PLATFORM_CONFIG = {
yaoshibang: {
name: '药师帮',
icon: '💊',
color: '#1890ff',
description: '国内最大医药B2B平台'
},
yaobangmang: {
name: '药帮忙',
icon: '🏥',
color: '#52c41a',
description: '西南地区领先平台'
},
yiyaocheng: {
name: '1药城',
icon: '💉',
color: '#faad14',
description: '华中地区知名平台'
}
};
function DrugPriceComparison() {
const [drugName, setDrugName] = useState('');
const [selectedPlatforms, setSelectedPlatforms] = useState(['yaoshibang']);
const [isComparing, setIsComparing] = useState(false);
const [priceResults, setPriceResults] = useState([]);
const [quotaInfo, setQuotaInfo] = useState(null);
// 启动比价
const startComparison = async () => {
if (!drugName.trim()) {
alert('请输入药品名称');
return;
}
if (selectedPlatforms.length === 0) {
alert('请至少选择一个平台');
return;
}
try {
setIsComparing(true);
const results = [];
// 逐个平台爬取价格
for (const platform of selectedPlatforms) {
try {
// 1. 消耗配额
await consumeQuota(platform);
// 2. 爬取该平台药品价格
const priceData = await crawlDrugPrice(drugName, platform);
results.push(priceData);
} catch (error) {
console.error(`${PLATFORM_CONFIG[platform].name}爬取失败:`, error);
// 继续处理其他平台
}
}
// 按价格从低到高排序
results.sort((a, b) => a.price - b.price);
setPriceResults(results);
// 刷新配额状态
await refreshQuotaStatus();
} catch (error) {
console.error('比价失败:', error);
alert('操作失败,请重试');
} finally {
setIsComparing(false);
}
};
// 消耗配额
const consumeQuota = async (platform) => {
const response = await fetch('/api/crawler/consume', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
},
body: JSON.stringify({
count: 1,
useCouponFirst: false,
platform: platform // ✅ 必填
})
});
const result = await response.json();
if (result.code !== 200) {
throw new Error(result.message || `${PLATFORM_CONFIG[platform].name}配额不足`);
}
return result.data;
};
// 爬取药品价格(实际应调用后端爬虫服务)
const crawlDrugPrice = async (drugName, platform) => {
// TODO: 调用后端爬虫接口
// const response = await fetch(`/api/crawler/search?drug=${drugName}&platform=${platform}`);
// 模拟数据
return {
platform,
drugName,
price: Math.random() * 100 + 10,
purchaseLink: `https://${platform}.com/buy?drug=${encodeURIComponent(drugName)}`,
supplier: '某某医药公司',
stock: '有货',
specification: '0.25g*24粒'
};
};
// 刷新配额状态
const refreshQuotaStatus = async () => {
const response = await fetch('/api/crawler/status', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
}
});
const result = await response.json();
setQuotaInfo(result.data);
};
return (
💊 药品采购比价
{/* 配额信息 */}
{quotaInfo && (
今日剩余: {quotaInfo.dailyRemaining}/{quotaInfo.dailyLimit}次
本月剩余: {quotaInfo.monthlyRemaining}/{quotaInfo.monthlyLimit}次
)}
{/* 药品搜索 */}
setDrugName(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && startComparison()}
/>
{/* 平台选择 */}
选择比价平台:
{Object.entries(PLATFORM_CONFIG).map(([code, info]) => (
))}
{/* 开始比价按钮 */}
{/* 比价结果 */}
{priceResults.length > 0 && (
比价结果(按价格从低到高)
| 排名 |
平台 |
供应商 |
规格 |
价格 |
库存 |
操作 |
{priceResults.map((result, index) => (
|
{index === 0 && 🏆 最低价}
{index + 1}
|
{PLATFORM_CONFIG[result.platform].icon} {PLATFORM_CONFIG[result.platform].name}
|
{result.supplier} |
{result.specification} |
¥{result.price.toFixed(2)} |
{result.stock} |
🔗 去采购
|
))}
💡 点击"去采购"将跳转到对应平台的购买页面
)}
);
}
export default DrugPriceComparison;
```
### CSS样式(配套)
```css
.drug-price-comparison {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.quota-info {
background: #f0f5ff;
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
gap: 20px;
font-size: 14px;
}
.search-box input {
width: 100%;
padding: 12px;
font-size: 16px;
border: 2px solid #d9d9d9;
border-radius: 8px;
margin-bottom: 20px;
}
.platform-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
margin-top: 12px;
}
.platform-card {
display: flex;
align-items: center;
padding: 12px;
border: 2px solid #d9d9d9;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
}
.platform-card.selected {
border-color: #1890ff;
background: #f0f5ff;
}
.platform-card:hover {
border-color: #1890ff;
}
.platform-info {
display: flex;
flex-direction: column;
margin-left: 8px;
}
.platform-name {
font-weight: bold;
}
.platform-desc {
font-size: 12px;
color: #999;
}
.compare-btn {
width: 100%;
padding: 14px;
font-size: 16px;
background: #1890ff;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
margin: 20px 0;
}
.compare-btn:disabled {
background: #d9d9d9;
cursor: not-allowed;
}
.price-table {
width: 100%;
border-collapse: collapse;
margin-top: 16px;
}
.price-table th,
.price-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #f0f0f0;
}
.best-price {
background: #fff7e6;
}
.badge {
background: #ff4d4f;
color: white;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.platform-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 16px;
color: white;
font-size: 14px;
}
.price {
color: #ff4d4f;
font-weight: bold;
font-size: 16px;
}
.buy-link {
color: #1890ff;
text-decoration: none;
}
.buy-link:hover {
text-decoration: underline;
}
.tip {
margin-top: 12px;
color: #999;
font-size: 14px;
}
```
---
## 📊 数据流说明
### 完整业务流程
```
┌─────────────┐
│ 用户搜索药品 │
└──────┬──────┘
│
▼
┌──────────────────┐
│ 选择比价平台 │ ← MVP推荐3个核心平台
└──────┬───────────┘
│
▼
┌──────────────────────────┐
│ 循环每个平台: │
│ 1. 调用 /consume 接口 │ ← 扣减配额,记录platform
│ 2. 调用爬虫服务 │ ← 爬取价格数据
│ 3. 生成采购链接 │ ← 返回购买URL
└──────┬───────────────────┘
│
▼
┌──────────────────┐
│ 按价格排序展示 │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ 用户点击购买 │ → 跳转到对应平台
└──────────────────┘
```
### 数据流转图
```
前端 后端 数据库
│ │ │
│ POST /consume │ │
│ {platform: 'yaoshibang'} │ │
├─────────────────────────────>│ │
│ │ 验证配额 │
│ ├───────────────────────────>│
│ │ │
│ │ 扣减配额 │
│ │ INSERT usage_log │
│ │ (含platform字段) │
│ │ │
│ │<───────────────────────────┤
│ │ │
│ 返回成功 │ │
│<─────────────────────────────┤ │
│ │ │
│ GET /logs │ │
├─────────────────────────────>│ │
│ │ 查询使用记录 │
│ ├───────────────────────────>│
│ │ SELECT * FROM usage_log │
│ │ WHERE platform IS NOT NULL │
│ │ │
│ 返回记录(含platform) │<───────────────────────────┤
│<─────────────────────────────┤ │
```
---
## ⚠️ 注意事项
### 1. 必填参数
**❌ 错误做法:**
```javascript
// 不传platform参数
await fetch('/api/crawler/consume', {
body: JSON.stringify({ count: 1 })
});
```
**✅ 正确做法:**
```javascript
// 必须传递platform参数
await fetch('/api/crawler/consume', {
body: JSON.stringify({
count: 1,
platform: 'yaoshibang' // 必填
})
});
```
### 2. 平台代码规范
- 使用小写字母和下划线:`yaoshibang` ✅
- 不要使用中文:`药师帮` ❌
- 不要使用大写:`Yaoshibang` ❌
### 3. 错误处理
```javascript
try {
await consumeQuota(platform);
} catch (error) {
// 常见错误:
// - 配额不足:code=400, message="每日配额已用完"
// - 未登录:code=401, message="未认证"
// - 参数错误:code=400, message="平台代码无效"
if (error.message.includes('配额')) {
// 引导用户升级会员或等待明天
showUpgradeModal();
} else {
// 显示错误提示
showError(error.message);
}
}
```
### 4. 性能优化
- **并发控制**:建议串行爬取,避免同时发起多个请求导致配额快速耗尽
- **缓存策略**:相同药品的比价结果可缓存5分钟,减少重复爬取
- **懒加载**:先展示部分平台结果,其余平台异步加载
### 5. 向后兼容
- 历史数据的 `platform` 字段可能为 `null`
- 前端需要做空值判断:`log.platform || '未知平台'`
---
## ❓ 常见问题
### Q1: 为什么要传递platform参数?
**A:** 用于记录用户主要在哪些平台进行比价,便于:
- 运营分析用户偏好
- 优化爬虫资源分配
- 提供个性化推荐
### Q2: 如果用户选择了多个平台,如何调用接口?
**A:** 逐个平台调用,每次调用传递对应的platform参数:
```javascript
for (const platform of selectedPlatforms) {
await consumeQuota(platform); // 每次传不同的platform
const priceData = await crawlPrice(drugName, platform);
}
```
### Q3: 平台代码从哪里获取?
**A:** 建议使用常量定义:
```javascript
// constants/platforms.js
export const PLATFORM_CODES = {
YAOSHBANG: 'yaoshibang',
YAOBANGMANG: 'yaobangmang',
YIYAOCHENG: 'yiyaocheng'
};
// 使用时
import { PLATFORM_CODES } from '@/constants/platforms';
await consumeQuota(PLATFORM_CODES.YAOSHBANG);
```
### Q4: 如何处理配额不足的情况?
**A:** 捕获错误并引导用户:
```javascript
try {
await consumeQuota(platform);
} catch (error) {
if (error.message.includes('配额')) {
// 显示升级弹窗
Modal.confirm({
title: '配额不足',
content: '每日配额已用完,升级VIP可获得更多配额',
okText: '立即升级',
onOk: () => navigate('/upgrade')
});
}
}
```
### Q5: 能否一次性消耗多次配额?
**A:** 可以,但不建议。建议每次爬取一个平台就调用一次接口:
```javascript
// ✅ 推荐:逐个平台调用
for (const platform of platforms) {
await consumeQuota(platform); // count=1
}
// ❌ 不推荐:一次性消耗多次
await consumeQuota('yaoshibang', { count: 3 });
```
---
## 📞 技术支持
如有问题,请联系:
- 📧 邮箱:dev@zhijiayun.com
- 💬 企业微信:药店采购比价系统技术群
- 📱 电话:400-XXX-XXXX
---
**最后更新**:2026-06-16
**文档版本**:v1.0-MVP
**维护团队**:后端开发组