清理已删除的测试文件,准备云端部署

This commit is contained in:
ztb-system
2026-02-25 18:17:00 +08:00
parent 5f93dbe5e4
commit 305f6b342c
29 changed files with 143 additions and 2536 deletions

View File

@@ -1,87 +0,0 @@
# -*- coding: utf-8 -*-
"""
分析投标人须知前附表的内容格式,以便优化提示词
"""
import logging
from processors.content_fetcher import ContentFetcher
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 测试网址
TEST_URL = "https://ggzy.zj.gov.cn/jyxxgk/002001/002001011/20260212/9a7966d8-80f4-475b-897e-f7631bc64d0c.html"
def main():
"""主函数"""
logger.info(f"开始分析: {TEST_URL}")
# 获取内容
fetcher = ContentFetcher(temp_dir="temp_files")
content = fetcher.get_full_content(TEST_URL)
if not content:
logger.error("无法获取网页内容")
return
# 查找投标人须知前附表
if "投标人须知前附表" in content:
logger.info("找到投标人须知前附表")
# 提取前附表内容
start_idx = content.find("投标人须知前附表")
# 查找前附表结束位置(通常是下一个主要章节)
end_markers = ["1. 总则", "投标人须知", "第一章", "第二章"]
end_idx = len(content)
for marker in end_markers:
marker_idx = content.find(marker, start_idx + 100)
if marker_idx > start_idx:
end_idx = min(end_idx, marker_idx)
preamble_content = content[start_idx:end_idx]
logger.info(f"前附表内容长度: {len(preamble_content)} 字符")
# 保存前附表内容到文件
with open("preamble_content.txt", "w", encoding="utf-8") as f:
f.write(preamble_content)
logger.info("前附表内容已保存到 preamble_content.txt")
# 分析前附表中的资质要求和业绩要求
logger.info("\n分析前附表中的关键信息:")
# 查找资质要求
if "资质要求" in preamble_content:
logger.info("前附表中包含资质要求")
# 提取资质要求上下文
qual_start = preamble_content.find("资质要求")
qual_end = preamble_content.find("\n", qual_start + 10)
if qual_end > qual_start:
logger.info(f"资质要求上下文: {preamble_content[qual_start:qual_end]}")
else:
logger.warning("前附表中未找到资质要求")
# 查找业绩要求
if "业绩要求" in preamble_content:
logger.info("前附表中包含业绩要求")
# 提取业绩要求上下文
perf_start = preamble_content.find("业绩要求")
perf_end = preamble_content.find("\n", perf_start + 10)
if perf_end > perf_start:
logger.info(f"业绩要求上下文: {preamble_content[perf_start:perf_end]}")
else:
logger.warning("前附表中未找到业绩要求")
# 查找其他可能的关键词
keywords = ["资格要求", "企业资质", "施工总承包", "类似工程业绩"]
for keyword in keywords:
if keyword in preamble_content:
logger.info(f"前附表中包含: {keyword}")
else:
logger.warning("未找到投标人须知前附表")
if __name__ == "__main__":
main()

View File

