招标采集与智能匹配操作说明
招标信息采集与商家智能匹配系统 — 方案报告
一、目标与范围
1.1 业务目标
- 招标源:以滁州市公共资源交易中心(Zz_ggzy.chuzhou.gov.cn)为典型,支持从类似政府招投标网站自动采集每日发布的招标信息。
- 商家匹配:将采集到的招标信息根据本站商家(shop)的资质、业绩、人员等综合信息,自动匹配并推送给合适商家。
- 扩展性:采集侧提供万能接口(可配置变量网址 + 条件1~条件10),数据驱动、可配置多站点多栏目,无需改代码即可接入新站点。
1.2 三大版块划分(建议实施顺序)
| 版块 | 名称 | 说明 | 接口预留 |
|---|---|---|---|
| 版块一 | 采集入库版块 | 采集源配置、定时/手动采集、招标信息入库、采集日志。先做此版块。 | 采集完成后调用「匹配触发接口」:传入本批新增的 tender_id 列表,供版块三拉取并执行匹配。 |
| 版块二 | 商家信息版块 | 商家资质、业绩、人员等扩展信息维护,形成「商家画像」。 | 提供接口:按 shop_id 获取商家画像(文本或结构化),供版块三匹配时使用。 |
| 版块三 | AI 对接自动匹配版块 | 调用 AI/规则引擎,将招标与商家画像匹配,写入匹配结果;商家端展示与操作。 | 输入:tender_id 或招标摘要+类别;输出:匹配到的 shop_id 列表 + score + reason。依赖版块一的数据、版块二的画像接口。 |
版块一预留接口(供版块三调用)
- 采集任务结束时,将本次新增的
tender_id列表写入队列或调用TenderMatchService::onNewTenders($tender_ids)(预留空实现,后续版块三实现具体匹配逻辑)。
版块二预留接口
getShopProfileForMatch($shop_id):返回该商家的资质、类型、业绩、人员摘要,用于匹配。
版块三预留接口
- 由版块一在入库后调用:
triggerMatchForTenders($tender_ids);内部可调用版块二的getShopProfileForMatch与 AI 服务,结果写入by_tender_shop_match。
1.3 系统范围(总览)
| 模块 | 所属版块 | 说明 |
|---|---|---|
| 采集配置 | 版块一 | 管理采集源(变量网址、条件1~10、列表/详情规则、采集时间等) |
| 定时采集 | 版块一 | 按配置自动执行采集,写入本站数据库 |
| 招标信息库 | 版块一 | 存储标题、链接、发布时间、类别、正文等 |
| 采集日志 | 版块一 | 每次采集的起止时间、成功/失败条数 |
| 商家扩展信息 | 版块二 | 商家资质、业绩、人员等(预留表与接口) |
| AI 智能匹配 | 版块三 | 根据招标内容与商家画像计算匹配度并推荐(预留接口) |
| 匹配结果表 | 版块三 | tender_id, shop_id, score, reason, status(预留表) |
| 推送与展示 | 版块三 | 商家端“推荐招标”列表与操作(预留) |
后台入口:登录后台后,左侧菜单 「招标采集」→「采集管理」→「采集源设置」(即 Backstage/tendercollect/index)。若未显示菜单,请执行 数据库变更-招标采集-后台菜单.sql 或到 系统-菜单管理 中手动添加。
二、招标网站分析(以滁州公共资源交易中心为例)
2.1 网站结构概览
- 站点:滁州市公共资源交易中心(ggzy.chuzhou.gov.cn)
- 栏目:交易信息 → 工程建设(005001)、政府采购(005002)、土地矿权(005003)、国有产权(005004)、农村产权(005005)、其他交易(005006)、自主交易(005007) 等。
- 工程建设子类(data-value):招标计划(005001014)、招标公告(005001003)、招标文件(005001005)、澄清修改(005001004)、开标记录(005001006)、中标候选人公示(005001007)、中标结果公示(005001008)、中标通知书(005001009)、合同订立(005001011) 等。
2.2 页面与数据特征
| 类型 | 路径/形式 | 说明 |
|---|---|---|
| 首页 | index.html | 入口,含交易信息等导航 |
| 列表页 | bussinessJSGC.html 等 | 工程建设等,列表由 JS 加载(如 bussinessJsgc.js),模板含 visiturl、infoid、title、infodate |
| 详情页 | UUID.html / 数字ID.html | 正文页,meta 含 ArticleTite / ArticleTitle、PubDate、ContentSource,正文在 .ewb-article 或 .ewb-article-info |
| 列表数据 | 可能来自接口 | 若站点提供列表 API,可配置为“接口采集”;否则用 HTML 解析 |
2.3 采集要点
- 列表:需支持 HTML 抓取(如解析 iframe/JS 渲染后的 DOM)或接口 JSON(若可拿到列表 API)。
- 详情:标题、发布时间、来源、正文为必备字段;可选:项目编号、预算、截止时间等(若页面有)。
- 去重:以源站唯一标识(如 infoid + 来源站点)做唯一约束,避免重复入库。
- 礼貌爬取:间隔请求、可配置 User-Agent、并发数,避免对目标站造成压力。
三、系统架构
┌─────────────────────────────────────────────────────────────────────────┐
│ 管理后台 │
│ 采集源配置(万能接口) │ 招标列表/详情 │ 商家资质维护 │ 匹配规则 │ 日志 │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 采集调度(Cron/队列) │
│ 按 by_tender_collect_config 逐条执行:请求(变量URL+条件1~10) → 解析 → 入库 │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 招标信息库(by_tender_info) │
│ 去重、状态(待匹配/已匹配)、原始URL、正文等 │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ AI 匹配服务(内网 API 或第三方) │
│ 输入:招标标题+摘要+类别;商家:资质、类型、人员、业绩 → 匹配度 + 推荐理由 │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 匹配结果(by_tender_shop_match) │
│ tender_id, shop_id, score, reason, status(未读/已读/已报名) │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 商家端(现有 shop 体系) │
│ “推荐招标”列表、详情、已读/报名操作 │
└─────────────────────────────────────────────────────────────────────────┘
四、万能采集接口设计
4.1 设计原则
- 配置驱动:采集行为由数据库配置决定,不写死 URL 和参数。
- 变量网址:支持占位符,如
{base_url}、{category}、{page}、{date}等,由执行时替换。 - 条件 1~条件 10:对应 10 个可配置键值(如栏目编码、类型、地区),用于拼接到 URL 或请求体,或作为解析时的过滤条件。
4.2 采集源配置表(by_tender_collect_config)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int, PK | 主键 |
| name | varchar(128) | 采集源名称(如:滁州公共资源-工程建设招标公告) |
| site_domain | varchar(128) | 站点域名(用于去重、展示) |
| list_url_tpl | varchar(512) | 列表页 URL 模板,支持变量 {base_url},{cond1}~{cond10},{page},{date} |
| detail_url_tpl | varchar(512) | 详情页 URL 模板,如 {base_url}/{id}.html |
| list_type | tinyint | 列表类型:1=HTML 列表页,2=API 列表 |
| list_selector | varchar(256) | 列表项选择器(CSS 或 JSONPath),如 .detailList a 或 $.data.list[*] |
| list_link_attr | varchar(64) | 列表项链接属性,如 href 或 data-url |
| list_title_attr | varchar(64) | 列表项标题所在属性或文本节点 |
| list_date_attr | varchar(64) | 列表项日期所在属性或文本节点 |
| detail_selector_title | varchar(128) | 详情页标题选择器,如 meta[name=ArticleTite] 或 .ewb-article h3 |
| detail_selector_date | varchar(128) | 详情页发布时间选择器 |
| detail_selector_content | varchar(128) | 详情页正文选择器,如 .ewb-article-info |
| cond1~cond10 | varchar(128) | 条件 1~10 的值,对应替换 {cond1}~{cond10} |
| base_url | varchar(256) | 基础 URL,替换 {base_url} |
| page_start | int | 起始页码(默认 1) |
| page_max | int | 最大抓取页数 |
| request_interval | int | 请求间隔(秒) |
| run_cron | varchar(64) | Cron 表达式,如 0 8 * * * 表示每日 8 点 |
| status | tinyint | 0=禁用,1=启用 |
| last_run_time | int | 上次执行时间 |
| create_time | int | 创建时间 |
| update_time | int | 更新时间 |
变量约定:
{base_url}→ base_url{cond1}~{cond10}→ cond1~cond10{page}→ 当前页码{date}→ 当前日期 Y-m-d(可选)
示例(滁州工程建设列表若为接口):
- list_url_tpl:
https://ggzy.chuzhou.gov.cn/api/list?category={cond1}&page={page} - cond1:
005001003(招标公告)
若为 HTML:
- list_url_tpl:
{base_url}/bussinessJSGC.html - base_url:
https://ggzy.chuzhou.gov.cn - list_selector 需根据实际 DOM 或 JS 返回结构配置(如 iframe 内列表或 data 接口)。
4.3 采集执行流程
- 定时任务:按 run_cron 触发,或后台“立即采集”按钮。
- 读取配置:只处理 status=1 的 by_tender_collect_config。
- 替换变量:用 base_url、cond1~cond10、page、date 生成实际 list_url。
- 抓取列表:根据 list_type 请求 HTML 或 API,用 list_selector 等解析出链接、标题、日期。
- 去重:用 source_id(站点+原始ID)查 by_tender_info,已存在则跳过。
- 抓取详情:对每条新记录用 detail_url_tpl 抓详情,解析标题、日期、正文,写入 by_tender_info。
- 写日志:记录 by_tender_collect_log(采集源 id、开始/结束时间、成功/失败条数、错误信息)。
- 触发匹配:采集批次结束后,对本次新增的招标执行“AI 匹配到商家”。
五、数据库设计
5.1 招标信息表(by_tender_info)
| 字段名 | 类型 | 说明 |
|---|---|---|
| tender_id | int, PK | 主键 |
| collect_id | int | 采集配置 id |
| source_id | varchar(64) | 源站唯一标识(如站点+infoid),唯一索引 |
| title | varchar(512) | 标题 |
| url | varchar(512) | 详情页 URL |
| publish_time | int | 发布时间(时间戳) |
| source_name | varchar(128) | 来源(如滁州市公共资源交易中心) |
| category | varchar(64) | 类别(如 招标公告、中标公示) |
| category_code | varchar(32) | 类别编码(如 005001003) |
| content | mediumtext | 正文(纯文本或 HTML,按需) |
| content_summary | varchar(1024) | 摘要(用于匹配,可 200~500 字) |
| extra_json | text | 扩展字段(JSON),如项目编号、预算、截止时间 |
| match_status | tinyint | 0=待匹配,1=已匹配 |
| create_time | int | 入库时间 |
| update_time | int | 更新时间 |
唯一索引:(source_id) 或 (collect_id, source_id)。
5.2 商家-招标匹配结果表(by_tender_shop_match)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int, PK | 主键 |
| tender_id | int | 招标 id |
| shop_id | int | 商家 id |
| score | decimal(5,2) | 匹配度 0~100 |
| reason | varchar(512) | 推荐理由(AI 生成简短说明) |
| status | tinyint | 0=未读,1=已读,2=已报名/感兴趣 |
| read_time | int | 已读时间 |
| create_time | int | 匹配时间 |
索引:tender_id, shop_id, (shop_id, status)。
5.3 商家扩展信息(用于匹配)
在现有商家体系上扩展,便于 AI 使用:
- by_shop:已有 shop_id, shop_name, cate_id, lixing(服务/工业/农业/建设/行政)等。
- by_shop_details:已有简介、地址等,可增加“主营业务/资质简述”字段(或用现有 intro 等)。
- by_shop_worker:员工列表;by_worker_qualification、by_worker_achievement:员工资质与业绩(已有表)。
建议新增(若暂无):
- by_shop_qualification:商家级资质(如建筑业企业资质、行业许可),字段:shop_id, title, level, cert_no, valid_until, intro。
- by_shop_achievement:商家业绩(项目名称、类型、金额、时间),字段:shop_id, title, category, amount, project_date, intro。
匹配时汇总:商家类型(lixing)+ 类目(cate)+ 商家资质 + 员工资质 + 员工业绩 + 商家业绩,形成“商家画像”文本或向量,与招标标题+摘要+类别做匹配。
5.4 采集日志表(by_tender_collect_log)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int, PK | 主键 |
| collect_id | int | 采集配置 id |
| start_time | int | 开始时间 |
| end_time | int | 结束时间 |
| total_count | int | 本批发现条数 |
| new_count | int | 新增入库条数 |
| fail_count | int | 失败条数 |
| error_msg | text | 错误信息(汇总或最后一条) |
六、AI 智能匹配设计
6.1 输入
- 招标侧:title、content_summary(或正文前 N 字)、category/category_code。
- 商家侧(聚合为“商家画像”文本或向量):
- 商家:shop_name, lixing, cate 名称、简介、主营业务。
- 资质:by_shop_qualification / by_worker_qualification 的 title、level、intro。
- 业绩:by_shop_achievement / by_worker_achievement 的 title、category、intro。
- 人员:可选,如“持证人员数量、专业方向”等摘要。
6.2 匹配方式(两种可选或组合)
- 规则匹配:
- 类别映射:如 工程建设(005001) → 优先推给 lixing=建设类 的商家。
- 关键词:招标标题/摘要含“装修”“消防”等 → 资质或业绩中含对应关键词的商家加分。
- 可配置规则表(by_tender_match_rule):规则类型、关键词/类别、权重、适用商家条件。
- AI 匹配:
- 将招标摘要 + 商家画像文本发给大模型(或本地模型),返回匹配度 0~100 + 一句话推荐理由。
- 或使用向量模型:招标摘要与商家画像分别向量化,计算相似度作为 score,再让 LLM 生成 reason。
6.3 输出与存储
- 对每条新入库招标,筛选“可参与匹配”的商家(如 audit=1、closed=0、可选:已开通招标推荐服务)。
- 调用匹配服务,得到 (shop_id, score, reason) 列表;过滤 score ≥ 阈值(如 60)的商家。
- 写入 by_tender_shop_match,并更新 by_tender_info.match_status=1。
- 推送:站内消息或微信模板(若已对接),通知商家“有 N 条与您匹配的招标”。
6.4 去重与频控
- 同一 (tender_id, shop_id) 只保留一条匹配记录(最新一次匹配结果)。
- 若招标更新后重新匹配,可先删除该 tender_id 的旧 match 再写入新结果,或按业务选择“仅对新招标匹配”。
七、实施步骤建议
| 阶段 | 内容 | 说明 |
|---|---|---|
| 1. 库表与配置后台 | 建表 by_tender_collect_config、by_tender_info、by_tender_shop_match、by_tender_collect_log;商家扩展表(若需要);后台“采集源管理”CRUD、条件1~10 与变量说明 | 先实现配置与存储,不跑真实采集 |
| 2. 采集引擎 | 实现通用采集脚本/服务:读配置 → 变量替换 → 请求列表 → 解析 → 请求详情 → 入库;写日志;支持 HTML 与简单 API | 可先用滁州站一条配置做联调 |
| 3. 滁州站具体配置 | 根据实际列表页(或接口)确定 list_url_tpl、list_selector、detail_url_tpl、detail_selector_*;若列表为 JS 渲染,可考虑内置无头浏览器或对接站点接口(若有) | 保证至少一个源稳定跑通 |
| 4. 定时任务 | Cron 或队列定时执行“已启用”的采集配置;失败重试与告警 | 每日自动采集 |
| 5. 规则匹配 | 实现类别/关键词规则表与规则引擎,输出 (shop_id, score, reason);写入 by_tender_shop_match | 先不用 AI 也能用 |
| 6. AI 对接 | 封装“招标+商家画像→匹配度+理由”的 API;对接大模型或向量服务;将结果写入 by_tender_shop_match | 可替代或补充规则 |
| 7. 商家端展示 | 商家后台/员工端“推荐招标”列表、详情、已读/报名;未读数量角标 | 与现有 shop 权限体系对接 |
| 8. 推送与运营 | 站内消息、可选微信模板推送;统计“查看率、报名率”便于优化规则与 AI | 按需 |
八、风险与注意点
- 合规:采集仅用于本站商家服务,遵守目标站 robots.txt 与使用条款;不对外转卖原始数据。
- 反爬:政府站可能改版或限频,需做请求间隔、错误重试、变更告警;若提供正式接口,优先用接口。
- 数据质量:列表/详情选择器随站点改版可能失效,需有“采集质量巡检”或人工抽检,配置可快速调整。
- AI 成本:若用按次计费的大模型,可对高匹配度候选再调 AI 生成理由,或仅对部分招标使用 AI,其余用规则。
九、附录:表结构 SQL 草稿(前缀 by_)
以下为关键表建表草稿,实际表名前缀以项目约定为准(如 by_)。
-- 采集源配置表
CREATE TABLE IF NOT EXISTS `by_tender_collect_config` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL COMMENT '采集源名称',
`site_domain` varchar(128) DEFAULT NULL COMMENT '站点域名',
`list_url_tpl` varchar(512) NOT NULL COMMENT '列表URL模板',
`detail_url_tpl` varchar(512) DEFAULT NULL COMMENT '详情URL模板',
`list_type` tinyint(4) DEFAULT '1' COMMENT '1=HTML 2=API',
`list_selector` varchar(256) DEFAULT NULL,
`list_link_attr` varchar(64) DEFAULT NULL,
`list_title_attr` varchar(64) DEFAULT NULL,
`list_date_attr` varchar(64) DEFAULT NULL,
`detail_selector_title` varchar(128) DEFAULT NULL,
`detail_selector_date` varchar(128) DEFAULT NULL,
`detail_selector_content` varchar(256) DEFAULT NULL,
`cond1` varchar(128) DEFAULT NULL,
`cond2` varchar(128) DEFAULT NULL,
`cond3` varchar(128) DEFAULT NULL,
`cond4` varchar(128) DEFAULT NULL,
`cond5` varchar(128) DEFAULT NULL,
`cond6` varchar(128) DEFAULT NULL,
`cond7` varchar(128) DEFAULT NULL,
`cond8` varchar(128) DEFAULT NULL,
`cond9` varchar(128) DEFAULT NULL,
`cond10` varchar(128) DEFAULT NULL,
`base_url` varchar(256) DEFAULT NULL,
`page_start` int(11) DEFAULT '1',
`page_max` int(11) DEFAULT '5',
`request_interval` int(11) DEFAULT '2',
`run_cron` varchar(64) DEFAULT NULL,
`status` tinyint(4) DEFAULT '1',
`last_run_time` int(11) DEFAULT NULL,
`create_time` int(11) NOT NULL,
`update_time` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='招标采集源配置';
-- 招标信息表
CREATE TABLE IF NOT EXISTS `by_tender_info` (
`tender_id` int(11) NOT NULL AUTO_INCREMENT,
`collect_id` int(11) NOT NULL,
`source_id` varchar(64) NOT NULL COMMENT '源站唯一ID',
`title` varchar(512) NOT NULL,
`url` varchar(512) DEFAULT NULL,
`publish_time` int(11) DEFAULT NULL,
`source_name` varchar(128) DEFAULT NULL,
`category` varchar(64) DEFAULT NULL,
`category_code` varchar(32) DEFAULT NULL,
`content` mediumtext,
`content_summary` varchar(1024) DEFAULT NULL,
`extra_json` text,
`match_status` tinyint(4) DEFAULT '0',
`create_time` int(11) NOT NULL,
`update_time` int(11) NOT NULL,
PRIMARY KEY (`tender_id`),
UNIQUE KEY `source_id` (`source_id`),
KEY `collect_id` (`collect_id`),
KEY `publish_time` (`publish_time`),
KEY `match_status` (`match_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='招标信息';
-- 商家-招标匹配结果表
CREATE TABLE IF NOT EXISTS `by_tender_shop_match` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tender_id` int(11) NOT NULL,
`shop_id` int(11) NOT NULL,
`score` decimal(5,2) DEFAULT NULL,
`reason` varchar(512) DEFAULT NULL,
`status` tinyint(4) DEFAULT '0' COMMENT '0未读 1已读 2已报名',
`read_time` int(11) DEFAULT NULL,
`create_time` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `tender_shop` (`tender_id`,`shop_id`),
KEY `shop_status` (`shop_id`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='招标-商家匹配结果';
-- 采集日志表
CREATE TABLE IF NOT EXISTS `by_tender_collect_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`collect_id` int(11) NOT NULL,
`start_time` int(11) NOT NULL,
`end_time` int(11) DEFAULT NULL,
`total_count` int(11) DEFAULT '0',
`new_count` int(11) DEFAULT '0',
`fail_count` int(11) DEFAULT '0',
`error_msg` text,
PRIMARY KEY (`id`),
KEY `collect_id` (`collect_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='招标采集日志';
以上为招标采集与商家智能匹配的完整方案设计,可直接作为评审与开发依据。若需要先落地“采集配置 + 单源采集 + 规则匹配”的最小闭环,可优先做第二、四、五、七阶段;AI 与推送后续迭代。
