1.20 pypdf2读取失败可转为使用fitz库

This commit is contained in:
zy123 2025-01-21 15:53:31 +08:00
parent 6618c6d247
commit c7ba7a5c93
8 changed files with 236 additions and 184 deletions

View File

@ -121,7 +121,7 @@ def extract_common_header(pdf_path):
current_common = find_common_headers(headers)
if current_common:
common_headers = current_common
print(f"使用策略{idx + 1}找到共同的页眉: {common_headers}")
# print(f"使用策略{idx + 1}找到共同的页眉: {common_headers}")
break # 找到共同部分后退出
# 如果没有找到,继续下一个策略

View File

@ -1,53 +1,106 @@
import os
import fitz
from PyPDF2 import PdfReader, PdfWriter
from flask_app.general.截取pdf通用函数 import create_get_text_function
#合并PDF
# 主合并函数,尝试使用 PyPDF2若失败则使用 fitz
def merge_pdfs(paths, output_path):
# 检查是否所有路径都是空字符串或仅包含空白字符
# 检查所有路径是否为空或仅包含空白字符
if not any(path.strip() for path in paths):
return ""
# 优先尝试 PyPDF2 合并
try:
return merge_with_pypdf2(paths, output_path)
except Exception as e:
print(f"使用 PyPDF2 合并失败,尝试使用 fitz。错误: {e}")
# 若失败则尝试 fitz
try:
return merge_with_fitz(paths, output_path)
except Exception as ex:
print(f"使用 fitz 合并也失败了。错误: {ex}")
return ""
def merge_with_pypdf2(paths, output_path):
pdf_writer = PdfWriter()
last_page_text = None # 用于存储上一个PDF的最后一页的文本
last_page_text = None # 存储上一个 PDF 的最后一页文本
for path in paths:
# 跳过空字符串或仅包含空白字符的路径
if not path.strip():
continue
try:
pdf_reader = PdfReader(path)
pages = pdf_reader.pages
start_index = 0 # 从第一页开始添加
get_text = create_get_text_function('pypdf2', pdf_reader)
# 如果这不是第一个文件,并且有上一个文件的最后一页文本
start_index = 0
if last_page_text is not None and len(pages) > 0:
current_first_page_text = pages[0].extract_text() or ""
# 比较当前文件的第一页和上一个文件的最后一页的文本
current_first_page_text = get_text(0)
if current_first_page_text == last_page_text:
start_index = 1 # 如果相同,跳过当前文件的第一页
start_index = 1
# 添加当前PDF的页面到写入器
for page_num in range(start_index, len(pages)):
pdf_writer.add_page(pages[page_num])
# 更新 last_page_text 为当前PDF的最后一页的文本
if len(pages) > 0:
last_page_text = pages[-1].extract_text() or ""
last_page_text = get_text(len(pages) - 1)
except Exception as e:
print(f"文件 '{path}' 无法处理,错误: {e}")
print(f"文件 '{path}' 无法使用 PyPDF2 处理,错误: {e}")
continue
# 如果没有添加任何页面,返回空字符串
if len(pdf_writer.pages) == 0:
return ""
# 写入合并后的PDF到文件
with open(output_path, 'wb') as out:
pdf_writer.write(out)
return output_path
def merge_with_fitz(paths, output_path):
# 使用 fitz 创建新文档
merged_pdf = fitz.open()
last_page_text = None
for path in paths:
if not path.strip():
continue
try:
pdf_doc = fitz.open(path)
get_text = create_get_text_function('fitz', pdf_doc)
page_count = pdf_doc.page_count
start_index = 0
if last_page_text is not None and page_count > 0:
current_first_page_text = get_text(0)
if current_first_page_text == last_page_text:
start_index = 1
# 插入页面到新文档
for page_num in range(start_index, page_count):
merged_pdf.insert_pdf(pdf_doc, from_page=page_num, to_page=page_num)
if page_count > 0:
last_page_text = get_text(page_count - 1)
pdf_doc.close()
except Exception as e:
print(f"文件 '{path}' 无法使用 fitz 处理,错误: {e}")
continue
if merged_pdf.page_count == 0:
merged_pdf.close()
return ""
try:
with open(output_path, 'wb') as out:
pdf_writer.write(out)
merged_pdf.save(output_path)
merged_pdf.close()
return output_path
except Exception as e:
print(f"无法写入输出文件,错误: {e}")
merged_pdf.close()
return ""
def judge_file_exist(original_path, new_suffix):

