尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

招投标推荐可视化系统实战:Flask+Vue+爬虫+推荐算法

发布时间:2026/9/29 18:07:12

资讯中心
01
ARTICLE

招投标推荐可视化系统实战:Flask+Vue+爬虫+推荐算法

招投标推荐可视化系统实战:Flask+Vue+爬虫+推荐算法
做过招投标的人都懂每天上班第一件事就是刷各个公共资源交易平台翻公告、筛资质、记时间一不小心漏掉一条和自己业务匹配的项目半个月业绩就悬了。我做的这套“浙江招投标推荐可视化系统”核心就干三件事用爬虫把公开的招投标公告抓下来用推荐算法把公告和企业画像做匹配再用Vue把匹配结果做成可视化的驾驶舱。整个系统基于Flask提供接口、SQLAlchemy存数据前端走Vue整套东西不大但能把“人肉刷公告”变成“系统推荐地图展示”的工作流。这套东西适合三类人看想给自己或团队做一个投标辅助工具的开发、刚接触FlaskVue全栈想找个完整实战项目的学生、以及手里有公共数据想做二次加工的从业者。F053_ZJ 浙江招投标推荐可视化系统Vue Flask 推荐算法 爬虫实战1. 项目全景与需求拆解1.1 为什么需要一套独立的招投标推荐系统招投标行业有个很现实的问题公开数据都在那里但分散在省、市、区各级平台公告格式不统一更新频率不一样早期可能还有邮件订阅但关键词订阅的颗粒度太粗——你订阅“智能化”来的可能是“智能化办公设备采购”也可能是“智能化工厂设计”前者是货物类后者是工程类完全是两条业务线。靠人去分辨这个信息噪音成本非常高。我接到这个项目需求时对方的原始诉求很简单“能不能让系统每天自己看公告然后只把和我公司相关的推给我”这就是这套系统最早的出发点。后来加了可视化是因为客户发现光是推荐列表还不够他们想知道自己关注的领域每周有多少新项目、项目集中在哪个城市、哪个业主单位发布频率最高这些维度能直接影响投标决策——比如在某市项目量明显上升时值得提前去那边维护关系。所以F053这套系统的核心不是“做网站”而是“做信息漏斗”爬虫负责把全网公开数据收进来算法负责把噪音过滤掉可视化负责把过滤后的结果变成可决策的信息。ZJ在项目编号里代表浙江但整个架构换数据源就能迁到其他省份数据层和业务层是解耦的。1.2 系统核心能力拆解从功能上看这套系统可以拆成四个能力模块数据采集定时抓取目标网站的招标公告、中标结果、采购意向等公开页面提取结构化字段存库。企业画像给每家企业建立标签体系包括经营范围关键词、历史中标领域、资质信息、常用投标地区等。智能匹配新公告入库后算法自动计算公告和企业画像的相似度超过阈值的进入推荐列表再按相关度排序。可视化看板用ECharts展示项目趋势、地域分布、推荐命中情况用腾讯地图展示项目位置和企业位置关系。交互流程是爬虫每天增量采集 → 数据清洗入库 → 推荐引擎在晚间或者公告入库时实时计算 → 前端早上一打开就是当天的推荐结果同时能看到历史数据趋势。这套流程跑通以后使用者的日常工作从“盯平台”变成了“看推荐”效率提升非常明显。1.3 技术选型为什么要用 Vue Flask 而不是其他组合这套技术栈是我和客户反复讨论后定下来的。先说后端Flask的定位是“微框架”本身不带ORM、不带表单校验、不带后台管理但恰恰因为轻它在做一个中等体量的内部工具时非常舒服。招投标推荐系统不是电商平台不需要高并发的用户体系核心逻辑就是爬虫入库、算法计算、接口输出Flask SQLAlchemy这种“轻后端”完全够用。Django也不是不能用但Django的设计哲学是“全家桶”它的admin后台、迁移管理、auth体系对这个小项目来说属于“用不上但占学习成本”的东西。Flask的蓝图Blueprint机制足够把项目按模块拆分配合SQLAlchemy的模型定义代码结构可以非常干净。前端选Vue是考虑到这套系统要长期迭代Vue的单文件组件结构适合多人协作而且Vue生态里ECharts的封装非常成熟做数据可视化比React生态更顺手。再说了Vue的开发和调试门槛相对低客户的IT团队后续自己维护起来也更容易。2. 数据采集层爬虫架构与反爬规避2.1 数据源规划与采集字段设计招投标数据的重点是“准”而不是“多”。浙江本地的公共资源交易信息主要分布在省公共资源交易中心和各市区级平台有些平台有RSS有些只能网页访问。我第一版把数据源分成三类优先级从高到低结构良好型有固定列表页和详情页URL规律明显用Requests直接抓解析HTML就行。动态渲染型数据通过JavaScript异步加载直接Requests拿不到内容这种情况我准备了Playwright渲染抓取。文件公告型PDF或者Word附件里才是完整招标信息网页只给了标题和摘要这需要额外做附件下载和文本解析。字段设计上公告表我定义了十几个核心字段标题、文号、采购人、代理机构、预算金额、发布日期、报名截止时间、开标时间、项目地点、项目类型工程/货物/服务、行业关键词、原文链接、来源平台。这里有个容易忽略的点公告的唯一性不能靠标题判断。同一项目在不同阶段会发好几个公告比如“采购意向”“招标公告”“更正公告”“结果公告”标题可能一样但文号不同。我后来用“来源文号”组合作为唯一键才解决了重复入库的问题。2.2 爬虫框架选型Requests、Scrapy、Playwright 怎么配合第一版我直接用Requests写循环抓取代码写着很快但问题是一个请求出错就会中断整个任务。第二版我换成了Scrapy把每个数据源配成一个Spider中间件里统一处理User-Agent轮换、超时重试和限速这样每个源独立运行一个挂了不影响其他。Scrapy的Item Pipeline逻辑很适合做清洗——原始HTML进来Pipeline里做字段抽取、去重、格式化最后进SQLAlchemy模型。动态渲染型的页面我用了Playwright。这里提示一下不要一上来就上SeleniumSelenium太重了而且新版Selenium的API变化比较大。Playwright的API更现代自带自动等待还支持直接拦截响应有些接口数据其实藏在XHR请求里用Playwright拦截到JSON直接解析比渲染完整页面再抓要快得多。不过这类抓取频率必须降下来动态渲染型的网站一般都有更严格的风险控制策略我每个页面的请求间隔设置在8到15秒随机波动跑了几个月没有出过问题。2.3 合规采集与异常处理现在爬虫相关的合规要求越来越严格我在项目里专门加了一个“合规开关”采集前自动读取目标网站的robots.txt声明不允许抓的路径直接跳过默认只抓公开信息不碰需要登录才能看到的非公开数据采集频率控制在对方服务器的合理承受范围内。这套系统的定位是给企业做公开信息整合不是做损害平台的数据搬运这一点在项目交付文档里写得非常明确。异常处理方面我总结了三个高发问题对应解法如下表问题现象原因解决办法请求偶尔返回403频率过高触发策略请求间隔随机化备用请求代理池轮换列表页抓到了但详情页解析为空详情页结构变化用CSS选择器时预留多套备用规则解析失败自动切换增量爬虫漏数据页面分页规则不稳定每次采集后用标题发布日期的MD5做集合比对回补缺失项注意采集频率控制不是小事如果你在高峰期集中抓取哪怕目标网站没有明确禁止也会给服务器造成压力。我常用的经验是“慢就是快”——把总时长拉长一倍反而更稳定数据完整度更高。3. 数据持久化与清洗SQLAlchemy 存储实战3.1 SQLAlchemy ORM 表结构设计数据存什么、怎么存直接决定推荐算法能不能跑起来。我用了SQLAlchemy作为ORM数据库先用SQLite起步数据量超过几十万条后再无缝切换MySQL——ORM的好处就是换数据库只需要改连接串模型代码不用动。核心表我设计了四张announcements公告表、companies企业画像表、rec_rules推荐规则表、recommendations推荐结果缓存表。公告表存储采集到的原始信息企业画像表存储企业的基础信息和标签推荐规则表用来配置不同行业的权重推荐结果缓存表是算法跑完后生成的匹配记录前端查询直接读这张表不用实时跑算法。SQLAlchemy模型定义时我踩过一个坑JSON字段的使用。公告信息里有“行业关键词列表”这种结构用JSON存确实方便但SQLite对JSON的支持不如MySQL好查询起来麻烦。后来我采用了“标签用逗号分隔存字符串查询时用LIKE匹配”的方案。如果是在MySQL里可以直接用JSON_CONTAINS但是考虑到这套系统要在各种环境下本地部署兼容性比高级查询功能更重要。3.2 数据清洗流程从HTML到结构化记录爬虫抓回来的详情页HTML第一步是提取正文。我最开始用BeautifulSoup做正文抽取但不同网站的HTML结构差异很大选择器经常要改。后来换成了trafilatura这个库它对新闻公告类页面的正文抽取效果非常稳定自动过滤掉导航栏、页脚、相关推荐这些噪音返回干净的文本。虽然它主要面向文章正文但处理招投标公告这类“段落型文本”时表现也相当好。正文抽取完下一步是清洗去掉空白字符和特殊符号做简繁体统一——浙江的公告偶尔会夹带繁体字用opencc-python做转换再按标点切分成句子列表为后面的关键词提取做准备。清洗这一步的意义再怎么强调都不为过推荐算法的上限取决于数据质量而不是算法复杂度。你喂进去的是“弱电智能化改造、工程量清单、资质要求电子与智能化工程专业承包二级”和喂“本项目已具备招标条件现对该项目进行公开招标”算出来的推荐质量天差地别。3.3 无效信息过滤与编码陷阱无效信息过滤是个反复迭代的活。第一版我只做了简单的停用词过滤后来发现招投标公告里有大量的“公共噪音词”比如“招标编号”“受委托”“资金已落实”“有意向的投标人”。这些词本身有意义但在几乎所有公告里都出现对区分不同行业没有帮助。我维护了一个“业务停用词表”大概一百多个词在关键词提取前先做一轮过滤。编码问题是爬虫新手最容易忽略的很多政府网站的页面写的是UTF-8但部分老站是GBK或者页面meta声明和实际编码不一致。我用Requests的resp.apparent_encoding检测实际编码再统一转为UTF-8存库。这个细节看起来小但如果不处理数据库里会出现大量乱码最头疼的是乱码会直接影响中文分词和关键词提取——分词器看到一个错字都会影响整个句子的切分效果。4. 推荐算法设计与实现中文分词 TF-IDF 余弦相似度4.1 为什么不用简单标签匹配而用 TF-IDF 余弦相似度第一版我图省事直接做了关键词交集匹配公告标签和企业标签的重合词越多相关度越高。这个方案实现很简单但实际效果不好原因有三个一是标签列表太短交集经常为空召回率极低二是不同词的重要程度被一视同仁“施工”这个词在建筑类企业里几乎人人都有但“医疗洁净工程”才是真正区分专业方向的词三是公告不是标签集合它是一段文本强行抽取几个标签出来会丢失大量语义信息。所以第二版换成了业界用烂但非常稳妥的方案TF-IDF 向量化 余弦相似度。思路是把公告正文当作一袋词TF代表词在本文档里出现的频率IDF代表词在整个语料库中的稀缺程度。一个词在一篇公告里出现得多、但在所有公告里很少见它的TF-IDF权重就高说明这个词能代表这篇公告的特色。企业画像同理然后在同一个向量空间里计算两个向量的夹角余弦值。这背后的直觉是文本相似度不是看字面重合多少而是看在“词的分布”层面是否对齐。生活化一点说两个人都聊“手术室净化”比两个人都聊“项目”更相似因为前者是小众词后者是大众词。4.2 TF-IDF 向量化完整实现代码关键代码分几步先用jieba做分词再用jieba.analyse.extract_tags提取关键词权重最后用scikit-learn的TfidfVectorizer做向量化。这里是实际跑通的核心逻辑import jieba import jieba.analyse import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity # 加载停用词 stopwords set() with open(data/stopwords.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) def text_to_terms(text): 清洗 分词 去除停用词返回空格分隔的词序列 words jieba.lcut(text) filtered [w for w in words if w.strip() and w not in stopwords and len(w) 1] return .join(filtered) def build_vectors(corpus_texts): corpus_texts: 所有公告文本列表 企业画像文本追加到末尾 vectorizer TfidfVectorizer(token_patternr(?u)\b\w\b, lowercaseFalse) tfidf_matrix vectorizer.fit_transform(corpus_texts) return vectorizer, tfidf_matrix # 假设 announcements 是公告对象列表companies 是企业对象列表 ann_texts [text_to_terms(a.cleaned_content) for a in announcements] comp_texts [text_to_terms(c.profile_text) for c in companies] # 合并建向量空间保证公告和企业画像在同一空间可比 all_texts ann_texts comp_texts vectorizer, matrix build_vectors(all_texts) # 矩阵前 N 行是公告后面的行是企业 ann_matrix matrix[:len(ann_texts)] comp_matrix matrix[len(ann_texts):] # 计算每个企业和每条公告的相似度矩阵 for i, company in enumerate(companies): sims cosine_similarity(comp_matrix[i:i1], ann_matrix)[0] top_indices sims.argsort()[-5:][::-1] # 输出 top5 推荐公告我跑真实数据时公告正文有两三千字企业画像也有几百字计算相似度矩阵是稠密矩阵操作数据量在几千条时毫秒级出结果完全不需要上ES或者向量数据库。如果你面对的是十万级数据那就要考虑把计算放到离线任务里或者用Faiss做近邻检索但F053这个场景用暴力余弦就够了。4.3 从“相似度”到“推荐分数”加权规则让算法落地纯相似度算出来的结果还有一个问题它只考虑文本相似度没有考虑业务约束。比如一家注册在宁波的企业和一家注册在杭州的企业文本画像可能高度相似但宁波企业根本不去杭州投标。所以最终推荐分数我用了加权组合推荐分数 0.6 * 文本相似度 0.2 * 地域匹配分 0.1 * 时间衰减分 0.1 * 规模匹配分
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。