From d78dc655ee77ab846642b57cfc8b2e5d64423e4f Mon Sep 17 00:00:00 2001 From: zhaojunlong <5482498@qq.com> Date: Thu, 19 Mar 2026 10:18:25 +0800 Subject: [PATCH] =?UTF-8?q?```=20chore(config):=20=E6=9B=B4=E6=96=B0.gitig?= =?UTF-8?q?nore=E6=96=87=E4=BB=B6=E4=BB=A5=E5=BF=BD=E7=95=A5=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E7=9B=B8=E5=85=B3=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加了data/目录、SQLite数据库文件及相关临时文件到.gitignore中, 避免敏感数据和临时文件被提交到版本控制系统。 ``` --- .gitignore | 5 + config.json | 573 +---------------------------------- package-lock.json | 721 ++++++++++++++++++++++++++------------------ package.json | 3 +- public/index.html | 128 ++++++-- results.json | 266 ---------------- src/agentService.js | 6 +- src/resultStore.js | 471 +++++++++++++++++++++++++++++ src/scheduler.js | 187 +++++------- src/server.js | 545 +++++++++++++++++---------------- 10 files changed, 1367 insertions(+), 1538 deletions(-) delete mode 100644 results.json create mode 100644 src/resultStore.js diff --git a/.gitignore b/.gitignore index 5089459..58de3ad 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,11 @@ pnpm-debug.log* # 配置文件(包含敏感信息) config.json +data/ +*.sqlite +*.sqlite-shm +*.sqlite-wal +*.migrated.bak # 编辑器目录和文件 .vscode/ diff --git a/config.json b/config.json index 963c677..c084879 100644 --- a/config.json +++ b/config.json @@ -1,582 +1,21 @@ { "agent": { "baseUrl": "http://192.168.3.65:18777", - "useBrowser": false, "pollInterval": 3000, "timeout": 3600000 }, - "tasks": [ - { - "id": "task-1773213332763", - "city": "徐州市-建设工程-招标文件提前公示", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.zwb.xz.gov.cn/jyxx/003001/003001014/list.html\n1. 建设工程板块中的【招标文件提前公示】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": true, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773215122877", - "city": "徐州市-建设工程-招标公告/资审公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.zwb.xz.gov.cn/jyxx/003001/003001001/list.html\n1. 建设工程板块中的【招标公告/资审公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": true, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773215206067", - "city": "徐州市-交通工程-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.zwb.xz.gov.cn/jyxx/003002/003002005/list.html\n1. 交通工程板块中的【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": true, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773215248600", - "city": "徐州市-交通工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.zwb.xz.gov.cn/jyxx/003002/003002001/list.html\n1. 交通工程板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": true, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773215628722", - "city": "徐州市-水务工程-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.zwb.xz.gov.cn/jyxx/003003/003003005/list.html\n1. 水务工程板块中的【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": true, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773215674753", - "city": "徐州市-水务工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.zwb.xz.gov.cn/jyxx/003003/003003001/list.html\n1. 水务工程板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": true, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773217569819", - "city": "无锡市-建设工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjy.wuxi.gov.cn/wxsggzyjyzxzl/jyxx/jsgc/index.shtml\n1. 建设工程板块中的招标公告【工程类】【非工程类】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773217660649", - "city": "无锡市-水利工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjy.wuxi.gov.cn/wxsggzyjyzxzl/jyxx/slgc/index.shtml\n1. 水利工程板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773217743111", - "city": "无锡市-交通工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjy.wuxi.gov.cn/wxsggzyjyzxzl/jyxx/jtgc/index.shtml\n1. 交通工程板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773218603505", - "city": "南京市-房建市政-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://njggzy.nanjing.gov.cn/njweb/fjsz/buildService1.html\n1. 房建市政板块中的招标公告中的【工程类】、【服务类】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773218673384", - "city": "南京市-交通水务-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://njggzy.nanjing.gov.cn/njweb/jtsw/traffic.html\n1. 交通水务板块中的招标公告中的【交通】、【水务】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773220796068", - "city": "南通市-建设工程-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjy.nantong.gov.cn/jyxx/003001/003001009/tradeInfo.html\n1. 建设工程板块中的【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773220861223", - "city": "南通市-建设工程-招标公告/资审公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjy.nantong.gov.cn/jyxx/003001/003001001/tradeInfo.html\n1. 建设工程板块【招标公告/资审公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773222149540", - "city": "南通市-交通工程-招标公告/招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjy.nantong.gov.cn/jyxx/003005/003005001/tradeInfo.html\n1. 交通工程板块【招标公告/招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773222268523", - "city": "南通市-水利工程-招标公告/招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjy.nantong.gov.cn/jyxx/003006/003006001/tradeInfo.html\n1. 水利工程板块【招标公告/招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773222554722", - "city": "连云港市-建设工程-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.lyg.gov.cn/lygweb/jyxx/001001/001001008/tradeInfo.html\n1. 建设工程板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773222633782", - "city": "连云港市-建设工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.lyg.gov.cn/lygweb/jyxx/001001/001001001/tradeInfo.html\n1. 建设工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773222806709", - "city": "连云港市-交通工程-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.lyg.gov.cn/lygweb/jyxx/001002/001002004/tradeInfo.html\n1. 交通工程板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773222852359", - "city": "连云港市-交通工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.lyg.gov.cn/lygweb/jyxx/001002/001002001/tradeInfo.html\n1. 交通工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773222920195", - "city": "连云港市-水利工程-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.lyg.gov.cn/lygweb/jyxx/001003/001003004/tradeInfo.html\n1. 水利工程板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773222960973", - "city": "连云港市-水利工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.lyg.gov.cn/lygweb/jyxx/001003/001003001/tradeInfo.html\n1. 水利工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773223258041", - "city": "淮安市-建设工程-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.huaian.gov.cn/jyxx/001001/001001009/tradeInfo.html\n1. 建设工程板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773223302642", - "city": "淮安市-建设工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.huaian.gov.cn/jyxx/001001/001001001/tradeInfo.html\n1. 建设工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773225146455", - "city": "淮安市-交通工程-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.huaian.gov.cn/jyxx/001002/001002009/tradeInfo.html\n1. 交通工程板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773225198650", - "city": "淮安市-交通工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.huaian.gov.cn/jyxx/001002/001002001/tradeInfo.html\n1. 交通工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773225266784", - "city": "淮安市-水利工程-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.huaian.gov.cn/jyxx/001003/001003006/tradeInfo.html\n1. 水利工程板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773225305718", - "city": "淮安市-水利工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.huaian.gov.cn/jyxx/001003/001003001/tradeInfo.html\n1. 水利工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773225383907", - "city": "淮安市-土地整治-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.huaian.gov.cn/jyxx/001009/001009005/tradeInfo.html\n1. 土地整治板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773225424486", - "city": "淮安市-土地整治-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.huaian.gov.cn/jyxx/001009/001009001/tradeInfo.html\n1. 土地整治板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773225496197", - "city": "淮安市-农田建设-招标计划", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.huaian.gov.cn/jyxx/001010/001010005/tradeInfo.html\n1. 农田建设板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773225533513", - "city": "淮安市-农田建设-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.huaian.gov.cn/jyxx/001010/001010001/tradeInfo.html\n1. 农田建设板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773226587983", - "city": "扬州市-房建市政-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjyzx.yangzhou.gov.cn/jyxx/fjsz/zbgg/index.html\n1. 房建市政板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773227503951", - "city": "扬州市-交通工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjyzx.yangzhou.gov.cn/jyxx/jtgc/zbgg/index.html\n1. 交通工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773227565067", - "city": "扬州市-水利工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjyzx.yangzhou.gov.cn/jyxx/slgc/zbgg/index.html\n1. 水利工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773227623709", - "city": "扬州市-农业工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzyjyzx.yangzhou.gov.cn/jyxx/nygc/nyzbgg/index.html\n1. 农业工程板块【农业招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773227798306", - "city": "泰州市-建设工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.taizhou.gov.cn/jyxx/001001/001001001/secondPagejyxx.html\n1. 建设工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773227859708", - "city": "泰州市-能源工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.taizhou.gov.cn/jyxx/001013/001013001/secondPagejyxx.html\n1. 能源工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773227916651", - "city": "泰州市-交通工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.taizhou.gov.cn/jyxx/001002/001002001/secondPagejyxx.html\n1. 交通工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773228017356", - "city": "泰州市-水利工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.taizhou.gov.cn/jyxx/001003/001003001/secondPagejyxx.html\n1. 水利工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773228065067", - "city": "泰州市-农业工程-招标公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttp://ggzy.taizhou.gov.cn/jyxx/001012/001012001/secondPagejyxx.html\n1. 农业工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773228205941", - "city": "宿迁市-招标计划提前发布", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.xzspj.suqian.gov.cn/jyxx/001010/tradeInfo.html\n1. 招标计划提前发布板块项目信息\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773228260574", - "city": "宿迁市-建设工程-招标预公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.xzspj.suqian.gov.cn/jyxx/001001/001001010/tradeInfo.html\n1. 建设工程板块【招标预公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773228334419", - "city": "宿迁市-建设工程-招标公告/资审公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.xzspj.suqian.gov.cn/jyxx/001001/001001001/tradeInfo.html\n1. 建设工程板块【招标公告/资审公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773229198813", - "city": "宿迁市-交通工程-招标公告/资审公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.xzspj.suqian.gov.cn/jyxx/001002/001002001/tradeInfo.html\n1. 交通工程板块【招标公告/资审公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773229960773", - "city": "宿迁市-水利工程-招标公告/资审公告", - "prompt": "使用scrapling技能的fetch来爬取\nhttps://ggzy.xzspj.suqian.gov.cn/jyxx/001003/001003001/tradeInfo.html\n1. 水利工程板块【招标公告/资审公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n爬取思路\n 1. 先初步探索需要采集的内容\n 2. 根据实际情况,制定详细的采集计划,并将计划写入到plans.md文件中\n 3. 按照计划,委托给subagent去完成采集任务,不要自己去采集\n 4. 校验subagent的采集结果,复查计划进度\n 5. 计划完成后汇总结果,形成用户需要的数据", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773282112257", - "city": "盐城市-工程建设-招标计划", - "prompt": "爬取\nhttps://ycggzy.jszwfw.gov.cn/tradeInfor?secondId=19&secondCode=transactionInfo\n1. 工程建设板块中的【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政-招标文公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选工程建设 + 招标计划\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773282738792", - "city": "盐城市-工程建设-招标公告", - "prompt": "爬取\nhttps://ycggzy.jszwfw.gov.cn/tradeInfor?secondId=19&secondCode=transactionInfo\n1. 工程建设板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如工程建设-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选工程建设 + 招标公告\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773282811404", - "city": "盐城市-交通工程-招标计划", - "prompt": "爬取https://ycggzy.jszwfw.gov.cn/tradeInfor?secondId=19&secondCode=transactionInfo\n1. 交通工程板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如交通工程-招标计划\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选交通工程 + 招标计划\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773282843104", - "city": "盐城市-交通工程-招标公告", - "prompt": "爬取\nhttps://ycggzy.jszwfw.gov.cn/tradeInfor?secondId=19&secondCode=transactionInfo\n1. 交通工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如交通工程-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选交通工程 + 招标公告\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773286430226", - "city": "盐城市-水利工程-招标计划", - "prompt": "爬取https://ycggzy.jszwfw.gov.cn/tradeInfor?secondId=19&secondCode=transactionInfo\n1. 水利工程板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如水利工程-招标计划\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选水利工程-招标计划\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773286494770", - "city": "盐城市-水利工程-招标公告", - "prompt": "爬取https://ycggzy.jszwfw.gov.cn/tradeInfor?secondId=19&secondCode=transactionInfo\n1. 水利工程板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如水利工程-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选水利工程 + 招标公告\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773286562021", - "city": "盐城市-农业农村-招标计划", - "prompt": "爬取https://ycggzy.jszwfw.gov.cn/tradeInfor?secondId=19&secondCode=transactionInfo\n1. 农业农村板块【招标计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如农业农村-招标计划\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选农业农村 + 招标计划\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773286599089", - "city": "盐城市-农业农村-招标公告", - "prompt": "爬取https://ycggzy.jszwfw.gov.cn/tradeInfor?secondId=19&secondCode=transactionInfo\n1. 农业农村板块【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如农业农村-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选农业农村 + 招标公告\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773287068033", - "city": "苏州市-建设工程-招标预计划", - "prompt": "爬取http://ggzy.suzhou.gov.cn/jyxx/003001/tradeInfo.html\n1. 建设工程板块中的【招标预计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如建设工程-招标预计划\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选建设工程 + 招标预计划,筛选“发布时间”为“近三天”的\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773287206110", - "city": "苏州市-建设工程-招标公告/资审公告", - "prompt": "爬取http://ggzy.suzhou.gov.cn/jyxx/003001/tradeInfo.html\n1. 建设工程板块中的【招标公告/资审公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如建设工程-招标公告/资审公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选建设工程 + 招标公告/资审公告,筛选“发布时间”为“近三天”的\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773295605929", - "city": "苏州市-交通工程-招标预计划", - "prompt": "爬取http://ggzy.suzhou.gov.cn/jyxx/003001/tradeInfo.html\n1. 交通工程板块中的【招标预计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如交通工程-招标预计划\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选交通工程-招标预计划,筛选“发布时间”为“近三天”的\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773295651095", - "city": "苏州市-交通工程-招标公告", - "prompt": "爬取http://ggzy.suzhou.gov.cn/jyxx/003001/tradeInfo.html\n1. 交通工程板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如交通工程-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选交通工程 + 招标公告,筛选“发布时间”为“近三天”的\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773295707567", - "city": "苏州市-水利工程-招标预计划", - "prompt": "爬取http://ggzy.suzhou.gov.cn/jyxx/003001/tradeInfo.html\n1. 水利工程板块中的【招标预计划】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如水利工程-招标预计划\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选水利工程-招标预计划, 筛选“发布时间”为“近三天”的\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773295748272", - "city": "苏州市-水利工程-招标公告", - "prompt": "爬取http://ggzy.suzhou.gov.cn/jyxx/003001/tradeInfo.html\n1. 水利工程板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如水利工程-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选水利工程 + 招标公告,筛选“发布时间”为“近三天”\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773367789161", - "city": "省公共平台-建设工程-招标计划/招标计划变更公告", - "prompt": "爬取http://jsggzy.jszwfw.gov.cn/jyxx/tradeInfonew.html?type=jsgc\n1. 建设工程板块中的【招标计划/招标计划变更公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如建设工程-招标计划/招标计划变更公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选建设工程 + 招标计划/招标计划变更公告,筛选“发布时间”为“近三天”\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773368044054", - "city": "省公共平台-建设工程-招标公告/资审公告", - "prompt": "爬取http://jsggzy.jszwfw.gov.cn/jyxx/tradeInfonew.html?type=jsgc\n1. 建设工程板块中的【招标公告/资审公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如建设工程-招标公告/资审公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选建设工程 + 招标公告/资审公告,筛选“发布时间”为“近三天”\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773369115954", - "city": "省公共平台-交通工程-招标计划/招标计划变更公告", - "prompt": "爬取http://jsggzy.jszwfw.gov.cn/jyxx/tradeInfonew.html?type=jsgc\n1. 交通工程板块中的【招标计划/招标计划变更公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如交通工程-招标计划/招标计划变更公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选交通工程 + 招标计划/招标计划变更公告,筛选“发布时间”为“近三天”\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773369189083", - "city": "省公共平台-交通工程-招标公告", - "prompt": "爬取http://jsggzy.jszwfw.gov.cn/jyxx/tradeInfonew.html?type=jsgc\n1. 交通工程板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如交通工程-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选交通工程 + 招标公告,筛选“发布时间”为“近三天”\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773369264817", - "city": "省公共平台-水利工程-招标计划/招标计划变更公告", - "prompt": "爬取http://jsggzy.jszwfw.gov.cn/jyxx/tradeInfonew.html?type=jsgc\n1. 水利工程板块中的【招标计划/招标计划变更公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如水利工程-招标计划/招标计划变更公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选水利工程 + 招标计划/招标计划变更公告,筛选“发布时间”为“近三天”\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773369324882", - "city": "省公共平台-水利工程-招标公告", - "prompt": "爬取http://jsggzy.jszwfw.gov.cn/jyxx/tradeInfonew.html?type=jsgc\n1. 水利工程板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如水利工程-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选水利工程 + 招标公告,筛选“发布时间”为“近三天”\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": true, - "mode": "glm-5" - }, - { - "id": "task-1773369457387", - "city": "省建设平台-房屋建筑施工-招标公告", - "prompt": "爬取http://www.jszb.com.cn/JSZB/YW_info/ZhaoBiaoGG/MoreInfo_ZBGG.aspx?Type=%B7%BF%CE%DD%BD%A8%D6%FE%CA%A9%B9%A4\n1. 房屋建筑施工板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房屋建筑施工-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选房屋建筑施工-招标公告\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773369549735", - "city": "省建设平台-市政工程施工-招标公告", - "prompt": "爬取http://www.jszb.com.cn/JSZB/YW_info/ZhaoBiaoGG/MoreInfo_ZBGG.aspx?Type=%CA%D0%D5%FE%B9%A4%B3%CC%CA%A9%B9%A4\n1. 市政工程施工板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如市政工程施工-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选市政工程施工-招标公告\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": false, - "mode": "qwen3.5-plus" - }, - { - "id": "task-1773369738593", - "city": "省建设平台-单独装饰装修施工-招标公告", - "prompt": "爬取http://www.jszb.com.cn/JSZB/YW_info/ZhaoBiaoGG/MoreInfo_ZBGG.aspx?Type=%B5%A5%B6%C0%D7%B0%CA%CE%D7%B0%D0%DE%CA%A9%B9%A4\n1. 单独装饰装修施工板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如单独装饰装修施工-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选单独装饰装修施工-招标公告\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773369993193", - "city": "省建设平台-设计-招标公告", - "prompt": "爬取http://www.jszb.com.cn/JSZB/YW_info/ZhaoBiaoGG/MoreInfo_ZBGG.aspx?Type=%C9%E8%BC%C6\n1. 设计板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如设计-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选设计-招标公告\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - }, - { - "id": "task-1773370032974", - "city": "省建设平台-水利-招标公告", - "prompt": "爬取http://www.jszb.com.cn/JSZB/YW_info/ZhaoBiaoGG/MoreInfo_ZBGG.aspx?Type=%CB%AE%C0%FB\n1. 水利板块中的【招标公告】模块\n\n按照以下要求进行:\n【要求一】获取列表数据:\n如果当前列表没有当天的数据,默认获取最新的1条数据。如果当前列表有多条当天数据,请获取当天的全部数据。将获取到的列表数据先存下来:\n【要求二】列表数据处理:\n(1)项目名称:必须要使用列表中的项目名称!而且必须先记住列表中的项目名称!不可以用其他项目名称来替代!记录到“project_name”字段中。\n(2)如果列表已经有金额,依据表头里的金额单位,比如“万元”,需要转为元,不需要打开详情页面去爬取项目金额,记录到“amount_yuan”字段中。\n如果列表没有金额,打开详情页去查看项目金额。记录到“amount_yuan”字段中。\n(3)项目金额:可能为合同预估价/最高投标限价等等。不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n(4)项目名称和项目金额必须对应,如果获取不到金额,将金额直接设置为null值。禁止联想生成项目金额。\n\n【要求三】\n(1)必须记录下每个项目的详情链接,记录到\"target_link\"字段中。\n\n输出结果为json,json结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如水利-招标公告\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link\": \"http...\"\n\t }\n ]\n}\n```\n## 采集方法\n1. 使用 agent-browser 工具访问页\n2. 筛选水利-招标公告\n3. 点击项目进入详情页\n4. 获取详情页URL\n5. 保存详情页内容到文件\n6. 使用 takeMoney 工具提取金额", - "enabled": false, - "useBrowser": false, - "mode": "glm-5" - } - ], "scheduler": { - "enabled": false, + "enabled": true, "cronTime": "0 9 * * *", - "description": "每天9点采集当日项目" + "threshold": 100000, + "description": "每天9点采集当日项目", + "timeRange": "thisMonth" }, "email": { "smtpHost": "smtp.qq.com", "smtpPort": 587, "smtpUser": "1076597680@qq.com", "smtpPass": "nfrjdiraqddsjeeh", - "recipients": "5482498@qq.com" + "recipients": "1650243281@qq.com" } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index a019c4d..cf0e049 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,14 +8,14 @@ "name": "njggzy-scraper", "version": "2.0.0", "dependencies": { - "@mendable/firecrawl-js": "^4.15.2", - "cheerio": "^1.0.0-rc.12", + "@mendable/firecrawl-js": "latest", + "better-sqlite3": "^12.8.0", "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^5.2.1", "node-cron": "^4.2.1", "nodemailer": "^7.0.11", - "zod": "^4.3.6" + "zod": "^3.24.2" } }, "node_modules/@mendable/firecrawl-js": { @@ -33,15 +33,6 @@ "node": ">=22.0.0" } }, - "node_modules/@mendable/firecrawl-js/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", @@ -97,6 +88,60 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "12.8.0", + "resolved": "https://registry.npmmirror.com/better-sqlite3/-/better-sqlite3-12.8.0.tgz", + "integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { "version": "2.2.1", "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.1.tgz", @@ -137,11 +182,29 @@ "url": "https://opencollective.com/express" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } }, "node_modules/bytes": { "version": "3.1.2", @@ -181,47 +244,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cheerio": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/cheerio/-/cheerio-1.1.2.tgz", - "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.0.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.12.0", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=20.18.1" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -288,34 +315,6 @@ "node": ">= 0.10" } }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmmirror.com/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmmirror.com/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", @@ -333,6 +332,30 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -351,59 +374,13 @@ "node": ">= 0.8" } }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmmirror.com/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "node": ">=8" } }, "node_modules/dotenv": { @@ -447,29 +424,13 @@ "node": ">= 0.8" } }, - "node_modules/encoding-sniffer": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "once": "^1.4.0" } }, "node_modules/es-define-property": { @@ -532,6 +493,15 @@ "node": ">= 0.6" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/express": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/express/-/express-5.2.1.tgz", @@ -600,6 +570,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.1.tgz", @@ -675,6 +651,12 @@ "node": ">= 0.8" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", @@ -721,6 +703,12 @@ "node": ">= 0.4" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", @@ -772,37 +760,6 @@ "node": ">= 0.4" } }, - "node_modules/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", @@ -823,17 +780,25 @@ "url": "https://opencollective.com/express" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" }, "node_modules/inherits": { "version": "2.0.4", @@ -841,6 +806,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -907,12 +878,45 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "license": "MIT" + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz", @@ -922,6 +926,18 @@ "node": ">= 0.6" } }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-cron": { "version": "4.2.1", "resolved": "https://registry.npmmirror.com/node-cron/-/node-cron-4.2.1.tgz", @@ -940,18 +956,6 @@ "node": ">=6.0.0" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", @@ -994,55 +998,6 @@ "wrappy": "1" } }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmmirror.com/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", @@ -1062,6 +1017,32 @@ "url": "https://opencollective.com/express" } }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1081,6 +1062,16 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz", @@ -1136,6 +1127,35 @@ "url": "https://opencollective.com/express" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", @@ -1152,12 +1172,44 @@ "node": ">= 18" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/send/-/send-1.2.0.tgz", @@ -1298,6 +1350,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", @@ -1307,6 +1404,52 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1316,6 +1459,18 @@ "node": ">=0.6" } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz", @@ -1361,15 +1516,6 @@ "integrity": "sha512-TvkrTUpv7gCPlcnSoEwUVUBwsdheKm+HF5u2tPAKubkIGMfovdSizCTaZRY/NhR8+Ijy8iZZUapbVQAsNrkFrw==", "license": "MIT" }, - "node_modules/undici": { - "version": "7.16.0", - "resolved": "https://registry.npmmirror.com/undici/-/undici-7.16.0.tgz", - "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", @@ -1379,6 +1525,12 @@ "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", @@ -1388,27 +1540,6 @@ "node": ">= 0.8" } }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", @@ -1416,9 +1547,9 @@ "license": "ISC" }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "version": "3.25.76", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index c218076..6b1c7be 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@mendable/firecrawl-js": "latest", + "better-sqlite3": "^12.8.0", "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^5.2.1", @@ -16,4 +17,4 @@ "nodemailer": "^7.0.11", "zod": "^3.24.2" } -} \ No newline at end of file +} diff --git a/public/index.html b/public/index.html index 77c57a4..0f1ab2a 100644 --- a/public/index.html +++ b/public/index.html @@ -534,6 +534,13 @@
+
+
+ + +
+
+ @@ -542,17 +549,18 @@ - - +
提示词 模型 状态浏览器 操作
加载中...加载中...
+
+ @@ -582,13 +590,6 @@ -
- - -
@@ -698,11 +699,6 @@ 启用
-
- -
@@ -713,6 +709,8 @@ - \ No newline at end of file + diff --git a/results.json b/results.json deleted file mode 100644 index 7381f54..0000000 --- a/results.json +++ /dev/null @@ -1,266 +0,0 @@ -[ - { - "taskId": "task-1710000000001", - "city": "南京市", - "scrapedAt": "2026-03-10T08:12:18.355Z", - "data": { - "results": [ - { - "type": "房建市政", - "project_name": "星甸污水处理厂提标改造工程(一期)- 施工", - "amount_yuan": 5205328.1, - "date": "2026-03-10", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/fjsz/068001/068001002/20260310/84ce20ef-3ccf-414e-ad95-969296ae19a6.html" - }, - { - "type": "交通水务", - "project_name": "南京港华燃气 sueper 大客户管理系统项目", - "amount_yuan": 8820000, - "date": "2026-03-10", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/jtsw/069002/20260310/4c99f020-5b79-dffb-e063-3345c40a2f88.html" - }, - { - "type": "政府采购", - "project_name": "南京市公安局交通管理局机动车驾驶人考试中心考场服务项目", - "amount_yuan": 11130000, - "date": "2026-03-10", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/zfcg/055001/055001001/20260310/4c99f020-5c2c-dffb-e063-3345c40a2f88.html" - } - ], - "total": 3 - }, - "id": "result-1773130338356-w41hf" - }, - { - "taskId": "task-1710000000001", - "city": "南京市", - "scrapedAt": "2026-03-10T07:53:50.177Z", - "data": { - "results": [ - { - "type": "房建市政", - "project_name": "星甸污水处理厂提标改造工程(一期)", - "amount_yuan": 2776800, - "date": "2026-03-10", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/fjsz/068001/068001002/20260310/84ce20ef-3ccf-414e-ad95-969296ae19a6.html" - }, - { - "type": "房建市政", - "project_name": "【澄清公告】麒麟科创园具身智能训练场装修项目", - "amount_yuan": 833000, - "date": "2026-03-10", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/fjsz/068001/068001002/20260310/945533de-a0ed-4118-a30e-ec831a4e337b.html" - }, - { - "type": "房建市政", - "project_name": "【澄清公告】轻质耐热合金制造基地项目", - "amount_yuan": 100000000, - "date": "2026-03-09", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/fjsz/068001/068001002/20260309/fb9e7e41-d809-458c-9cd4-59b90e84fbc5.html" - }, - { - "type": "工程货物", - "project_name": "JNFJ2500777-04HW-GHa01", - "amount_yuan": 66000000, - "date": "2026-03-07", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/gchw/070001/20260307/041138ff-870c-4240-b78c-f6861f242594.html" - }, - { - "type": "工程货物", - "project_name": "NJFJ2500863-03HWGH", - "amount_yuan": 484057073, - "date": "2026-03-06", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/gchw/070001/20260306/a547fb75-c984-4479-948b-b5800829742a.html" - }, - { - "type": "工程货物", - "project_name": "JNFJ2500777-06QTGH", - "amount_yuan": 535784084, - "date": "2026-03-06", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/gchw/070001/20260306/cab7606b-df8f-419e-be5a-4ce5213ef5af.html" - }, - { - "type": "交通水务", - "project_name": "(市交易中心) 八卦洲街道CBA3上坝子堤路等农村公路改善提升工程施工招标公告", - "amount_yuan": 338600000, - "date": "2026-03-04", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/jtsw/069001/069001001/20260304/3d25610f-239d-4e1c-9923-0d22820c711a.html" - }, - { - "type": "交通水务", - "project_name": "(市交易中心) 南京地铁1号线通信信号系统设备更新改造项目通信系统安装施工招标公告", - "amount_yuan": 1693000000, - "date": "2026-02-14", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/jtsw/069001/069001001/20260214/4ddc9129-c508-4ae9-b103-f52851ff2ee2.html" - }, - { - "type": "交通水务", - "project_name": "(市交易中心) 356省道浦口西江路至苏皖省界段建设工程起点至龙港路段供电管线迁改工程招标公告", - "amount_yuan": 2000000, - "date": "2026-02-12", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/jtsw/069001/069001001/20260212/5ae97a66-ca0d-40e4-991c-f91d6164661f.html" - }, - { - "type": "政府采购", - "project_name": "JSZC-320111-JZCG-G2026-0013", - "amount_yuan": 204000, - "date": "2026-03-10", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/zfcg/067001/067001001/20260310/adb322241a19459cbaaf158e4ad87500.html" - }, - { - "type": "政府采购", - "project_name": "JSZC-320100-JZCG-C2026-0026", - "amount_yuan": 1020000, - "date": "2026-03-10", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/zfcg/067001/067001001/20260310/a801f9ba484741f9b373207edcbc0320.html" - }, - { - "type": "政府采购", - "project_name": "JSZC-320100-JZCG-C2026-0030", - "amount_yuan": 50000, - "date": "2026-03-10", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/zfcg/067001/067001001/20260310/19c6d526a57c451fbbbe2c1b7a779c02.html" - }, - { - "type": "产权交易", - "project_name": "北京北汽福斯特股份有限公司1.3884%股权(138.8373万股)股权转让", - "amount_yuan": 66000000, - "date": "2026-04-20", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/gycq/071001/071001002/20260309/64b16240-07de-417c-8248-c44967c508f9.html" - }, - { - "type": "产权交易", - "project_name": "北京北汽九龙出租汽车股份有限公司1.3884%(91.6327万股)股权转让", - "amount_yuan": 484057073, - "date": "2026-04-20", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/gycq/071001/071001002/20260309/57c7f45a-b28e-4ba9-b11b-62cca6f6218e.html" - }, - { - "type": "产权交易", - "project_name": "南京鑫一汇企业管理有限公司20%股权转让", - "amount_yuan": 2000000, - "date": "2026-02-25", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/gycq/071001/071001002/20260108/442d7bf6-f11d-49f5-9409-aef8acd4fbf6.html" - }, - { - "type": "土地矿产", - "project_name": "南京市规划和自然资源局六合分局国有土地使用权挂牌出让公告(宁六工[2026]第5号)", - "amount_yuan": 664000, - "date": "2026-03-06", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/tdkc/072001/20260306/10586876.html" - }, - { - "type": "土地矿产", - "project_name": "南京市规划和自然资源局六合分局国有土地使用权挂牌出让公告(宁六工[2026]第4号)", - "amount_yuan": 3320000, - "date": "2026-03-04", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/tdkc/072001/20260304/10561567.html" - }, - { - "type": "土地矿产", - "project_name": "南京市规划和自然资源局国有土地使用权挂牌出让公告(宁出[2026]01号)", - "amount_yuan": 50000, - "date": "2026-02-28", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/tdkc/072001/20260228/10528773.html" - }, - { - "type": "铁路航运", - "project_name": "【施工类】暂无最新项目信息", - "amount_yuan": 0, - "date": "2026-03-02", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/tlhy/073001/073001001/20240702/3a9b3413-3931-4d51-af72-78e11162ed74.html" - }, - { - "type": "农村产权", - "project_name": "栖霞区龙潭街道马渡村新桥组12.01亩土地出租通知公告主要信息", - "amount_yuan": 28824, - "date": "2026-03-09", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/nccq/102001/20260309/4c99efee-79f1-e35e-e063-3345c40a7178.html" - }, - { - "type": "农村产权", - "project_name": "栖霞区龙潭街道马渡村新春组18.93亩土地出租通知公告主要信息", - "amount_yuan": 18015, - "date": "2026-03-09", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/nccq/102001/20260309/4c99efee-79ed-e35e-e063-3345c40a7178.html" - }, - { - "type": "农村产权", - "project_name": "栖霞区龙潭街道马渡村厢林组47.37亩土地出租通知公告主要信息", - "amount_yuan": 113688, - "date": "2026-03-09", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/nccq/102001/20260309/4c99efee-79e9-e35e-e063-3345c40a7178.html" - } - ], - "total": 22 - }, - "id": "result-1773129230177-5tjno" - }, - { - "taskId": "task-1710000000001", - "city": "南京市", - "scrapedAt": "2026-03-10T07:34:51.928Z", - "data": { - "results": [ - { - "type": "房建市政", - "project_name": "星甸污水处理厂提标改造工程(一期)", - "amount_yuan": 5205328.1, - "date": "2026-03-10", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/fjsz/068001/068001002/20260310/84ce20ef-3ccf-414e-ad95-969296ae19a6.html" - }, - { - "type": "工程货物", - "project_name": "【澄清公告】NO.2024G39地块建设项目 - 电梯设备采购及相关服务(暂估价)", - "amount_yuan": 4069576.31, - "date": "2026-03-07", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/gchw/070001/20260307/041138ff-870c-4240-b78c-f6861f242594.html" - }, - { - "type": "交通水务", - "project_name": "【施工】八卦洲街道CBA3上坝子堤路等农村公路改善提升工程施工招标公告", - "amount_yuan": 4020000, - "date": "2026-03-04", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/jtsw/069001/069001001/20260304/3d25610f-239d-4e1c-9923-0d22820c711a.html" - }, - { - "type": "政府采购", - "project_name": "JSZC-320111-JZCG-G2026-0013 政府采购公告", - "amount_yuan": 11130000, - "date": "2026-03-10", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/zfcg/067001/067001001/20260310/adb322241a19459cbaaf158e4ad87500.html" - }, - { - "type": "产权交易", - "project_name": "2026320100CA00003 产权交易公告", - "amount_yuan": 2776800, - "date": "2026-04-20", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/gycq/071001/071001002/20260309/64b16240-07de-417c-8248-c44967c508f9.html" - }, - { - "type": "土地矿产", - "project_name": "南京市规划和自然资源局六合分局国有土地使用权挂牌出让公告(宁六工[2026]第5号)", - "amount_yuan": 664000, - "date": "2026-03-06", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/tdkc/072001/20260306/10586876.html" - }, - { - "type": "铁路航运", - "project_name": "【施工类】暂无最新项目信息", - "amount_yuan": 0, - "date": "2026-03-02", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/tlhy/073001/073001001/20240702/3a9b3413-3931-4d51-af72-78e11162ed74.html" - }, - { - "type": "农村产权", - "project_name": "栖霞区龙潭街道马渡村新桥组12.01亩土地出租通知公告", - "amount_yuan": 9608, - "date": "2026-03-09", - "target_link": "https://njggzy.nanjing.gov.cn/njweb/nccq/102001/20260309/4c99efee-79f1-e35e-e063-3345c40a7178.html" - } - ], - "total": 8 - }, - "id": "result-1773128091930-edg4p" - } -] \ No newline at end of file diff --git a/src/agentService.js b/src/agentService.js index c758d9e..2aff06f 100644 --- a/src/agentService.js +++ b/src/agentService.js @@ -49,7 +49,6 @@ async function fetchWithRetry(url, fetchOptions, retries = MAX_FETCH_RETRIES, lo */ async function createTask(prompt, options = {}) { const baseUrl = options.baseUrl || DEFAULT_BASE_URL; - const useBrowser = options.useBrowser ?? false; const mode = normalizeMode(options.mode); const taskId = generateTaskId(); const logPrefix = options.logPrefix || '[Agent]'; @@ -57,7 +56,7 @@ async function createTask(prompt, options = {}) { const res = await fetchWithRetry(`${baseUrl}/agent/createTask`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ taskId, prompt, useBrowser, mode }), + body: JSON.stringify({ taskId, prompt, mode }), }, MAX_FETCH_RETRIES, logPrefix); if (!res.ok) { @@ -104,7 +103,6 @@ async function checkTask(taskId, options = {}) { */ export async function runAgentTask(prompt, options = {}) { const baseUrl = options.baseUrl || DEFAULT_BASE_URL; - const useBrowser = options.useBrowser ?? false; const mode = normalizeMode(options.mode); const pollInterval = options.pollInterval || DEFAULT_POLL_INTERVAL; const timeout = options.timeout || DEFAULT_TIMEOUT; @@ -112,7 +110,7 @@ export async function runAgentTask(prompt, options = {}) { console.log(`${logPrefix} 创建任务...`); console.log(`${logPrefix} 使用 mode: ${mode}`); - const { taskId } = await createTask(prompt, { baseUrl, useBrowser, mode, logPrefix }); + const { taskId } = await createTask(prompt, { baseUrl, mode, logPrefix }); console.log(`${logPrefix} 任务已创建: ${taskId}`); const startTime = Date.now(); diff --git a/src/resultStore.js b/src/resultStore.js new file mode 100644 index 0000000..305d437 --- /dev/null +++ b/src/resultStore.js @@ -0,0 +1,471 @@ +import Database from 'better-sqlite3'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const DB_PATH = + process.env.APP_DB_PATH || + process.env.RESULTS_DB_PATH || + join(__dirname, '..', 'data', 'results.sqlite'); +const CONFIG_PATH = join(__dirname, '..', 'config.json'); +const MAX_RESULT_RECORDS = 500; +const DEFAULT_TASK_MODE = 'qwen3.5-plus'; +const TASK_COLUMNS = ['id', 'city', 'plate_name', 'prompt', 'enabled', 'mode', 'created_at', 'updated_at']; + +let db = null; +let initialized = false; + +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + +function generateResultId() { + return `result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; +} + +function generateTaskId() { + return `task-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`; +} + +function getDefaultJsonConfig() { + return { + agent: { + baseUrl: '', + pollInterval: 3000, + timeout: 300000, + }, + scheduler: { + enabled: false, + cronTime: '0 9 * * *', + threshold: 100000, + description: '', + timeRange: 'thisMonth', + }, + email: { + smtpHost: '', + smtpPort: 587, + smtpUser: '', + smtpPass: '', + recipients: '', + }, + }; +} + +function normalizeJsonConfig(input = {}) { + const defaults = getDefaultJsonConfig(); + const agent = input.agent || {}; + const scheduler = input.scheduler || {}; + const email = input.email || {}; + + return { + agent: { + baseUrl: typeof agent.baseUrl === 'string' ? agent.baseUrl : defaults.agent.baseUrl, + pollInterval: Number.isFinite(agent.pollInterval) ? agent.pollInterval : defaults.agent.pollInterval, + timeout: Number.isFinite(agent.timeout) ? agent.timeout : defaults.agent.timeout, + }, + scheduler: { + enabled: scheduler.enabled === true, + cronTime: typeof scheduler.cronTime === 'string' && scheduler.cronTime.trim() + ? scheduler.cronTime + : defaults.scheduler.cronTime, + threshold: Number.isFinite(scheduler.threshold) ? scheduler.threshold : defaults.scheduler.threshold, + description: typeof scheduler.description === 'string' ? scheduler.description : defaults.scheduler.description, + timeRange: typeof scheduler.timeRange === 'string' && scheduler.timeRange.trim() + ? scheduler.timeRange + : defaults.scheduler.timeRange, + }, + email: { + smtpHost: typeof email.smtpHost === 'string' ? email.smtpHost : defaults.email.smtpHost, + smtpPort: Number.isFinite(email.smtpPort) ? email.smtpPort : defaults.email.smtpPort, + smtpUser: typeof email.smtpUser === 'string' ? email.smtpUser : defaults.email.smtpUser, + smtpPass: typeof email.smtpPass === 'string' ? email.smtpPass : defaults.email.smtpPass, + recipients: typeof email.recipients === 'string' ? email.recipients : defaults.email.recipients, + }, + }; +} + +function normalizeTaskMode(value) { + if (typeof value === 'string' && value.trim()) return value.trim(); + return DEFAULT_TASK_MODE; +} + +function buildTaskRecord(task = {}) { + return { + id: task.id || generateTaskId(), + city: task.city || '', + plateName: task.plateName || '', + prompt: task.prompt || '', + enabled: task.enabled !== false, + mode: normalizeTaskMode(task.mode), + }; +} + +function buildResultRecord(result = {}) { + return { + id: result.id || generateResultId(), + taskId: result.taskId || null, + city: result.city || null, + scrapedAt: result.scrapedAt || new Date().toISOString(), + error: result.error || null, + data: result.data ?? null, + }; +} + +function parseTaskRow(row) { + return { + id: row.id, + city: row.city, + plateName: row.plate_name, + prompt: row.prompt, + enabled: row.enabled === 1, + mode: normalizeTaskMode(row.mode), + }; +} + +function parseResultRow(row) { + return { + id: row.id, + taskId: row.task_id, + city: row.city, + scrapedAt: row.scraped_at, + error: row.error, + data: row.data_json ? JSON.parse(row.data_json) : null, + }; +} + +function getDb() { + if (db) return db; + + mkdirSync(dirname(DB_PATH), { recursive: true }); + db = new Database(DB_PATH); + db.pragma('journal_mode = WAL'); + return db; +} + +function ensureSchema() { + getDb().exec(` + CREATE TABLE IF NOT EXISTS results ( + id TEXT PRIMARY KEY, + task_id TEXT, + city TEXT, + scraped_at TEXT NOT NULL, + error TEXT, + data_json TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + CREATE INDEX IF NOT EXISTS idx_results_scraped_at ON results (scraped_at DESC); + CREATE INDEX IF NOT EXISTS idx_results_city ON results (city); + CREATE INDEX IF NOT EXISTS idx_results_task_id ON results (task_id); + + CREATE TABLE IF NOT EXISTS tasks ( + id TEXT PRIMARY KEY, + city TEXT, + plate_name TEXT, + prompt TEXT, + enabled INTEGER NOT NULL DEFAULT 1, + mode TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + `); +} + +function ensureTasksTableShape() { + const columns = getDb().prepare(`PRAGMA table_info(tasks)`).all(); + const columnNames = columns.map((column) => column.name); + const hasLegacyBrowserColumn = columnNames.includes('use_browser'); + const matchesExpectedShape = + columnNames.length === TASK_COLUMNS.length && + TASK_COLUMNS.every((column, index) => columnNames[index] === column); + + if (!hasLegacyBrowserColumn && matchesExpectedShape) return; + + getDb().exec(` + BEGIN; + + ALTER TABLE tasks RENAME TO tasks_legacy; + + CREATE TABLE tasks ( + id TEXT PRIMARY KEY, + city TEXT, + plate_name TEXT, + prompt TEXT, + enabled INTEGER NOT NULL DEFAULT 1, + mode TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + INSERT INTO tasks (id, city, plate_name, prompt, enabled, mode, created_at, updated_at) + SELECT + id, + city, + plate_name, + prompt, + COALESCE(enabled, 1), + COALESCE(mode, '${DEFAULT_TASK_MODE}'), + COALESCE(created_at, datetime('now')), + COALESCE(updated_at, datetime('now')) + FROM tasks_legacy; + + DROP TABLE tasks_legacy; + + COMMIT; + `); +} + +function trimResults(limit = MAX_RESULT_RECORDS) { + getDb().prepare(` + DELETE FROM results + WHERE id NOT IN ( + SELECT id + FROM results + ORDER BY scraped_at DESC, rowid DESC + LIMIT ? + ) + `).run(limit); +} + +function readJsonIfExists(filePath) { + if (!existsSync(filePath)) return null; + return JSON.parse(readFileSync(filePath, 'utf-8')); +} + +function stripTasksFromConfig(config) { + if (!config || typeof config !== 'object') return getDefaultJsonConfig(); + const { agent, scheduler, email } = config; + return normalizeJsonConfig({ agent, scheduler, email }); +} + +function ensureJsonConfigExists() { + if (existsSync(CONFIG_PATH)) return; + writeFileSync(CONFIG_PATH, JSON.stringify(getDefaultJsonConfig(), null, 2), 'utf-8'); +} + +function queryBaseRows({ city, taskId }) { + const clauses = []; + const params = []; + + if (city) { + clauses.push('city = ?'); + params.push(city); + } + + if (taskId) { + clauses.push('task_id = ?'); + params.push(taskId); + } + + const whereSql = clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : ''; + const sql = ` + SELECT id, task_id, city, scraped_at, error, data_json + FROM results + ${whereSql} + ORDER BY scraped_at DESC, rowid DESC + `; + + return getDb().prepare(sql).all(...params).map(parseResultRow); +} + +function matchSection(record, section) { + if (!section) return true; + if (record.section === section || record.subsection === section) return true; + + const items = record.data?.results || []; + return items.some((item) => item.section === section || item.subsection === section); +} + +function matchType(record, type) { + if (!type) return true; + if (record.type === type) return true; + + const items = record.data?.results || []; + return items.some((item) => item.type === type); +} + +export function initResultsStore() { + if (initialized) return; + ensureSchema(); + ensureTasksTableShape(); + ensureJsonConfigExists(); + initialized = true; +} + +export function loadConfig() { + initResultsStore(); + const jsonConfig = normalizeJsonConfig(readJsonIfExists(CONFIG_PATH) || getDefaultJsonConfig()); + return { + ...clone(jsonConfig), + tasks: listTasks(), + }; +} + +export function saveConfig(config) { + initResultsStore(); + const jsonConfig = stripTasksFromConfig(config); + writeFileSync(CONFIG_PATH, JSON.stringify(jsonConfig, null, 2), 'utf-8'); + return { + ...clone(jsonConfig), + tasks: listTasks(), + }; +} + +export function listTasks() { + initResultsStore(); + return getDb() + .prepare(` + SELECT id, city, plate_name, prompt, enabled, mode + FROM tasks + ORDER BY rowid DESC + `) + .all() + .map(parseTaskRow); +} + +export function getTaskById(id) { + initResultsStore(); + const row = getDb() + .prepare(` + SELECT id, city, plate_name, prompt, enabled, mode + FROM tasks + WHERE id = ? + `) + .get(id); + + return row ? parseTaskRow(row) : null; +} + +export function createTask(task) { + initResultsStore(); + const record = buildTaskRecord(task); + getDb().prepare(` + INSERT INTO tasks (id, city, plate_name, prompt, enabled, mode, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now')) + `).run( + record.id, + record.city, + record.plateName, + record.prompt, + record.enabled ? 1 : 0, + record.mode, + ); + return record; +} + +export function updateTask(id, patch) { + initResultsStore(); + const current = getTaskById(id); + if (!current) return null; + + const next = buildTaskRecord({ ...current, ...patch, id }); + getDb().prepare(` + UPDATE tasks + SET city = ?, + plate_name = ?, + prompt = ?, + enabled = ?, + mode = ?, + updated_at = datetime('now') + WHERE id = ? + `).run( + next.city, + next.plateName, + next.prompt, + next.enabled ? 1 : 0, + next.mode, + id, + ); + + return next; +} + +export function deleteTaskById(id) { + initResultsStore(); + const result = getDb().prepare('DELETE FROM tasks WHERE id = ?').run(id); + return result.changes > 0; +} + +export function appendResult(result) { + initResultsStore(); + const record = buildResultRecord(result); + + getDb().prepare(` + INSERT INTO results (id, task_id, city, scraped_at, error, data_json) + VALUES (?, ?, ?, ?, ?, ?) + `).run( + record.id, + record.taskId, + record.city, + record.scrapedAt, + record.error, + record.data === null ? null : JSON.stringify(record.data), + ); + + trimResults(); + return record; +} + +export function listResults({ city, section, type, taskId, page = 1, pageSize = 20 } = {}) { + initResultsStore(); + + let results = queryBaseRows({ city, taskId }); + if (section) results = results.filter((record) => matchSection(record, section)); + if (type) results = results.filter((record) => matchType(record, type)); + + const normalizedPage = Math.max(1, parseInt(page, 10) || 1); + const normalizedPageSize = Math.max(1, parseInt(pageSize, 10) || 20); + const start = (normalizedPage - 1) * normalizedPageSize; + + return { + total: results.length, + page: normalizedPage, + pageSize: normalizedPageSize, + data: results.slice(start, start + normalizedPageSize), + }; +} + +export function deleteResultById(id) { + initResultsStore(); + const result = getDb().prepare('DELETE FROM results WHERE id = ?').run(id); + return result.changes > 0; +} + +export function clearResults() { + initResultsStore(); + getDb().prepare('DELETE FROM results').run(); +} + +export function getResultFilters() { + initResultsStore(); + + const rows = queryBaseRows({}); + const cities = [...new Set(rows.map((row) => row.city).filter(Boolean))]; + const sections = new Set(); + const types = new Set(); + + for (const row of rows) { + if (row.section) sections.add(row.section); + if (row.subsection) sections.add(row.subsection); + if (row.type) types.add(row.type); + + for (const item of row.data?.results || []) { + if (item.section) sections.add(item.section); + if (item.subsection) sections.add(item.subsection); + if (item.type) types.add(item.type); + } + } + + return { + cities, + sections: [...sections], + types: [...types], + }; +} + +export function getResultsDbPath() { + return DB_PATH; +} diff --git a/src/scheduler.js b/src/scheduler.js index 5e16c3a..a84ccca 100644 --- a/src/scheduler.js +++ b/src/scheduler.js @@ -1,208 +1,177 @@ import 'dotenv/config'; import cron from 'node-cron'; -import { readFileSync, writeFileSync, existsSync } from 'fs'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; import { sendScraperResultsEmail } from './emailService.js'; import { runAgentTask } from './agentService.js'; +import { initResultsStore, loadConfig, appendResult } from './resultStore.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const RESULTS_PATH = join(__dirname, '..', 'results.json'); const DEFAULT_TASK_MODE = 'qwen3.5-plus'; +let currentScheduledTask = null; + function normalizeTaskMode(value) { if (typeof value === 'string' && value.trim()) return value.trim(); return DEFAULT_TASK_MODE; } -function loadConfig() { - try { - const configPath = join(__dirname, '..', 'config.json'); - return JSON.parse(readFileSync(configPath, 'utf-8')); - } catch (error) { - console.error('加载配置文件失败:', error.message); - return null; - } -} - -// ========== 结果存取 ========== - -function readResults() { - if (!existsSync(RESULTS_PATH)) return []; - try { - return JSON.parse(readFileSync(RESULTS_PATH, 'utf-8')); - } catch (e) { - return []; - } -} - -function saveResults(results) { - writeFileSync(RESULTS_PATH, JSON.stringify(results, null, 2), 'utf-8'); -} - -function appendResult(result) { - const results = readResults(); - results.unshift({ ...result, id: `result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}` }); - if (results.length > 500) results.splice(500); - saveResults(results); -} - -// ========== 任务执行 ========== - async function runTask(task, agentCfg) { - const useBrowser = typeof task.useBrowser === 'boolean' ? task.useBrowser : agentCfg.useBrowser; const mode = normalizeTaskMode(task.mode); - console.log(`[定时任务][Agent] ${task.city}:开始执行`); - console.log(`[定时任务][Agent] ${task.city}:mode=${mode}`); + + console.log(`[Scheduler][Agent] ${task.city}: start`); + console.log(`[Scheduler][Agent] ${task.city}: mode=${mode}`); + const { results } = await runAgentTask(task.prompt, { baseUrl: agentCfg.baseUrl, - useBrowser, mode, pollInterval: agentCfg.pollInterval, timeout: agentCfg.timeout, - logPrefix: `[定时任务][Agent][${task.city}]`, + logPrefix: `[Scheduler][Agent][${task.city}]`, }); - console.log(`[定时任务][Agent] ${task.city}:获取到 ${results.length} 条结果`); - const record = { + console.log(`[Scheduler][Agent] ${task.city}: ${results.length} results`); + + return appendResult({ taskId: task.id, city: task.city, scrapedAt: new Date().toISOString(), data: { results, total: results.length }, - }; - appendResult(record); - return record; + }); } -// ========== 定时任务执行函数 ========== - async function executeScheduledTask(config) { try { console.log('========================================'); - console.log('定时任务开始执行'); - console.log('执行时间:', new Date().toLocaleString('zh-CN')); + console.log('Scheduler started'); + console.log('Time:', new Date().toLocaleString('zh-CN')); console.log('========================================'); - const tasks = (config.tasks || []).filter(t => t.enabled); + const tasks = (config.tasks || []).filter((task) => task.enabled); const agentCfg = config.agent || {}; if (tasks.length === 0) { - console.log('没有已启用的任务,跳过'); + console.log('No enabled tasks, skip'); return; } - console.log(`共 ${tasks.length} 个已启用的任务`); + console.log(`Enabled tasks: ${tasks.length}`); const results = []; for (const task of tasks) { try { - console.log(`\n---------- 任务: ${task.city} ----------`); - const r = await runTask(task, agentCfg); - results.push(r); - console.log(`✓ 执行成功`); - } catch (err) { - console.error(`✗ 执行失败: ${err.message}`); - const errRecord = { + console.log(`\n---------- Task: ${task.city} ----------`); + const record = await runTask(task, agentCfg); + results.push(record); + console.log('Task completed'); + } catch (error) { + console.error(`Task failed: ${error.message}`); + const errorRecord = appendResult({ taskId: task.id, city: task.city, scrapedAt: new Date().toISOString(), - error: err.message, + error: error.message, data: null, - }; - appendResult(errRecord); - results.push(errRecord); + }); + results.push(errorRecord); } } - const successCount = results.filter(r => !r.error).length; - const failCount = results.filter(r => r.error).length; - console.log(`\n========== 执行完成 ==========`); - console.log(`成功: ${successCount},失败: ${failCount}`); + const successCount = results.filter((item) => !item.error).length; + const failCount = results.filter((item) => item.error).length; + console.log('\n========== Scheduler finished =========='); + console.log(`Success: ${successCount}, Failed: ${failCount}`); if (successCount === 0) { - console.log('没有成功的结果,不发送邮件'); + console.log('No successful results, skip email'); return; } if (config.email?.smtpHost && config.email?.smtpUser) { - console.log('\n正在发送结果邮件...'); + console.log('\nSending email...'); try { const emailResult = await sendScraperResultsEmail(config.email, results); - console.log('邮件发送成功! MessageId:', emailResult.messageId); - } catch (emailErr) { - console.error('邮件发送失败:', emailErr.message); + console.log('Email sent:', emailResult.messageId); + } catch (error) { + console.error('Email failed:', error.message); } } else { - console.log('邮件配置不完整,跳过邮件发送'); + console.log('Email config incomplete, skip email'); } console.log('========================================'); } catch (error) { console.error('========================================'); - console.error('定时任务执行失败:', error.message); + console.error('Scheduler failed:', error.message); console.error(error.stack); console.error('========================================'); } } -// 存储当前的定时任务 -let currentScheduledTask = null; - export function initScheduler() { + initResultsStore(); + const config = loadConfig(); - if (!config) { console.error('无法启动定时任务: 配置文件加载失败'); return; } - if (!config.scheduler?.enabled) { console.log('定时任务已禁用'); return; } + if (!config.scheduler?.enabled) { + console.log('Scheduler disabled'); + return; + } const cronTime = config.scheduler.cronTime || '0 9 * * *'; - const enabledCount = (config.tasks || []).filter(t => t.enabled).length; + const enabledCount = (config.tasks || []).filter((task) => task.enabled).length; + console.log('========================================'); - console.log('定时任务已启动,执行计划:', cronTime); - console.log(`已启用的任务: ${enabledCount} 个`); - if (config.email?.recipients) console.log('收件人:', config.email.recipients); + console.log('Scheduler enabled:', cronTime); + console.log(`Enabled tasks: ${enabledCount}`); + if (config.email?.recipients) { + console.log('Recipients:', config.email.recipients); + } console.log('========================================'); - if (currentScheduledTask) { currentScheduledTask.stop(); } + if (currentScheduledTask) { + currentScheduledTask.stop(); + } - currentScheduledTask = cron.schedule(cronTime, () => { - const latestConfig = loadConfig(); - if (latestConfig) { - executeScheduledTask(latestConfig); - } - }, { timezone: 'Asia/Shanghai' }); + currentScheduledTask = cron.schedule( + cronTime, + () => { + executeScheduledTask(loadConfig()); + }, + { timezone: 'Asia/Shanghai' }, + ); } export function reloadScheduler() { - console.log('重新加载定时任务配置...'); - if (currentScheduledTask) { currentScheduledTask.stop(); currentScheduledTask = null; } + console.log('Reloading scheduler...'); + if (currentScheduledTask) { + currentScheduledTask.stop(); + currentScheduledTask = null; + } initScheduler(); } export function stopScheduler() { - if (currentScheduledTask) { - currentScheduledTask.stop(); currentScheduledTask = null; - console.log('定时任务已停止'); return true; - } - return false; + if (!currentScheduledTask) return false; + + currentScheduledTask.stop(); + currentScheduledTask = null; + console.log('Scheduler stopped'); + return true; } export function getSchedulerStatus() { const config = loadConfig(); - const enabledTasks = (config?.tasks || []).filter(t => t.enabled).length; + const enabledTasks = (config.tasks || []).filter((task) => task.enabled).length; + return { isRunning: currentScheduledTask !== null, enabledTasks, - config: config ? { + config: { enabled: config.scheduler?.enabled || false, cronTime: config.scheduler?.cronTime || '0 9 * * *', description: config.scheduler?.description || '', - } : null, + }, }; } export async function runTaskNow() { - const config = loadConfig(); - if (!config) throw new Error('配置文件加载失败'); - await executeScheduledTask(config); + initResultsStore(); + await executeScheduledTask(loadConfig()); } diff --git a/src/server.js b/src/server.js index 7450ff9..533c986 100644 --- a/src/server.js +++ b/src/server.js @@ -1,263 +1,272 @@ import 'dotenv/config'; import express from 'express'; import cors from 'cors'; -import { readFileSync, writeFileSync, existsSync } from 'fs'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; +import { + initResultsStore, + loadConfig, + saveConfig, + listTasks, + getTaskById, + createTask, + updateTask, + deleteTaskById, + listResults, + deleteResultById, + clearResults, + getResultFilters, + appendResult, +} from './resultStore.js'; import { initScheduler, runTaskNow, reloadScheduler, getSchedulerStatus } from './scheduler.js'; import { runAgentTask } from './agentService.js'; const app = express(); const PORT = process.env.PORT || 5000; +const DEFAULT_TASK_MODE = 'qwen3.5-plus'; +const MASKED_PASSWORD = '***已配置***'; app.use(cors()); app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ limit: '50mb', extended: true })); app.use(express.static('public')); -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const CONFIG_PATH = join(__dirname, '..', 'config.json'); -const RESULTS_PATH = join(__dirname, '..', 'results.json'); -const DEFAULT_TASK_MODE = 'qwen3.5-plus'; - -function readConfig() { - return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); -} - -function saveConfig(cfg) { - writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf-8'); -} - -function normalizeUseBrowser(value) { - return value === true || value === 'true'; -} - function normalizeTaskMode(value) { if (typeof value === 'string' && value.trim()) return value.trim(); return DEFAULT_TASK_MODE; } -// ========== 抓取结果存取 ========== +function buildTaskPayload(body = {}, { partial = false } = {}) { + const payload = {}; -function readResults() { - if (!existsSync(RESULTS_PATH)) return []; - try { - return JSON.parse(readFileSync(RESULTS_PATH, 'utf-8')); - } catch (e) { - return []; + if (!partial || Object.prototype.hasOwnProperty.call(body, 'city')) { + payload.city = body.city || ''; } + + if (!partial || Object.prototype.hasOwnProperty.call(body, 'plateName')) { + payload.plateName = body.plateName || ''; + } + + if (!partial || Object.prototype.hasOwnProperty.call(body, 'prompt')) { + payload.prompt = body.prompt || ''; + } + + if (!partial || Object.prototype.hasOwnProperty.call(body, 'enabled')) { + payload.enabled = body.enabled !== false; + } + + if (!partial || Object.prototype.hasOwnProperty.call(body, 'mode')) { + payload.mode = normalizeTaskMode(body.mode); + } + + return payload; } -function saveResults(results) { - writeFileSync(RESULTS_PATH, JSON.stringify(results, null, 2), 'utf-8'); -} - -function appendResult(result) { - const results = readResults(); - results.unshift({ ...result, id: `result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}` }); - if (results.length > 500) results.splice(500); - saveResults(results); -} - -// 查询结果(支持分页与筛选) -app.get('/api/results', (req, res) => { - try { - const { city, type, page = 1, pageSize = 20, taskId } = req.query; - let results = readResults(); - if (city) results = results.filter(r => r.city === city); - if (type) results = results.filter(r => { - const items = r.data?.results || []; - return items.some(item => item.type === type); - }); - if (taskId) results = results.filter(r => r.taskId === taskId); - const total = results.length; - const start = (parseInt(page) - 1) * parseInt(pageSize); - const data = results.slice(start, start + parseInt(pageSize)); - res.json({ success: true, total, page: parseInt(page), pageSize: parseInt(pageSize), data }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); - } -}); - -// 删除单条结果 -app.delete('/api/results/:id', (req, res) => { - try { - const results = readResults(); - const before = results.length; - const updated = results.filter(r => r.id !== req.params.id); - if (updated.length === before) return res.status(404).json({ success: false, error: '未找到' }); - saveResults(updated); - res.json({ success: true }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); - } -}); - -// 清空所有结果 -app.delete('/api/results', (_req, res) => { - try { - saveResults([]); - res.json({ success: true, message: '已清空所有结果' }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); - } -}); - -// 获取结果的筛选选项 -app.get('/api/results/filters', (req, res) => { - try { - const results = readResults(); - const cities = [...new Set(results.map(r => r.city).filter(Boolean))]; - const types = new Set(); - for (const r of results) { - for (const item of (r.data?.results || [])) { - if (item.type) types.add(item.type); - } - } - res.json({ success: true, data: { cities, types: [...types] } }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); - } -}); - -// ========== 任务配置 CRUD ========== - -app.get('/api/tasks', (req, res) => { - try { - const cfg = readConfig(); - const tasks = (cfg.tasks || []).map(t => ({ ...t, mode: normalizeTaskMode(t.mode) })); - res.json({ success: true, data: tasks }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); - } -}); - -app.post('/api/tasks', (req, res) => { - try { - const cfg = readConfig(); - if (!cfg.tasks) cfg.tasks = []; - const item = { - id: `task-${Date.now()}`, - city: req.body.city || '', - prompt: req.body.prompt || '', - enabled: req.body.enabled !== false, - useBrowser: normalizeUseBrowser(req.body.useBrowser), - mode: normalizeTaskMode(req.body.mode), +function maskConfigSecrets(config) { + const next = { ...config }; + if (config.email) { + next.email = { + ...config.email, + smtpPass: config.email.smtpPass ? MASKED_PASSWORD : '', }; - cfg.tasks.push(item); - saveConfig(cfg); - res.json({ success: true, data: item }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); } -}); + return next; +} -app.put('/api/tasks/:id', (req, res) => { - try { - const cfg = readConfig(); - const idx = (cfg.tasks || []).findIndex(t => t.id === req.params.id); - if (idx === -1) return res.status(404).json({ success: false, error: '未找到该配置' }); - const patch = { ...req.body }; - if (Object.prototype.hasOwnProperty.call(patch, 'useBrowser')) { - patch.useBrowser = normalizeUseBrowser(patch.useBrowser); - } - if (Object.prototype.hasOwnProperty.call(patch, 'mode')) { - patch.mode = normalizeTaskMode(patch.mode); - } - cfg.tasks[idx] = { ...cfg.tasks[idx], ...patch, id: req.params.id }; - saveConfig(cfg); - res.json({ success: true, data: cfg.tasks[idx] }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); +function mergeConfigWithExistingSecrets(incoming = {}) { + const current = loadConfig(); + const next = { + ...current, + ...incoming, + agent: { ...(current.agent || {}), ...(incoming.agent || {}) }, + scheduler: { ...(current.scheduler || {}), ...(incoming.scheduler || {}) }, + email: { ...(current.email || {}), ...(incoming.email || {}) }, + }; + + if (next.email?.smtpPass === MASKED_PASSWORD) { + next.email.smtpPass = current.email?.smtpPass || ''; } -}); -app.delete('/api/tasks/:id', (req, res) => { - try { - const cfg = readConfig(); - const before = (cfg.tasks || []).length; - cfg.tasks = (cfg.tasks || []).filter(t => t.id !== req.params.id); - if (cfg.tasks.length === before) return res.status(404).json({ success: false, error: '未找到' }); - saveConfig(cfg); - res.json({ success: true }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); - } -}); - -// ========== 任务执行(异步 + 串行锁) ========== + return next; +} let isRunning = false; -let runningStatus = null; // { taskId, city, startTime, current, total, finished, error } +let runningStatus = null; async function runTask(task) { - const cfg = readConfig(); - const agentCfg = cfg.agent || {}; - const useBrowser = typeof task.useBrowser === 'boolean' ? task.useBrowser : agentCfg.useBrowser; + const config = loadConfig(); + const agentCfg = config.agent || {}; const mode = normalizeTaskMode(task.mode); - console.log(`[Agent] ${task.city}:开始执行`); - console.log(`[Agent] ${task.city}:mode=${mode}`); + console.log(`[Agent] ${task.city}: start`); + console.log(`[Agent] ${task.city}: mode=${mode}`); + const { results } = await runAgentTask(task.prompt, { baseUrl: agentCfg.baseUrl, - useBrowser, mode, pollInterval: agentCfg.pollInterval, timeout: agentCfg.timeout, logPrefix: `[Agent][${task.city}]`, }); - const record = { + return appendResult({ taskId: task.id, city: task.city, scrapedAt: new Date().toISOString(), data: { results, total: results.length }, - }; - appendResult(record); - return record; -} - -// 后台执行单个任务 -function runTaskInBackground(task) { - runningStatus = { taskId: task.id, city: task.city, startTime: Date.now(), current: 0, total: 1, finished: false, error: null }; - runTask(task).then(record => { - runningStatus = { ...runningStatus, finished: true, result: record, current: 1 }; - }).catch(err => { - console.error('任务执行失败:', err.message); - runningStatus = { ...runningStatus, finished: true, error: err.message, current: 1 }; - }).finally(() => { - isRunning = false; }); } -// 后台执行批量任务 +function runTaskInBackground(task) { + runningStatus = { + taskId: task.id, + city: task.city, + startTime: Date.now(), + current: 0, + total: 1, + finished: false, + error: null, + }; + + runTask(task) + .then((record) => { + runningStatus = { ...runningStatus, finished: true, result: record, current: 1 }; + }) + .catch((error) => { + console.error('任务执行失败:', error.message); + runningStatus = { ...runningStatus, finished: true, error: error.message, current: 1 }; + }) + .finally(() => { + isRunning = false; + }); +} + function runTasksInBackground(tasks) { - runningStatus = { taskId: null, city: null, startTime: Date.now(), current: 0, total: tasks.length, finished: false, error: null, results: [] }; + runningStatus = { + taskId: null, + city: null, + startTime: Date.now(), + current: 0, + total: tasks.length, + finished: false, + error: null, + results: [], + }; + (async () => { for (const task of tasks) { runningStatus = { ...runningStatus, taskId: task.id, city: task.city }; + try { - const r = await runTask(task); - runningStatus.results.push(r); - } catch (err) { - const errRecord = { taskId: task.id, city: task.city, scrapedAt: new Date().toISOString(), error: err.message, data: null }; - appendResult(errRecord); - runningStatus.results.push(errRecord); + const record = await runTask(task); + runningStatus.results.push(record); + } catch (error) { + const errorRecord = appendResult({ + taskId: task.id, + city: task.city, + scrapedAt: new Date().toISOString(), + error: error.message, + data: null, + }); + runningStatus.results.push(errorRecord); } - runningStatus.current++; + + runningStatus.current += 1; } + runningStatus.finished = true; - })().catch(err => { - runningStatus = { ...runningStatus, finished: true, error: err.message }; - }).finally(() => { - isRunning = false; - }); + })() + .catch((error) => { + runningStatus = { ...runningStatus, finished: true, error: error.message }; + }) + .finally(() => { + isRunning = false; + }); } -// 查询运行状态 +app.get('/api/results', (req, res) => { + try { + const { city, section, type, page = 1, pageSize = 20, taskId } = req.query; + const result = listResults({ city, section, type, page, pageSize, taskId }); + res.json({ success: true, ...result }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +app.delete('/api/results/:id', (req, res) => { + try { + const deleted = deleteResultById(req.params.id); + if (!deleted) { + return res.status(404).json({ success: false, error: '未找到结果' }); + } + res.json({ success: true }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +app.delete('/api/results', (_req, res) => { + try { + clearResults(); + res.json({ success: true, message: '已清空所有结果' }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +app.get('/api/results/filters', (_req, res) => { + try { + res.json({ success: true, data: getResultFilters() }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +app.get('/api/tasks', (_req, res) => { + try { + res.json({ success: true, data: listTasks() }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +app.post('/api/tasks', (req, res) => { + try { + const task = createTask(buildTaskPayload(req.body)); + res.json({ success: true, data: task }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +app.put('/api/tasks/:id', (req, res) => { + try { + const task = updateTask(req.params.id, buildTaskPayload(req.body, { partial: true })); + if (!task) { + return res.status(404).json({ success: false, error: '未找到该任务' }); + } + res.json({ success: true, data: task }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +app.delete('/api/tasks/:id', (req, res) => { + try { + const deleted = deleteTaskById(req.params.id); + if (!deleted) { + return res.status(404).json({ success: false, error: '未找到该任务' }); + } + res.json({ success: true }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + app.get('/api/tasks/status', (_req, res) => { - if (!runningStatus) return res.json({ success: true, data: { isRunning: false } }); + if (!runningStatus) { + return res.json({ success: true, data: { isRunning: false } }); + } + const elapsed = Math.round((Date.now() - runningStatus.startTime) / 1000); res.json({ success: true, @@ -269,109 +278,119 @@ app.get('/api/tasks/status', (_req, res) => { total: runningStatus.total, finished: runningStatus.finished, error: runningStatus.error, - results: runningStatus.finished ? (runningStatus.results || (runningStatus.result ? [runningStatus.result] : [])) : undefined, - } + results: runningStatus.finished + ? (runningStatus.results || (runningStatus.result ? [runningStatus.result] : [])) + : undefined, + }, }); }); -// 运行单个任务(立即返回) app.post('/api/tasks/:id/run', (req, res) => { - if (isRunning) return res.status(409).json({ success: false, error: '有任务正在运行中,请等待完成后再试' }); - const cfg = readConfig(); - const task = (cfg.tasks || []).find(t => t.id === req.params.id); - if (!task) return res.status(404).json({ success: false, error: '未找到该配置' }); - isRunning = true; - runTaskInBackground(task); - res.json({ success: true, message: `任务「${task.city}」已开始执行` }); -}); - -// 批量运行任务(立即返回) -app.post('/api/tasks/run', (req, res) => { - if (isRunning) return res.status(409).json({ success: false, error: '有任务正在运行中,请等待完成后再试' }); - const cfg = readConfig(); - let tasks = cfg.tasks || []; - - if (req.body.ids && req.body.ids.length > 0) { - tasks = tasks.filter(t => req.body.ids.includes(t.id)); - } else { - tasks = tasks.filter(t => t.enabled); + if (isRunning) { + return res.status(409).json({ success: false, error: '当前已有任务在运行,请稍后再试' }); } - if (tasks.length === 0) { - return res.json({ success: true, data: [], message: '没有可运行的任务' }); - } - - isRunning = true; - runTasksInBackground(tasks); - res.json({ success: true, message: `${tasks.length} 个任务已开始执行` }); -}); - -// ========== 配置管理 ========== - -app.get('/api/config', (req, res) => { try { - const cfg = readConfig(); - if (cfg.email?.smtpPass) cfg.email.smtpPass = '***已配置***'; - res.json({ success: true, data: cfg }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); + const task = getTaskById(req.params.id); + if (!task) { + return res.status(404).json({ success: false, error: '未找到该任务' }); + } + + isRunning = true; + runTaskInBackground(task); + res.json({ success: true, message: `任务“${task.city}”已开始执行` }); + } catch (error) { + isRunning = false; + res.status(500).json({ success: false, error: error.message }); + } +}); + +app.post('/api/tasks/run', (req, res) => { + if (isRunning) { + return res.status(409).json({ success: false, error: '当前已有任务在运行,请稍后再试' }); + } + + try { + let tasks = listTasks(); + + if (Array.isArray(req.body?.ids) && req.body.ids.length > 0) { + const idSet = new Set(req.body.ids); + tasks = tasks.filter((task) => idSet.has(task.id)); + } else { + tasks = tasks.filter((task) => task.enabled); + } + + if (tasks.length === 0) { + return res.json({ success: true, data: [], message: '没有可运行的任务' }); + } + + isRunning = true; + runTasksInBackground(tasks); + res.json({ success: true, message: `${tasks.length} 个任务已开始执行` }); + } catch (error) { + isRunning = false; + res.status(500).json({ success: false, error: error.message }); + } +}); + +app.get('/api/config', (_req, res) => { + try { + res.json({ success: true, data: maskConfigSecrets(loadConfig()) }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); } }); app.post('/api/config', (req, res) => { try { - const newCfg = req.body; - const oldCfg = readConfig(); - if (newCfg.email?.smtpPass === '***已配置***') { - newCfg.email.smtpPass = oldCfg.email?.smtpPass || ''; - } - saveConfig(newCfg); + saveConfig(mergeConfigWithExistingSecrets(req.body)); reloadScheduler(); res.json({ success: true, message: '配置已保存' }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); } }); -// ========== 邮件 ========== - app.post('/api/send-email', async (req, res) => { try { const { emailConfig, report } = req.body; - if (!emailConfig?.smtpHost || !emailConfig?.smtpUser || !emailConfig?.smtpPass) + if (!emailConfig?.smtpHost || !emailConfig?.smtpUser || !emailConfig?.smtpPass) { return res.status(400).json({ success: false, error: '邮件配置不完整' }); - if (!emailConfig.recipients?.trim()) + } + if (!emailConfig.recipients?.trim()) { return res.status(400).json({ success: false, error: '请指定收件人' }); - if (!report) + } + if (!report) { return res.status(400).json({ success: false, error: '没有报告数据' }); + } const { sendReportEmail } = await import('./emailService.js'); const result = await sendReportEmail(emailConfig, report); res.json({ success: true, message: '邮件发送成功', messageId: result.messageId }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); } }); -// ========== 定时任务 ========== - -app.get('/api/scheduler/status', (req, res) => { +app.get('/api/scheduler/status', (_req, res) => { try { res.json({ success: true, data: getSchedulerStatus() }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); } }); -app.post('/api/run-scheduled-task', (req, res) => { +app.post('/api/run-scheduled-task', (_req, res) => { try { - runTaskNow().catch(err => console.error('定时任务执行失败:', err)); + runTaskNow().catch((error) => console.error('定时任务执行失败:', error)); res.json({ success: true, message: '定时任务已在后台触发' }); - } catch (e) { - res.status(500).json({ success: false, error: e.message }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); } }); +initResultsStore(); + app.listen(PORT, () => { console.log(`Server running at http://localhost:${PORT}`); initScheduler();