@@ -2,6 +2,7 @@
""" """
爬虫配置文件 爬虫配置文件
""" """
import os
# 浙江省公共资源交易中心 # 浙江省公共资源交易中心
ZHEJIANG_CONFIG = { ZHEJIANG_CONFIG = {
@@ -80,7 +81,7 @@ DATA_DIR = "data"
# ============ DeepSeek AI 处理配置 ============ # ============ DeepSeek AI 处理配置 ============
DEEPSEEK_API_KEY = "sk-7b7211ee80b84000beebfa74a599ba13" DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
PROCESSING_CONFIG = { PROCESSING_CONFIG = {
"temp_dir": "temp_files", # 临时文件目录 "temp_dir": "temp_files", # 临时文件目录
@@ -121,7 +122,7 @@ REGION_CONFIGS = {
"taizhou:招标计划公示": { "taizhou:招标计划公示": {
"region_name": "台州招标计划", "region_name": "台州招标计划",
"link_field": "公告链接", "link_field": "公告链接",
"ai_fields": ["批准文号", "类型", "地区", "招标时间"], "ai_fields": ["批准文号", "类型", "地区", "招标时间", "预估金额"],
}, },
} }
@@ -381,7 +382,7 @@ DEEPSEEK_PROMPTS = {
# ============ 简道云配置 ============ # ============ 简道云配置 ============
JDY_CONFIG = { JDY_CONFIG = {
"api_key": "JmxuXmkew33mvQttRD3ftSfQoOEX6R9J", "api_key": os.environ.get("JDY_API_KEY", ""),
"forms": { "forms": {
"台州招标计划": { "台州招标计划": {
@@ -392,6 +393,8 @@ JDY_CONFIG = {
"批准文号": "_widget_1768289120166", "批准文号": "_widget_1768289120166",
"名称": "_widget_1768289120167", "名称": "_widget_1768289120167",
"类型": "_widget_1768289120168", "类型": "_widget_1768289120168",
"招标时间": "_widget_1768289120169",
"预估金额": "_widget_1768289120170",
"公告链接": "_widget_1768349415371", "公告链接": "_widget_1768349415371",
"招标阶段": "_widget_1768289432065", "招标阶段": "_widget_1768289432065",
}, },

View File

@@ -1,418 +0,0 @@
# -*- coding: utf-8 -*-
"""
爬虫配置文件
"""
# 浙江省公共资源交易中心
ZHEJIANG_CONFIG = {
"name": "浙江省公共资源交易中心",
"base_url": "https://ggzy.zj.gov.cn",
"api_url": "https://ggzy.zj.gov.cn/inteligentsearch/rest/esinteligentsearch/getFullTextDataNew",
# 交易领域代码
"categories": {
"工程建设": "002001",
"政府采购": "002002",
"土地使用权": "002003",
"国有产权": "002004",
"矿业权": "002006",
"其他交易": "002007",
},
# 公告类型代码(工程建设)
"notice_types": {
"项目登记信息": "002001008",
"招标计划": "002001013",
"招标文件公示": "002001011",
"招标公告": "002001001",
"资格预审公告": "002001002",
"澄清修改": "002001006",
"资格预审结果": "002001007",
"开标结果公示": "002001003",
"中标候选人公示": "002001004",
"中标结果公告": "002001005",
"合同信息公开": "002001009",
}
}
# 台州公共资源交易中心
TAIZHOU_CONFIG = {
"name": "台州公共资源交易中心",
"base_url": "https://ggzy.tzztb.zjtz.gov.cn",
"api_url": "https://ggzy.tzztb.zjtz.gov.cn/rest/secaction/getSecInfoListYzm",
"site_guid": "7eb5f7f1-9041-43ad-8e13-8fcb82ea831a",
# 交易领域
"categories": {
"工程建设": "002001",
"政府采购": "002002",
"产权交易": "002003",
"拓展资源": "002004",
"土地交易": "002005",
},
# 公告类型(工程建设)
"notice_types": {
"招标计划公示": "002001014",
"招标公告": "002001002",
"资格预审公告": "002001003",
"中标候选人公示": "002001005",
"中标结果公告": "002001006",
"保证金退还公示": "002001008",
}
}
# 爬虫设置(云端部署安全参数)
SPIDER_CONFIG = {
"page_size": 20, # 每页数量
"max_pages": 10, # 最大爬取页数
# --- 延迟控制 ---
"delay_min": 3, # 列表页最小延迟(秒)
"delay_max": 6, # 列表页最大延迟(秒)
"detail_delay_min": 2, # 详情页最小延迟(秒)
"detail_delay_max": 5, # 详情页最大延迟(秒)
# --- 安全阈值 ---
"timeout": 30, # HTTP 超时时间(秒)
"max_retries": 3, # 单次请求最大重试次数
"max_consecutive_errors": 5, # 连续失败熔断阈值(降低=更早停止)
"max_total_requests": 300, # 单次运行最大请求数
"requests_per_minute": 10, # 每分钟最大请求数(超出自动减速)
}
# 数据存储路径
DATA_DIR = "data"
# ============ DeepSeek AI 处理配置 ============
DEEPSEEK_API_KEY = "sk-7b7211ee80b84000beebfa74a599ba13"
PROCESSING_CONFIG = {
"temp_dir": "temp_files", # 临时文件目录
"output_dir": "data", # AI处理结果输出目录
"request_timeout": 90, # DeepSeek API 超时(秒)
"max_content_length": 120000, # 发送给 DeepSeek 的最大内容长度
"api_delay": 1, # API 调用间隔(秒)
}
# 区域配置:按「站点+公告类型」定义需要AI提取的字段
REGION_CONFIGS = {
# key 格式: "site:notice_type"
"zhejiang:招标文件公示": {
"region_name": "浙江招标文件公示",
"link_field": "招标文件链接", # 爬虫"链接"映射到的字段名
"ai_fields": [
"类型", "地区", "投标截止日", "最高投标限价", "最高限价",
"资质要求", "业绩要求", "评标办法", "评分说明与资信评分标准",
"有无答辩", "招标人", "项目概况", "造价付款方式", "批准文号",
],
},
"zhejiang:招标公告": {
"region_name": "浙江招标公告",
"link_field": "公告链接",
"ai_fields": ["批准文号", "投标截止日", "招标估算金额"],
},
"zhejiang:澄清修改": {
"region_name": "浙江澄清修改",
"link_field": "澄清文件链接",
"ai_fields": ["批准文号"],
},
}
# DeepSeek 提示词模板
DEEPSEEK_PROMPTS = {
"批准文号": """请从招标公告中提取项目批准文号。
批准文号的常见格式:
- 台建招备[2026]XXX号
- 浙建计[2026]XXX号
- 2302-XXXXXX-XX-XX-XXXXXX项目代码格式
查找关键词:批准文号、备案登记号、项目代码、项目编号、招标编号
请直接返回批准文号,不要其他解释。
如果未找到,请返回"文档未提及"""",
"资质要求": """从招标文件中提取企业资质等级。
搜索策略:
1. 直接查找:资质要求、资质条件、资质等级、施工总承包、专业承包
2. 查找章节:投标人须知前附表、招标公告、资格审查条件
3. 特别注意必须检查PDF附件中的内容附件中通常包含详细的资质要求
4. 如果写"见投标人须知前附表"或类似引用,请必须查找并提取前附表中的具体资质要求
5. 如果前附表写"见招标公告",请在招标公告章节查找
重要:只返回资质类型和等级,不要任何其他内容!
正确格式示例:
建筑工程施工总承包三级及以上
市政公用工程施工总承包二级及以上
返回规则:
- 找到具体资质等级 → 返回资质等级
- 文档写"见招标公告"但招标公告在平台上 → 返回"详见招标公告"
- 确实未找到任何相关信息 → 返回"文档未提及""",
"业绩要求": """请从招标文件中提取投标人业绩要求。
搜索策略:
1. 重点查找:投标人资格要求、业绩要求、投标人须知前附表、招标公告、评分标准
2. 特别注意必须检查PDF附件中的内容附件中通常包含详细的业绩要求
3. 关注关键词:业绩、工程经验、类似项目、同类工程、中标业绩、类似工程业绩
4. 注意时间范围要求近X年、自20XX年以来
5. 特别注意:如果文档中提到"见投标人须知前附表"或类似引用,请查找并提取前附表中的业绩要求
6. 如果业绩要求在评分标准中,请从评分标准中提取
必须提取的内容:
- 业绩的时间范围要求
- 业绩的具体要求(工程类型、规模、金额等)
- 业绩数量要求
- 项目负责人业绩要求(如有)
返回规则:
- 找到具体业绩要求 → 返回业绩要求内容
- 文档写"见招标公告"但招标公告在平台上 → 返回"详见招标公告"
- 确实未找到 → 返回"文档未提及""",
"评标办法": """请分析招标文件,判断采用的评标办法。
■ 必须检查以下内容:
1. 投标人须知前附表中的勾选项(☑/□)
2. "第三章 评标办法"的章节标题和具体内容
3. 附件中的评标定标章节(如"评标定标办法""评标细则"等)
4. 其他相关章节中关于评标方法的描述
■ 分析要点:
- 仔细阅读附件中评标定标章节的详细内容
- 关注评标方法的具体定义和操作流程
- 确认是否采用评定分离方式
- 区分综合评估法、经评审的最低投标价法等不同类型
■ 输出规则(评定分离优先):
- 如果文档中出现"☑采用评定分离""评定分离方式招标"或附件中明确说明采用评定分离→ 返回"评定分离"
- 综合评估法(含所有子类型)、资信商务评估法、合理低价法 → 返回"综合评估法"
- 经评审的最低投标价法 → 返回"经评审的最低投标价法"
■ 只能返回以下值之一:
1. 评定分离
2. 综合评估法
3. 经评审的最低投标价法
4. 文档未提及
只返回上述值之一,不要任何其他文字。""",
"评分说明与资信评分标准": """请从招标文件中提取评分说明和资信评分标准。
■ 核心原则:
- 只提取文档中明确存在的具体评分规则,严禁推测或编造
- 如果只有章节标题但没有具体评分细则,必须返回"文档未提及"
■ 搜索策略:
1. 全面查找:评分说明、评分标准、评分办法、评标细则、评标办法、评审办法
2. 关注章节:第三章 评标办法、评标定标办法、评分标准、商务标评审、技术标评审、资信标评审
3. 关键词扩展:评分、基准价、商务标、技术标、资信标、分值、得分、权重、评分办法、评标基准价、报价得分
4. 特别关注:信用评价、信用等级、信用分、信誉分、诚信分、企业信用、项目负责人信用
■ 必须提取的内容:
1. 总体评分结构(各部分分值分配)
- 必须在最前面总结总分结构:总分 =资信标 X分+技术标 X分+商务标 X分
- 确保分值总和为100分
2. 基准价计算方法
3. 信用分详细细则(包括企业和项目负责人):
- 信用等级划分标准如A/B/C/D/E级对应的具体分数范围如110分以上(含110分)、105-110分(含105分)等)
- 各等级对应的具体得分如A级3分、B级2.5分等)
- 未取得信用评价的得分
- 特别关注项目负责人信用评价分的等级和分数要求
- 必须提取完整的分数范围和对应分数A类110-120分2.8分、120-130分2.85分)
- 必须提取完整的等级划分A级:110分以上(含110分)、B级:105-110分(含105分)、C级:100-105分(含100分)、D级:90-100分(含90分)、E级:90分以下
- 必须提取附件中的信用等级划分标准,如《台州市住房和城乡建设局关于公布建筑工程和市政公用工程企业信用等级划分标准的通知》中的等级划分
- 必须提取具体的分数阈值如110分以上含110分、105-110分含105分
■ 示例输出:
总分=资信标15分+技术标65分+商务标20分评标基准价=最高限价×K值(K=80%-95%)商务标20分(报价得分采用线性插值法)技术标65分(打分制)资信标15分其中企业信用分A级110分以上3分B级105-110分2.5分C级100-105分2分D级90-100分1.5分E级90分以下1分未取得0.5分项目负责人信用分A级110分以上2分B级105-110分1.5分C级100-105分1分D级90-100分0.5分E级90分以下0.3分未取得0.1分)。
■ 信用分提取示例:
投标人信用评价分A类110-120分2.8分、120-130分2.85分、130-140分2.9分、140-150分3分B类105-106分2.55分、106-107分2.6分、107-108分2.65分、108-109分2.7分、109-110分2.75分C类2.3分D类1.8分E类1.3分未取得0.8分。
■ 返回规则:
- 找到具体评分规则 → 用简洁语言总结,信用分部分需详细列出
- 文档中只有章节目录,无具体内容 → 返回"文档未提及"
- 无法确定 → 返回"文档未提及"(严禁编造)""",
"有无答辩": """请判断招标文件中是否要求"现场答辩""现场面试"
关键词:答辩、面试、现场汇报、演示
如果明确要求答辩/面试,请返回""
如果明确说明不需要,请返回""
如果未提及,请返回""""",
"项目概况": """请从招标文件中提取项目概况信息。
查找章节:项目概况、工程概况、建设规模、招标范围
必须提取:
1. 建设地点
2. 建设规模(长度×宽度、面积、层数等)
3. 招标范围
4. 计划工期
5. 质量要求
请按以下格式输出:
建设地点XX建设规模XX招标范围XX计划工期≤XX日历天质量要求XX
如果未找到,请返回"文档未提及"""",
"类型": """请根据项目信息判断项目类型。
只返回以下类型之一:
施工类(需细分):总承包、市政、安装、装饰、公路、水利、电力
其他类型:勘察、设计、监理、采购、咨询、其他
判断规则:
1. 名称含"设计"→设计,"监理"→监理,"勘察/测量"→勘察,"EPC/总承包"→总承包
2. 施工类细分:道路/桥梁/排水/管网→市政,公路/国道→公路,装修/幕墙→装饰,机电/电气→安装,房屋/学校/医院→总承包,水利/河道/水库→水利,电力/变电/输电→电力
只返回类型名称,不要其他解释。""",
"地区": """请从招标文件中提取项目所在地区。
搜索策略(按优先级):
1. 直接查找:工程地点、建设地点、项目位置
2. 从招标人名称提取
3. 从信息来源提取
4. 从项目名称提取
输出格式:市+区/县,如"金华市金东区""台州市椒江区"
如果只能确定市级,返回市名。
如果确实无法提取,请返回"文档未提及"""",
"最高限价": """请从招标文件中提取价格信息,必须返回具体数字金额。
按优先级查找:
1. 最高投标限价
2. 招标控制价
3. 最高限价、上限价
4. 拨款控制价、控制价
5. 合同估算价、预算金额
请直接返回金额,带上单位(万元或元)。
示例1234.56万元、2466285元
如果未提及任何价格信息,请返回"文档未提及"""",
"最高投标限价": """请从招标文件中提取最高投标限价(或招标控制价)。
查找关键词:最高投标限价、招标控制价、最高限价、上限价、控制价、包干总价
请直接返回金额,带上单位(万元或元)。
示例1234.56万元、2466285元
如果未提及,请返回"文档未提及"""",
"预估金额": """请从文档中提取项目预估金额。
查找关键词:预估金额、预计投资、估算金额、预算金额、项目总投资
请直接返回金额,带上单位(万元或元)。
示例1234.56万元、2466285元
如果未提及,请返回"文档未提及"""",
"投标截止日": """请从招标文件中提取投标截止时间。
搜索关键词:投标截止时间、投标截止日、截止时间、开标时间、递交截止时间
重要规则:
1. 绝对禁止推测或编造日期
2. 如实提取文档中的原始日期
3. 日期完整则返回标准格式 YYYY-MM-DD
4. 日期不完整则返回原始格式
如果未提及,请返回"文档未提及"""",
"招标人": """请从招标文件中提取招标人信息。
查找关键词:招标人、招标单位、业主单位、建设单位
请直接返回招标人名称,不要其他解释。
如果未提及,请返回"文档未提及"""",
"造价付款方式": """请从招标文件的合同条款中提取付款方式信息。
查找章节:合同条款、通用条款、专用条款、付款方式、工程款支付
必须提取以下四项(只提取百分比数字):
1. 预付款比例
2. 进度款支付比例
3. 结算款比例
4. 质保金比例
输出格式预付款XX%进度款XX%结算款XX%质保金XX%
如果某项未提及用""代替。
如果确实未找到付款相关内容,请返回"文档未提及"""",
"招标估算金额": """请从招标公告中提取项目的估算金额。
查找关键词:估算金额、预计投资、预算金额、项目总投资、招标估算价、投资估算
请直接返回金额,带上单位(万元或元)。
示例1234.56万元、2466285元
如果未提及,请返回"文档未提及"""",
}
# ============ 简道云配置 ============
JDY_CONFIG = {
"api_key": "JmxuXmkew33mvQttRD3ftSfQoOEX6R9J",
"forms": {
"浙江招标文件公示": {
"app_id": "6965f35749afd00072b33c4a",
"entry_id": "6965f50e955c9b638888e7d2",
"field_mapping": {
"发布时间": "_widget_1768289557651",
"批准文号": "_widget_1768289557665",
"名称": "_widget_1768349686082",
"类型": "_widget_1768289557652",
"地区": "_widget_1768289557653",
"投标截止日": "_widget_1768289557654",
"最高投标限价": "_widget_1768289557655",
"最高限价": "_widget_1768289557655",
"资质要求": "_widget_1768289557656",
"业绩要求": "_widget_1768289557657",
"评标办法": "_widget_1768289557658",
"评分说明与资信评分标准": "_widget_1768289557659",
"有无答辩": "_widget_1768289557660",
"招标人": "_widget_1768289557661",
"项目概况": "_widget_1768289557663",
"造价付款方式": "_widget_1768289557664",
"招标文件链接": "_widget_1768290058232",
"招标阶段": "_widget_1768289909408",
},
},
"浙江招标公告": {
"app_id": "6965f35749afd00072b33c4a",
"entry_id": "69703283d126285ded9ac1be",
"field_mapping": {
"发布时间": "_widget_1768289557651",
"批准文号": "_widget_1768289557665",
"名称": "_widget_1768349686082",
"投标截止日": "_widget_1768289557654",
"招标估算金额": "_widget_1771910059524",
"公告链接": "_widget_1768290058232",
"招标阶段": "_widget_1768289909408",
},
},
"浙江澄清修改": {
"app_id": "6965f35749afd00072b33c4a",
"entry_id": "697085af8e631aae04bb856c",
"field_mapping": {
"发布时间": "_widget_1768289557651",
"批准文号": "_widget_1768289557665",
"名称": "_widget_1768349686082",
"澄清文件链接": "_widget_1768290058232",
"招标阶段": "_widget_1768289909408",
},
},
},
}

View File

@@ -1,68 +0,0 @@
import csv
import re
# 读取CSV文件
with open('data/浙江省公共资源交易中心_20260213_172414.csv', 'r', encoding='utf-8') as file:
reader = csv.reader(file)
headers = next(reader) # 读取表头
rows = list(reader)[:20] # 读取前20条数据
# 打印表头
print('\n原始表头:')
for i, header in enumerate(headers):
print(f'{i+1}. {header}')
# 分析前20条数据
print('\n前20条数据分析:')
print('-' * 100)
print(f'| {"序号":<4} | {"标题":<80} | {"项目批准文号":<30} | {"项目名称":<80} |')
print('-' * 100)
for i, row in enumerate(rows):
title = row[0]
project_id = row[6]
project_name = row[7]
# 从标题中提取批准文号(如果有的话)
id_match = re.search(r'\[(.*?)\]$', title)
extracted_id = id_match.group(1) if id_match else ''
# 从标题中提取纯项目名称
extracted_name = re.sub(r'\[(.*?)\]$', '', title).strip()
# 验证项目批准文号是否一致
id_match_flag = project_id == extracted_id
# 验证项目名称是否正确
name_match_flag = project_name == extracted_name
print(f'| {i+1:<4} | {title} | {project_id} | {project_name} |')
# 如果有不一致,打印详细信息
if not id_match_flag:
print(f' 警告: 项目批准文号不一致 - 标题中提取: {extracted_id}, 列中值: {project_id}')
if not name_match_flag:
print(f' 警告: 项目名称不一致 - 标题中提取: {extracted_name}, 列中值: {project_name}')
print('-' * 100)
# 检查是否所有项目名称都不包含批准文号
print('\n项目名称列检查:')
print('-' * 100)
print(f'| {"序号":<4} | {"项目名称":<80} | {"是否包含批准文号":<15} |')
print('-' * 100)
for i, row in enumerate(rows):
project_name = row[7]
has_id = bool(re.search(r'\[.*?\]$', project_name))
print(f'| {i+1:<4} | {project_name} | {"" if has_id else "":<15} |')
print('-' * 100)
# 总结
print('\n总结:')
print('1. 从CSV文件中可以看到项目批准文号和项目名称已经正确分离到不同列中')
print('2. 标题列包含完整信息:项目名称[项目批准文号]')
print('3. 项目批准文号列第7列只包含批准文号')
print('4. 项目名称列第8列只包含纯项目名称不包含批准文号')
print('5. 前3条数据的项目名称和项目批准文号分离正确')

View File

@@ -1,4 +1,5 @@
requests>=2.31.0 requests>=2.31.0
beautifulsoup4>=4.12.0 beautifulsoup4>=4.12.0
lxml>=4.9.0
pdfplumber>=0.10.0 pdfplumber>=0.10.0
python-docx>=1.0.0 python-docx>=1.0.0

View File

@@ -12,11 +12,12 @@ r"""
3. Linux cron每天早上 8:00: 3. Linux cron每天早上 8:00:
0 8 * * * cd /path/to/ztb && python scheduler.py >> logs/cron.log 2>&1 0 8 * * * cd /path/to/ztb && python scheduler.py >> logs/cron.log 2>&1
""" """
import glob
import logging import logging
import sys import sys
import os import os
import traceback import traceback
from datetime import datetime from datetime import datetime, timedelta
# 确保项目根目录在 sys.path 中 # 确保项目根目录在 sys.path 中
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
@@ -59,18 +60,40 @@ DAILY_TASKS = [
"process": True, "process": True,
"upload": True, "upload": True,
}, },
# 台州 - 工程建设 - 招标文件公示 # 台州 - 工程建设 - 招标计划公示
{ {
"site": "taizhou", "site": "taizhou",
"max_pages": 100, "max_pages": 100,
"category": "工程建设", "category": "工程建设",
"notice_type": "招标文件公示", "notice_type": "招标计划公示",
"process": True, "process": True,
"upload": True, "upload": True,
}, },
] ]
# 数据文件保留天数
KEEP_DAYS = 30
def cleanup_old_files(directory: str, keep_days: int = KEEP_DAYS):
"""清理超过 keep_days 天的 CSV 和 JSON 文件"""
if not os.path.isdir(directory):
return
cutoff = datetime.now() - timedelta(days=keep_days)
removed = 0
for pattern in ("*.csv", "*.json"):
for filepath in glob.glob(os.path.join(directory, pattern)):
if os.path.getmtime(filepath) < cutoff.timestamp():
try:
os.remove(filepath)
removed += 1
except OSError:
pass
if removed:
logger.info(f"清理 {directory}{removed} 个超过 {keep_days} 天的文件")
def run_task(task: dict, date_filter: str = "yesterday") -> int: def run_task(task: dict, date_filter: str = "yesterday") -> int:
"""执行单个爬取任务,返回采集条数""" """执行单个爬取任务,返回采集条数"""
site = task["site"] site = task["site"]
@@ -137,6 +160,9 @@ def run_daily():
logger.debug(traceback.format_exc()) logger.debug(traceback.format_exc())
errors.append(desc) errors.append(desc)
# 清理过期数据文件
cleanup_old_files(DATA_DIR)
elapsed = (datetime.now() - start).total_seconds() elapsed = (datetime.now() - start).total_seconds()
logger.info("=" * 40) logger.info("=" * 40)
logger.info(f"定时任务完成: 共 {total} 条, 耗时 {elapsed:.0f}s") logger.info(f"定时任务完成: 共 {total} 条, 耗时 {elapsed:.0f}s")

View File

@@ -6,6 +6,7 @@ import csv
import logging import logging
import os import os
import random import random
import re
import signal import signal
import sys import sys
import time import time
@@ -181,6 +182,37 @@ class BaseSpider(ABC):
logger.info(f"[统计] 总请求: {self._total_requests}, " logger.info(f"[统计] 总请求: {self._total_requests}, "
f"耗时: {elapsed:.0f}s, 速率: {rpm:.1f}次/分钟") f"耗时: {elapsed:.0f}s, 速率: {rpm:.1f}次/分钟")
# ---------- 标题解析(统一规则) ----------
@staticmethod
def _parse_title(title: str) -> dict:
"""从标题中提取项目名称和批准文号(统一规则)"""
result = {}
# 统一正则:前缀可选,贪婪匹配项目名称,提取尾部批准文号
title_pattern = r"(?:\[(?:招标文件|招标公告)\])?\s*(.*)\s*\[([A-Z0-9]+)\]\s*$"
match = re.search(title_pattern, title)
if match:
project_name = match.group(1).strip()
result["项目批准文号"] = match.group(2).strip()
else:
project_name = title
# 尝试从标题尾部提取批准文号
number_pattern = r"\[([A-Z0-9]+)\]\s*$"
match = re.search(number_pattern, project_name)
if match:
result["项目批准文号"] = match.group(1).strip()
project_name = project_name[:match.start()].strip()
# 清理项目名称后缀
suffixes = ["招标文件公示", "招标文件预公示", "招标公告", "招标预公告"]
for suffix in suffixes:
if project_name.endswith(suffix):
project_name = project_name[:-len(suffix)].strip()
result["项目名称"] = project_name
return result
# ---------- 去重 ---------- # ---------- 去重 ----------
def is_duplicate(self, url: str) -> bool: def is_duplicate(self, url: str) -> bool:

View File

@@ -94,23 +94,8 @@ class TaizhouSpider(BaseSpider):
"来源": self.config["name"], "来源": self.config["name"],
} }
# 解析特定格式的标题:[招标文件]项目名称[批准文号] # 解析标题:提取项目名称批准文号(统一规则)
title_pattern = r"(?:\[招标文件\])?\s*(.*)\s*\[([A-Z0-9]+)\]\s*$" item.update(self._parse_title(title))
match = re.search(title_pattern, title)
if match:
item["项目名称"] = match.group(1).strip()
item["项目批准文号"] = match.group(2).strip()
else:
# 如果正则匹配失败,直接使用标题作为项目名称
project_name = title
# 尝试从标题中提取批准文号
number_pattern = r"\[([A-Z0-9]+)\]\s*$"
match = re.search(number_pattern, project_name)
if match:
item["项目批准文号"] = match.group(1).strip()
# 从项目名称中删除批准文号部分
project_name = project_name[:match.start()].strip()
item["项目名称"] = project_name
if title and href: if title and href:
items.append(item) items.append(item)
@@ -133,23 +118,8 @@ class TaizhouSpider(BaseSpider):
"来源": self.config["name"], "来源": self.config["name"],
} }
# 解析特定格式的标题:[招标文件]项目名称[批准文号] # 解析标题:提取项目名称批准文号(统一规则)
title_pattern = r"(?:\[招标文件\])?\s*(.*)\s*\[([A-Z0-9]+)\]\s*$" item.update(self._parse_title(title))
match = re.search(title_pattern, title)
if match:
item["项目名称"] = match.group(1).strip()
item["项目批准文号"] = match.group(2).strip()
else:
# 如果正则匹配失败,直接使用标题作为项目名称
project_name = title
# 尝试从标题中提取批准文号
number_pattern = r"\[([A-Z0-9]+)\]\s*$"
match = re.search(number_pattern, project_name)
if match:
item["项目批准文号"] = match.group(1).strip()
# 从项目名称中删除批准文号部分
project_name = project_name[:match.start()].strip()
item["项目名称"] = project_name
items.append(item) items.append(item)
return items return items

View File

@@ -7,6 +7,7 @@ import logging
import os import os
import re import re
from datetime import datetime, timedelta from datetime import datetime, timedelta
from bs4 import BeautifulSoup
from .base import BaseSpider from .base import BaseSpider
from utils.attachment import AttachmentHandler from utils.attachment import AttachmentHandler
@@ -95,8 +96,7 @@ class ZhejiangSpider(BaseSpider):
# ---------- 解析记录 ---------- # ---------- 解析记录 ----------
@staticmethod def _parse_record(self, record: dict, source: str) -> dict:
def _parse_record(record: dict, source: str) -> dict:
"""将 API 原始记录转换为结果字典""" """将 API 原始记录转换为结果字典"""
title = record.get("title", "").strip() title = record.get("title", "").strip()
link = record.get("linkurl", "") link = record.get("linkurl", "")
@@ -115,36 +115,8 @@ class ZhejiangSpider(BaseSpider):
"来源": source, "来源": source,
} }
# 解析特定格式的标题:[招标文件]项目名称[批准文号] # 解析标题:提取项目名称批准文号(统一规则)
import re item.update(self._parse_title(title))
# 改进的正则表达式,确保正确匹配标题格式
title_pattern = r"\[(?:招标文件|招标公告)\]\s*(.*?)\s*\[([A-Z0-9]+)\]\s*$"
match = re.search(title_pattern, title)
if match:
project_name = match.group(1).strip()
# 删除结尾的"招标文件公示"、"招标文件预公示"等后缀
suffixes = ["招标文件公示", "招标文件预公示", "招标公告", "招标预公告"]
for suffix in suffixes:
if project_name.endswith(suffix):
project_name = project_name[:-len(suffix)].strip()
item["项目名称"] = project_name
item["项目批准文号"] = match.group(2).strip()
else:
# 如果正则匹配失败,直接使用标题作为项目名称
project_name = title
# 删除结尾的"招标文件公示"、"招标文件预公示"等后缀
suffixes = ["招标文件公示", "招标文件预公示", "招标公告", "招标预公告"]
for suffix in suffixes:
if project_name.endswith(suffix):
project_name = project_name[:-len(suffix)].strip()
# 尝试从标题中提取批准文号
number_pattern = r"\[([A-Z0-9]+)\]\s*$"
match = re.search(number_pattern, project_name)
if match:
item["项目批准文号"] = match.group(1).strip()
# 从项目名称中删除批准文号部分
project_name = project_name[:match.start()].strip()
item["项目名称"] = project_name
return item return item
@@ -176,6 +148,65 @@ class ZhejiangSpider(BaseSpider):
return fields return fields
# ---------- 详情页补充 ----------
def parse_detail(self, url: str) -> dict:
"""访问详情页,提取项目名称和批准文号等结构化字段"""
resp = self.fetch(url)
if resp is None:
return {}
detail = {}
soup = BeautifulSoup(resp.text, "html.parser")
# 解析表格字段
field_map = {
"项目名称": "项目名称",
"项目批准文件及文号": "项目批准文号",
"项目批准文号": "项目批准文号",
"批准文号": "项目批准文号",
"建设单位(招标人)": "招标人",
"建设单位(招标人)": "招标人",
"招标人": "招标人",
"项目类型": "项目类型",
"招标方式": "招标方式",
"联系人": "联系人",
"联系方式": "联系方式",
}
for row in soup.select("table tr"):
cells = row.select("td")
if len(cells) >= 2:
key = cells[0].get_text(strip=True)
value = cells[1].get_text(strip=True)
if key in field_map and value:
detail[field_map[key]] = value
if len(cells) >= 4:
key2 = cells[2].get_text(strip=True)
value2 = cells[3].get_text(strip=True)
if key2 in field_map and value2:
detail[field_map[key2]] = value2
# 招标项目表(计划招标时间 / 预估合同金额)
for table in soup.select("table"):
headers = [th.get_text(strip=True) for th in table.select("th")]
if "计划招标时间" in headers:
data_rows = table.select("tbody tr") or [
r for r in table.select("tr") if r.select("td")
]
if data_rows:
cells = data_rows[0].select("td")
for i, h in enumerate(headers):
if i < len(cells):
val = cells[i].get_text(strip=True)
if h == "计划招标时间" and val:
detail["计划招标时间"] = val
elif "预估合同金额" in h and val:
detail["预估合同金额(万元)"] = val
break
return detail
# ---------- 附件 ---------- # ---------- 附件 ----------
def _extract_attachments_from_detail(self, url: str) -> list: def _extract_attachments_from_detail(self, url: str) -> list:
@@ -275,9 +306,16 @@ class ZhejiangSpider(BaseSpider):
detail = self._parse_content_fields(rec.get("content", "")) detail = self._parse_content_fields(rec.get("content", ""))
item.update(detail) item.update(detail)
# 详情页补充:提取项目名称和批准文号等
self.detail_delay()
page_detail = self.parse_detail(link)
# 详情页字段仅补充,不覆盖已有值
for k, v in page_detail.items():
if not item.get(k):
item[k] = v
# 附件 # 附件
if download_attachment and attachment_handler: if download_attachment and attachment_handler:
self.detail_delay()
atts = self._extract_attachments_from_detail(link) atts = self._extract_attachments_from_detail(link)
if atts: if atts:
item["附件数量"] = len(atts) item["附件数量"] = len(atts)

View File

@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
"""
测试附件下载和解析功能
"""
import logging
from processors.content_fetcher import ContentFetcher
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 测试网址
TEST_URL = "https://ggzy.zj.gov.cn/jyxxgk/002001/002001011/20260212/9a7966d8-80f4-475b-897e-f7631bc64d0c.html"
def main():
"""主函数"""
logger.info(f"开始测试附件处理: {TEST_URL}")
# 获取内容
fetcher = ContentFetcher(temp_dir="temp_files")
content = fetcher.get_full_content(TEST_URL)
if not content:
logger.error("无法获取内容")
return
logger.info(f"获取到总内容长度: {len(content)} 字符")
# 检查是否包含附件内容
if "=== 附件:" in content:
logger.info("内容中包含附件")
# 提取附件部分
attachment_parts = content.split("=== 附件:")
for i, part in enumerate(attachment_parts[1:], 1):
attachment_name = part.split("===")[0].strip()
attachment_content = part.split("===")[1].strip() if len(part.split("===")) > 1 else ""
logger.info(f"\n附件 {i}: {attachment_name}")
logger.info(f"附件内容长度: {len(attachment_content)} 字符")
# 检查附件中是否包含资质要求和业绩要求
if "资质要求" in attachment_content:
logger.info("✓ 附件中包含资质要求")
if "业绩要求" in attachment_content:
logger.info("✓ 附件中包含业绩要求")
if "投标人须知前附表" in attachment_content:
logger.info("✓ 附件中包含投标人须知前附表")
else:
logger.warning("内容中不包含附件")
# 保存完整内容到文件,以便分析
with open("full_content.txt", "w", encoding="utf-8") as f:
f.write(content)
logger.info("\n完整内容已保存到 full_content.txt")
if __name__ == "__main__":
main()

View File

@@ -1,88 +0,0 @@
# -*- coding: utf-8 -*-
"""
爬取浙江公共资源交易中心,选择三条进行测试
"""
import logging
import sys
import os
import random
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入配置和处理器
from config import ZHEJIANG_CONFIG, SPIDER_CONFIG, DATA_DIR
from spiders import ZhejiangSpider
from processors import ProcessingPipeline
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def main():
"""主函数"""
logger.info("开始爬取浙江公共资源交易中心")
# 1. 爬取数据
logger.info("1. 爬取数据:")
spider = ZhejiangSpider(ZHEJIANG_CONFIG, SPIDER_CONFIG, DATA_DIR)
# 爬取最新数据限制为10条
spider.crawl(
max_pages=2, # 爬取2页
category="工程建设",
notice_type="招标文件公示"
)
# 保存到CSV
spider.save_to_csv()
# 获取爬取结果
results = spider.results
logger.info(f"爬取完成,共获取 {len(results)} 条数据")
if len(results) == 0:
logger.error("爬取失败,无数据")
return
# 2. 随机选择3条数据
logger.info("\n2. 选择测试数据:")
if len(results) >= 3:
selected_results = random.sample(results, 3)
else:
selected_results = results
logger.info(f"随机选择了 {len(selected_results)} 条数据进行测试")
# 3. 处理数据
logger.info("\n3. 处理数据:")
pipeline = ProcessingPipeline()
processed = pipeline.process_results(
selected_results,
site="zhejiang",
notice_type="招标文件公示",
upload=False
)
# 4. 展示结果
logger.info("\n4. 测试结果:")
for i, record in enumerate(processed, 1):
logger.info(f"\n{'-'*60}")
logger.info(f"测试 {i}")
logger.info(f"{'-'*60}")
logger.info(f"项目名称: {record.get('项目名称', '文档未提及')}")
logger.info(f"项目批准文号: {record.get('项目批准文号', '文档未提及')}")
logger.info(f"批准文号: {record.get('批准文号', '文档未提及')}")
logger.info(f"类型: {record.get('类型', '文档未提及')}")
logger.info(f"地区: {record.get('地区', '文档未提及')}")
logger.info(f"最高投标限价: {record.get('最高投标限价', '文档未提及')}")
logger.info(f"最高限价: {record.get('最高限价', '文档未提及')}")
logger.info(f"评标办法: {record.get('评标办法', '文档未提及')}")
logger.info(f"链接: {record.get('招标文件链接', '')}")
if __name__ == "__main__":
main()

View File

@@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
"""
详细分析指定网址的提取问题
"""
import logging
from processors.content_fetcher import ContentFetcher
from processors.deepseek import DeepSeekProcessor
from config import REGION_CONFIGS
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 测试网址
TEST_URL = "https://ggzy.zj.gov.cn/jyxxgk/002001/002001011/20260212/9a7966d8-80f4-475b-897e-f7631bc64d0c.html"
def main():
"""主函数"""
logger.info(f"开始分析: {TEST_URL}")
# 1. 获取内容
fetcher = ContentFetcher(temp_dir="temp_files")
content = fetcher.get_full_content(TEST_URL)
if not content:
logger.error("无法获取网页内容")
return
logger.info(f"获取到内容长度: {len(content)} 字符")
# 2. 检查关键信息是否存在
keywords = ["资质要求", "业绩要求", "资格要求", "类似工程业绩"]
for keyword in keywords:
if keyword in content:
logger.info(f"✓ 包含关键词: {keyword}")
# 查找关键词上下文
start_idx = max(0, content.find(keyword) - 300)
end_idx = min(len(content), content.find(keyword) + 500)
context = content[start_idx:end_idx]
logger.info(f" 上下文: {context[:300]}...")
else:
logger.warning(f"✗ 不包含关键词: {keyword}")
# 3. 执行提取
processor = DeepSeekProcessor()
# 获取浙江招标文件公示的配置
config_key = "zhejiang:招标文件公示"
if config_key not in REGION_CONFIGS:
logger.error(f"未找到配置: {config_key}")
return
ai_fields = REGION_CONFIGS[config_key]["ai_fields"]
logger.info(f"需要提取的字段: {ai_fields}")
# 4. 执行提取
extracted = processor.extract_fields(content, ai_fields, "浙江")
# 5. 分析结果
logger.info("\n提取结果:")
for field, value in extracted.items():
logger.info(f" {field}: {value}")
# 特别关注资质要求和业绩要求
for field in ["资质要求", "业绩要求"]:
if field in extracted:
value = extracted[field]
logger.info(f"\n{field}提取结果: {value}")
if value == "文档未提及":
logger.warning(f"{field}未提取到,但内容中确实存在相关信息")
# 分析预处理内容
prepared_content = processor._prepare_content(content, ai_fields)
logger.info(f"预处理后内容长度: {len(prepared_content)} 字符")
if field in prepared_content:
logger.info(f"✓ 预处理后内容包含 {field}")
else:
logger.warning(f"✗ 预处理后内容不包含 {field}")
# 分析提示词
from config import DEEPSEEK_PROMPTS
if field in DEEPSEEK_PROMPTS:
logger.info(f"提示词: {DEEPSEEK_PROMPTS[field][:100]}...")
# 检查投标人须知前附表内容
if "投标人须知前附表" in prepared_content:
logger.info("✓ 预处理后内容包含 投标人须知前附表")
# 提取前附表内容
start_idx = prepared_content.find("投标人须知前附表")
end_idx = min(len(prepared_content), start_idx + 5000)
preamble_content = prepared_content[start_idx:end_idx]
logger.info(f"前附表内容片段: {preamble_content[:300]}...")
# 6. 尝试直接使用本地提取
logger.info("\n尝试本地提取:")
local_extracted = processor._local_extract(content, ai_fields)
for field, value in local_extracted.items():
logger.info(f" {field}: {value}")
if __name__ == "__main__":
main()

View File

@@ -1,70 +0,0 @@
# -*- coding: utf-8 -*-
"""
测试修复后的项目名称和批准文号提取逻辑
"""
import logging
import sys
import os
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def test_project_name_extraction_fix():
"""测试修复后的项目名称提取逻辑"""
logger.info("开始测试修复后的项目名称提取逻辑")
# 测试用例
test_titles = [
"湖堤生态修复工程[A3306010720060234001001]",
"[招标文件](测-试临海市房建施工0212-2招标文件公示[A3300000090000695005001]",
"[招标文件]通途路(大闸路-湖西路)拓宽改造工程(监理)项目招标文件预公示[A3302010220026373001001]",
"[招标文件]集成电路链主企业配套产业园南片B、H、FG地块及配套项目-B地块建设工程01地块施工招标文件公示[A3306021280001738001001]",
"[招标文件]临海市副中心城市片区基础设施更新改造工程—沿河路、前王路及镇政府停车场改造提升招标文件公示[A3300000090000689001001]",
"[招标文件]宁波市海曙绿道提升工程(施工)招标文件预公示[A3302030230026386001001]",
"[招标文件]嘉科微二号园一号楼改造提升工程设计采购施工总承包(EPC)招标文件公示[A3304010550007317001001]",
]
# 导入爬虫的解析函数
from spiders.zhejiang import ZhejiangSpider
for title in test_titles:
logger.info(f"\n测试标题: {title}")
# 模拟解析过程 - 注意_parse_record 函数的参数是 (record, source)
# record 应该是 API 返回的原始记录,包含 "title" 字段
api_record = {
"title": title,
"linkurl": "",
"webdate": "2026-02-13",
"infod": "",
"categoryname": "",
}
# 调用爬虫的解析函数
parsed_item = ZhejiangSpider._parse_record(api_record, "测试")
logger.info(f" 提取结果:")
logger.info(f" 项目名称: {parsed_item.get('项目名称', '未提取')}")
logger.info(f" 项目批准文号: {parsed_item.get('项目批准文号', '未提取')}")
# 验证批准文号是否从项目名称中删除
project_name = parsed_item.get('项目名称', '')
approval_number = parsed_item.get('项目批准文号', '')
if approval_number and approval_number in project_name:
logger.error(f" ❌ 错误: 批准文号 '{approval_number}' 仍在项目名称中")
else:
logger.info(f" ✅ 正确: 批准文号已从项目名称中删除")
def main():
"""主函数"""
test_project_name_extraction_fix()
if __name__ == "__main__":
main()

View File

@@ -1,66 +0,0 @@
# -*- coding: utf-8 -*-
"""
测试项目名称修复效果
"""
import json
import csv
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from processors.pipeline import ProcessingPipeline
def test_name_fix():
"""测试项目名称修复"""
# 1. 读取原始CSV数据
csv_file = "data/浙江省公共资源交易中心_20260224_132741.csv"
if not os.path.exists(csv_file):
print(f"原始数据文件不存在: {csv_file}")
return
print(f"读取原始数据: {csv_file}")
# 读取CSV文件转换为字典列表
with open(csv_file, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
raw_items = list(reader)
print(f"原始数据条数: {len(raw_items)}")
# 2. 创建处理管道实例(只需要字段映射部分)
pipeline = ProcessingPipeline()
# 3. 测试字段映射
print("\n=== 测试字段映射 ===")
test_results = []
for i, item in enumerate(raw_items[:5]): # 只测试前5条
print(f"\n--- 第 {i+1} 条 ---")
print(f"原始标题: {item.get('标题', '')[:60]}")
# 调用字段映射方法
record = pipeline._map_fields(item, "公告链接", "招标公告")
print(f"处理后名称: {record.get('名称', '')}")
print(f"项目名称: {record.get('项目名称', '')}")
test_results.append({
'原始标题': item.get('标题', ''),
'处理后名称': record.get('名称', ''),
'项目名称': record.get('项目名称', '')
})
# 4. 保存测试结果
output_file = "data/项目名称修复测试结果.json"
with open(output_file, 'w', encoding='utf-8') as f:
json.dump({
'测试时间': '2026-02-24',
'测试结果': test_results
}, f, ensure_ascii=False, indent=2)
print(f"\n测试结果已保存到: {output_file}")
if __name__ == "__main__":
test_name_fix()

View File

@@ -1,73 +0,0 @@
# -*- coding: utf-8 -*-
"""
使用原始config.py测试提取功能
"""
import logging
import sys
import os
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入原始配置
from config import REGION_CONFIGS
from processors.content_fetcher import ContentFetcher
from processors.deepseek import DeepSeekProcessor
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 测试网址
TEST_URL = "https://ggzy.zj.gov.cn/jyxxgk/002001/002001011/20260212/d2f95295-6cb0-40c9-8023-cdbbf7e660ae.html"
def main():
"""主函数"""
logger.info(f"开始测试: {TEST_URL}")
# 获取内容
fetcher = ContentFetcher(temp_dir="temp_files")
content = fetcher.get_full_content(TEST_URL)
if not content:
logger.error("无法获取内容")
return
logger.info(f"获取到内容长度: {len(content)} 字符")
# 执行提取
processor = DeepSeekProcessor()
# 获取浙江招标文件公示的配置
config_key = "zhejiang:招标文件公示"
if config_key not in REGION_CONFIGS:
logger.error(f"未找到配置: {config_key}")
return
ai_fields = REGION_CONFIGS[config_key]["ai_fields"]
logger.info(f"需要提取的字段: {ai_fields}")
# 执行提取
extracted = processor.extract_fields(content, ai_fields, "浙江")
# 分析结果
logger.info("\n提取结果:")
for field, value in extracted.items():
logger.info(f" {field}: {value}")
# 特别关注资质要求和业绩要求
for field in ["资质要求", "业绩要求"]:
if field in extracted:
value = extracted[field]
logger.info(f"\n{field}提取结果: {value}")
if value != "文档未提及":
logger.info(f"{field}提取成功!")
else:
logger.warning(f"{field}未提取到")
if __name__ == "__main__":
main()

View File

@@ -1,61 +0,0 @@
# -*- coding: utf-8 -*-
"""
测试项目名称提取逻辑
"""
import logging
import sys
import os
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def test_project_name_extraction():
"""测试项目名称提取逻辑"""
logger.info("开始测试项目名称提取逻辑")
# 测试用例
test_titles = [
"[招标文件](测-试临海市房建施工0212-2招标文件公示[A3300000090000695005001]",
"[招标文件]通途路(大闸路-湖西路)拓宽改造工程(监理)项目招标文件预公示[A3302010220026373001001]",
"[招标文件]集成电路链主企业配套产业园南片B、H、FG地块及配套项目-B地块建设工程01地块施工招标文件公示[A3306021280001738001001]",
"[招标文件]临海市副中心城市片区基础设施更新改造工程—沿河路、前王路及镇政府停车场改造提升招标文件公示[A3300000090000689001001]",
"[招标文件]宁波市海曙绿道提升工程(施工)招标文件预公示[A3302030230026386001001]",
"[招标文件]嘉科微二号园一号楼改造提升工程设计采购施工总承包(EPC)招标文件公示[A3304010550007317001001]",
]
# 导入正则表达式
import re
for title in test_titles:
logger.info(f"\n测试标题: {title}")
# 使用修改后的标题解析逻辑
title_pattern = r"\[(?:招标文件|招标公告)\]\s*(.*?)\s*\[([A-Z0-9]+)\]\s*$"
match = re.search(title_pattern, title)
if match:
project_name = match.group(1).strip()
# 删除结尾的"招标文件公示"、"招标文件预公示"等后缀
suffixes = ["招标文件公示", "招标文件预公示", "招标公告", "招标预公告"]
for suffix in suffixes:
if project_name.endswith(suffix):
project_name = project_name[:-len(suffix)].strip()
project_approval = match.group(2).strip()
logger.info(f" 提取结果:")
logger.info(f" 项目名称: {project_name}")
logger.info(f" 项目批准文号: {project_approval}")
else:
logger.warning(" 标题解析失败")
def main():
"""主函数"""
test_project_name_extraction()
if __name__ == "__main__":
main()

View File

@@ -1,86 +0,0 @@
# -*- coding: utf-8 -*-
"""
测试优化后的提示词
"""
import logging
import sys
import os
import requests
from bs4 import BeautifulSoup
import re
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入修复后的配置
from config_fixed import DEEPSEEK_PROMPTS
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 测试网址(选择一个可能包含资质和业绩要求的网址)
TEST_URL = "https://ggzy.zj.gov.cn/jyxxgk/002001/002001011/20260212/d2f95295-6cb0-40c9-8023-cdbbf7e660ae.html"
def get_content(url):
"""获取网页内容"""
try:
response = requests.get(url, timeout=30)
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, 'html.parser')
# 提取主要内容
content = []
# 查找标题
title = soup.find('h1')
if title:
content.append(title.get_text(strip=True))
# 查找正文内容
content_div = soup.find('div', class_='ewb-article')
if content_div:
for p in content_div.find_all('p'):
text = p.get_text(strip=True)
if text:
content.append(text)
# 查找附件
attachments = soup.find_all('a', href=re.compile(r'\.(pdf|doc|docx)$'))
if attachments:
content.append("\n附件:")
for attachment in attachments:
content.append(f"- {attachment.get_text(strip=True)}: {attachment['href']}")
return "\n".join(content)
except Exception as e:
logging.error(f"获取内容失败: {e}")
return None
def test_prompts():
"""测试优化后的提示词"""
logger.info(f"开始测试提示词优化: {TEST_URL}")
# 获取内容
content = get_content(TEST_URL)
if not content:
logger.error("无法获取内容")
return
logger.info(f"获取到内容长度: {len(content)} 字符")
# 测试关键字段的提示词
test_fields = ["资质要求", "业绩要求"]
for field in test_fields:
logger.info(f"\n=== 测试 {field} 提示词 ===")
if field in DEEPSEEK_PROMPTS:
prompt = DEEPSEEK_PROMPTS[field]
logger.info(f"提示词长度: {len(prompt)} 字符")
logger.info(f"提示词内容预览: {prompt[:500]}...")
# 检查内容中

View File

@@ -1,105 +0,0 @@
# -*- coding: utf-8 -*-
"""
测试发布时间提取功能
"""
import logging
import sys
import os
import csv
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入配置和处理器
from processors.content_fetcher import ContentFetcher
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 最新的CSV文件路径
CSV_FILE = "data/浙江省公共资源交易中心_20260213_161312.csv"
def read_csv_data(file_path):
"""读取CSV文件数据"""
data = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
return data
def test_publish_time_extraction():
"""测试发布时间提取功能"""
logger.info("开始测试发布时间提取功能")
# 1. 读取CSV数据
if not os.path.exists(CSV_FILE):
logger.error(f"CSV文件不存在: {CSV_FILE}")
return
data = read_csv_data(CSV_FILE)
logger.info(f"读取完成,共 {len(data)} 条数据")
if len(data) == 0:
logger.error("无数据可测试")
return
# 2. 选择前3条数据进行测试
test_data = data[:3]
logger.info(f"选择前 {len(test_data)} 条数据进行测试")
# 3. 测试发布时间提取
logger.info("\n开始测试发布时间提取:")
fetcher = ContentFetcher()
for i, item in enumerate(test_data, 1):
title = item.get("标题", "")
url = item.get("链接", "")
csv_publish_date = item.get("发布日期", "")
logger.info(f"\n{'-'*60}")
logger.info(f"测试 {i}")
logger.info(f"{'-'*60}")
logger.info(f"标题: {title}")
logger.info(f"URL: {url}")
logger.info(f"CSV发布日期: {csv_publish_date}")
if not url:
logger.warning("无链接,跳过")
continue
# 获取内容
content = fetcher.get_full_content(url)
if not content:
logger.warning("获取内容失败,跳过")
continue
# 检查是否包含发布时间
if "发布时间:" in content:
# 提取发布时间
import re
match = re.search(r'发布时间:\s*(.*?)\n', content)
if match:
publish_time = match.group(1).strip()
logger.info(f"提取的发布时间: {publish_time}")
# 比较CSV发布日期和提取的发布时间
if csv_publish_date in publish_time:
logger.info("✓ 发布时间提取正确")
else:
logger.warning("✗ 发布时间与CSV日期不一致")
else:
logger.warning("✗ 发布时间格式不正确")
else:
logger.warning("✗ 未提取到发布时间")
def main():
"""主函数"""
test_publish_time_extraction()
if __name__ == "__main__":
main()

View File

@@ -1,163 +0,0 @@
# -*- coding: utf-8 -*-
"""
随机选择原始数据进行提取测试
特别关注项目名称和项目批准文号
"""
import logging
import sys
import os
import csv
import random
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入配置和处理器
from config import REGION_CONFIGS
from processors.content_fetcher import ContentFetcher
from processors.deepseek import DeepSeekProcessor
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 原始数据文件路径
CSV_FILE = "data/浙江省公共资源交易中心_20260213_142920.csv"
# 结果输出文件
OUTPUT_MD = "随机提取分析报告.md"
def read_csv_data(file_path):
"""读取CSV文件数据"""
data = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
return data
def extract_data_from_url(url, title):
"""从URL提取数据"""
try:
# 获取内容
fetcher = ContentFetcher(temp_dir="temp_files")
content = fetcher.get_full_content(url)
if not content:
logger.warning(f"无法获取内容: {url}")
return None
logger.info(f"获取到内容长度: {len(content)} 字符")
# 执行提取
processor = DeepSeekProcessor()
# 获取浙江招标文件公示的配置
config_key = "zhejiang:招标文件公示"
if config_key not in REGION_CONFIGS:
logger.error(f"未找到配置: {config_key}")
return None
ai_fields = REGION_CONFIGS[config_key]["ai_fields"]
logger.info(f"需要提取的字段: {ai_fields}")
# 执行提取
extracted = processor.extract_fields(content, ai_fields, "浙江")
# 添加项目名称
extracted["项目名称"] = title
return extracted
except Exception as e:
logger.error(f"提取失败: {e}")
return None
def main():
"""主函数"""
logger.info("开始随机提取测试")
# 读取CSV数据
if not os.path.exists(CSV_FILE):
logger.error(f"CSV文件不存在: {CSV_FILE}")
return
data = read_csv_data(CSV_FILE)
logger.info(f"总数据量: {len(data)}")
# 随机选择5条数据
if len(data) > 5:
selected_data = random.sample(data, 5)
else:
selected_data = data
logger.info(f"随机选择了 {len(selected_data)} 条数据")
# 提取结果
results = []
for i, item in enumerate(selected_data, 1):
title = item.get("标题", "")
url = item.get("链接", "")
project_name = item.get("项目名称", "")
approval_number = item.get("项目批准文号", "")
logger.info(f"\n{'-'*50}")
logger.info(f"测试 {i}: {title}")
logger.info(f"URL: {url}")
logger.info(f"项目名称: {project_name}")
logger.info(f"项目批准文号: {approval_number}")
if not url:
logger.warning("无链接,跳过")
continue
# 提取数据
extracted = extract_data_from_url(url, project_name)
# 直接从CSV中添加项目批准文号
if approval_number:
extracted["批准文号"] = approval_number
if extracted:
results.append(extracted)
logger.info(f"提取成功!")
else:
logger.warning("提取失败")
# 处理最高限价字段:优先使用最高投标限价,为空时使用最高限价
for result in results:
max_price = result.get("最高投标限价", "")
if not max_price:
max_price = result.get("最高限价", "")
result["最高投标限价"] = max_price or "文档未提及"
# 生成MD报告
import datetime
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(OUTPUT_MD, 'w', encoding='utf-8') as f:
f.write("# 随机提取分析报告\n\n")
f.write(f"生成时间: {current_time}\n\n")
f.write(f"共提取了 {len(results)} 条数据\n\n")
for i, result in enumerate(results, 1):
f.write(f"## 提取结果 {i}\n\n")
f.write(f"### 项目名称\n{result.get('项目名称', '文档未提及')}\n\n")
f.write(f"### 项目批准文号\n{result.get('批准文号', '文档未提及')}\n\n")
f.write(f"### 其他关键信息\n")
f.write("| 字段 | 值 |\n")
f.write("|------|------|\n")
# 选择重要字段展示
important_fields = ["类型", "地区", "投标截止日", "最高投标限价", "资质要求", "业绩要求", "评标办法", "有无答辩", "招标人"]
for field in important_fields:
value = result.get(field, "文档未提及")
f.write(f"| {field} | {value} |\n")
f.write("\n")
logger.info(f"报告生成完成: {OUTPUT_MD}")
if __name__ == "__main__":
main()

View File

@@ -1,138 +0,0 @@
# -*- coding: utf-8 -*-
"""
随机选择几条原始数据执行提取流程,并生成分析报告
"""
import logging
import random
from processors.content_fetcher import ContentFetcher
from processors.deepseek import DeepSeekProcessor
from config import REGION_CONFIGS
import csv
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 原始数据文件路径
CSV_FILE = "data/浙江省公共资源交易中心_20260213_142920.csv"
# 结果输出文件
OUTPUT_MD = "随机提取分析报告.md"
def read_csv_data(file_path):
"""读取CSV文件数据"""
data = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
return data
def extract_data(url, title):
"""执行数据提取"""
logger.info(f"\n开始提取: {title}")
logger.info(f"URL: {url}")
# 1. 获取内容
fetcher = ContentFetcher(temp_dir="temp_files")
content = fetcher.get_full_content(url)
if not content:
logger.error("无法获取网页内容")
return None
logger.info(f"获取到内容长度: {len(content)} 字符")
# 2. 提取字段
processor = DeepSeekProcessor()
# 获取浙江招标文件公示的配置
config_key = "zhejiang:招标文件公示"
if config_key not in REGION_CONFIGS:
logger.error(f"未找到配置: {config_key}")
return None
ai_fields = REGION_CONFIGS[config_key]["ai_fields"]
# 3. 执行提取
extracted = processor.extract_fields(content, ai_fields, "浙江")
return extracted
def generate_md_report(data_list, extracted_results):
"""生成MD格式报告"""
md_content = "# 随机提取分析报告\n\n"
md_content += f"生成时间: {logging.Formatter('%(asctime)s').formatTime(logging.LogRecord('', 0, '', 0, '', '', '', ''))}\n\n"
md_content += "## 分析结果\n\n"
for i, (data, extracted) in enumerate(zip(data_list, extracted_results)):
title = data.get("标题", "未知")
url = data.get("链接", "")
publish_date = data.get("发布日期", "")
region = data.get("地区", "")
md_content += f"### 项目 {i+1}: {title}\n"
md_content += f"- 发布日期: {publish_date}\n"
md_content += f"- 地区: {region}\n"
md_content += f"- 链接: {url}\n\n"
if not extracted:
md_content += "**提取失败: 无法获取内容**\n\n"
continue
md_content += "#### 提取结果\n"
md_content += "| 字段 | 提取值 | 分析 |\n"
md_content += "|------|--------|------|\n"
for field, value in extracted.items():
analysis = ""
if value == "文档未提及":
analysis = "**空白原因**: 文档中未明确提及该信息"
elif value:
analysis = "提取成功"
else:
analysis = "**空白原因**: 提取结果为空"
# 处理表格中的特殊字符
value_clean = value.replace("|", "").replace("\n", " ")
md_content += f"| {field} | {value_clean} | {analysis} |\n"
md_content += "\n"
# 保存MD文件
with open(OUTPUT_MD, 'w', encoding='utf-8') as f:
f.write(md_content)
logger.info(f"报告已生成: {OUTPUT_MD}")
def main():
"""主函数"""
# 读取原始数据
data = read_csv_data(CSV_FILE)
logger.info(f"原始数据条数: {len(data)}")
# 随机选择5条数据
random.seed(42) # 设置随机种子以保证可重复性
selected_data = random.sample(data, 5)
logger.info(f"随机选择了 {len(selected_data)} 条数据")
# 执行提取
extracted_results = []
for item in selected_data:
url = item.get("链接", "")
title = item.get("标题", "")
if url:
result = extract_data(url, title)
extracted_results.append(result)
else:
extracted_results.append(None)
# 生成报告
generate_md_report(selected_data, extracted_results)
if __name__ == "__main__":
main()

View File

@@ -1,98 +0,0 @@
# -*- coding: utf-8 -*-
"""
使用修复后的配置文件测试真实提取功能
"""
import logging
import sys
import os
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 保存原始配置导入
import importlib
# 备份原始config模块
if 'config' in sys.modules:
del sys.modules['config']
# 临时替换config模块为config_fixed
import config_fixed
import sys
# 保存原始的config模块引用
original_config = None
if 'config' in sys.modules:
original_config = sys.modules['config']
# 将config_fixed设置为config模块
sys.modules['config'] = config_fixed
# 现在导入处理器
from processors.content_fetcher import ContentFetcher
from processors.deepseek import DeepSeekProcessor
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 测试网址
TEST_URL = "https://ggzy.zj.gov.cn/jyxxgk/002001/002001011/20260212/9a7966d8-80f4-475b-897e-f7631bc64d0c.html"
def main():
"""主函数"""
logger.info(f"开始测试: {TEST_URL}")
# 获取内容
fetcher = ContentFetcher(temp_dir="temp_files")
content = fetcher.get_full_content(TEST_URL)
if not content:
logger.error("无法获取内容")
return
logger.info(f"获取到内容长度: {len(content)} 字符")
# 执行提取
processor = DeepSeekProcessor()
# 获取浙江招标文件公示的配置
config_key = "zhejiang:招标文件公示"
from config import REGION_CONFIGS
if config_key not in REGION_CONFIGS:
logger.error(f"未找到配置: {config_key}")
return
ai_fields = REGION_CONFIGS[config_key]["ai_fields"]
logger.info(f"需要提取的字段: {ai_fields}")
# 执行提取
extracted = processor.extract_fields(content, ai_fields, "浙江")
# 分析结果
logger.info("\n提取结果:")
for field, value in extracted.items():
logger.info(f" {field}: {value}")
# 特别关注资质要求和业绩要求
for field in ["资质要求", "业绩要求"]:
if field in extracted:
value = extracted[field]
logger.info(f"\n{field}提取结果: {value}")
if value != "文档未提及":
logger.info(f"{field}提取成功!")
else:
logger.warning(f"{field}未提取到")
if __name__ == "__main__":
try:
main()
finally:
# 恢复原始配置
if original_config:
sys.modules['config'] = original_config

View File

@@ -1,108 +0,0 @@
# -*- coding: utf-8 -*-
"""
针对指定网址的重新提取测试
分析业绩要求未提取到的原因
"""
import logging
from processors.content_fetcher import ContentFetcher
from processors.deepseek import DeepSeekProcessor
from config import REGION_CONFIGS
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 测试网址
TARGET_URL = "https://ggzy.zj.gov.cn/jyxxgk/002001/002001011/20260212/d2f95295-6cb0-40c9-8023-cdbbf7e660ae.html"
def test_reextract():
"""重新提取指定网址的信息"""
logger.info(f"开始测试重新提取: {TARGET_URL}")
# 1. 获取内容
fetcher = ContentFetcher(temp_dir="temp_files")
content = fetcher.get_full_content(TARGET_URL)
if not content:
logger.error("无法获取网页内容")
return
logger.info(f"获取到内容长度: {len(content)} 字符")
# 2. 提取字段
processor = DeepSeekProcessor()
# 获取浙江招标文件公示的配置
config_key = "zhejiang:招标文件公示"
if config_key not in REGION_CONFIGS:
logger.error(f"未找到配置: {config_key}")
return
ai_fields = REGION_CONFIGS[config_key]["ai_fields"]
logger.info(f"需要提取的字段: {ai_fields}")
# 3. 执行提取
extracted = processor.extract_fields(content, ai_fields, "浙江")
# 4. 分析结果
logger.info("\n提取结果:")
for field, value in extracted.items():
logger.info(f" {field}: {value}")
# 特别关注业绩要求
if "业绩要求" in extracted:
performance_req = extracted["业绩要求"]
logger.info(f"\n业绩要求提取结果: {performance_req}")
if performance_req == "文档未提及":
logger.warning("业绩要求未提取到,开始分析原因...")
# 分析内容中是否包含业绩相关关键词
performance_keywords = ["业绩要求", "业绩条件", "投标人业绩", "类似项目", "工程经验"]
found_keywords = []
for keyword in performance_keywords:
if keyword in content:
found_keywords.append(keyword)
# 找到关键词上下文
start_idx = max(0, content.find(keyword) - 200)
end_idx = min(len(content), content.find(keyword) + 800)
context = content[start_idx:end_idx]
logger.info(f"\n找到关键词 '{keyword}' 的上下文:")
logger.info(f"{context[:500]}...")
if found_keywords:
logger.info(f"\n在内容中找到相关关键词: {found_keywords}")
logger.info("可能的问题: 关键词存在但提取逻辑未正确识别")
else:
logger.info("\n在内容中未找到业绩相关关键词")
logger.info("可能的问题: 内容中确实没有业绩要求信息")
else:
logger.info("业绩要求提取成功")
else:
logger.error("提取结果中没有业绩要求字段")
# 5. 分析内容预处理
logger.info("\n分析内容预处理...")
prepared_content = processor._prepare_content(content, ai_fields)
logger.info(f"预处理后内容长度: {len(prepared_content)} 字符")
# 检查预处理后是否包含业绩相关内容
performance_keywords = ["业绩要求", "业绩条件", "投标人业绩"]
found_in_prepared = []
for keyword in performance_keywords:
if keyword in prepared_content:
found_in_prepared.append(keyword)
if found_in_prepared:
logger.info(f"预处理后内容中包含业绩相关关键词: {found_in_prepared}")
else:
logger.warning("预处理后内容中不包含业绩相关关键词")
logger.warning("可能的问题: 内容预处理时未包含业绩相关部分")
if __name__ == "__main__":
test_reextract()

View File

@@ -1,100 +0,0 @@
# -*- coding: utf-8 -*-
"""
测试单条数据处理
"""
import logging
import sys
import os
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入配置和处理器
from config import REGION_CONFIGS, PROCESSING_CONFIG
from spiders.zhejiang import ZhejiangSpider
from processors.pipeline import ProcessingPipeline
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 测试URL
TEST_URL = "https://ggzy.zj.gov.cn/jyxxgk/002001/002001011/20260212/9a7966d8-80f4-475b-897e-f7631bc64d0c.html"
# 模拟爬虫结果
TEST_ITEM = {
"标题": "[招标文件](测-试临海市房建施工0212-2招标文件公示[A3300000090000695005001]",
"发布日期": "2026-02-12",
"地区": "临海市",
"公告类型": "招标文件公示",
"链接": TEST_URL,
"来源": "浙江省公共资源交易中心"
}
def main():
"""主函数"""
logger.info("开始单条数据测试")
# 1. 测试标题解析
from spiders.zhejiang import ZhejiangSpider
# 模拟标题解析
import re
title = TEST_ITEM["标题"]
# 使用修复后的正则表达式
title_pattern = r"\[(?:招标文件|招标公告)\]\s*(.*?)\s*\[([A-Z0-9]+)\]\s*$"
match = re.search(title_pattern, title)
if match:
project_name = match.group(1).strip()
project_approval = match.group(2).strip()
logger.info(f"标题解析结果:")
logger.info(f" 项目名称: {project_name}")
logger.info(f" 项目批准文号: {project_approval}")
else:
logger.warning("标题解析失败")
# 2. 测试处理管道
logger.info("\n测试处理管道:")
pipeline = ProcessingPipeline()
# 模拟ZhejiangSpider的处理过程添加项目名称和项目批准文号
test_item_with_fields = TEST_ITEM.copy()
# 使用修复后的标题解析
title = test_item_with_fields["标题"]
title_pattern = r"\[(?:招标文件|招标公告)\]\s*(.*?)\s*\[([A-Z0-9]+)\]\s*$"
match = re.search(title_pattern, title)
if match:
test_item_with_fields["项目名称"] = match.group(1).strip()
test_item_with_fields["项目批准文号"] = match.group(2).strip()
logger.info(f"添加字段后的测试项:")
logger.info(f" 项目名称: {test_item_with_fields.get('项目名称', '')}")
logger.info(f" 项目批准文号: {test_item_with_fields.get('项目批准文号', '')}")
# 模拟爬虫结果列表
results = [test_item_with_fields]
# 处理结果
processed = pipeline.process_results(
results,
site="zhejiang",
notice_type="招标文件公示",
upload=False
)
# 分析结果
if processed:
record = processed[0]
logger.info("\n处理结果:")
logger.info(f" 项目名称: {record.get('项目名称', '文档未提及')}")
logger.info(f" 项目批准文号: {record.get('项目批准文号', '文档未提及')}")
logger.info(f" 批准文号: {record.get('批准文号', '文档未提及')}")
logger.info(f" 最高投标限价: {record.get('最高投标限价', '文档未提及')}")
logger.info(f" 最高限价: {record.get('最高限价', '文档未提及')}")
else:
logger.error("处理失败")
if __name__ == "__main__":
main()

View File

@@ -1,76 +0,0 @@
# -*- coding: utf-8 -*-
"""
测试上传招标公告数据到简道云
"""
import json
import logging
import os
# 添加当前目录到模块搜索路径
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入简道云上传器
from processors.jiandaoyun import JiandaoyunUploader
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def upload_test_data():
"""
上传测试数据到简道云
"""
# 最新的AI处理结果文件
json_file = "data/浙江招标公告_AI处理_20260224_133102.json"
region_name = "浙江招标公告"
logger.info(f"开始上传 {json_file} 到简道云")
# 1. 读取JSON文件
if not os.path.exists(json_file):
logger.error(f"JSON文件不存在: {json_file}")
return
try:
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
except Exception as e:
logger.error(f"读取JSON文件失败: {e}")
return
# 2. 提取记录数据
records = data.get('data', [])
if not records:
logger.error("JSON文件中没有数据")
return
logger.info(f"读取完成,共 {len(records)} 条记录")
# 3. 上传到简道云
uploader = JiandaoyunUploader()
result = uploader.upload_records(region_name, records)
# 4. 输出结果
logger.info(f"上传完成: 成功 {result['success']}, 失败 {result['failed']}")
if result['failed'] > 0:
logger.error("上传失败的记录:")
for error in result.get('errors', []):
logger.error(f" - {error}")
return result
def main():
"""
主函数
"""
logger.info("=== 测试简道云上传 ===")
result = upload_test_data()
logger.info("=== 测试完成 ===")
if __name__ == "__main__":
main()

View File

@@ -1,86 +0,0 @@
# -*- coding: utf-8 -*-
"""
测试上传招标公告和招标计划表单
"""
import logging
import sys
import os
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入配置和处理器
from config import ZHEJIANG_CONFIG, SPIDER_CONFIG, DATA_DIR
from spiders import ZhejiangSpider
from processors import ProcessingPipeline
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def crawl_and_upload(notice_type, max_pages=1):
"""爬取并上传指定类型的表单"""
logger.info(f"\n{'='*70}")
logger.info(f"开始处理: {notice_type}")
logger.info(f"{'='*70}")
# 1. 爬取数据
logger.info("1. 爬取数据:")
spider = ZhejiangSpider(ZHEJIANG_CONFIG, SPIDER_CONFIG, DATA_DIR)
# 爬取数据
spider.crawl(
max_pages=max_pages,
category="工程建设",
notice_type=notice_type
)
# 保存到CSV
spider.save_to_csv()
# 获取爬取结果
results = spider.results
logger.info(f"爬取完成,共获取 {len(results)} 条数据")
if len(results) == 0:
logger.error("爬取失败,无数据")
return
# 2. 处理数据
logger.info("\n2. 处理数据:")
pipeline = ProcessingPipeline()
processed = pipeline.process_results(
results,
site="zhejiang",
notice_type=notice_type,
upload=True # 上传到简道云
)
# 3. 展示结果
logger.info("\n3. 处理结果:")
logger.info(f"成功处理 {len(processed)} 条数据")
# 展示前2条的关键信息
logger.info("\n前2条数据关键信息:")
for i, record in enumerate(processed[:2], 1):
logger.info(f"\n测试 {i}")
logger.info(f"项目名称: {record.get('项目名称', '文档未提及')}")
logger.info(f"项目批准文号: {record.get('项目批准文号', '文档未提及')}")
logger.info(f"批准文号: {record.get('批准文号', '文档未提及')}")
def main():
"""主函数"""
logger.info("开始上传招标公告和招标计划表单")
# 处理招标公告
crawl_and_upload("招标公告", max_pages=1)
# 处理招标计划
crawl_and_upload("招标计划", max_pages=1)
if __name__ == "__main__":
main()

View File

@@ -1,138 +0,0 @@
# -*- coding: utf-8 -*-
"""
使用修复后的配置文件测试提取功能
"""
import logging
import sys
import os
import requests
from bs4 import BeautifulSoup
import json
import re
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入修复后的配置
from config_fixed import REGION_CONFIGS, DEEPSEEK_PROMPTS
# 简化的ContentFetcher类
class ContentFetcher:
def __init__(self, temp_dir="temp_files"):
self.temp_dir = temp_dir
if not os.path.exists(temp_dir):
os.makedirs(temp_dir)
def get_full_content(self, url):
try:
response = requests.get(url, timeout=30)
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, 'html.parser')
# 提取主要内容
content = []
# 查找标题
title = soup.find('h1')
if title:
content.append(title.get_text(strip=True))
# 查找正文内容
content_div = soup.find('div', class_='ewb-article')
if content_div:
for p in content_div.find_all('p'):
text = p.get_text(strip=True)
if text:
content.append(text)
# 查找附件
attachments = soup.find_all('a', href=re.compile(r'\.(pdf|doc|docx)$'))
if attachments:
content.append("\n附件:")
for attachment in attachments:
content.append(f"- {attachment.get_text(strip=True)}: {attachment['href']}")
return "\n".join(content)
except Exception as e:
logging.error(f"获取内容失败: {e}")
return None
# 简化的DeepSeekProcessor类
class DeepSeekProcessor:
def __init__(self):
pass
def extract_fields(self, content, fields, region_name):
results = {}
for field in fields:
if field in DEEPSEEK_PROMPTS:
# 这里我们只是模拟提取实际项目中会调用DeepSeek API
prompt = DEEPSEEK_PROMPTS[field]
# 简单模拟:如果内容包含关键词,返回模拟结果
if field == "资质要求" and any(keyword in content for keyword in ["资质", "资格"]):
results[field] = "建筑工程施工总承包三级及以上"
elif field == "业绩要求" and any(keyword in content for keyword in ["业绩", "经验"]):
results[field] = "近3年类似工程业绩不少于2项"
else:
results[field] = "文档未提及"
else:
results[field] = "文档未提及"
return results
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 测试网址
TEST_URL = "https://ggzy.zj.gov.cn/jyxxgk/002001/002001011/20260212/9a7966d8-80f4-475b-897e-f7631bc64d0c.html"
def main():
"""主函数"""
logger.info(f"开始测试: {TEST_URL}")
# 获取内容
fetcher = ContentFetcher(temp_dir="temp_files")
content = fetcher.get_full_content(TEST_URL)
if not content:
logger.error("无法获取内容")
return
logger.info(f"获取到内容长度: {len(content)} 字符")
# 执行提取
processor = DeepSeekProcessor()
# 获取浙江招标文件公示的配置
config_key = "zhejiang:招标文件公示"
if config_key not in REGION_CONFIGS:
logger.error(f"未找到配置: {config_key}")
return
ai_fields = REGION_CONFIGS[config_key]["ai_fields"]
logger.info(f"需要提取的字段: {ai_fields}")
# 执行提取
extracted = processor.extract_fields(content, ai_fields, "浙江")
# 分析结果
logger.info("\n提取结果:")
for field, value in extracted.items():
logger.info(f" {field}: {value}")
# 特别关注资质要求和业绩要求
for field in ["资质要求", "业绩要求"]:
if field in extracted:
value = extracted[field]
logger.info(f"\n{field}提取结果: {value}")
if value != "文档未提及":
logger.info(f"{field}提取成功!")
else:
logger.warning(f"{field}未提取到")
if __name__ == "__main__":
main()

View File

@@ -1,72 +0,0 @@
# -*- coding: utf-8 -*-
"""
从JSON文件上传数据到简道云
"""
import json
import logging
import os
# 添加当前目录到模块搜索路径
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入简道云上传器
from processors.jiandaoyun import JiandaoyunUploader
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def upload_json_to_jdy(json_file, region_name):
"""
从JSON文件上传数据到简道云
Args:
json_file: JSON文件路径
region_name: 区域名称(对应简道云表单配置)
"""
logger.info(f"开始上传 {json_file} 到简道云")
# 1. 读取JSON文件
if not os.path.exists(json_file):
logger.error(f"JSON文件不存在: {json_file}")
return
try:
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
except Exception as e:
logger.error(f"读取JSON文件失败: {e}")
return
# 2. 提取记录数据
records = data.get('data', [])
if not records:
logger.error("JSON文件中没有数据")
return
logger.info(f"读取完成,共 {len(records)} 条记录")
# 3. 上传到简道云
uploader = JiandaoyunUploader()
result = uploader.upload_records(region_name, records)
# 4. 输出结果
logger.info(f"上传完成: 成功 {result['success']}, 失败 {result['failed']}")
return result
def main():
"""
主函数
"""
# 最新的AI处理结果文件
json_file = "data/浙江招标公告_AI处理_20260213_175357.json"
region_name = "浙江招标公告"
upload_json_to_jdy(json_file, region_name)
if __name__ == "__main__":
main()

View File

@@ -1,85 +0,0 @@
# -*- coding: utf-8 -*-
"""
上传最后8条数据到简道云
"""
import logging
import sys
import os
import csv
# 添加当前目录到模块搜索路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入配置和处理器
from config import REGION_CONFIGS, PROCESSING_CONFIG
from processors import ProcessingPipeline
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 最新的CSV文件路径
CSV_FILE = "data/浙江省公共资源交易中心_20260213_161312.csv"
def read_csv_data(file_path):
"""读取CSV文件数据"""
data = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
return data
def main():
"""主函数"""
logger.info("开始处理并上传最后8条数据")
# 1. 读取CSV数据
if not os.path.exists(CSV_FILE):
logger.error(f"CSV文件不存在: {CSV_FILE}")
return
data = read_csv_data(CSV_FILE)
logger.info(f"读取完成,共 {len(data)} 条数据")
if len(data) == 0:
logger.error("无数据可处理")
return
# 2. 取最后8条数据
logger.info("\n2. 选择数据:")
if len(data) >= 8:
selected_data = data[-8:]
else:
selected_data = data
logger.info(f"选择了最后 {len(selected_data)} 条数据")
# 3. 处理数据
logger.info("\n3. 处理数据:")
pipeline = ProcessingPipeline()
processed = pipeline.process_results(
selected_data,
site="zhejiang",
notice_type="招标文件公示",
upload=True # 上传到简道云
)
# 4. 展示结果
logger.info("\n4. 处理结果:")
logger.info(f"成功处理 {len(processed)} 条数据")
# 展示前3条的关键信息
logger.info("\n前3条数据关键信息:")
for i, record in enumerate(processed[:3], 1):
logger.info(f"\n测试 {i}")
logger.info(f"项目名称: {record.get('项目名称', '文档未提及')}")
logger.info(f"项目批准文号: {record.get('项目批准文号', '文档未提及')}")
logger.info(f"批准文号: {record.get('批准文号', '文档未提及')}")
if __name__ == "__main__":
main()

View File

@@ -1,111 +0,0 @@
# 随机提取分析报告
生成时间: 2026-02-13 15:32:50
共提取了 5 条数据
## 提取结果 1
### 项目名称
### 项目批准文号
温发改【2024】52号、温发改【2025】98号
### 其他关键信息
| 字段 | 值 |
|------|------|
| 类型 | 监理 |
| 地区 | 台州市温岭市 |
| 投标截止日 | 文档未提及 |
| 最高投标限价 | 4089187元 |
| 资质要求 | 水运工程乙级及以上监理资质 |
| 业绩要求 | 自2021年1月1日以实际交工日期为准以来完成过一个水运工程堆场面积10万平方米及以上的新或改建堆场修复工程除外工程施工监理项目。 |
| 评标办法 | 综合评估法 |
| 有无答辩 | 无 |
| 招标人 | 浙江白岩山港务有限公司 |
## 提取结果 2
### 项目名称
### 项目批准文号
东发改审批受理20246号文件、东发改审批202575号文件
### 其他关键信息
| 字段 | 值 |
|------|------|
| 类型 | 总承包 |
| 地区 | 金华市东阳市 |
| 投标截止日 | 文档未提及 |
| 最高投标限价 | 6427.24386万元 |
| 资质要求 | 建筑工程施工总承包叁级及以上 |
| 业绩要求 | 文档未提及 |
| 评标办法 | 评定分离 |
| 有无答辩 | 无 |
| 招标人 | 中共东阳市委党史研究室 |
## 提取结果 3
### 项目名称
### 项目批准文号
临发改基综2025199号
### 其他关键信息
| 字段 | 值 |
|------|------|
| 类型 | 市政 |
| 地区 | 台州市临海市 |
| 投标截止日 | 文档未提及 |
| 最高投标限价 | 8784343元 |
| 资质要求 | 市政公用工程施工总承包三级及以上 |
| 业绩要求 | 文档未提及 |
| 评标办法 | 评定分离 |
| 有无答辩 | 无 |
| 招标人 | 临海市杜桥镇人民政府 |
## 提取结果 4
### 项目名称
### 项目批准文号
浙发改项字202514号
### 其他关键信息
| 字段 | 值 |
|------|------|
| 类型 | 设计 |
| 地区 | 衢州市江山市 |
| 投标截止日 | 2026年 月 日9时00分 |
| 最高投标限价 | 181万元 |
| 资质要求 | 工程勘察专业类(岩土工程(勘察))甲级或工程勘察综合类甲级资质;公路行业(公路)专业设计甲级或公路行业设计甲级或工程设计综合甲级资质 |
| 业绩要求 | 自2021年1月1日以来以施工图设计批复或准予行政许可决定书时间为准完成过1条一级及以上公路的勘察自2021年1月1日以来以施工图设计批复或准予行政许可决定书时间为准完成过1条一级及以上公路的设计 |
| 评标办法 | 综合评估法 |
| 有无答辩 | 无 |
| 招标人 | 江山市交通运输局 |
## 提取结果 5
### 项目名称
### 项目批准文号
舟发改审批[2025]67号
### 其他关键信息
| 字段 | 值 |
|------|------|
| 类型 | 总承包 |
| 地区 | 舟山市临城新区 |
| 投标截止日 | 2026年 月 日9时00分 |
| 最高投标限价 | 文档未提及 |
| 资质要求 | 建筑工程施工总承包贰级及以上 |
| 业绩要求 | 文档未提及 |
| 评标办法 | 综合评估法 |
| 有无答辩 | 有 |
| 招标人 | 浙江国际海运职业技术学院 |