noteparse 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2024] [maoyuyan]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: noteparse
3
+ Version: 1.0.1
4
+ Summary: a package for parse html to noteinfo
5
+ Author: maoyuyan
6
+ Author-email: 294567571@qq.com
7
+ License: MIT
8
+ Platform: all
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Natural Language :: Chinese (Simplified)
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+
19
+ 一个项目
@@ -0,0 +1 @@
1
+ 一个项目
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: noteparse
3
+ Version: 1.0.1
4
+ Summary: a package for parse html to noteinfo
5
+ Author: maoyuyan
6
+ Author-email: 294567571@qq.com
7
+ License: MIT
8
+ Platform: all
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Natural Language :: Chinese (Simplified)
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+
19
+ 一个项目
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ setup.cfg
4
+ setup.py
5
+ noteparse.egg-info/PKG-INFO
6
+ noteparse.egg-info/SOURCES.txt
7
+ noteparse.egg-info/dependency_links.txt
8
+ noteparse.egg-info/entry_points.txt
9
+ noteparse.egg-info/requires.txt
10
+ noteparse.egg-info/top_level.txt
11
+ src/__init__.py
12
+ src/dataHelper.py
13
+ src/dbHelper.py
14
+ src/parse.py
15
+ src/parseService.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ noteparse = noteparse.__init__:init
@@ -0,0 +1,6 @@
1
+ requests>=2.32.3
2
+ bs4>=0.0.2
3
+ fastapi>=0.115.4
4
+ pydantic>=2.7.3
5
+ loguru>=0.7.2
6
+ zhipuai>=2.1.5.20230904
@@ -0,0 +1 @@
1
+ src
@@ -0,0 +1,7 @@
1
+ [metadata]
2
+ description-file = README.md
3
+
4
+ [egg_info]
5
+ tag_build =
6
+ tag_date = 0
7
+
@@ -0,0 +1,47 @@
1
+ import setuptools # 导入setuptools打包工具
2
+
3
+ # with open("README.md", "r", encoding="utf-8") as fh:
4
+ # long_description = fh.read()
5
+
6
+ setuptools.setup(
7
+ name="noteparse", # 用自己的名替换其中的YOUR_USERNAME_
8
+ version="1.0.1", # 包版本号,便于维护版本,保证每次发布都是版本都是唯一的
9
+ author="maoyuyan", # 作者,可以写自己的姓名
10
+ author_email="294567571@qq.com", # 作者联系方式,可写自己的邮箱地址
11
+ description="a package for parse html to noteinfo", # 包的简述
12
+ long_description=open("README.md", "r", encoding="utf-8").read(), # 包的详细介绍,一般在README.md文件内
13
+ long_description_content_type="text/markdown",
14
+ platforms=["all"],
15
+ # url="https://github.com/m294567571/noteparse.git", # 自己项目地址,比如github的项目地址
16
+ # http://gitlab.uniview.com/mkt_uic/uic-spider.git
17
+ packages=setuptools.find_packages(),
18
+ entry_points={
19
+ "console_scripts" : ['noteparse = noteparse.__init__:init']
20
+ }, #安装成功后,在命令行输入mwjApiTest 就相当于执行了mwjApiTest.manage.py中的run了
21
+ install_requires=[
22
+ 'requests>=2.32.3',
23
+ 'bs4>=0.0.2',
24
+ 'fastapi>=0.115.4',
25
+ 'pydantic>=2.7.3',
26
+ 'loguru>=0.7.2',
27
+ 'zhipuai>=2.1.5.20230904',
28
+ ],
29
+ classifiers=[
30
+ # 发展时期,常见的如下
31
+ # 3 - Alpha
32
+ # 4 - Beta
33
+ # 5 - Production/Stable
34
+ 'Development Status :: 4 - Beta',
35
+ # 开发的目标用户
36
+ 'Intended Audience :: Developers',
37
+ # 许可证信息
38
+ 'License :: OSI Approved :: MIT License',
39
+ 'Operating System :: Microsoft :: Windows',
40
+ 'Natural Language :: Chinese (Simplified)',
41
+ # 目标 Python 版本
42
+ 'Programming Language :: Python :: 3',
43
+ # 属于什么类型
44
+ 'Topic :: Software Development :: Libraries :: Python Modules'
45
+ ],
46
+ license='MIT'
47
+ )
@@ -0,0 +1,5 @@
1
+ from dbHelper import create_connection
2
+
3
+ def init(host,port,user,password,db):
4
+ # create_connection(host,port,user,password,db)
5
+ create_connection()
@@ -0,0 +1,268 @@
1
+ # -*- coding: UTF-8 -*-
2
+ """
3
+ @File :dataHelper.py
4
+ @Author :VerSion/08398
5
+ @Date :2023/12/07 14:29
6
+ @Corp :Uniview
7
+ """
8
+ import json
9
+ import os
10
+ import time
11
+
12
+ import pymysql
13
+ import requests
14
+
15
+ db_config = {
16
+ 'host': '10.220.6.138',
17
+ 'port': 3306,
18
+ 'user': 'root',
19
+ 'password': '*Ab123456',
20
+ 'db': 'unvbasicx_khgz'
21
+ }
22
+
23
+
24
+ def write_log(announce_list, province_key):
25
+ print('正在写入日志...')
26
+ # 记录日志
27
+ try:
28
+ # 获取当前脚本所在的目录
29
+ current_dir = os.path.dirname(os.path.abspath(__file__))
30
+ # 创建log文件夹
31
+ log_folder = os.path.join(current_dir, 'log')
32
+ if not os.path.exists(log_folder):
33
+ os.makedirs(log_folder)
34
+ # 获取当前时间
35
+ current_time = time.strftime("%Y-%m-%d", time.localtime())
36
+ # 导出JSON到文件
37
+ file_name = f"{current_time}_{province_key}.json"
38
+ file_path = os.path.join(log_folder, file_name)
39
+ announce_dict = [item.__json__() for item in announce_list]
40
+ with open(file_path, 'a', encoding='utf-8') as file:
41
+ json.dump(announce_dict, file, ensure_ascii=False)
42
+ print(f"JSON已成功导出到文件: {file_path}")
43
+ except Exception as log_ex:
44
+ print('日志写入异常:' + str(log_ex))
45
+ raise Exception('日志写入异常:' + str(log_ex))
46
+ return file_path
47
+
48
+
49
+ def upload_datas(announce_list, province_key):
50
+ upload_msg = ''
51
+ # 上传数据
52
+ try:
53
+ upload_url = 'http://10.220.6.138/api/khgz/project'
54
+ headers = {
55
+ 'Content-Type': 'application/json'
56
+ }
57
+
58
+ upload_response = requests.post(upload_url, headers=headers,
59
+ data=json.dumps([item.__json__() for item in announce_list],
60
+ ensure_ascii=False).encode('utf-8'))
61
+
62
+ if upload_response.status_code == 200:
63
+ upload_result = json.loads(upload_response.text)
64
+ if upload_result['ok']:
65
+ upload_msg = f'\033[1;33;42m数据上传成功\033[0m'
66
+ else:
67
+ upload_msg = f'\033[1;31;40m数据上传失败,详细信息:{upload_result["msg"]}\033[0m'
68
+ else:
69
+ upload_msg = f'数据上传失败,错误码:{upload_response.status_code}'
70
+ except Exception as upload_ex:
71
+ upload_msg = '数据上传异常:' + str(upload_ex)
72
+ raise Exception('数据上传异常:' + str(upload_ex))
73
+ finally:
74
+ # 获取当前脚本所在的目录
75
+ current_dir = os.path.dirname(os.path.abspath(__file__))
76
+ # 创建log文件夹
77
+ log_folder = os.path.join(current_dir, 'log')
78
+ if not os.path.exists(log_folder):
79
+ os.makedirs(log_folder)
80
+ # 获取当前时间
81
+ current_time = time.strftime("%Y-%m-%d", time.localtime())
82
+ # 导出JSON到文件
83
+ file_name = f"{current_time}_数据上传.json"
84
+ file_path = os.path.join(log_folder, file_name)
85
+ with open(file_path, 'a', encoding='utf-8') as file:
86
+ file.write(f'\n[{province_key}]数据上传日志 => ' + upload_msg)
87
+ print(upload_msg)
88
+
89
+
90
+ def excute_sql(sql_str):
91
+ # 连接数据库
92
+ conn = pymysql.connect(host=db_config.get('host', ''), port=db_config.get('port', ''),
93
+ user=db_config.get('user', ''), password=db_config.get('password', ''),
94
+ db=db_config.get('db', ''))
95
+ # 创建游标对象
96
+ with conn.cursor() as cursor:
97
+ # 执行SQL语句
98
+ cursor.execute(sql_str)
99
+ # 提交更改
100
+ conn.commit()
101
+
102
+
103
+ def execute_sql_query(sql_query):
104
+ # 建立数据库连接
105
+ conn = pymysql.connect(host=db_config.get('host', ''), port=db_config.get('port', ''),
106
+ user=db_config.get('user', ''), password=db_config.get('password', ''),
107
+ db=db_config.get('db', ''))
108
+ # 创建游标对象
109
+ cursor = conn.cursor(pymysql.cursors.DictCursor)
110
+ try:
111
+ # 执行 SQL 查询
112
+ cursor.execute(sql_query)
113
+ # 获取查询结果
114
+ result = cursor.fetchall()
115
+
116
+ return result
117
+
118
+ except Exception as e:
119
+ # 如果发生异常,回滚事务
120
+ conn.rollback()
121
+ raise Exception('数据库查询异常:' + str(e))
122
+
123
+ finally:
124
+ # 关闭游标和连接
125
+ cursor.close()
126
+ conn.close()
127
+
128
+
129
+ def insert_engineering_datas(engineering_list):
130
+ print('正在写入数据...')
131
+ template_sql = "INSERT INTO tbl_engineering_project (project_name, project_number, publish_date, version_type, project_stage, province_id, city_id, area_id, origin_location, address, construction_period, investment_amount, total_investment, engineering_type, client_type, industry, building_area, land_occupation_area,decoration_situation, foreign_investment, topic, project_scale, installed_capacity, industry_level, project_overview, construction_desc, procures_equipment, client_infos, designer_infos, epcs_infos, contractor_infos, subcontractor_infos, project_link,special_name, data_source, data_source_id) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);"
132
+ data = []
133
+ for item in engineering_list:
134
+ data.append((item.project_name,
135
+ item.project_number,
136
+ item.publish_date,
137
+ item.version_type,
138
+ item.project_stage,
139
+ item.province_id,
140
+ item.city_id,
141
+ item.area_id,
142
+ item.origin_location,
143
+ item.address,
144
+ item.construction_period,
145
+ item.investment_amount,
146
+ item.total_investment,
147
+ item.engineering_type,
148
+ item.client_type,
149
+ item.industry,
150
+ item.building_area,
151
+ item.land_occupation_area,
152
+ item.decoration_situation,
153
+ item.foreign_investment,
154
+ item.topic,
155
+ item.project_scale,
156
+ item.installed_capacity,
157
+ item.industry_level,
158
+ item.project_overview,
159
+ item.construction_desc,
160
+ item.procures_equipment,
161
+ item.client_infos,
162
+ item.designer_infos,
163
+ item.epcs_infos,
164
+ item.contractor_infos,
165
+ item.subcontractor_infos,
166
+ item.project_link,
167
+ item.special_name,
168
+ item.data_source,
169
+ item.data_source_id))
170
+
171
+ err_msg = ''
172
+ try:
173
+ # 连接数据库
174
+ conn = pymysql.connect(host='10.220.6.138', port=3306, user='root', password='*Ab123456', db='unvbasicx_khgz')
175
+ # 创建游标对象
176
+ with conn.cursor() as cursor:
177
+ # 执行SQL语句
178
+ cursor.executemany(template_sql, data)
179
+ # 提交更改
180
+ conn.commit()
181
+
182
+ err_msg = f'写入数据成功!本次写入 {len(engineering_list)} 条数据。'
183
+ print(f'写入数据成功!本次写入 {len(engineering_list)} 条数据。')
184
+ except Exception as sql_ex:
185
+ err_msg = '数据写入数据库异常:' + str(sql_ex)
186
+ raise Exception('数据写入数据库异常:' + str(sql_ex))
187
+ finally:
188
+ # 获取当前脚本所在的目录
189
+ current_dir = os.path.dirname(os.path.abspath(__file__))
190
+ # 创建log文件夹
191
+ log_folder = os.path.join(current_dir, 'log')
192
+ if not os.path.exists(log_folder):
193
+ os.makedirs(log_folder)
194
+ # 获取当前时间
195
+ current_time = time.strftime("%Y-%m-%d", time.localtime())
196
+ # 导出JSON到文件
197
+ file_name = f"{current_time}_数据上传.json"
198
+ file_path = os.path.join(log_folder, file_name)
199
+ with open(file_path, 'a', encoding='utf-8') as file:
200
+ file.write(json.dumps([item.__json__() for item in engineering_list]) + '\r\n')
201
+ file.write(err_msg + '\r\n')
202
+ file.write('################################################################\r\n')
203
+
204
+
205
+ def redis_set(key, value, expire=30, time_type=1):
206
+ """
207
+ 在redis中设置数据
208
+ :param key: 键
209
+ :param value: 值
210
+ :param expire: 超时时间
211
+ :param time_type: 时间类型(1-分钟,2-小时,3-天)
212
+ :return:
213
+ """
214
+ # 如果没有传入key,则抛出异常
215
+ if key is None or len(key) == 0:
216
+ raise Exception('未传入键')
217
+ try:
218
+ upload_url = 'http://10.220.6.63:8090/api/khgz/redis/saveCache'
219
+ headers = {
220
+ 'Content-Type': 'application/json'
221
+ }
222
+ payload = {
223
+ "key": key,
224
+ "value": value,
225
+ "timeout": expire,
226
+ "type": time_type
227
+ }
228
+ redis_response = requests.post(upload_url, headers=headers, data=json.dumps(payload).encode('utf-8'))
229
+ if redis_response.status_code == 200:
230
+ redis_result = json.loads(redis_response.text)
231
+ if redis_result['ok']:
232
+ print(f'Redis设置成功')
233
+ else:
234
+ raise Exception(f'Redis设置失败,详细信息:{redis_result["msg"]}')
235
+ else:
236
+ raise Exception('接口调用失败,错误码:' + str(redis_response.status_code))
237
+ except Exception as redis_ex:
238
+ print('Redis设置异常:' + str(redis_ex))
239
+ raise Exception('Redis设置异常:' + str(redis_ex))
240
+
241
+
242
+ def redis_get(key):
243
+ """
244
+ 从redis中获取数据
245
+ :param key: 键
246
+ :return:
247
+ """
248
+ # 如果没有传入key,则抛出异常
249
+ if key is None or len(key) == 0:
250
+ raise Exception('未传入键')
251
+ try:
252
+ upload_url = 'http://10.220.6.138/api/khgz/redis/getCache'
253
+ headers = {
254
+ 'Content-Type': 'application/x-www-form-urlencoded'
255
+ }
256
+ redis_response = requests.get(upload_url, headers=headers, params={"key": key})
257
+ if redis_response.status_code == 200:
258
+ redis_result = json.loads(redis_response.text)
259
+ if redis_result['ok']:
260
+ return str(redis_result['data'])
261
+ else:
262
+ raise Exception(f'Redis获取失败,详细信息:{redis_result["msg"]}')
263
+ else:
264
+ raise Exception('接口调用失败,错误码:' + str(redis_response.status_code))
265
+
266
+ except Exception as redis_ex:
267
+ print('Redis获取异常:' + str(redis_ex))
268
+ raise Exception('Redis获取异常:' + str(redis_ex))
@@ -0,0 +1,116 @@
1
+ import pymysql
2
+ from datetime import datetime
3
+ import traceback
4
+
5
+
6
+ db_config = {
7
+ 'host': '10.220.6.63',
8
+ 'port': 3306,
9
+ 'user': 'root',
10
+ 'password': '*Ab123456',
11
+ 'db': 'unvbasicx_khgz'
12
+ }
13
+
14
+ # 创建数据库连接
15
+ def create_connection():
16
+ try:
17
+ print(datetime.now(),'开启数据库链接')
18
+ # 连接数据库
19
+ connection = pymysql.connect(host=db_config.get('host', ''), port=db_config.get('port', ''),
20
+ user=db_config.get('user', ''), password=db_config.get('password', ''),
21
+ db=db_config.get('db', ''))
22
+ print(datetime.now(),'数据库链接成功')
23
+
24
+ except pymysql.MySQLError as e:
25
+ print(datetime.now(),'数据库链接失败',e)
26
+ return connection
27
+
28
+ # # 创建数据库连接
29
+ # def create_connection(host,port,user,password,db):
30
+ # try:
31
+ # print(datetime.now(),'开启数据库链接')
32
+ # # 连接数据库
33
+ # connection = pymysql.connect(host=host, port=port,
34
+ # user=user, password=password,
35
+ # db=db)
36
+ # print(datetime.now(),'数据库链接成功')
37
+
38
+ # except pymysql.MySQLError as e:
39
+ # print(datetime.now(),'数据库链接失败',e)
40
+ # return connection
41
+
42
+ # 确保在程序退出时关闭数据库连接
43
+ def close_connection(connection):
44
+ if connection and connection.open:
45
+ print(datetime.now(),'关闭SQL连接')
46
+ connection.close()
47
+
48
+ connection = create_connection()
49
+
50
+ # 通过城市名称查询城市信息
51
+ def queryCityInfo(cityName):
52
+ query = f'''
53
+ (SELECT
54
+ a.areaid,
55
+ a.area,
56
+ c.cityid,
57
+ c.city,
58
+ p.provinceid,
59
+ p.province
60
+ FROM
61
+ unvbasicx_khgz.areas a
62
+ JOIN
63
+ unvbasicx_khgz.cities c ON a.cityid = c.cityid
64
+ JOIN
65
+ unvbasicx_khgz.provinces p ON c.provinceid = p.provinceid
66
+ WHERE
67
+ a.area like '%{cityName}%')
68
+ UNION ALL
69
+ (SELECT
70
+ '' AS areaid,
71
+ '' AS area,
72
+ c.cityid,
73
+ c.city,
74
+ p.provinceid,
75
+ p.province
76
+ FROM
77
+ unvbasicx_khgz.cities c
78
+ JOIN
79
+ unvbasicx_khgz.provinces p ON c.provinceid = p.provinceid
80
+ WHERE
81
+ c.city like '%{cityName}%'
82
+ AND NOT EXISTS (
83
+ SELECT 1 FROM unvbasicx_khgz.areas WHERE area like '%{cityName}%'
84
+ )
85
+ )
86
+ UNION ALL
87
+ (SELECT
88
+ '' AS areaid,
89
+ '' AS area,
90
+ '' AS cityid,
91
+ '' AS city,
92
+ p.provinceid,
93
+ p.province
94
+ FROM
95
+ unvbasicx_khgz.provinces p
96
+ WHERE
97
+ p.province like '%{cityName}%'
98
+ AND NOT EXISTS (
99
+ SELECT 1 FROM unvbasicx_khgz.cities WHERE city like '%{cityName}%'
100
+ )
101
+ AND NOT EXISTS (
102
+ SELECT 1 FROM unvbasicx_khgz.areas WHERE area like '%{cityName}%'
103
+ )
104
+ )
105
+ LIMIT 1
106
+ '''
107
+ if connection == None:
108
+ create_connection()
109
+ try:
110
+ with connection.cursor() as cursor:
111
+ cursor.execute(query)
112
+ result = cursor.fetchall() # 获取所有查询结果
113
+ return result
114
+ except Exception as e:
115
+ print(datetime.now(),'通过城市名查询城市信息异常',e)
116
+ traceback.print_exc()
File without changes
@@ -0,0 +1,734 @@
1
+ from datetime import datetime
2
+ import re
3
+ import requests
4
+ import json
5
+ from bs4 import BeautifulSoup
6
+ import traceback
7
+ import dbHelper
8
+ # from fastapi import FastAPI,HTTPException, Request,Depends
9
+ # from fastapi.responses import JSONResponse
10
+ # import uvicorn
11
+ from pydantic import BaseModel, ValidationError
12
+ from typing import List, Literal, Optional, Union
13
+ from loguru import logger
14
+ from zhipuai import ZhipuAI
15
+ # from flask import Flask, request, jsonify
16
+
17
+ # 金额正则
18
+ pattern = r'(\d+\.?\d*\s*(亿元|万元|元))'
19
+
20
+ # 项目编号正则
21
+ proejctNumPattern = r'(?:编号|代码|编码|标号):([A-Za-z0-9-]+)'
22
+ # 字段对应关键字的set,有另外的直接加
23
+ keySet = {
24
+ 'bidUnit' : [
25
+ '采购人:','招标人单位:','建设单位(招标人):',
26
+ '比选人:',
27
+ '发布单位:',
28
+ '单位名称:',
29
+ '招标人:',
30
+ '招募人:',
31
+ '业主单位:',
32
+ '建设单位:',
33
+ '项目单位:',
34
+ '项目业主名称:',
35
+ '法人单位:','招标人名称:','联系人(采购人):','采购单位:','建设单位(招标人):','建设单位(或招标人):','招标(采购)人名称:'
36
+ ],
37
+ 'proxyUnit' : ['采购代理机构:','代理机构:','招标代理机构:','招募代理机构:','招标机构:','招标代理名称:','招标代理机构名称:','联系人(代理机构):','招标代理:','代理机构单位:',],
38
+ 'contact' : ['联系人:','项目法人:','申请人:',],
39
+ 'address' : ['地址:','建设地点:','项目所在地:'],
40
+ 'phone' : ['联系方式:','电话:','联系电话:'],
41
+ 'publishTime' : ['发布时间:','发布日期:','成交时间:'],
42
+ 'winBidUnit': ['成交供应商:','中标人:','中标候选人名称:','供应商名称:','中标候选人单位名称:','中标单位名称:','中标供应商名称:','中标(成交)单位名称:','第一中标(成交)候选人名称:'],
43
+ 'bidAmount': ['预算金额:','预算金额(元):'],
44
+ 'winBidAmount': ['成交金额/成交下浮率:','中标价(费率或单价等):','中标总价(元)/费率:','中标金额:','中标(成交)金额:','中标金额:','预期中标价/元:','成交金额:','投标价格:'],
45
+ 'bidContact': ['招标人联系人:','采购经办人:'],
46
+ 'bidPhone': ['招标人联系方式:','采购人电话:'],
47
+ 'proxyContact': ['招标代理联系人:','代理机构经办人:'],
48
+ 'proxyPhone': ['招标代理联系方式:','代理机构电话:']
49
+ }
50
+
51
+
52
+ # content--正文html; noteInfo--调接口的入参对象,其他如name、publishTime字段可以解析前就设定好noteInfo对象,以下解析是往里面加的
53
+ def parseContent(content,noteInfo):
54
+ contentDiv = BeautifulSoup(content,'html.parser')
55
+ screenStr = []
56
+ # 第一种是最普通的p标签分行
57
+ pList = contentDiv.find_all('p')
58
+ # print('--------len(plist)',pList)
59
+ if pList and len(pList)>1:
60
+ for p in pList:
61
+ # 如果有br,每个br间隔都是一行
62
+ contains_br = any(child.name == 'br' for child in p.children)
63
+ if contains_br:
64
+ text_and_tag = p.contents
65
+ current_paragraph = ''
66
+ for item in text_and_tag:
67
+ if isinstance(item,str):
68
+ current_paragraph += item
69
+ elif item.name == 'br':
70
+ screenStr.append(current_paragraph.strip())
71
+ current_paragraph = ''
72
+ if current_paragraph:
73
+ screenStr.append(current_paragraph.strip())
74
+ # 如果一行多个信息,多是用空格分割,如:联系人:黄工/汪工 联系电话:0572-********
75
+ elif ' ' in p.text and p.text.count(':')>1:
76
+ strList = p.text.split(' ')
77
+ screenStr.extend(strList)
78
+ else:
79
+ screenStr.append(p.text)
80
+ # 如果没有P标签,或者p标签只有一个,尝试获取span标签,这类的公告可能用span标签取代p标签,但是span标签较细小,多是包在div、p标签内,所以不单独处理,否则screenStr内容会非常混乱,解析准确度更差
81
+ else:
82
+ spanList = contentDiv.find_all('span')
83
+ for span in spanList:
84
+ if ' ' in span.text and span.text.count(':')>1:
85
+ strList = span.text.split(' ')
86
+ screenStr.extend(strList)
87
+ else:
88
+ screenStr.append(span.text)
89
+
90
+ # 第二种是table表格格式,按th和td的数量匹配分段的规则,进行标题和内容拼接
91
+ tableList = contentDiv.find_all('table')
92
+ for table in tableList:
93
+ if table:
94
+ trList = table.find_all('tr')
95
+ if trList:
96
+ if len(trList) == 2:
97
+ # 先查看标题是不是用th标签
98
+ thList = trList[0].find_all('th')
99
+ tdList = trList[1].find_all('td')
100
+ if len(thList) == len(tdList):
101
+ # 只有两个tr,一个tr全是th,另一个全是td,则是成排的
102
+ for index,th in enumerate(thList):
103
+ screenStr.append(th.text+':'+tdList[index].text)
104
+ elif len(thList) == 0:
105
+ # 如果没有th标签,查看第一个tr内是不是也是用的td标签
106
+ firstTdList = trList[0].find_all('td')
107
+ if len(firstTdList) == len(tdList):
108
+ for index,firstTd in enumerate(firstTdList):
109
+ screenStr.append(firstTd.text+':'+tdList[index].text)
110
+ for index,tr in enumerate(trList):
111
+ tdList = tr.find_all('td')
112
+ thList = tr.find_all('th')
113
+ # 表格里只有td标签的,一般是双数,一个标签td对一个值td
114
+ if len(thList)==0 and len(tdList) > 0:
115
+ if len(tdList) == 1:
116
+ screenStr.append(tdList[0].text)
117
+ elif len(tdList) % 2 == 0:
118
+ for i in range(0,int(len(tdList)),2) :
119
+ if ':' in tdList[i].text:
120
+ screenStr.append(tdList[i].text + tdList[i+1].text )
121
+ elif ':' in tdList[i].text:
122
+ screenStr.append(tdList[i].text.replace(':',':') + tdList[i+1].text )
123
+ else:
124
+ screenStr.append(tdList[i].text + ':' +tdList[i+1].text )
125
+
126
+ # elif len(tdList) >1:
127
+ # if ':' in tdList[0].text:
128
+ # screenStr.append(tdList[0].text + tdList[1].text )
129
+ # elif ':' in tdList[0].text:
130
+ # screenStr.append(tdList[0].text.replace(':',':') + tdList[1].text)
131
+ # else:
132
+ # screenStr.append(tdList[0].text + ':' +tdList[1].text )
133
+
134
+ # elif len(tdList) == 4:
135
+ # if ':' in tdList[0].text:
136
+ # screenStr.append(tdList[0].text + tdList[1].text )
137
+ # elif ':' in tdList[0].text:
138
+ # screenStr.append(tdList[0].text.replace(':',':') + tdList[1].text )
139
+ # else:
140
+ # screenStr.append(tdList[0].text + ':' +tdList[1].text )
141
+
142
+ # if ':' in tdList[2].text:
143
+ # screenStr.append(tdList[2].text + tdList[3].text )
144
+ # elif ':' in tdList[2].text:
145
+ # screenStr.append(tdList[2].text.replace(':',':') + tdList[3].text )
146
+ # else:
147
+ # screenStr.append(tdList[2].text + ':' +tdList[3].text )
148
+ # 表格里一个th和一个td的,一般是一个th标签对一个td标签
149
+ elif len(thList) == 1 and len(tdList) == 1:
150
+ if ':' in thList[0].text:
151
+ screenStr.append(thList[0].text + tdList[0].text )
152
+ elif ':' in thList[0].text:
153
+ screenStr.append(thList[0].text.replace(':',':') + tdList[0].text )
154
+ else:
155
+ screenStr.append(thList[0].text + ':' +tdList[0].text )
156
+ elif len(tdList) == 0 and len(thList)>0:
157
+ if index < len(trList)-1:
158
+ nextThList = trList[index+1].find_all('th')
159
+ for index2,th in enumerate(thList):
160
+ if '中标候选人名称' == th.text or '投标报价(元)' == th.text:
161
+ screenStr.append(th.text+':'+nextThList[index2].text)
162
+
163
+
164
+ # 第三种是div标签分段的,目前抽样到有br标签分行
165
+ # if len(pList) == 0:
166
+ divList = contentDiv.find_all('div')
167
+ for divItem in divList:
168
+ # 如果有br,每个br间隔都是一行
169
+ contains_br = any(child.name == 'br' for child in divItem.children)
170
+ if contains_br:
171
+ text_and_tag = divItem.contents
172
+ current_paragraph = ''
173
+ for item in text_and_tag:
174
+ if isinstance(item,str):
175
+ current_paragraph += item
176
+ elif item.name == 'br':
177
+ screenStr.append(current_paragraph.strip())
178
+ current_paragraph = ''
179
+ # 第四种是只有span标签的,这类的因为span标签太细小,大多都会包在div标签、p标签内,所以这类公告不单独分析,否则文本列表会非常混乱
180
+ elif item.name == 'span':
181
+ screenStr.append(item.text.strip())
182
+ current_paragraph = ''
183
+ if current_paragraph:
184
+ screenStr.append(current_paragraph.strip())
185
+ # 没有Br的,只保存只有一个:的段落,有些公告内使用div标签分行
186
+ else:
187
+ if divItem.text.count(':') == 1:
188
+ screenStr.append(divItem.text)
189
+
190
+
191
+
192
+
193
+
194
+ amountStr = ''
195
+ ddlTimeStr = ''
196
+ for pStr in screenStr:
197
+ text = re.sub('\s+','',pStr.replace(u'\xa0','').strip().replace('\n','').replace(':',':'))
198
+ projectNum = re.search(proejctNumPattern,text)
199
+ if 'projectNum' not in noteInfo and projectNum:
200
+ noteInfo.projectNum = projectNum.group(1)
201
+ # 直接把包含元的内容全给GPT,让GPT识别
202
+ if '元' in text:
203
+ amountStr += text.replace(',','')
204
+ if '截止时间' in text:
205
+ ddlTimeStr += text
206
+ # 如果文本中包含多个:,则不处理,因为无法判断需要的内容在第几个分号分割
207
+ if text.count(':') > 1:
208
+ continue
209
+ # 因为有很多会有1.2采购人:xxx这种格式,所以只能用in,不能用startwith
210
+ # 匹配上其中一个关键字之后就不用继续匹配了,优化效率
211
+ hasMatched = False
212
+ for keyword, field in keySet.items():
213
+ if hasMatched:
214
+ break
215
+ for key in field:
216
+ if key in text:
217
+ val = text.split(':')[1]
218
+ # 所有字段长度理论上都是大于1的,正好可以用来处理单位名称:空这类的情况
219
+ if val and len(val)>1:
220
+ if keyword == 'bidUnit' and 'bidUnit' not in noteInfo:
221
+ print('--------match bidunit-----',val)
222
+ noteInfo.bidUnit = val
223
+ hasMatched = True
224
+ break
225
+ elif keyword == 'proxyUnit' and '公章' not in val:
226
+ noteInfo.proxyUnit = val
227
+ hasMatched = True
228
+ break
229
+ elif keyword == 'winBidUnit' and 'winBidUnit' not in noteInfo:
230
+ noteInfo.winBidUnit = val
231
+ hasMatched = True
232
+ break
233
+ elif keyword == 'bidContact' and 'bidContact' not in noteInfo:
234
+ noteInfo.bidContact = val
235
+ hasMatched = True
236
+ break
237
+ elif keyword == 'bidPhone' and 'bidPhone' not in noteInfo:
238
+ noteInfo.bidPhone = val
239
+ hasMatched = True
240
+ break
241
+ elif keyword == 'proxyContact' and 'proxyContact' not in noteInfo:
242
+ noteInfo.proxyContact = val
243
+ hasMatched = True
244
+ break
245
+ elif keyword == 'proxyPhone' and 'proxyPhone' not in noteInfo:
246
+ noteInfo.proxyPhone = val
247
+ hasMatched = True
248
+ break
249
+ elif keyword == 'address' and 'address' not in noteInfo:
250
+ noteInfo.address = val
251
+ hasMatched = True
252
+ break
253
+ elif keyword == 'contact':
254
+ if 'bidContact' not in noteInfo:
255
+ noteInfo.bidContact = val
256
+ elif 'proxyContact' not in noteInfo:
257
+ noteInfo.proxyContact = val
258
+
259
+ hasMatched = True
260
+ break
261
+
262
+ elif keyword == 'phone':
263
+ phone = val.replace('拨打','')
264
+ if len(phone) > 25:
265
+ print(datetime.now(),'电话获取错误跳过',phone)
266
+ continue
267
+ if 'bidPhone' not in noteInfo:
268
+ noteInfo.bidPhone = phone
269
+ elif 'proxyPhone' not in noteInfo:
270
+ noteInfo.proxyPhone = phone
271
+ hasMatched = True
272
+ break
273
+ elif keyword == 'winBidAmount':
274
+ try:
275
+ winBidAmount = val.replace(',','')
276
+ if winBidAmount.endswith('万元'):
277
+ amount = float(winBidAmount.replace('万元','')) * 10000
278
+ noteInfo.winBidAmount = amount
279
+ elif winBidAmount.endswith('元'):
280
+ amount = float(winBidAmount.replace('元',''))
281
+ noteInfo.winBidAmount = amount
282
+ else:
283
+ noteInfo.winBidAmount = float(amount)
284
+ except Exception as parse_amount_err:
285
+ print(datetime.now(),'中标金额转换失败',winBidAmount,parse_amount_err)
286
+ elif keyword == 'bidAmount':
287
+ match = re.search(pattern, val.replace(',',''))
288
+ if match:
289
+ tender_number = match.group(1)
290
+ if '万元' in tender_number:
291
+ amountstr = tender_number.replace('万元','')
292
+ amount = float(amountstr) * 10000
293
+ noteInfo.bidAmount = amount
294
+ elif '元' in tender_number:
295
+ amount = float(tender_number.replace('元',''))
296
+ noteInfo.bidAmount = amount
297
+
298
+ # 名称这个词太宽泛,所以用startswith,以上词相对精准,可以用in匹配
299
+ if text.startswith('名称:') or text.startswith('单位名称:'):
300
+ if 'bidUnit' not in noteInfo:
301
+ noteInfo.bidUnit = text.split(':')[1]
302
+ elif 'proxyUnit' not in noteInfo:
303
+ noteInfo.proxyUnit = text.split(':')[1]
304
+ elif text.startswith('发布时间:') or text.startswith('发布日期:'):
305
+ publishTime = parsePublishTime(text.split(':')[1])
306
+ noteInfo.publishTime = publishTime
307
+
308
+ if amountStr != '':
309
+ # 最多传1000字
310
+ if 'bidAmount' not in noteInfo and noteInfo.noticeType != 1:
311
+ amount = getAmountByZhiPu(amountStr[:1000])
312
+ print(datetime.now(),'amount',amount)
313
+ if amount != None and amount != 2330000.0:
314
+ noteInfo.bidAmount = amount
315
+ elif 'winBidAmount' not in noteInfo and noteInfo.noticeType == 1:
316
+ amount = getAmountByZhiPu(amountStr[:1000])
317
+ print(datetime.now(),'amount',amount)
318
+
319
+ if amount != None and amount != 2330000.0:
320
+ noteInfo.winBidAmount = amount
321
+
322
+ if ddlTimeStr != '':
323
+ ddlDate = getBidDdlTimeByZhiPu(ddlTimeStr)
324
+ if ddlDate != None:
325
+ noteInfo.bidDdlTime = ddlDate.strftime('%Y-%m-%d %H:%M:%S')
326
+ print('----解析完毕---')
327
+ # noteInfo['content'] = ''
328
+ # 尝试获取地市信息,如果报错,不影响其他字段插入
329
+ try:
330
+ if 'cityId' not in noteInfo and 'address' in noteInfo:
331
+ # 优先使用公告标题查询公告所属地市
332
+ noteInfo = parseCityId(noteInfo.address,noteInfo)
333
+ # 如果通过公告名称无法提取地市名称,则通过采购单位名称查询
334
+ if 'cityId' not in noteInfo and 'bidUnit' in noteInfo:
335
+ noteInfo = parseCityId(noteInfo.bidUnit,noteInfo)
336
+ # 如果通过公告名称无法提取地市名称,则通过采购单位名称查询
337
+ if 'cityId' not in noteInfo:
338
+ noteInfo = parseCityId(noteInfo.name,noteInfo)
339
+ except Exception as get_city_info_err:
340
+ print(datetime.now(),'通过文本查询地市信息异常',get_city_info_err)
341
+
342
+
343
+ # 验证截止时间和发布时间,如果截止时间在发布时间之前,那这个时间错误,置为空
344
+ if 'publishTime' in noteInfo and 'bidDdlTime' in noteInfo:
345
+ publishTime = datetime.strptime(noteInfo.publishTime,'%Y-%m-%d %H:%M:%S')
346
+ bidDdlTime = datetime.strptime(noteInfo.bidDdlTime,'%Y-%m-%d %H:%M:%S')
347
+ if bidDdlTime < publishTime:
348
+ noteInfo.bidDdlTime = ''
349
+ # print('----noteInfo:',noteInfo)
350
+ return noteInfo
351
+
352
+ def parseCityId(cityParseStr,noteInfo):
353
+ cityName = getCityInfoByZhiPu(cityParseStr)
354
+ if cityName:
355
+ cityInfoList = dbHelper.queryCityInfo(cityName)
356
+ print(datetime.now(),'查询到公告所属省市区为',cityInfoList)
357
+ if len(cityInfoList)>0:
358
+ cityInfo = cityInfoList[0]
359
+ if '' != cityInfo[4]:
360
+ # 如果noteInfo已经定位了省份,则验证省份ID是否正确,正确则添加cityId或areaId,如果不正确,则地区匹配错误不作更新
361
+ if 'provinceId' in noteInfo:
362
+ if noteInfo.provinceId == cityInfo[4]:
363
+ if '' != cityInfo[0]:
364
+ noteInfo.areaId = cityInfo[0]
365
+ if '' != cityInfo[2]:
366
+ noteInfo.cityId = cityInfo[2]
367
+ else:
368
+ print(datetime.now(),'获取到地市信息与当前provinceId不符,数据抛弃')
369
+ else:
370
+ # 如果noteInfo没有定位省份,则直接添加provinceId,cityId和areaId
371
+ noteInfo.provinceId = cityInfo[4]
372
+ if '' != cityInfo[0]:
373
+ noteInfo.areaId = cityInfo[0]
374
+ if '' != cityInfo[2]:
375
+ noteInfo.cityId = cityInfo[2]
376
+ return noteInfo
377
+
378
+
379
+ def getBidDdlTimeByZhiPu(ddlTimeStr):
380
+ try:
381
+ client = ZhipuAI(api_key="9ece6c42cdca139afa262b16c93814d5.qVKFvLXWFfwuaHgp") # 填写您自己的APIKey
382
+ response = client.chat.completions.create(
383
+ model="glm-4-flash", # 填写需要调用的模型编码
384
+ messages=[
385
+ {"role": "system", "content": "# 角色:您是项目信息提取专家,专注于从项目公告的片段中提取投标文件的截止时间等## 技能:截止时间提取## 约束条件###1、时间来自用户输入原文###2、仅保留一个结果###3、结果按截止时间:XXXX-XX-XX XX:XX:XX的格式返回,且不要有其他描述,只显示一个截止时间即可###4、若结果没有秒数,则默认补充秒数为00###5、输出结果之前验算一下约束条件### 示例互动流程- **用户询问**:凡有意参加投标者,请于2024年10月16日00时00分至2024年10月23日23时59分(北京时间,下同),登录全国公共资源交易平台,通过数字证书免费下载招标文件(含招标文件的澄清、修改,通知等内容)。6.1 投标文件递交的截止时间(投标截止时间,下同)为2024年11月6日9时30分,投标人应在截止时间前通过登录全国- **处理步骤**: 1. 按分号;分割用户输入的内容 2. 遍历每一段内容并理解用户输入 3. 拆分出各个金额及金额对应的意思 4. 按照截止时间:XXXX-XX-XX XX:XX:XX的格式设定输出结果 -**输出结果案例**:截止时间:2024-11-06 09:30:00 "},
386
+ {"role": "user", "content": "请提取下文中的截止时间,只需回答截止时间即可,不要有其他描述,文本如下:%s" % (ddlTimeStr)}
387
+ ],
388
+ )
389
+ resContent = response.choices[0].message.content
390
+ ddlRes = resContent.split(':')[1]
391
+ ddlTime = datetime.strptime(ddlRes,'%Y-%m-%d %H:%M:%S')
392
+ return ddlTime
393
+ except Exception as parse_ddl_err:
394
+ return None
395
+
396
+
397
+ def getbidDdlTime(ddlTimeStr):
398
+ url = 'http://10.220.6.180:17860/v1/chat/completions'
399
+ # url = 'http://223.76.195.4:18012/api/v1/chat/completions'
400
+ param ={
401
+ "model": "chatglm3",
402
+ "messages": [
403
+ {
404
+ "role": "system",
405
+ "content": "# 角色:您是项目信息提取专家,专注于从项目公告的片段中提取投标文件的截止时间等## 技能:截止时间提取## 约束条件###1、时间来自用户输入原文###2、仅保留一个结果###3、结果按截止时间:XXXX年XX月XX日 XX时XX分的格式返回,且不要有其他描述,只显示一个截止时间即可###4、输出结果之前验算一下约束条件### 示例互动流程- **用户询问**:凡有意参加投标者,请于2024年10月16日00时00分至2024年10月23日23时59分(北京时间,下同),登录全国公共资源交易平台(贵州省·六盘水市),网址http://ggzy.gzlps.gov.cn,通过数字证书免费下载招标文件(含招标文件的澄清、修改,通知等内容)。6.1 投标文件递交的截止时间(投标截止时间,下同)为2024年11月6日9时30分,投标人应在截止时间前通过登录全国- **处理步骤**: 1. 按分号;分割用户输入的内容 2. 遍历每一段内容并理解用户输入 3. 拆分出各个金额及金额对应的意思 4. 按照截止时间:XXXX年XX月XX日 XX时XX分的格式设定输出结果 -**输出结果案例**:截止时间:2024年11月6日 9时30分 "
406
+ },
407
+ {
408
+ "role": "user",
409
+ "content": "请提取下文中的截止时间,只需回答截止时间即可,不要有其他描述,文本如下:%s" % (ddlTimeStr)
410
+ }
411
+ ],
412
+ "stream": False,
413
+ "temperature": 0,
414
+ "top-p": 0
415
+ }
416
+ header = {
417
+ 'Content-Type':'application/json',
418
+ 'Authorization': 'Bearer sk-n0voW3LMMrDGaRtG75F34330EbF94d3bA8606c6d9910F0E8'
419
+ # 'Authorization': 'Bearer fastgpt-78cMEaF8hoZVndBjsr0nSPexYrPuDiEtcDhx2egnNteN5sWhNqHYFvvhj5ySvuZ2h'
420
+ }
421
+ try:
422
+ print(datetime.now(),'请求GPT获取截标时间:',ddlTimeStr)
423
+ response = requests.post(url=url,data=json.dumps(param),headers=header,timeout=30)
424
+ print(datetime.now(),'get ddlTime response:',response.text)
425
+ res = json.loads(response.text)
426
+ resContent = res['choices'][0]['message']['content']
427
+ if ':':
428
+ ddlRes = resContent.split(':')[1]
429
+ try:
430
+ if '年' in ddlRes:
431
+ try:
432
+ date = datetime.strptime(ddlRes,'%Y年%m月%d日 %H时%M分')
433
+
434
+ except Exception:
435
+ date = datetime.strptime(ddlRes,'%Y年%m月%d日%H时%M分')
436
+
437
+ # print(date.strftime('%Y-%m-%d %H:%M:%S'))
438
+ else:
439
+ try:
440
+ date = datetime.strptime(ddlRes,'%Y-%m-%d %H:%M')
441
+ except Exception:
442
+ date = datetime.strptime(ddlRes,'%Y-%m-%d %H:%M:%S')
443
+ return date
444
+ except Exception as err:
445
+ print(datetime.now(),err)
446
+ return None
447
+ else:
448
+ # print(type(resContent))
449
+ return None
450
+ except Exception as get_amount_err:
451
+ print('获取截止时间失败',get_amount_err)
452
+ return None
453
+
454
+ def getAmountByZhiPu(amountStr):
455
+ client = ZhipuAI(api_key="9ece6c42cdca139afa262b16c93814d5.qVKFvLXWFfwuaHgp") # 填写您自己的APIKey
456
+ response = client.chat.completions.create(
457
+ model="glm-4-flash", # 填写需要调用的模型编码
458
+ messages=[
459
+ {"role": "system", "content": "# 角色:您是项目金额提取专家,专注于从项目公告的片段中提取项目金额等## 技能:项目金额提取## 约束条件###1、所有项目金额全部来自用户输入原文###2、所有金额转换为XXX元###3、仅保留一个结果###4、缴纳材料费、注册资本不是招标金额###5、预算金额,中标金额,招标金额,合同金额,项目投资,施工合同估算价,工程造价这些词都是项目金额,如果没有这些描述,取意思最相近的金额。如果确实无法匹配项目金额,返回项目金额:0元###6、如果有多个金额都是项目金额的描述,取金额最大的一个数字返回###7、结果按项目金额:XXX元的格式返回,且不要有其他描述,只显示一个项目金额:xx元即可###8、输出结果之前验算一下约束条件### 示例互动流程- **用户询问**:预算金额(万元):233.0000000;最高限价(如有):2350000.00元;售价:0元- **处理步骤**: 1. 按分号;分割用户输入的内容 2. 遍历每一段内容并理解用户输入 3. 拆分出各个金额及金额对应的意思 4. 分辨拆分出的金额哪一个才是公告的项目金额,如果金额单位是万元,则直接返回万元单位的结果,如果金额单位是亿元,则直接返回亿元单位的结果 5. 按照项目金额:xxx元的格式设定输出结果 7. 输出结果-**输出结果案例**:项目金额:233.0000000万元"},
460
+ {"role": "user", "content": "请提取下文中的项目金额,只需回答项目金额:xx元或者项目金额:xx万元即可,不要有其他描述,文本如下:%s" % (amountStr)}
461
+ ],
462
+ )
463
+ answerStr = response.choices[0].message.content
464
+ match = re.search(pattern, answerStr.replace(',',''))
465
+ if match:
466
+ # 提取并打印招标编号
467
+ tender_number = match.group(1)
468
+ if '万元' in tender_number:
469
+ amountstr = tender_number.replace('万元','')
470
+ amount = float(amountstr) * 10000
471
+ print(datetime.now(),'最终项目金额:',amount)
472
+ return amount
473
+ elif '亿元' in tender_number:
474
+ amountstr = tender_number.replace('亿元','')
475
+ amount = float(amountstr) * 100000000
476
+ print(datetime.now(),'最终项目金额:',amount)
477
+ return amount
478
+ elif '元' in tender_number:
479
+ amount = float(tender_number.replace('元',''))
480
+ print(datetime.now(),'最终项目金额:',amount)
481
+ return amount
482
+ else:
483
+ amount = float(tender_number)
484
+ print(datetime.now(),'最终项目金额:',amount)
485
+ return amount
486
+ else:
487
+ # print(type(resContent))
488
+ return None
489
+
490
+ def getAmount(amountStr):
491
+ # 正则表达式匹配浮点数
492
+ parPattern = r'(\d+(\.\d+)?)(?=[^\d])'
493
+
494
+ # 定义一个函数来处理匹配到的数字
495
+ def format_number(match):
496
+ num_str = match.group(0)
497
+ num = float(num_str)
498
+ # 格式化数字,去掉小数点后的多余0,并保留至少一位小数
499
+ formatted_num = f"{num:.2f}"
500
+ return formatted_num
501
+
502
+ # 使用正则表达式替换文本中的数字
503
+ amountStr = re.sub(parPattern, format_number, amountStr)
504
+ url = 'http://10.220.6.180:17860/v1/chat/completions'
505
+ # url = 'http://223.76.195.4:18012/api/v1/chat/completions'
506
+ param ={
507
+ "model": "chatglm3",
508
+ "messages": [
509
+ {
510
+ "role": "system",
511
+ "content": "# 角色:您是项目金额提取专家,专注于从项目公告的片段中提取项目金额等## 技能:项目金额提取## 约束条件###1、所有项目金额全部来自用户输入原文###2、所有金额转换为XXX元###3、仅保留一个结果###4、缴纳材料费、注册资本不是招标金额###5、预算金额,中标金额,招标金额,合同金额,项目投资,施工合同估算价,工程造价这些词都是项目金额,如果没有这些描述,取意思最相近的金额。如果确实无法匹配项目金额,返回项目金额:0元###6、如果有多个金额都是项目金额的描述,取金额最大的一个数字返回###7、结果按项目金额:XXX元的格式返回,且不要有其他描述,只显示一个项目金额:xx元即可###8、输出结果之前验算一下约束条件### 示例互动流程- **用户询问**:预算金额(万元):233.0000000;最高限价(如有):2350000.00元;售价:0元- **处理步骤**: 1. 按分号;分割用户输入的内容 2. 遍历每一段内容并理解用户输入 3. 拆分出各个金额及金额对应的意思 4. 分辨拆分出的金额哪一个才是公告的项目金额,如果金额单位是万元,则直接返回万元单位的结果,如果金额单位是亿元,则直接返回亿元单位的结果 5. 按照项目金额:xxx元的格式设定输出结果 7. 输出结果-**输出结果案例**:项目金额:233.0000000万元"
512
+ },
513
+ {"role": "user", "content": "请提取下文中的项目金额,只需回答项目金额:xx元或者项目金额:xx万元即可,不要有其他描述,文本如下:%s" % (amountStr)}
514
+ ],
515
+ "stream": False,
516
+ "temperature": 0
517
+ }
518
+ header = {
519
+ 'Content-Type':'application/json',
520
+ 'Authorization': 'Bearer sk-n0voW3LMMrDGaRtG75F34330EbF94d3bA8606c6d9910F0E8'
521
+ # 'Authorization': 'Bearer fastgpt-78cMEaF8hoZVndBjsr0nSPexYrPuDiEtcDhx2egnNteN5sWhNqHYFvvhj5ySvuZ2h'
522
+ }
523
+ try:
524
+ print(datetime.now(),'请求GPT获取金额:',amountStr)
525
+ response = requests.post(url=url,data=json.dumps(param),headers=header,timeout=30)
526
+ print(datetime.now(),'get amount response:',response.text)
527
+ res = json.loads(response.text)
528
+ resContent = res['choices'][0]['message']['content']
529
+ match = re.search(pattern, resContent.replace(',',''))
530
+ if match:
531
+ # 提取并打印招标编号
532
+ tender_number = match.group(1)
533
+ if '万元' in tender_number:
534
+ amountstr = tender_number.replace('万元','')
535
+ amount = float(amountstr) * 10000
536
+ print(datetime.now(),'最终项目金额:',amount)
537
+ return amount
538
+ elif '亿元' in tender_number:
539
+ amountstr = tender_number.replace('亿元','')
540
+ amount = float(amountstr) * 100000000
541
+ print(datetime.now(),'最终项目金额:',amount)
542
+ return amount
543
+ elif '元' in tender_number:
544
+ amount = float(tender_number.replace('元',''))
545
+ print(datetime.now(),'最终项目金额:',amount)
546
+ return amount
547
+ else:
548
+ amount = float(tender_number)
549
+ print(datetime.now(),'最终项目金额:',amount)
550
+ return amount
551
+ else:
552
+ # print(type(resContent))
553
+ return None
554
+ except Exception as get_amount_err:
555
+ print('获取金额失败',get_amount_err)
556
+ return None
557
+
558
+
559
+ def parsePublishTime(str):
560
+ publishTimeStr = str.replace(':',':').replace('/','-').strip()
561
+ try:
562
+ if '-' in publishTimeStr:
563
+ semCount = publishTimeStr.count(':')
564
+ if semCount == 0:
565
+ dateObj = datetime.strptime(publishTimeStr,'%Y-%m-%d')
566
+ # 如果时间是同一天,但是没有详细时间,返回当前的详细时间
567
+ if dateObj.date() == datetime.now().date():
568
+ return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
569
+ else:
570
+ return dateObj.strftime('%Y-%m-%d %H:%M:%S')
571
+ elif semCount == 1:
572
+ dateObj = datetime.strptime(publishTimeStr,'%Y-%m-%d %H:%M')
573
+ return dateObj.strftime('%Y-%m-%d %H:%M:%S')
574
+ elif semCount == 2:
575
+ dateObj = datetime.strptime(publishTimeStr,'%Y-%m-%d %H:%M:%S')
576
+ return dateObj.strftime('%Y-%m-%d %H:%M:%S')
577
+ elif '年' in publishTimeStr:
578
+ semCount = publishTimeStr.count(':')
579
+ if semCount == 0:
580
+ dateObj = datetime.strptime(publishTimeStr,'%Y年%m月%d日')
581
+ return dateObj.strftime('%Y-%m-%d %H:%M:%S')
582
+ elif semCount == 1:
583
+ dateObj = datetime.strptime(publishTimeStr,'%Y年%m月%d日 %H:%M')
584
+ return dateObj.strftime('%Y-%m-%d %H:%M:%S')
585
+ elif semCount == 2:
586
+ dateObj = datetime.strptime(publishTimeStr,'%Y年%m月%d日 %H:%M:%S')
587
+ return dateObj.strftime('%Y-%m-%d %H:%M:%S')
588
+ else:
589
+ return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
590
+ except Exception as parse_date_err:
591
+ print(datetime.now(),'时间识别错误,发布时间默认为当前时间',str)
592
+ return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
593
+
594
+
595
+
596
+
597
+ def remove_space(str):
598
+ return ' '.join(str.split())
599
+
600
+
601
+ def getCityInfoByZhiPu(cityInfoStr):
602
+ client = ZhipuAI(api_key="9ece6c42cdca139afa262b16c93814d5.qVKFvLXWFfwuaHgp") # 填写您自己的APIKey
603
+ response = client.chat.completions.create(
604
+ model="glm-4-flash", # 填写需要调用的模型编码
605
+ messages=[
606
+ {"role": "system", "content": "# 角色:您是项目所属地提取专家,专注于从项目公告中地名## 技能:项目金额提取## 约束条件###1、项目所属地名一定在用户输入的原文内,不要杜撰###2、如果项目名同时包含省、市、区,只返回区名###3、仅保留一个结果###3、结果按项目所属地:XXX的格式返回,且不要有其他描述,只显示一个项目所属地:xx市/区即可###4、如果文中没有明确写明所属地,则范围空值即可###8、输出结果之前验算一下约束条件### 示例互动流程- **用户询问**:陕西省渭南市秦岭北麓(东部)森林火灾高风险区综合治理工程建设项目招标公告 **处理步骤**: 1. 先提取出用户输入文本内包含的省市区地名,如陕西省、渭南市 2. 遍历提取出的地名,判断地名数量,地名是属于省还是地市还是地区 3. 按地区>地市>省份的优先级排序 4. 取最高优先级的地名拼凑结果 7. 输出结果-**输出结果案例**:项目所属地:渭南"},
607
+ {"role": "user", "content": "请提取下标题中的的项目所属地,只需回答项目所属地:xx即可,不要有其他描述,文本如下%s" % (cityInfoStr)}
608
+ ],
609
+ )
610
+ resContent = response.choices[0].message.content
611
+ if '项目所属地:'in resContent:
612
+ # 提取并打印招标编号
613
+ cityName = resContent.split(':')[1]
614
+ return cityName
615
+ else:
616
+ return None
617
+
618
+ def getCityInfo(cityInfoStr):
619
+ url = 'http://10.220.6.180:17860/v1/chat/completions'
620
+ # url = 'http://223.76.195.4:18012/api/v1/chat/completions'
621
+ param ={
622
+ "model": "chatglm3",
623
+ "messages": [
624
+ {
625
+ "role": "system",
626
+ "content": "# 角色:您是项目所属地提取专家,专注于从项目公告中地名## 技能:项目金额提取## 约束条件###1、项目所属地名一定在用户输入的原文内,不要杜撰###2、如果项目名同时包含省、市、区,只返回区名###3、仅保留一个结果###3、结果按项目所属地:XXX的格式返回,且不要有其他描述,只显示一个项目所属地:xx市/区即可###4、如果文中没有明确写明所属地,则范围空值即可###8、输出结果之前验算一下约束条件### 示例互动流程- **用户询问**:陕西省渭南市秦岭北麓(东部)森林火灾高风险区综合治理工程建设项目招标公告 **处理步骤**: 1. 先提取出用户输入文本内包含的省市区地名,如陕西省、渭南市 2. 遍历提取出的地名,判断地名数量,地名是属于省还是地市还是地区 3. 按地区>地市>省份的优先级排序 4. 取最高优先级的地名拼凑结果 7. 输出结果-**输出结果案例**:项目所属地:渭南"
627
+ },
628
+ {
629
+ "role": "user",
630
+ "content": "请提取下标题中的的项目所属地,只需回答项目所属地:xx即可,不要有其他描述,文本如下%s" % (cityInfoStr)
631
+ }
632
+ ],
633
+ "stream": False,
634
+ "temperature": 0
635
+ }
636
+ header = {
637
+ 'Content-Type':'application/json',
638
+ 'Authorization': 'Bearer sk-n0voW3LMMrDGaRtG75F34330EbF94d3bA8606c6d9910F0E8'
639
+ # 'Authorization': 'Bearer fastgpt-78cMEaF8hoZVndBjsr0nSPexYrPuDiEtcDhx2egnNteN5sWhNqHYFvvhj5ySvuZ2h'
640
+ }
641
+ try:
642
+ print(datetime.now(),'请求GPT提取地市名称:',cityInfoStr)
643
+ response = requests.post(url=url,data=json.dumps(param),headers=header,timeout=30)
644
+ print(datetime.now(),'get amount response:',response.text)
645
+ res = json.loads(response.text)
646
+ resContent = res['choices'][0]['message']['content']
647
+ if '项目所属地:'in resContent:
648
+ # 提取并打印招标编号
649
+ cityName = resContent.split(':')[1]
650
+ return cityName
651
+ else:
652
+ return None
653
+ except Exception as get_amount_err:
654
+ print('获取地市名称失败',get_amount_err)
655
+ return None
656
+
657
+
658
+
659
+
660
+ # class NoteInfo(BaseModel):
661
+ # # 公告名称
662
+ # name: str
663
+ # url: str # 原文链接
664
+ # dataSource: str # 数据源
665
+ # publishTime: Optional[str] = '' # 发布时间
666
+ # provinceId: Optional[str] = '' # 省份id
667
+ # cityId: Optional[str] = '' # 城市id
668
+ # areaId: Optional[str] = '' # 区id
669
+ # projectNum: Optional[str] = '' # 项目编码
670
+ # noticeType: Optional[int] = 0 # 公告类型
671
+ # bidUnit: Optional[str] = '' # 招标单位
672
+ # bidContact: Optional[str] = '' # 招标单位联系人
673
+ # bidPhone: Optional[str] = '' # 招标单位联系人电话
674
+ # bidAmount: Optional[float] = 0.0 # 招标金额
675
+ # proxyUnit: Optional[str] = '' # 代理公司名称
676
+ # proxyContact: Optional[str] = '' # 代理公司联系人
677
+ # proxyPhone: Optional[str] = '' # 代理公司联系人电话
678
+ # winBidUnit: Optional[str] = '' # 中标单位
679
+ # winBidAmount: Optional[float] = 0.0 # 中标金额
680
+ # address: Optional[str] = '' # 招标单位地址
681
+ # bidDdlTime: Optional[str] = '' # 投标截止时间
682
+ # content: Optional[str] = '' # 公告内容
683
+
684
+ # class ParseResponse(BaseModel):
685
+ # code: int = 0
686
+ # msg: str
687
+ # data: Optional[NoteInfo] = None
688
+
689
+ # app = FastAPI()
690
+
691
+
692
+
693
+ # @app.post('/content/parse')
694
+ # async def parsePost(noteInfo: NoteInfo ):
695
+ # # if not noteInfo.name or not noteInfo.url:
696
+ # # print('缺少必填字段')
697
+ # # raise HTTPException(status_code=400,detail="公告名称与路径必填")
698
+ # print(datetime.now(),f'=====开始解析{noteInfo.dataSource}--{noteInfo.name}--{noteInfo.url}')
699
+ # content = noteInfo.content
700
+ # if len(content) > 1:
701
+ # noteInfo = parseContent(content,noteInfo)
702
+ # # # 返回响应,确保使用UTF-8编码
703
+ # # response = app.response_class(
704
+ # # response=json.dumps(noteInfo, ensure_ascii=False),
705
+ # # status=200,
706
+ # # mimetype='application/json'
707
+ # # )
708
+ # # response.headers["Content-Type"] = "application/json; charset=utf-8"
709
+ # # return ParseResponse(code=0, msg='success', data=noteInfo)
710
+ # print(datetime.now(),f'=====解析完毕{noteInfo.dataSource}--{noteInfo.name}--{noteInfo.url}')
711
+ # return noteInfo
712
+
713
+
714
+
715
+ # 设定main函数,程序起点
716
+ if __name__ == '__main__':
717
+ try:
718
+ # uvicorn.run(app, host='0.0.0.0', port=18881, workers=1)
719
+
720
+ # # noteInfo = testMatch('电话:ABC公司')
721
+ pStr = '名 称:抚松县教育局局'
722
+ # text = re.sub('\s+','',pStr.replace(u'\xa0','').strip().replace('\n','').replace(':',':'))
723
+ # print('======text:',text)
724
+ # a = remove_space(text)
725
+ # print(a)
726
+ # # a = ' 1.2852万元。收取对象:中标(成交)供应商。2.采购预算总金额:1,000,000.00元,最高限价:978,000.00元。地址:中国(四川)自由贸易试验区成都高新区益州大道中段722号3栋1单元603号中标(成交)金额:952,000.00元金额(元):952,000.00'
727
+ # # amount = getAmount(a[:1000])
728
+ # # print('noteInfo',amount)
729
+
730
+ except Exception as err:
731
+ print(f'任务执行失败',err)
732
+ traceback.print_exc()
733
+
734
+