View File

@ -119,10 +119,10 @@ if __name__ == "__main__":
# input_path = r"C:\Users\Administrator\Desktop\new招标文件\工程标"
# pdf_path=r"C:\Users\Administrator\Desktop\货物标\zbfiles\094定稿-湖北工业大学轻武器模拟射击设备采购项目招标文件.pdf"
# pdf_path = r"C:\Users\Administrator\Desktop\货物标\zbfiles\zbtest4_evaluation_method.pdf"
pdf_path=r"D:\flask_project\flask_app\static\output\output1\139ecc93-bc85-4007-a4c5-1fb158a88719\ztbfile.pdf"
pdf_path=r"C:\Users\Administrator\Desktop\新建文件夹 (3)\temp\鄂州市急救中心展厅布展项目.pdf"
# pdf_path=r"C:\Users\Administrator\Desktop\货物标\zbfiles\zbtest4_evaluation_method.pdf"
# input_path=r"C:\Users\Administrator\Desktop\招标文件\招标test文件夹\zbtest8.pdf"
output_folder = r"D:\flask_project\flask_app\static\output\output1\139ecc93-bc85-4007-a4c5-1fb158a88719"
output_folder = r"C:\Users\Administrator\Desktop\新建文件夹 (3)\temp"
# selections = [1, 4] # 仅处理 selection 4、1
# selections = [1, 2, 3, 5]
# files = truncate_pdf_multiple(pdf_path, output_folder, logger, 'goods', selections) #engineering

View File

@ -76,7 +76,7 @@ def get_start_and_common_header(input_path, end_page):
cleaned_text = clean_page_content(text, common_header)
# 检查是否存在"目录"
if catalog_pattern.search(cleaned_text):
print(f"页面 {i} 包含目录,跳过。")
# print(f"页面 {i} 包含目录,跳过。")
continue # 如果存在目录,跳过当前页面
if begin_pattern.search(cleaned_text):
last_begin_index = i # 更新第一个匹配的索引页码从0开始
@ -278,7 +278,7 @@ def get_invalid_file(file_path, output_folder, common_header, begin_page):
regex.MULTILINE
),
regex.compile(
r"\s*(投标文件|响应文件|响应性文件|应答文件)(?:的)?格式\s*",
r"\s*(投标文件|响应文件|响应性文件|应答文件)(?:的)?格式(?:及相关附件)?\s*$",
regex.MULTILINE
),
]
@ -332,7 +332,7 @@ def get_invalid_file(file_path, output_folder, common_header, begin_page):
for i in range(30, total_pages):
text = page_texts[i]
exclusion_pattern = regex.compile(
r'(文件的构成|文件的组成|文件组成|文件构成|文件的编制|文件编制)\s*$', regex.MULTILINE)
r'(文件的构成|文件的组成|文件组成|文件构成|文件的编制|文件编制|评审要素|评审标准)\s*$', regex.MULTILINE)
if regex.search(exclusion_pattern,text):
continue
if regex.search(pattern, text):
@ -348,8 +348,11 @@ def get_invalid_file(file_path, output_folder, common_header, begin_page):
return i
# 如果没有匹配到设置end_page逻辑
if total_pages > 100:
print("未找到结束模式总页数大于100设置结束页为前100页。" + file_path)
if total_pages > 200:
print(f"未找到结束模式总页数大于200平滑调整结束页为 {end_page}" + file_path)
return max(100, int(total_pages * 1 / 2))
elif 100 < total_pages <= 200:
print("未找到结束模式总页数在100到200之间设置结束页为总页数的三分之二。" + file_path)
return max(100, int(total_pages * 2 / 3))
else:
print("未找到结束模式,设置结束页为最后一页。" + file_path)
@ -417,11 +420,7 @@ def extract_pages_generic(pdf_path, begin_pattern, end_pattern, begin_page, comm
return None, None # 或者根据需求抛出异常
end_limit = total_pages if invalid_endpage == -1 else min(invalid_endpage, total_pages)
for i in range(begin_page, end_limit):
try:
text = get_text(i)
except Exception as e_extract:
print(f"提取第 {i} 页文本时出错: {e_extract}")
continue # 跳过出错的页面
text = get_text(i)
if text:
cleaned_text = clean_page_content(text, common_header)
if output_suffix == "tobidders_notice2": #extract_pages_twice_tobidders_notice会调用该代码
@ -641,7 +640,8 @@ def extract_pages_tobidders_notice(pdf_path, output_folder, begin_page, common_h
# 第一次提取尝试,使用初始的 begin_pattern
begin_pattern = regex.compile(
r'^第[一二三四五六七八九十百千]+(?:章|部分).*(?:投标人?|磋商|谈判|供应商|应答人|比选申请人).*须知+|'
r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!见\s*)(?<!与\s*)(?<!同\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:投标人?|磋商|谈判|供应商|应答人|比选申请人).*须知前附表\s*$',
r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!见\s*)(?<!与\s*)(?<!同\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:投标人?|磋商|谈判|供应商|应答人|比选申请人).*须知前附表\s*$|'
r'(?:投标人?|磋商|谈判|供应商|应答人|比选申请人).*须知+\s*$',
regex.MULTILINE
)
start_page, mid_page, end_page = run_extraction(pdf_document,begin_pattern, extraction_stage='first')
@ -734,7 +734,9 @@ def get_notice(pdf_path, output_folder, begin_page, common_header,invalid_endpag
end_pattern = regex.compile(
r'^第[一二三四五六七八九十]+(?:章|部分)\s*[\u4e00-\u9fff]+|' # 第一部分
r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!见\s*)(?<!同\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)' # 否定前瞻部分
r'第[一二三四五六七八九十1-9]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$', # 第二部分
r'第[一二三四五六七八九十1-9]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$|' # 第二部分
r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!见\s*)(?<!同\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)'
r'(?:投标人?|磋商|谈判|供应商|应答人|比选申请人).*须知+\s*$', # 新增部分
regex.MULTILINE
)
exclusion_pattern = regex.compile(

View File

@ -117,7 +117,7 @@ def save_extracted_text_to_txt(pdf_path, txt_path):
if __name__ == '__main__':
# file_path='D:\\flask_project\\flask_app\\static\\output\\output1\\648e094b-e677-47ce-9073-09e0c82af210\\ztbfile_tobidders_notice_part2.pdf'
pdf_path=r"D:\flask_project\flask_app\static\output\output1\139ecc93-bc85-4007-a4c5-1fb158a88719\ztbfile.pdf"
pdf_path=r"D:\flask_project\flask_app\static\output\output1\139ecc93-bc85-4007-a4c5-1fb158a88719\ztbfile_invalid.pdf"
# file_path = r"C:\Users\Administrator\Desktop\招标文件\招标test文件夹\zbtest8.pdf"
# file_path = 'C:\\Users\\Administrator\\Desktop\\货物标\\截取test\\交警支队机动车查验监管系统项目采购_tobidders_notice_part1.pdf'
# file_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest8.pdf"

View File

@ -155,7 +155,7 @@ def truncate_pdf_main_engineering(input_path, output_folder, selection, logger,
r'(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:[\u4e00-\u9fff、()]*?)'
r'(?=.*(?:(?:磋商|谈判)(?=.*(?:办法|方法|内容))|(?:评标|评定|评审)))' # 修改的部分
r'[\u4e00-\u9fff、()]*\s*$|'
r'^\s*(?<!见\s*)(?<!与\s*)(?<!同\s*)(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!"\s*)(?<!"\s*)(?<!"\s*)评标(方法|办法)前附表\s*$)',
r'^\s*(?<!见\s*)(?<!与\s*)(?<!同\s*)(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!"\s*)(?<!"\s*)(?<!"\s*)评标(方法|办法)(?:前附表)?\s*$)',
# 第二种模式
regex.MULTILINE
),

View File

@ -4,7 +4,8 @@ import regex # 导入正则表达式库
import os # 用于文件和文件夹操作
from flask_app.general.clean_pdf import clean_page_content
from flask_app.general.merge_pdfs import merge_and_cleanup
from flask_app.general.截取pdf通用函数 import get_start_and_common_header, save_extracted_pages, is_pdf_or_doc, get_invalid_file, extract_pages_tobidders_notice, extract_pages_generic, get_notice
from flask_app.general.截取pdf通用函数 import get_start_and_common_header, save_extracted_pages, is_pdf_or_doc, \
get_invalid_file, extract_pages_tobidders_notice, extract_pages_generic, get_notice, create_get_text_function
from flask_app.general.通用功能函数 import get_global_logger
def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_pattern, common_header,output_suffix,logger,invalid_endpage=-1):
@ -46,7 +47,7 @@ def get_patterns_for_evaluation_method():
r'(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:[\u4e00-\u9fff、()]*?)'
r'(?=.*(?:(?:磋商|谈判)(?=.*(?:办法|方法|内容))|(?:评标|评定|评审)))'
r'[\u4e00-\u9fff、()]*\s*$|'
r'^\s*(?<!见\s*)(?<!与\s*)(?<!同\s*)(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!"\s*)(?<!“\s*)(?<!“\s*)评标(方法|办法)前附表\s*$)', # 第二种模式
r'^\s*(?<!见\s*)(?<!与\s*)(?<!同\s*)(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!"\s*)(?<!“\s*)(?<!“\s*)评标(方法|办法)(?:前附表)?\s*$)', # 第二种模式
regex.MULTILINE
)
end_pattern = regex.compile(
@ -73,7 +74,7 @@ def extract_pages_qualification(pdf_path, begin_page, common_header):
# 优先匹配模式,匹配以“资格性检查”、“资格审查”或“符合性审查”开头
priority_pattern = regex.compile(
r'^((资格|符合|资格性|符合性)(?:、(资格|符合|资格性|符合性))*)?(检查|审查)(?:表|比对表|对照表)?\s*$',
r'^((资格|符合|资格性|符合性)(、(资格|符合|资格性|符合性))*)(检查|审查)(?:表|比对表|对照表)?\s*$',
regex.MULTILINE
)
@ -98,7 +99,7 @@ def extract_pages_qualification(pdf_path, begin_page, common_header):
# 尝试使用 PyPDF2 读取 PDF
pdf_document = PdfReader(pdf_path)
total_pages = len(pdf_document.pages)
get_text = lambda page: page.extract_text()
get_text = create_get_text_function('pypdf2', pdf_document)
# print("使用 PyPDF2 成功读取 PDF 文件。")
except Exception as e_pypdf2:
print(f"extract_pages_qualification:使用 PyPDF2 读取 PDF 失败: {e_pypdf2}")
@ -106,18 +107,14 @@ def extract_pages_qualification(pdf_path, begin_page, common_header):
# 如果 PyPDF2 失败,尝试使用 PyMuPDF 读取 PDF
pdf_document = fitz.open(pdf_path)
total_pages = pdf_document.page_count
get_text = lambda page: pdf_document.load_page(page).get_text()
get_text = create_get_text_function('fitz', pdf_document)
# print("使用 PyMuPDF 成功读取 PDF 文件。")
except Exception as e_fitz:
print(f"使用 PyMuPDF 读取 PDF 也失败: {e_fitz}")
return None, None # 或者根据需求抛出异常
# 从指定的开始页开始遍历
for i in range(begin_page, total_pages):
try:
text = get_text(i)
except Exception as e_extract:
print(f"提取第 {i} 页文本时出错: {e_extract}")
continue # 跳过出错的页面
text = get_text(i)
if text:
cleaned_text = clean_page_content(text, common_header)
# 优先检查是否匹配优先模式
@ -334,10 +331,10 @@ if __name__ == "__main__":
logger = get_global_logger("123")
# input_path = r"C:\Users\Administrator\Desktop\new招标文件\货物标"
# pdf_path = r"C:\Users\Administrator\Desktop\招标文件-采购类\2024-贵州-贵州医科大学附属医院导视系统零星制作安装项目.pdf"
pdf_path=r"D:\flask_project\flask_app\static\output\output1\139ecc93-bc85-4007-a4c5-1fb158a88719\ztbfile.pdf"
pdf_path=r"C:\Users\Administrator\Desktop\新建文件夹 (3)\temp\鄂州市急救中心展厅布展项目.pdf"
# input_path = r"C:\Users\Administrator\Desktop\货物标\zbfiles\2-招标文件(广水市教育局封闭管理).pdf"
# pdf_path=r"C:\Users\Administrator\Desktop\文件解析问题\文件解析问题\1414cb9c-7bf4-401c-8761-2acde151b9c2\ztbfile.pdf"
output_folder = r"D:\flask_project\flask_app\static\output\output1\139ecc93-bc85-4007-a4c5-1fb158a88719"
output_folder = r"C:\Users\Administrator\Desktop\新建文件夹 (3)\temp"
# output_folder = r"C:\Users\Administrator\Desktop\new招标文件\output2"
selection = 3 # 例如1 - 公告, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2与评标办法一致 4.投标人须知前附表part1 投标人须知正文part2 5-采购需求 6-invalid_path
generated_files = truncate_pdf_main_goods(pdf_path, output_folder, selection,logger)

View File

@ -505,142 +505,142 @@ def get_technical_requirements(invalid_path,processed_filepath,model_type=1):
model_res=qianwen_plus(user_query)
print(model_res)
cleaned_res = clean_json_string(model_res) #转字典
if not cleaned_res:
print("本项目没有采购需求!!!")
return {"采购需求": {}}
preprocessed_data=preprocess_data(cleaned_res) #确保最内层为[]
processed_data=truncate_system_keys(preprocessed_data) #限制深度
key_paths, grouped_paths, good_list, data_copy= generate_key_paths(processed_data) # 提取需要采购的货物清单 key_list交通监控视频子系统.高清视频抓拍像机 ... grouped_paths是同一系统下同时有'交换机-1'和'交换机-2',提取'交换机' 输出eg:{'交通标志.标志牌铝板', '交通信号灯.交换机'}
modified_data=rename_keys(data_copy)
user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。
**重要限制**
- **仅提取技术参数或采购要求不包括任何商务要求**商务要求通常涉及供应商资格报价条款交货时间质保等内容是整体的要求而技术参数或采购要求则具体描述产品的技术规格功能性能指标等
- **商务要求的关键词示例**仅供参考不限于此报价交货合同资质认证服务保修期等如果内容包含上述关键词请仔细甄别是否属于商务要求
要求与指南
1. 你的键值应该全面不要遗漏
-a.若技术参数或采购要求在表格中那么单元格内的内容基本都要涵盖
-对于单元格内以序号分隔的各条参数要求应逐条提取并分别作为键值中的字符串列表项
-对于无序号标明且在同一单元格内的参数要求或功能说明也要根据语义分别添加进键值中
-b.若技术参数或采购要求在正文部分应准确定位到与目标货物设备系统功能模块相关的内容将其后的技术参数或采购要求或功能说明完整提取逐一添加到键值的字符串列表中不得擅自添加或修改序号
2. 如果存在嵌套结构且原文为Markdown 的表格语法'摄像机|有效像素|≥900W像素' 请不要返回该Markdown语法而是使用冒号':'将相关信息拼接在一起生成一条完整且清晰的技术参数或采购要求描述作为列表中的一个字符串"摄像机有效像素≥900W像素"
3. 字符串中的内容为具体的技术参数要求或采购要求请不要返回诸如'1高清录像功能'这种标题性质且不能体现要求的内容
4. 如果该货物没有相关采购要求或技术参数要求键值应为空列表[]
### 示例输出1如下
{{
"摄像机控制键盘": [
"1、▲支持串行 RS232/RS422 和 IP 混合控制,允许在一个控制器上使用 RS232/RS422/IP 控制单个系统中的摄像机;",
"2、支持 2 组 RS422 串口 VISCA 协议菊花链控制 2x7 台摄像机。",
"★能够自动对焦,提供检测报告"
]
}}
### 示例输出2如下包含嵌套结构
{{
"摄像机": [
"摄像机有效像素≥900W像素",
"摄像机最低照度彩色≤0.001lx",
"协议routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181应提供 SDK"
]
}}
{}
"""
user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\";键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。
要求与指南
1. 你的键值应该全面不要遗漏
-a.若技术参数或采购要求在表格中那么单元格内的内容基本都要涵盖
-对于单元格内以序号分隔的各条参数要求应逐条提取并分别作为键值中的字符串列表项
-对于无序号标明且在同一单元格内的参数要求或功能说明也要根据语义分别添加进键值中
-b.若技术参数或采购要求在正文部分应准确定位到与目标货物设备系统功能模块相关的内容将其后的技术参数或采购要求或功能说明完整提取逐一添加到键值的字符串列表中不得擅自添加或修改序号
2. 如果存在嵌套结构且原文为Markdown 的表格语法'摄像机|有效像素|≥900W像素' 请不要返回该Markdown语法而是使用冒号':'将相关信息拼接在一起生成一条完整且清晰的技术参数或采购要求描述作为列表中的一个字符串"摄像机有效像素≥900W像素"
3. 字符串中的内容为具体的技术参数要求或采购要求请不要返回诸如'1高清录像功能'这种标题性质且不能体现要求的内容
4. 如果该货物没有相关采购要求或技术参数要求键值应为空列表[]
### 示例输出1如下
{{
"交换机-1": [
"★1、支持固化千兆电口≥8 个固化千兆光口≥2 个,桌面型设备;",
"2、支持静态链路聚合"
],
"交换机-2": [
"1、交换容量≥52Gbps包转发率≥38.69Mpps",
"2、提供国家强制性产品认证证书及测试报告3C",
"★能实现信号控制独立传输"
]
}}
### 示例输出2如下包含嵌套结构
{{
"摄像机-1": [
"摄像机有效像素≥900W像素",
"摄像机最低照度彩色≤0.001lx",
"协议routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181应提供 SDK"
],
"摄像机-2": [
"支持夜视", "支持云存储"
]
}}
{}
"""
queries = []
for key in key_paths:
# 将键中的 '.' 替换为 '下的'
modified_key = key.replace('.', '下的')
# 使用修改后的键填充第一个占位符,原始键填充第二个占位符
if model_type==1:
new_query = user_query_template.format(modified_key, key, modified_key,f"文件内容:{full_text}") #转豆包后取消注释
else:
new_query = user_query_template.format(modified_key, key, modified_key,"")
queries.append(new_query)
# 处理 grouped_paths 中的项,应用 user_query_template_two
for grouped_dict in grouped_paths:
for grouped_key, grouped_key_cnt in grouped_dict.items():
# 将键中的 '.' 替换为 '下的'
modified_grouped_key = grouped_key.replace('.', '下的')
if model_type==1:
new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key,
modified_grouped_key, f"文件内容:{full_text}")
else:
new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key,
modified_grouped_key, "")
queries.append(new_query)
if model_type==1:
results = multi_threading(queries, "", "", 3,True) # 豆包
else:
results = multi_threading(queries, "", file_id, 2,True) # qianwen-long
temp_final={}
if not results:
print("errror!未获得大模型的回答!")
else:
# 第一步:收集需要调用 `continue_answer` 的问题和解析结果
questions_to_continue = [] # 存储需要调用 continue_answer 的 (question, parsed)
max_tokens=3900 if model_type==1 else 5900
for question, response in results:
message=response[0]
parsed = clean_json_string(message)
total_tokens=response[1]
if not parsed and total_tokens>max_tokens:
questions_to_continue.append((question, message))
else:
temp_final.update(parsed)
# 第二步:多线程处理需要调用 `continue_answer` 的问题
if questions_to_continue:
continued_results = process_continue_answers(questions_to_continue, model_type, file_id)
temp_final.update(continued_results)
"""根据所有键是否已添加处理技术要求"""
# 更新原始采购需求字典
final_res=combine_and_update_results(modified_data, temp_final)
ffinal_res=all_postprocess(final_res)
ffinal_res["货物列表"] = good_list
# 输出最终的 JSON 字符串
return {"采购需求":ffinal_res}
# cleaned_res = clean_json_string(model_res) #转字典
# if not cleaned_res:
# print("本项目没有采购需求!!!")
# return {"采购需求": {}}
# preprocessed_data=preprocess_data(cleaned_res) #确保最内层为[]
# processed_data=truncate_system_keys(preprocessed_data) #限制深度
# key_paths, grouped_paths, good_list, data_copy= generate_key_paths(processed_data) # 提取需要采购的货物清单 key_list交通监控视频子系统.高清视频抓拍像机 ... grouped_paths是同一系统下同时有'交换机-1'和'交换机-2',提取'交换机' 输出eg:{'交通标志.标志牌铝板', '交通信号灯.交换机'}
# modified_data=rename_keys(data_copy)
# user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。
# **重要限制**
# - **仅提取技术参数或采购要求,不包括任何商务要求**。商务要求通常涉及供应商资格、报价条款、交货时间、质保等内容,是整体的要求;而技术参数或采购要求则具体描述产品的技术规格、功能、性能指标等。
# - **商务要求的关键词示例**(仅供参考,不限于此):报价、交货、合同、资质、认证、服务、保修期等。如果内容包含上述关键词,请仔细甄别是否属于商务要求
#
# 要求与指南
# 1. 你的键值应该全面,不要遗漏
# -a.若技术参数或采购要求在表格中,那么单元格内的内容基本都要涵盖
# -对于单元格内以序号分隔的各条参数要求,应逐条提取,并分别作为键值中的字符串列表项。
# -对于无序号标明且在同一单元格内的参数要求或功能说明,也要根据语义分别添加进键值中。
# -b.若技术参数或采购要求在正文部分,应准确定位到与目标货物(设备、系统、功能模块)相关的内容,将其后的技术参数或采购要求或功能说明完整提取,逐一添加到键值的字符串列表中,不得擅自添加或修改序号。
# 2. 如果存在嵌套结构且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素' 请不要返回该Markdown语法而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机有效像素≥900W像素"
# 3. 字符串中的内容为具体的技术参数要求或采购要求,请不要返回诸如'1高清录像功能'这种标题性质且不能体现要求的内容。
# 4. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表[]。
#
# ### 示例输出1如下
# {{
# "摄像机控制键盘": [
# "1、▲支持串行 RS232/RS422 和 IP 混合控制,允许在一个控制器上使用 RS232/RS422/IP 控制单个系统中的摄像机;",
# "2、支持 2 组 RS422 串口 VISCA 协议菊花链控制 2x7 台摄像机。",
# "★能够自动对焦,提供检测报告"
# ]
# }}
#
# ### 示例输出2如下包含嵌套结构
# {{
# "摄像机": [
# "摄像机有效像素≥900W像素",
# "摄像机最低照度彩色≤0.001lx",
# "协议routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181应提供 SDK"
# ]
# }}
#
# {}
# """
# user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\";键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。
#
# 要求与指南
# 1. 你的键值应该全面,不要遗漏
# -a.若技术参数或采购要求在表格中,那么单元格内的内容基本都要涵盖
# -对于单元格内以序号分隔的各条参数要求,应逐条提取,并分别作为键值中的字符串列表项。
# -对于无序号标明且在同一单元格内的参数要求或功能说明,也要根据语义分别添加进键值中。
# -b.若技术参数或采购要求在正文部分,应准确定位到与目标货物(设备、系统、功能模块)相关的内容,将其后的技术参数或采购要求或功能说明完整提取,逐一添加到键值的字符串列表中,不得擅自添加或修改序号。
# 2. 如果存在嵌套结构且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素' 请不要返回该Markdown语法而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机有效像素≥900W像素"
# 3. 字符串中的内容为具体的技术参数要求或采购要求,请不要返回诸如'1高清录像功能'这种标题性质且不能体现要求的内容。
# 4. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表[]。
#
# ### 示例输出1如下
# {{
# "交换机-1": [
# "★1、支持固化千兆电口≥8 个固化千兆光口≥2 个,桌面型设备;",
# "2、支持静态链路聚合"
# ],
# "交换机-2": [
# "1、交换容量≥52Gbps包转发率≥38.69Mpps",
# "2、提供国家强制性产品认证证书及测试报告3C",
# "★能实现信号控制独立传输"
# ]
# }}
#
# ### 示例输出2如下包含嵌套结构
# {{
# "摄像机-1": [
# "摄像机有效像素≥900W像素",
# "摄像机最低照度彩色≤0.001lx",
# "协议routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181应提供 SDK"
# ],
# "摄像机-2": [
# "支持夜视", "支持云存储"
# ]
# }}
#
# {}
# """
# queries = []
# for key in key_paths:
# # 将键中的 '.' 替换为 '下的'
# modified_key = key.replace('.', '下的')
# # 使用修改后的键填充第一个占位符,原始键填充第二个占位符
# if model_type==1:
# new_query = user_query_template.format(modified_key, key, modified_key,f"文件内容:{full_text}") #转豆包后取消注释
# else:
# new_query = user_query_template.format(modified_key, key, modified_key,"")
# queries.append(new_query)
#
# # 处理 grouped_paths 中的项,应用 user_query_template_two
# for grouped_dict in grouped_paths:
# for grouped_key, grouped_key_cnt in grouped_dict.items():
# # 将键中的 '.' 替换为 '下的'
# modified_grouped_key = grouped_key.replace('.', '下的')
# if model_type==1:
# new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key,
# modified_grouped_key, f"文件内容:{full_text}")
# else:
# new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key,
# modified_grouped_key, "")
# queries.append(new_query)
# if model_type==1:
# results = multi_threading(queries, "", "", 3,True) # 豆包
# else:
# results = multi_threading(queries, "", file_id, 2,True) # qianwen-long
# temp_final={}
# if not results:
# print("errror!未获得大模型的回答!")
# else:
# # 第一步:收集需要调用 `continue_answer` 的问题和解析结果
# questions_to_continue = [] # 存储需要调用 continue_answer 的 (question, parsed)
# max_tokens=3900 if model_type==1 else 5900
# for question, response in results:
# message=response[0]
# parsed = clean_json_string(message)
# total_tokens=response[1]
# if not parsed and total_tokens>max_tokens:
# questions_to_continue.append((question, message))
# else:
# temp_final.update(parsed)
# # 第二步:多线程处理需要调用 `continue_answer` 的问题
# if questions_to_continue:
# continued_results = process_continue_answers(questions_to_continue, model_type, file_id)
# temp_final.update(continued_results)
#
# """根据所有键是否已添加处理技术要求"""
# # 更新原始采购需求字典
# final_res=combine_and_update_results(modified_data, temp_final)
# ffinal_res=all_postprocess(final_res)
# ffinal_res["货物列表"] = good_list
# # 输出最终的 JSON 字符串
# return {"采购需求":ffinal_res}
def test_all_files_in_folder(input_folder, output_folder):
# 确保输出文件夹存在
@ -669,11 +669,11 @@ if __name__ == "__main__":
start_time=time.time()
# invalid_path="D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile.pdf"
# file_id = upload_file(truncate_file)
truncate_file=r'C:\Users\Administrator\Desktop\new招标文件\output5\广水市公安局音视频监控系统设备采购项目_procurement.pdf'
truncate_file=r'C:\Users\Administrator\Desktop\新建文件夹 (3)\temp\鄂州市急救中心展厅布展项目_procurement.pdf'
invalid_path=r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\invalid_del.docx"
# file_id=upload_file(truncate_file)
# processed_filepath = convert_file_to_markdown(truncate_file)
processed_filepath=r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\extract1.txt"
processed_filepath = convert_file_to_markdown(truncate_file)
# processed_filepath=r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\extract1.txt"
res=get_technical_requirements(invalid_path,processed_filepath,1)
json_string = json.dumps(res, ensure_ascii=False, indent=4)
print(json_string)