1.15 废标+截取pdf

This commit is contained in:
zy123 2025-01-15 15:45:25 +08:00
parent aead98d31e
commit 99e0724390
8 changed files with 280 additions and 469 deletions

View File

@ -130,9 +130,9 @@ def process_all_part1_pdfs(folder_path, output_folder):
extract_tables_main(file_path, subfolder_path) extract_tables_main(file_path, subfolder_path)
if __name__ == "__main__": if __name__ == "__main__":
path =r"C:\Users\Administrator\Desktop\new招标文件\工程标\tmp\\HBDL-2024-0017-001-招标文件_evaluation_method.pdf" path =r"C:\Users\Administrator\Desktop\货物标\output4\094定稿-湖北工业大学轻武器模拟射击设备采购项目招标文件_tobidders_notice_part1.docx"
output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件\\new_test" # 前附表json文件 output_folder = r"C:\Users\Administrator\Desktop\货物标\output4\tmp" # 前附表json文件
res=extract_tables_main("", output_folder) res=extract_tables_main(path, output_folder)
print(res) print(res)
# folder_path='C:\\Users\\Administrator\\Desktop\\货物标\\output4' # folder_path='C:\\Users\\Administrator\\Desktop\\货物标\\output4'
# output_folder="C:\\Users\\Administrator\\Desktop\\货物标\\output4\\tmp2" # output_folder="C:\\Users\\Administrator\\Desktop\\货物标\\output4\\tmp2"

View File

@ -14,7 +14,7 @@ def get_start_and_common_header(input_path,end_page):
common_header = extract_common_header(input_path) common_header = extract_common_header(input_path)
last_begin_index = 0 last_begin_index = 0
begin_pattern = regex.compile( begin_pattern = regex.compile(
r'.*(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:招标公告|磋商公告|谈判公告|邀请书|邀请函|投标邀请|磋商邀请|谈判邀请|采购公告)[\)]?\s*$', r'.*(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:招标公告|磋商公告|谈判公告|邀请书|邀请函|投标邀请|磋商邀请|谈判邀请|采购公告)[\)]?\s*$',
regex.MULTILINE regex.MULTILINE
) )
# 新增目录匹配模式 # 新增目录匹配模式
@ -138,7 +138,7 @@ def get_invalid_file(file_path, output_folder, common_header,begin_page):
regex.MULTILINE regex.MULTILINE
), ),
regex.compile( regex.compile(
r'.*(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:招标公告|磋商公告|谈判公告|邀请书|邀请函|投标邀请|磋商邀请|谈判邀请)[\)]?\s*$', r'.*(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:招标公告|磋商公告|谈判公告|邀请书|邀请函|投标邀请|磋商邀请|谈判邀请)[\)]?\s*$',
regex.MULTILINE regex.MULTILINE
) )
] ]
@ -312,7 +312,7 @@ def generate_mid_pattern(chapter_type=None):
base_mid_pattern = ( base_mid_pattern = (
r'^\s*(?:[(]\s*[一二12]?\s*[)]\s*[、..]*|' r'^\s*(?:[(]\s*[一二12]?\s*[)]\s*[、..]*|'
r'[一二12][、..]+|[、..]+)\s*(说\s*明|总\s*则|名\s*词\s*解\s*释)|' r'[一二12][、..]+|[、..]+)\s*(说\s*明|总\s*则|名\s*词\s*解\s*释)|'
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:投标人?|磋商|谈判|供应商|应答人).*须知正文\s*$' r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:投标人?|磋商|谈判|供应商|应答人).*须知正文\s*$'
) )
additional_mid_pattern = '' additional_mid_pattern = ''
@ -342,19 +342,19 @@ def generate_end_pattern(extraction_stage, chapter_type=None):
""" """
# 定义公共匹配模式 # 定义公共匹配模式
common_patterns = ( common_patterns = (
r'^(?<!见\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)附录(?:一)?[:]|' r'^附录(?:一)?[:]|'
r'^(?<!见\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)附件(?:一)?[:]|' r'^附件(?:一)?[:]|'
r'^(?<!见\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)附表(?:一)?[:]' r'^附表(?:一)?[:]'
) )
# 定义 end_pattern 模板 # 定义 end_pattern 模板
end_pattern_template_first = ( end_pattern_template_first = (
r'^(?<!见\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九十百千]+(?:{chapter_type})\s*[\u4e00-\u9fff]+' r'^第[一二三四五六七八九十百千]+(?:{chapter_type})\s*[\u4e00-\u9fff]+'
r'|' + common_patterns r'|' + common_patterns
) )
end_pattern_template_third = ( end_pattern_template_third = (
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九十百千]+(?:{chapter_type})\s*[\u4e00-\u9fff、()/]+\s*$' r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九十百千]+(?:{chapter_type})\s*[\u4e00-\u9fff、()/]+\s*$'
r'|' + common_patterns r'|' + common_patterns
) )

View File

@ -5,78 +5,72 @@ import regex
import time import time
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from flask_app.general.doubao import generate_full_user_query from flask_app.general.doubao import generate_full_user_query
from flask_app.general.insert_del_pagemark import insert_mark
from flask_app.general.通义千问long import qianwen_plus from flask_app.general.通义千问long import qianwen_plus
from flask_app.general.通用功能函数 import process_string_list from flask_app.general.通用功能函数 import process_string_list
from collections import OrderedDict from collections import OrderedDict
import docx
from docx import Document from docx import Document
from flask_app.general.insert_del_pagemark import insert_mark
from flask_app.general.format_change import pdf2docx
def clean_dict_datas(extracted_contents, keywords, excludes): # 让正则表达式提取到的东西格式化
all_texts = []
# 定义用于分割句子的正则表达式,包括中文和西文的结束标点
split_pattern = r'(?<=[。!?\!\?])'
# 只读取前附表中的最后一列(省钱,但容易漏内容) for key, text_list in extracted_contents.items():
def read_docx_last_column(truncate_file): if len(text_list) == 1:
# 尝试打开文档 for data in text_list:
try: # print(data)
doc = Document(truncate_file) # 检查是否包含任何需要排除的字符串
except Exception as e: if any(exclude in data for exclude in excludes):
print(f"Error opening file: {e}") continue # 如果包含任何排除字符串,跳过这个数据
return [] # 去掉开头的序号eg:1 | (1) |2 | 1. | 2全角点| 3、 | 1.1 | 2.3.4 | A1 | C1.1 | 一、
pattern = r'^\s*(?:[(]\d+[))]|[A-Za-z]?\d+(?:\.\s*\d+)*[\s\.、.)\]+|[一二三四五六七八九十]+、|[A-Z][)\.、.]?\s*)'
data = re.sub(pattern, '', data).strip()
keyword_match = re.search(keywords, data)
if keyword_match:
# 从关键词位置开始查找结束标点符号
start_pos = keyword_match.start()
# 截取从关键词开始到后面的内容
substring = data[start_pos:]
# 按定义的结束标点分割
sentences = re.split(split_pattern, substring, 1)
if len(sentences) > 0 and sentences[0]:
# 只取第一句,保留标点
cleaned_text = data[:start_pos] + sentences[0] # eg:经采购人允许,潜在投标人可进入项目现场进行考察,但潜在投标人不得因此使采购人承担有关责任和蒙受损失。潜在投标人应自行承担现场考察的全部费用、责任和风险。
# 经采购人允许,潜在投标人可进入项目现场进行考察,但潜在投标人不得因此使采购人承担有关责任和蒙受损失。
else:
cleaned_text = data # 如果没有标点,使用整个字符串
else:
# 如果没有找到关键词,保留原文本
cleaned_text = data
# 删除空格
cleaned_text_no_spaces = cleaned_text.replace(' ', '').replace(' ', '')
# 如果长度大于8则添加到结果列表
if len(cleaned_text_no_spaces) > 8:
all_texts.append(cleaned_text_no_spaces)
last_column_values = [] else:
# print(text_list)
# 读取文档中的所有表格 # print("*********")
if not doc.tables: # 用于处理结构化文本,清理掉不必要的序号,并将分割后的段落合并,最终形成更简洁和格式化的输出。
print("No tables found in the document.") pattern = r'^\s*(?:[(]\d+[))]|[A-Za-z]?\d+(?:\.\s*\d+)*[\s\.、.)\]+|[一二三四五六七八九十]+、|[A-Z][)]\s+|[A-Z]\.\s*)'
return last_column_values data = re.sub(pattern, '', text_list[0]).strip() # 去除序号
# 将修改后的第一个元素和剩余的元素连接起来
# 遍历文档中的每个表格 text_list[0] = data # 更新列表中的第一个元素
for table in doc.tables: joined_text = "\n".join(text_list) # 如果列表中有多个元素,则连接它们
# 获取表格的最后一列 # 删除空格
for row in table.rows: joined_text_no_spaces = joined_text.replace(' ', '').replace(' ', '')
last_cell = row.cells[-1] # 获取最后一个单元格 all_texts.append(joined_text_no_spaces) # 将每个列表的内容添加到 all_texts 中
# 去除内容前后空白并删除文本中的所有空格
cleaned_text = last_cell.text.strip().replace(' ', '')
last_column_values.append(cleaned_text)
return last_column_values
# 完整读取文件中所有表格适合pdf转docx价格便宜的情况优先推荐内容完整
def read_tables_from_docx(file_path):
# print(file_path)
# 尝试打开文档
try:
doc = Document(file_path)
except Exception as e:
print(f"Error opening file: {e}")
return []
# 初始化列表来保存符合条件的单元格内容
cell_contents = []
# 读取文档中的所有表格
if not doc.tables:
print("No tables found in the document.")
return []
# 遍历文档中的每个表格
for table_idx, table in enumerate(doc.tables):
# 遍历表格中的每一行
for row_idx, row in enumerate(table.rows):
# 遍历每一行中的单元格
for cell in row.cells:
cell_text = cell.text.strip() # 去除单元格内容前后空白
if len(cell_text) > 8: # 检查文字数量是否大于8
cell_contents.append(cell_text)
# 返回符合条件的单元格内容
return cell_contents
return all_texts # all_texts1要额外用gpt all_text2直接返回结果
#处理跨页的段落 #处理跨页的段落
def preprocess_paragraphs(paragraphs): def preprocess_paragraphs(elements):
processed = [] # 初始化处理后的段落列表 processed = [] # 初始化处理后的段落列表
index = 0 index = 0
flag = False # 初始化标志位 flag = False # 初始化标志位
is_combine_table = False
# 定义两个新的正则表达式模式 # 定义两个新的正则表达式模式
pattern_numbered = re.compile(r'^\s*([一二三四五六七八九十]{1,2})\s*、\s*') pattern_numbered = re.compile(r'^\s*([一二三四五六七八九十]{1,2})\s*、\s*')
@ -114,19 +108,23 @@ def preprocess_paragraphs(paragraphs):
# 辅助函数:查找上一个非空且非标记的段落 # 辅助函数:查找上一个非空且非标记的段落
def find_prev_text(current_index): def find_prev_text(current_index):
for i in range(current_index - 1, -1, -1): for i in range(current_index - 1, -1, -1):
if isinstance(elements[i], str) and elements[i] != '[$$table_over$$]':
return elements[i], i, True
try: try:
text = paragraphs[i].text.strip() text = elements[i].text.strip()
except AttributeError: except AttributeError:
continue # 如果段落对象没有 text 属性,跳过 continue # 如果段落对象没有 text 属性,跳过
if text and not pattern_marker.search(text): if text and not pattern_marker.search(text):
return text, i return text, i, False
return '', -1 return '', -1, False
# 辅助函数:查找下一个非空且非标记的段落 # 辅助函数:查找下一个非空且非标记的段落
def find_next_text(current_index): def find_next_text(current_index):
for i in range(current_index + 1, len(paragraphs)): for i in range(current_index + 1, len(elements)):
if isinstance(elements[i], str) and elements[i] != '[$$table_start$$]':
return elements[i], i
try: try:
text = paragraphs[i].text.strip() text = elements[i].text.strip()
except AttributeError: except AttributeError:
continue # 如果段落对象没有 text 属性,跳过 continue # 如果段落对象没有 text 属性,跳过
# 跳过空白段落和页面标记 # 跳过空白段落和页面标记
@ -138,9 +136,13 @@ def preprocess_paragraphs(paragraphs):
return text, i return text, i
return '', -1 return '', -1
while index < len(paragraphs): while index < len(elements):
if isinstance(elements[index], str):
processed.append(elements[index])
index += 1
continue
try: try:
current_text = paragraphs[index].text.strip() # 去除当前段落的前后空白 current_text = elements[index].text.strip() # 去除当前段落的前后空白
except AttributeError: except AttributeError:
# 如果段落对象没有 text 属性,跳过该段落 # 如果段落对象没有 text 属性,跳过该段落
index += 1 index += 1
@ -149,7 +151,7 @@ def preprocess_paragraphs(paragraphs):
# 检查当前段落是否为页面标记 # 检查当前段落是否为页面标记
if pattern_marker.search(current_text): if pattern_marker.search(current_text):
# 动态查找前一个非空段落 # 动态查找前一个非空段落
prev_text, prev_index = find_prev_text(index) prev_text, prev_index, is_combine_table = find_prev_text(index)
# 动态查找后一个非空段落 # 动态查找后一个非空段落
next_text, next_index = find_next_text(index) next_text, next_index = find_next_text(index)
@ -160,13 +162,21 @@ def preprocess_paragraphs(paragraphs):
if not list_item_pattern.match(next_text) and len(prev_text) > 30: if not list_item_pattern.match(next_text) and len(prev_text) > 30:
# 合并前后段落 # 合并前后段落
merged_text = prev_text + ' ' + next_text # 为了可读性添加空格 merged_text = prev_text + ' ' + next_text # 为了可读性添加空格
if prev_index < len(paragraphs): if prev_index < len(elements):
# 移除 processed 中的前一个段落 # 移除 processed 中的前一个段落
if is_combine_table:
while(processed[-1] != '[$$table_over$$]'):
processed.pop()
processed.pop()
if processed and processed[-1] == prev_text: if processed and processed[-1] == prev_text:
processed.pop() processed.pop()
# 添加合并后的文本 # 添加合并后的文本
processed.append(merged_text) processed.append(merged_text)
if is_combine_table:
while(elements[index] != '[$$table_start$$]'):
index += 1
index += 1
# is_combine_table = False
# 跳过标记以及前后所有空白段落,直到 next_index # 跳过标记以及前后所有空白段落,直到 next_index
index = next_index + 1 index = next_index + 1
continue # 继续下一个循环 continue # 继续下一个循环
@ -175,9 +185,11 @@ def preprocess_paragraphs(paragraphs):
# 计算下一个需要处理的索引 # 计算下一个需要处理的索引
# 从当前 index 向下,跳过所有连续的空白段落和标记 # 从当前 index 向下,跳过所有连续的空白段落和标记
skip_index = index + 1 skip_index = index + 1
while skip_index < len(paragraphs): while skip_index < len(elements):
if isinstance(elements[skip_index], str):
break
try: try:
skip_text = paragraphs[skip_index].text.strip() skip_text = elements[skip_index].text.strip()
except AttributeError: except AttributeError:
skip_index += 1 skip_index += 1
continue # 如果段落对象没有 text 属性,跳过 continue # 如果段落对象没有 text 属性,跳过
@ -240,14 +252,9 @@ def preprocess_paragraphs(paragraphs):
return processed return processed
#如果当前段落有序号,则向下匹配直接遇到相同的序号样式 def extract_text_with_keywords(processed_paragraphs, keywords, follow_up_keywords):
#如果当前段落无序号,则向下匹配序号,把若干同类的序号都摘出来。
def extract_text_with_keywords(doc_path, keywords, follow_up_keywords):
if isinstance(keywords, str): if isinstance(keywords, str):
keywords = [keywords] keywords = [keywords]
doc = Document(doc_path)
extracted_paragraphs = OrderedDict() extracted_paragraphs = OrderedDict()
continue_collecting = False continue_collecting = False
current_section_pattern = None current_section_pattern = None
@ -266,6 +273,13 @@ def extract_text_with_keywords(doc_path, keywords, follow_up_keywords):
return current_index return current_index
if continue_collecting: if continue_collecting:
# 如果是收集状态并且下面有表格则把表格内容全部追加到active_key中去
if text == '[$$table_start$$]':
current_index += 1
while (processed_paragraphs[current_index] != '[$$table_over$$]'):
extracted_paragraphs[active_key].append(processed_paragraphs[current_index])
current_index += 1
return current_index
if current_section_pattern and re.match(current_section_pattern, text): if current_section_pattern and re.match(current_section_pattern, text):
continue_collecting = False continue_collecting = False
active_key = None active_key = None
@ -356,115 +370,19 @@ def extract_text_with_keywords(doc_path, keywords, follow_up_keywords):
return current_index return current_index
processed_paragraphs = preprocess_paragraphs(doc.paragraphs)
index = 0 index = 0
while index < len(processed_paragraphs): while index < len(processed_paragraphs):
# print(processed_paragraphs[index].strip()) # print(processed_paragraphs[index].strip())
index = extract_from_text(processed_paragraphs[index].strip(), index) index = extract_from_text(processed_paragraphs[index].strip(), index)
# print("--------------") # print("--------------")
index += 1 index += 1
return extracted_paragraphs return extracted_paragraphs
""" # 分割表格中单元格文本
eg: def split_cell_text(text):
text_list = ["这是第一句。 1. 接下来是第二句! (3) 最后一句。"]
new_text_list = ["这是第一句。", "1. 接下来是第二句!", "(3) 最后一句。"]
"""
def preprocess_text_list(text_list):
new_text_list = []
# 正则表达式匹配中文字符或标点后的空格,该空格后紧跟字母、数字或带括号的数字
split_pattern = re.compile(r'(?<=[\u4e00-\u9fff。?!;])(?=\s+[a-zA-Z\d]|\s+\([1-9]\d*\)|\s+\[1-9]\d*\)') #。;!??!;
for text in text_list:
# 使用正则表达式检查并拆分元素
parts = split_pattern.split(text)
new_text_list.extend(part.strip() for part in parts if part.strip()) # 添加非空字符串检查
return new_text_list
def clean_dict_datas(extracted_contents, keywords, excludes): # 让正则表达式提取到的东西格式化
all_texts1 = []
all_texts2 = []
# 定义用于分割句子的正则表达式,包括中文和西文的结束标点
split_pattern = r'(?<=[。!?\!\?])'
for key, text_list in extracted_contents.items():
if len(text_list) == 1:
for data in text_list:
# print(data)
# 检查是否包含任何需要排除的字符串
if any(exclude in data for exclude in excludes):
continue # 如果包含任何排除字符串,跳过这个数据
# 去掉开头的序号eg:1 | (1) |2 | 1. | 2全角点| 3、 | 1.1 | 2.3.4 | A1 | C1.1 | 一、
pattern = r'^\s*(?:[(]\d+[))]|[A-Za-z]?\d+(?:\.\s*\d+)*[\s\.、.)\]+|[一二三四五六七八九十]+、|[A-Z][)\.、.]?\s*)'
data = re.sub(pattern, '', data).strip()
keyword_match = re.search(keywords, data)
if keyword_match:
# 从关键词位置开始查找结束标点符号
start_pos = keyword_match.start()
# 截取从关键词开始到后面的内容
substring = data[start_pos:]
# 按定义的结束标点分割
sentences = re.split(split_pattern, substring, 1)
if len(sentences) > 0 and sentences[0]:
# 只取第一句,保留标点
cleaned_text = data[:start_pos] + sentences[0] # eg:经采购人允许,潜在投标人可进入项目现场进行考察,但潜在投标人不得因此使采购人承担有关责任和蒙受损失。潜在投标人应自行承担现场考察的全部费用、责任和风险。
# 经采购人允许,潜在投标人可进入项目现场进行考察,但潜在投标人不得因此使采购人承担有关责任和蒙受损失。
else:
cleaned_text = data # 如果没有标点,使用整个字符串
else:
# 如果没有找到关键词,保留原文本
cleaned_text = data
# 删除空格
cleaned_text_no_spaces = cleaned_text.replace(' ', '').replace(' ', '')
# 如果长度大于8则添加到结果列表
if len(cleaned_text_no_spaces) > 8:
all_texts1.append(cleaned_text_no_spaces)
else:
# print(text_list)
# print("*********")
# new_text_list = preprocess_text_list(text_list)
# 用于处理结构化文本,清理掉不必要的序号,并将分割后的段落合并,最终形成更简洁和格式化的输出。
pattern = r'^\s*(?:[(]\d+[))]|[A-Za-z]?\d+(?:\.\s*\d+)*[\s\.、.)\]+|[一二三四五六七八九十]+、|[A-Z][)]\s+|[A-Z]\.\s*)'
data = re.sub(pattern, '', text_list[0]).strip() # 去除序号
# 将修改后的第一个元素和剩余的元素连接起来
text_list[0] = data # 更新列表中的第一个元素
joined_text = "\n".join(text_list) # 如果列表中有多个元素,则连接它们
# 删除空格
joined_text_no_spaces = joined_text.replace(' ', '').replace(' ', '')
all_texts2.append(joined_text_no_spaces) # 将每个列表的内容添加到 all_texts 中
return all_texts1, all_texts2 # all_texts1要额外用gpt all_text2直接返回结果
#从表格中提取数据
def extract_table_with_keywords(data, keywords, follow_up_keywords,flag=False):
"""遍历列表中的每个元素,查找并返回包含关键词的句子列表,并根据是否存在后续关键词分别存储到两个列表中。"""
sentences1 = [] # 保存没有后续关键词的情况
sentences2 = [] # 保存有后续关键词的情况
# 编译关键词的正则表达式,提高匹配性能
keywords_pattern = re.compile(keywords, re.IGNORECASE)
follow_up_patterns = [re.compile(fu, re.IGNORECASE) for fu in follow_up_keywords]
# 检查是否包含 '无效报价' 的关键词
check_invalid_bidding = bool(re.search(r'\s*效\s*报\s*价', keywords, re.IGNORECASE))
# 定义用于提取括号内容的正则表达式,支持中英文括号 # 定义用于提取括号内容的正则表达式,支持中英文括号
bracket_pattern = re.compile(r'[(][^())]+[)]') bracket_pattern = re.compile(r'[(][^())]+[)]')
# 遍历列表中的每个字符串元素
for item in data:
# 只有在 keywords 包含 '无效报价' 时,才检查 "无效报价"
if check_invalid_bidding and re.search(r'\s*效\s*报\s*价', item, re.IGNORECASE):
sentences1.append(item.strip())
continue
# 先检查 item 是否包含任意关键词,如果不包含,则跳过分割
if not keywords_pattern.search(item):
continue
# 1. 先提取并替换括号内容 # 1. 先提取并替换括号内容
bracket_contents = [] bracket_contents = []
@ -472,16 +390,15 @@ def extract_table_with_keywords(data, keywords, follow_up_keywords,flag=False):
bracket_contents.append(match.group(0)) # 保存括号内容 bracket_contents.append(match.group(0)) # 保存括号内容
return f"<BRACKET_{len(bracket_contents) - 1}>" # 使用占位符替换括号内容 return f"<BRACKET_{len(bracket_contents) - 1}>" # 使用占位符替换括号内容
item_with_placeholders = bracket_pattern.sub(replace_bracket_content, item) item_with_placeholders = bracket_pattern.sub(replace_bracket_content, text)
# 2. 分割句子,保证句子完整性(按标点符号和序号分割) # 2. 分割句子,保证句子完整性(按标点符号和序号分割)
split_sentences = regex.split( split_sentences = regex.split(
r'(?<=[。!?!?\?])|' # 在中文句号、感叹号、问号或分号分割 r'(?<=[。!?!?\?])|' # 在中文句号、感叹号、问号后分割
r'(?=\d+(?:[.]\d+)+)(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万])|' # 在类似1.1 1.1.1 的数字序号前分割 r'(?<![A-Za-z]\s*)(?<!\d[.])(?=\d+(?:[.]\d+)+(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万台份]))|' # 匹配多级编号,限制后面不能是指定关键字
r'(?<![+\-×÷*/]\s*|\d)(?=\d+\s(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万]))|' # 数字后面跟空格且空格后面不是指定关键字,且前面不是运算符和空格或数字 r'(?<![+\-×÷*/.\A-Za-z]\s*|\d)(?=\d+\s(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万台份]))|' # 数字后跟空格且不跟指定关键字时分割,且前面不包含 . 或 eg:'1.1 xx'
r'(?<![+\-×÷*/]\s*|\d)(?=\d+[、.])(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万])|' # 数字后直接跟顿号、半角点号或全角点号,且前面不是运算符和空格或数字 r'(?<![+\-×÷*/.\A-Za-z]\s*|\d)(?=\d+[、.](?!\d|(?:\s*[号条款节章项例页段部步点年月日时分秒个元千万台份])))|' # 数字后直接跟顿号、点号时分割,且点号后不跟数字 eg:'1.'
r'(?=[A-Za-z][.]\s*)|' # 在字母加点如A.、a.)前分割 r'(?<![A-Za-z])(?=[A-Za-z][.]\s*(?![A-Za-z]))|' # 单个字母+点号或单个字母+数字排除www.baidu.com 网址情况
r'(?=[A-Za-z]+\s*\d+\s*(?:[.]\s*\d+)*)|' # 在可选字母加数字或多级编号前分割 r'(?=[A-Za-z]+\s*\d+\s*(?:[.]\s*\d+)*)|' # 在字母加数字或多级编号前分割
r'(?=[一二三四五六七八九十]+、)', # 在中文数字加顿号(如一、二、)前分割 r'(?=[一二三四五六七八九十]+、)', # 在中文数字加顿号(如一、二、)前分割
item_with_placeholders item_with_placeholders
) )
@ -489,121 +406,41 @@ def extract_table_with_keywords(data, keywords, follow_up_keywords,flag=False):
# 3. 还原括号内容 # 3. 还原括号内容
split_sentences = [re.sub(r"<BRACKET_(\d+)>", lambda m: bracket_contents[int(m.group(1))], s) for s in split_sentences = [re.sub(r"<BRACKET_(\d+)>", lambda m: bracket_contents[int(m.group(1))], s) for s in
split_sentences] split_sentences]
# 4. 过滤空字符串
split_sentences = [s for s in split_sentences if s.strip()]
# 接下来是处理包含和不包含后续关键词的情况 return split_sentences
i = 0
# 清洗模式
clean_pattern = r'^\s*(?:[(]\s*\d+\s*[))]|[A-Za-z]?\d+(?:\.\s*\d+)*[\s\.、.)\]+|[一二三四五六七八九十]+、|[A-Z][)\.、.]?\s*)'
while i < len(split_sentences):
sentence = split_sentences[i].strip()
# 如果匹配关键词 # 文件预处理----按文件顺序提取文本和表格
if keywords_pattern.search(sentence): def extract_file_elements(file_path):
# 检查是否存在后续关键词 doc = Document(file_path)
follow_up_present = any(fp.search(sentence) for fp in follow_up_patterns) doc_elements = doc.element.body
if follow_up_present: doc_paragraphs = doc.paragraphs
# 如果存在后续关键词,则从当前位置开始截取 doc_tables = doc.tables
start_index = i paragraph_index = 0
end_index = start_index tables_index = 0
found_next_section = False doc_contents = []
for j in range(start_index + 1, len(split_sentences)):
if re.match(r'\d+[.]\d+([.]\d+)?', split_sentences[j].strip()):
end_index = j
found_next_section = True
break
if found_next_section:
full_text = ' '.join(split_sentences[start_index:end_index]).strip()
else:
full_text = ' '.join(split_sentences[start_index:]).strip()
full_text = re.sub(clean_pattern, '', full_text).replace(' ', '').strip() # 遍历文件元素
sentences2.append(full_text) # 存储有后续关键词的情况 for element in doc_elements:
i = end_index if found_next_section else len(split_sentences) # 如果是段落
else: if element.tag.endswith('}p'):
# 没有后续关键词的情况 doc_contents.append(doc_paragraphs[paragraph_index])
if flag: paragraph_index += 1
# 当 flag=True 时,简化逻辑,直接添加清洗后的句子 # 如果是表格
# 清洗文本 elif element.tag.endswith('}tbl'):
cleaned_sentence = re.sub(clean_pattern, '', sentence).replace('\n', '').replace(' ', '').strip() doc_contents.append('[$$table_start$$]')
if len(cleaned_sentence) > 8: table = doc_tables[tables_index]
sentences1.append(cleaned_sentence) for row_idx, row in enumerate(table.rows):
else: # 遍历每一行中的单元格
# 如果 \n 换行符前面满足匹配,如 '无效投标\n',那么不删该 \n for cell in row.cells:
sentence = re.sub(fr'({keywords})(\s*\n)', r'\1[换行标记]', sentence) cell_text = cell.text.strip() # 去除单元格内容前后空白
if len(cell_text) > 8: # 检查文字数量是否大于8
# 清洗文本 cell_text = split_cell_text(cell_text)
cleaned_sentence = re.sub(clean_pattern, '', sentence).replace('\n', '').replace(' ', '').strip() doc_contents += cell_text
doc_contents.append('[$$table_over$$]')
# 恢复保留的换行符 tables_index += 1
cleaned_sentence = cleaned_sentence.replace('[换行标记]', '\n') return doc_contents
# 检查匹配次数和是否需要切分
matches = list(keywords_pattern.finditer(sentence))
if len(matches) >= 2: # 如果匹配了两处及以上
split_points = []
for match in matches:
start, end = match.span()
print(sentence[end:end + 6])
if sentence[end:end + 6] == "[换行标记]":
split_points.append(end)
if len(split_points) >= 1: # 至少有一个有效切分点
split_index = split_points[0] # 取第一个切分点
part1 = sentence[:split_index].replace("[换行标记]", "\n").strip()
part2 = sentence[split_index:].replace("[换行标记]", "\n").strip()
# 对 part1 和 part2 进行清洗
part1 = re.sub(clean_pattern, '', part1).replace('\n', '').replace(' ', '').strip()
part2 = re.sub(clean_pattern, '', part2).replace('\n', '').replace(' ', '').strip()
sentences1.append(part1) # 将前半部分加入结果
sentences1.append(part2) # 将后半部分加入结果
else:
# 如果没有足够的有效切分点,直接保留完整句子
if len(cleaned_sentence) > 8:
sentences1.append(cleaned_sentence)
else:
# 如果只有一个匹配点或不足两处匹配
if len(cleaned_sentence) > 8:
sentences1.append(cleaned_sentence)
i += 1
else:
i += 1
return sentences1, sentences2 # 返回两个列表
def extract_values_if_contains(data, includes):
"""
递归检查字典中的值是否包含列表 'includes' 中的内容
如果包含将这些值添加到一个列表中并返回
参数:
data (dict): 字典或从 JSON 解析得到的数据
includes (list): 包含要检查的关键词的列表
返回:
list: 包含满足条件的值的列表
"""
included_values = [] # 初始化结果列表
# 定义递归函数来处理嵌套字典
def recursive_search(current_data):
if isinstance(current_data, dict):
for key, value in current_data.items():
if isinstance(value, dict):
# 如果值是字典,递归搜索
recursive_search(value)
elif isinstance(value, str):
# 如果值是字符串,检查是否包含任何 includes 中的关键词
if any(include in value for include in includes):
included_values.append(value)
elif isinstance(current_data, list):
for item in current_data:
# 如果是列表,递归每个元素
recursive_search(item)
# 开始递归搜索
recursive_search(data)
return included_values
def handle_query(file_path, user_query, output_file, result_key, keywords): def handle_query(file_path, user_query, output_file, result_key, keywords):
try: try:
@ -616,22 +453,20 @@ def handle_query(file_path, user_query, output_file, result_key, keywords):
r'\s*他.*?情\s*形\s*[:]', r'\s*他.*?情\s*形\s*[:]',
r'\s*括' r'\s*括'
] ]
extracted_contents = extract_text_with_keywords(file_path, [keywords], follow_up_keywords) # 字典结果
all_texts1, all_texts2 = clean_dict_datas(extracted_contents, keywords, excludes) # 列表 doc_contents = extract_file_elements(file_path)
# print(all_texts2) processed_paragraphs = preprocess_paragraphs(doc_contents)
# table_data_list=read_docx_last_column(file_path) #从投标人须知前附表中提取信息生成列表data每个元素为'一行信息' extracted_contents = extract_text_with_keywords(processed_paragraphs, [keywords], follow_up_keywords)
table_data_list = read_tables_from_docx(file_path) all_texts = clean_dict_datas(extracted_contents, keywords, excludes) # 列表
# print(table_data_list)
all_tables1, all_tables2 = extract_table_with_keywords(table_data_list, keywords, follow_up_keywords) # print(all_texts)
qianwen_txt = all_texts1 + all_tables1
# Proceed only if there is content to write # Proceed only if there is content to write
selected_contents = [] # 使用列表保持顺序 selected_contents = [] # 使用列表保持顺序
seen_contents = set() # 使用集合跟踪已添加的内容以去重 seen_contents = set() # 使用集合跟踪已添加的内容以去重
if all_texts:
if qianwen_txt:
with open(output_file, 'w', encoding='utf-8') as file: with open(output_file, 'w', encoding='utf-8') as file:
counter = 1 counter = 1
for content in qianwen_txt: for content in all_texts:
# 使用内容的前25个字符作为去重的依据 # 使用内容的前25个字符作为去重的依据
key = content[:25] # 提取前25个字符 key = content[:25] # 提取前25个字符
if key not in seen_contents: # 如果前30个字符未出现过 if key not in seen_contents: # 如果前30个字符未出现过
@ -649,19 +484,11 @@ def handle_query(file_path, user_query, output_file, result_key, keywords):
print(result_key + "选中的序号:" + str(num_list)) print(result_key + "选中的序号:" + str(num_list))
for index in num_list: for index in num_list:
if 1 <= index <= len(qianwen_txt): if 1 <= index <= len(all_texts):
content = qianwen_txt[index - 1] content = all_texts[index - 1]
# 直接添加到 selected_contents因为前面已经按前30字符去重 # 直接添加到 selected_contents因为前面已经按前30字符去重
selected_contents.append(content) selected_contents.append(content)
# 无论 qianwen_txt 是否为空,都添加 all_texts2 和 all_tables2 的内容
for item in all_texts2 + all_tables2:
# 同样使用前25个字符判断去重
key = item[:25] # 提取前30个字符
if key not in seen_contents:
selected_contents.append(item)
seen_contents.add(key)
# 如果 selected_contents 不为空,则返回结果,否则返回空字符串 # 如果 selected_contents 不为空,则返回结果,否则返回空字符串
if selected_contents: if selected_contents:
res = {result_key: list(selected_contents)} res = {result_key: list(selected_contents)}
@ -672,14 +499,6 @@ def handle_query(file_path, user_query, output_file, result_key, keywords):
print(f"handle_query 在处理 {result_key} 时发生异常: {e}") print(f"handle_query 在处理 {result_key} 时发生异常: {e}")
return {result_key: ""} return {result_key: ""}
# 你是一个文本助手,文本内的信息以'...............'分割,你负责准确筛选所需的信息并返回,每块信息要求完整,不遗漏,你不得擅自进行总结或删减。
# 以上是从招标文件中摘取的内容,文本内之间的信息以'...............'分割,请你根据该内容回答:否决投标或拒绝投标或无效投标或使投标失效的情况有哪些?文本中可能存在无关的信息,请你准确筛选符合的信息并将它的序号返回。请以[x,x,x]格式返回给我结果x为符合的信息的序号。
# 以上是原文内容,文本内的信息以'...............'分割请你根据该信息回答否决投标或拒绝投标或无效投标或使投标失效的情况有哪些文本中可能存在无关的信息请你准确筛选所需的信息并返回。最终结果以json列表格式返回给我键名为'否决和无效投标情形',你的回答完全忠于原文内容,且回答内容与原文内容一致,要求完整与准确,不能擅自总结或者概括。",
#"以上是从招标文件中摘取的内容,文本内之间的信息以'...............'分割,每条信息规定了各方不得存在的情形或是禁止投标的情形,在这些信息中,我作为投标方,需要关注和我相关的信息,请你筛选主语是投标人或中标人或供应商或联合体投标各方或磋商小组的信息,不要返回主语是招标人或采购人或评标委员会的信息,请你筛选所需的信息并将它的序号返回。请以[x,x,x]格式返回给我结果,示例返回为[1,4,6],若情况不存在,返回[]。",
#"以上是从招标文件中摘取的内容,文本内之间的信息以'...............'分割,每条信息规定了各方不得存在的情形,请回答:在这些信息中,主语是投标人或中标人或供应商或联合体投标各方或磋商小组的信息有哪些?不要返回主语是招标人或采购人或评标委员会的信息,请你筛选所需的信息并将它的序号返回。请以[x,x,x]格式返回给我结果,示例返回为[1,4,6],若情况不存在,返回[]。",
def combine_find_invalid(invalid_docpath, output_dir): def combine_find_invalid(invalid_docpath, output_dir):
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
queries = [ queries = [
@ -792,13 +611,13 @@ if __name__ == '__main__':
# clause_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\77a48c63-f39f-419b-af2a-7b3dbf41b70b\\clause1.json" # clause_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\77a48c63-f39f-419b-af2a-7b3dbf41b70b\\clause1.json"
# doc_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfilesdocx\\磋商文件(1).docx" # doc_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfilesdocx\\磋商文件(1).docx"
# doc_path = r'C:\Users\Administrator\Desktop\new招标文件\tmp\2024-贵州-贵州省罗甸县 2024 年度广州市协作资金龙坪镇、边阳镇产业路硬化建设项目.docx' # doc_path = r'C:\Users\Administrator\Desktop\new招标文件\tmp\2024-贵州-贵州省罗甸县 2024 年度广州市协作资金龙坪镇、边阳镇产业路硬化建设项目.docx'
pdf_path = r'C:\Users\Administrator\Desktop\货物\test\磋商采购文件-恩施市森林火灾风险普查样品检测服务_invalid.pdf' pdf_path = r'C:\Users\Administrator\Desktop\货物\test5\磋商采购文件-恩施市森林火灾风险普查样品检测服务.pdf'
output_dir = r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\tmp" output_dir = r"C:\Users\Administrator\Desktop\货物\test5"
# invalid_added = insert_mark(pdf_path) # invalid_added = insert_mark(pdf_path)
# invalid_added_docx = pdf2docx(invalid_added) # invalid_added_docx = pdf2docx(invalid_added)
invalid_added_docx=r'D:\flask_project\flask_app\static\output\output1\8a662477-a954-4b84-b9c2-d68ebd4f537b\invalid_added.docx' invalid_added_docx=r'C:\Users\Administrator\Desktop\货物\test3\invalid_added.docx'
results = combine_find_invalid(invalid_added_docx, output_dir) results = combine_find_invalid(invalid_added_docx, output_dir)
end_time = time.time() # end_time = time.time()
print("Results:", json.dumps(results, ensure_ascii=False, indent=4)) # print("Results:", json.dumps(results, ensure_ascii=False, indent=4))
print("Elapsed time:", str(end_time - start_time)) # print("Elapsed time:", str(end_time - start_time))

View File

@ -366,11 +366,6 @@ def extract_text_with_keywords(doc_path, keywords, follow_up_keywords):
return extracted_paragraphs return extracted_paragraphs
"""
eg:
text_list = ["这是第一句。 1. 接下来是第二句! (3) 最后一句。"]
new_text_list = ["这是第一句。", "1. 接下来是第二句!", "(3) 最后一句。"]
"""
def preprocess_text_list(text_list): def preprocess_text_list(text_list):
new_text_list = [] new_text_list = []
# 正则表达式匹配中文字符或标点后的空格,该空格后紧跟字母、数字或带括号的数字 # 正则表达式匹配中文字符或标点后的空格,该空格后紧跟字母、数字或带括号的数字
@ -476,12 +471,12 @@ def extract_table_with_keywords(data, keywords, follow_up_keywords,flag=False):
# 2. 分割句子,保证句子完整性(按标点符号和序号分割) # 2. 分割句子,保证句子完整性(按标点符号和序号分割)
split_sentences = regex.split( split_sentences = regex.split(
r'(?<=[。!?!?\?])|' # 在中文句号、感叹号、问号或分号分割 r'(?<=[。!?!?\?])|' # 在中文句号、感叹号、问号后分割
r'(?=\d+(?:[.]\d+)+)(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万])|' # 在类似1.1 1.1.1 的数字序号前分割 r'(?<![A-Za-z]\s*)(?<!\d[.])(?=\d+(?:[.]\d+)+(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万台份]))|' # 匹配多级编号,限制后面不能是指定关键字
r'(?<![+\-×÷*/]\s*|\d)(?=\d+\s(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万]))|' # 数字后面跟空格且空格后面不是指定关键字,且前面不是运算符和空格或数字 r'(?<![+\-×÷*/.\A-Za-z]\s*|\d)(?=\d+\s(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万台份]))|' # 数字后跟空格且不跟指定关键字时分割,且前面不包含 . 或 eg:'1.1 xx'
r'(?<![+\-×÷*/]\s*|\d)(?=\d+[、.])(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万])|' # 数字后直接跟顿号、半角点号或全角点号,且前面不是运算符和空格或数字 r'(?<![+\-×÷*/.\A-Za-z]\s*|\d)(?=\d+[、.](?!\d|(?:\s*[号条款节章项例页段部步点年月日时分秒个元千万台份])))|' # 数字后直接跟顿号、点号时分割,且点号后不跟数字 eg:'1.'
r'(?=[A-Za-z][.]\s*)|' # 在字母加点如A.、a.)前分割 r'(?<![A-Za-z])(?=[A-Za-z][.]\s*(?![A-Za-z]))|' # 单个字母+点号或单个字母+数字排除www.baidu.com 网址情况
r'(?=[A-Za-z]+\s*\d+\s*(?:[.]\s*\d+)*)|' # 在可选字母加数字或多级编号前分割 r'(?=[A-Za-z]+\s*\d+\s*(?:[.]\s*\d+)*)|' # 在字母加数字或多级编号前分割
r'(?=[一二三四五六七八九十]+、)', # 在中文数字加顿号(如一、二、)前分割 r'(?=[一二三四五六七八九十]+、)', # 在中文数字加顿号(如一、二、)前分割
item_with_placeholders item_with_placeholders
) )
@ -543,7 +538,7 @@ def extract_table_with_keywords(data, keywords, follow_up_keywords,flag=False):
split_points = [] split_points = []
for match in matches: for match in matches:
start, end = match.span() start, end = match.span()
print(sentence[end:end + 6]) # print(sentence[end:end + 6])
if sentence[end:end + 6] == "[换行标记]": if sentence[end:end + 6] == "[换行标记]":
split_points.append(end) split_points.append(end)
@ -623,6 +618,7 @@ def handle_query(file_path, user_query, output_file, result_key, keywords):
table_data_list = read_tables_from_docx(file_path) table_data_list = read_tables_from_docx(file_path)
# print(table_data_list) # print(table_data_list)
all_tables1, all_tables2 = extract_table_with_keywords(table_data_list, keywords, follow_up_keywords) all_tables1, all_tables2 = extract_table_with_keywords(table_data_list, keywords, follow_up_keywords)
print(all_tables2)
qianwen_txt = all_texts1 + all_tables1 qianwen_txt = all_texts1 + all_tables1
# Proceed only if there is content to write # Proceed only if there is content to write
selected_contents = [] # 使用列表保持顺序 selected_contents = [] # 使用列表保持顺序
@ -792,12 +788,12 @@ if __name__ == '__main__':
# clause_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\77a48c63-f39f-419b-af2a-7b3dbf41b70b\\clause1.json" # clause_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\77a48c63-f39f-419b-af2a-7b3dbf41b70b\\clause1.json"
# doc_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfilesdocx\\磋商文件(1).docx" # doc_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfilesdocx\\磋商文件(1).docx"
# doc_path = r'C:\Users\Administrator\Desktop\new招标文件\tmp\2024-贵州-贵州省罗甸县 2024 年度广州市协作资金龙坪镇、边阳镇产业路硬化建设项目.docx' # doc_path = r'C:\Users\Administrator\Desktop\new招标文件\tmp\2024-贵州-贵州省罗甸县 2024 年度广州市协作资金龙坪镇、边阳镇产业路硬化建设项目.docx'
pdf_path = r'C:\Users\Administrator\Desktop\货物\test\磋商采购文件-恩施市森林火灾风险普查样品检测服务_invalid.pdf' pdf_path = r'C:\Users\Administrator\Desktop\货物\test3\2024-福建-2024年度防汛抢险物资储备 (1).pdf'
output_dir = r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\tmp" output_dir = r"C:\Users\Administrator\Desktop\货物\test3"
# invalid_added = insert_mark(pdf_path) invalid_added = insert_mark(pdf_path)
# invalid_added_docx = pdf2docx(invalid_added) invalid_added_docx = pdf2docx(invalid_added)
invalid_added_docx=r'D:\flask_project\flask_app\static\output\output1\8a662477-a954-4b84-b9c2-d68ebd4f537b\invalid_added.docx' # invalid_added_docx=r'D:\flask_project\flask_app\static\output\output1\8a662477-a954-4b84-b9c2-d68ebd4f537b\invalid_added.docx'
results = combine_find_invalid(invalid_added_docx, output_dir) results = combine_find_invalid(invalid_added_docx, output_dir)
end_time = time.time() end_time = time.time()
print("Results:", json.dumps(results, ensure_ascii=False, indent=4)) print("Results:", json.dumps(results, ensure_ascii=False, indent=4))

View File

@ -286,7 +286,6 @@ def goods_bid_main(output_folder, file_path, file_type, unique_id):
# D:\flask_project\flask_app\static\output\output1\2c4be864-bdab-405d-95cb-9d945d8627b3排查一下 clause 有问题+ # D:\flask_project\flask_app\static\output\output1\2c4be864-bdab-405d-95cb-9d945d8627b3排查一下 clause 有问题+
# C:\Users\Administrator\Desktop\fsdownload\bbf7504f-3c75-45e5-b3e2-ab0a15ec9c14 # C:\Users\Administrator\Desktop\fsdownload\bbf7504f-3c75-45e5-b3e2-ab0a15ec9c14
# 解决禅道 测试的bug # 解决禅道 测试的bug
# 截取:对应 按照 根据
# 评分点 # 评分点
# 国网湖北电力荆州供电公司2024年第四次服务授权竞争性谈判采购-采购文件15DJ04.docx 废标项 段落和表格混杂 # 国网湖北电力荆州供电公司2024年第四次服务授权竞争性谈判采购-采购文件15DJ04.docx 废标项 段落和表格混杂
# 货物标和工程标的资格审查整合 # 货物标和工程标的资格审查整合
@ -294,6 +293,9 @@ def goods_bid_main(output_folder, file_path, file_type, unique_id):
# 写一个统计解析失败的脚本 # 写一个统计解析失败的脚本
##TODO:招标文件111_tobidders_notice_part2.pdf 陕西省公安厅交通警察总队高速公路交通安全智能感知巡查系统项目(1)_tobidders_notice_part2.pdf 唐山市公安交通警察支队机动车查验机构视频存储回放系统竞争性谈判-招标文件正文(1)_tobidders_notice_part1.pdf ##TODO:招标文件111_tobidders_notice_part2.pdf 陕西省公安厅交通警察总队高速公路交通安全智能感知巡查系统项目(1)_tobidders_notice_part2.pdf 唐山市公安交通警察支队机动车查验机构视频存储回放系统竞争性谈判-招标文件正文(1)_tobidders_notice_part1.pdf
#TODO:2024-陕西-陕西省某单位2024年执勤化妆服采购项目.pdf #TODO:2024-陕西-陕西省某单位2024年执勤化妆服采购项目.pdf
# exclusion_pattern = regex.compile(r'文件的构成|文件的组成|文件构成|文件组成|文件的编制|文件编制') 改一下 设置结尾\s*$
# # (?<!见\s*|与\s*|对\s*应\s*|按\s*照\s*|根\s*据\s*|"\s*|“\s*|”\s*)
# C:\Users\Administrator\Desktop\货物\test3 废标排查
if __name__ == "__main__": if __name__ == "__main__":
# 配置日志器 # 配置日志器

View File

@ -5,6 +5,7 @@ import regex
import time import time
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from flask_app.general.doubao import generate_full_user_query from flask_app.general.doubao import generate_full_user_query
from flask_app.general.insert_del_pagemark import insert_mark
from flask_app.general.通义千问long import qianwen_plus from flask_app.general.通义千问long import qianwen_plus
from flask_app.general.通用功能函数 import process_string_list from flask_app.general.通用功能函数 import process_string_list
from collections import OrderedDict from collections import OrderedDict
@ -390,15 +391,14 @@ def split_cell_text(text):
return f"<BRACKET_{len(bracket_contents) - 1}>" # 使用占位符替换括号内容 return f"<BRACKET_{len(bracket_contents) - 1}>" # 使用占位符替换括号内容
item_with_placeholders = bracket_pattern.sub(replace_bracket_content, text) item_with_placeholders = bracket_pattern.sub(replace_bracket_content, text)
# 2. 分割句子,保证句子完整性(按标点符号和序号分割) # 2. 分割句子,保证句子完整性(按标点符号和序号分割)
split_sentences = regex.split( split_sentences = regex.split(
r'(?<=[。!?!?\?])|' # 在中文句号、感叹号、问号或分号分割 r'(?<=[。!?!?\?])|' # 在中文句号、感叹号、问号后分割
r'(?=\d+(?:[.]\d+)+)(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万])|' # 在类似1.1 1.1.1 的数字序号前分割 r'(?<![A-Za-z]\s*)(?<!\d[.])(?=\d+(?:[.]\d+)+(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万台份]))|' # 匹配多级编号,限制后面不能是指定关键字
r'(?<![+\-×÷*/]\s*|\d)(?=\d+\s(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万]))|' # 数字后面跟空格且空格后面不是指定关键字,且前面不是运算符和空格或数字 r'(?<![+\-×÷*/.\A-Za-z]\s*|\d)(?=\d+\s(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万台份]))|' # 数字后跟空格且不跟指定关键字时分割,且前面不包含 . 或 eg:'1.1 xx'
r'(?<![+\-×÷*/]\s*|\d)(?=\d+[、.])(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万])|' # 数字后直接跟顿号、半角点号或全角点号,且前面不是运算符和空格或数字 r'(?<![+\-×÷*/.\A-Za-z]\s*|\d)(?=\d+[、.](?!\d|(?:\s*[号条款节章项例页段部步点年月日时分秒个元千万台份])))|' # 数字后直接跟顿号、点号时分割,且点号后不跟数字 eg:'1.'
r'(?=[A-Za-z][.]\s*)|' # 在字母加点如A.、a.)前分割 r'(?<![A-Za-z])(?=[A-Za-z][.]\s*(?![A-Za-z]))|' # 单个字母+点号或单个字母+数字排除www.baidu.com 网址情况
r'(?=[A-Za-z]+\s*\d+\s*(?:[.]\s*\d+)*)|' # 在可选字母加数字或多级编号前分割 r'(?=[A-Za-z]+\s*\d+\s*(?:[.]\s*\d+)*)|' # 在字母加数字或多级编号前分割
r'(?=[一二三四五六七八九十]+、)', # 在中文数字加顿号(如一、二、)前分割 r'(?=[一二三四五六七八九十]+、)', # 在中文数字加顿号(如一、二、)前分割
item_with_placeholders item_with_placeholders
) )
@ -406,6 +406,8 @@ def split_cell_text(text):
# 3. 还原括号内容 # 3. 还原括号内容
split_sentences = [re.sub(r"<BRACKET_(\d+)>", lambda m: bracket_contents[int(m.group(1))], s) for s in split_sentences = [re.sub(r"<BRACKET_(\d+)>", lambda m: bracket_contents[int(m.group(1))], s) for s in
split_sentences] split_sentences]
# 4. 过滤空字符串
split_sentences = [s for s in split_sentences if s.strip()]
return split_sentences return split_sentences
@ -457,22 +459,22 @@ def handle_query(file_path, user_query, output_file, result_key, keywords):
extracted_contents = extract_text_with_keywords(processed_paragraphs, [keywords], follow_up_keywords) extracted_contents = extract_text_with_keywords(processed_paragraphs, [keywords], follow_up_keywords)
all_texts = clean_dict_datas(extracted_contents, keywords, excludes) # 列表 all_texts = clean_dict_datas(extracted_contents, keywords, excludes) # 列表
print(all_texts) # print(all_texts)
# Proceed only if there is content to write # Proceed only if there is content to write
selected_contents = [] # 使用列表保持顺序 selected_contents = [] # 使用列表保持顺序
seen_contents = set() # 使用集合跟踪已添加的内容以去重 seen_contents = set() # 使用集合跟踪已添加的内容以去重
# if qianwen_txt: if all_texts:
# with open(output_file, 'w', encoding='utf-8') as file: with open(output_file, 'w', encoding='utf-8') as file:
# counter = 1 counter = 1
# for content in qianwen_txt: for content in all_texts:
# # 使用内容的前25个字符作为去重的依据 # 使用内容的前25个字符作为去重的依据
# key = content[:25] # 提取前25个字符 key = content[:25] # 提取前25个字符
# if key not in seen_contents: # 如果前30个字符未出现过 if key not in seen_contents: # 如果前30个字符未出现过
# file.write(f"{counter}. {content}\n") file.write(f"{counter}. {content}\n")
# file.write("..............." + '\n') file.write("..............." + '\n')
# seen_contents.add(key) # 标记前30个字符为已写入 seen_contents.add(key) # 标记前30个字符为已写入
# counter += 1 counter += 1
#
# # 生成用户查询 # # 生成用户查询
# user_query = generate_full_user_query(output_file, user_query) # user_query = generate_full_user_query(output_file, user_query)
# model_ans = qianwen_plus(user_query) # 豆包模型返回结果 # model_ans = qianwen_plus(user_query) # 豆包模型返回结果
@ -482,19 +484,11 @@ def handle_query(file_path, user_query, output_file, result_key, keywords):
# print(result_key + "选中的序号:" + str(num_list)) # print(result_key + "选中的序号:" + str(num_list))
# #
# for index in num_list: # for index in num_list:
# if 1 <= index <= len(qianwen_txt): # if 1 <= index <= len(all_texts):
# content = qianwen_txt[index - 1] # content = all_texts[index - 1]
# # 直接添加到 selected_contents因为前面已经按前30字符去重 # # 直接添加到 selected_contents因为前面已经按前30字符去重
# selected_contents.append(content) # selected_contents.append(content)
# #
# # 无论 qianwen_txt 是否为空,都添加 all_texts2 和 all_tables2 的内容
# for item in all_content:
# # 同样使用前25个字符判断去重
# key = item[:25] # 提取前30个字符
# if key not in seen_contents:
# selected_contents.append(item)
# seen_contents.add(key)
#
# # 如果 selected_contents 不为空,则返回结果,否则返回空字符串 # # 如果 selected_contents 不为空,则返回结果,否则返回空字符串
# if selected_contents: # if selected_contents:
# res = {result_key: list(selected_contents)} # res = {result_key: list(selected_contents)}
@ -537,54 +531,54 @@ def combine_find_invalid(invalid_docpath, output_dir):
os.path.join(output_dir, "temp1.txt"), os.path.join(output_dir, "temp1.txt"),
"否决和无效投标情形" "否决和无效投标情形"
), ),
# ( (
# r'废\s*标', r'\s*标',
# """以下是从招标文件中摘取的内容,文本中序号分明,文本内之间的信息以'...............'分割。 """以下是从招标文件中摘取的内容,文本中序号分明,文本内之间的信息以'...............'分割。
# 任务目标 任务目标
# 请根据以下内容,筛选出 废标项的情况 (明确描述导致 废标 的情况)并返回对应的序号 请根据以下内容筛选出 废标项的情况 明确描述导致 废标 的情况并返回对应的序号
# 要求与指南 要求与指南
# 文本中可能存在无关的信息,请准确筛选符合条件的信息,并将符合条件的信息的序号返回。 文本中可能存在无关的信息请准确筛选符合条件的信息并将符合条件的信息的序号返回
# 输出格式 输出格式
# 返回结果以 [x, x, x] 的形式,其中 x 为符合条件的信息的序号,为自然数 返回结果以 [x, x, x] 的形式其中 x 为符合条件的信息的序号为自然数
# 如果文本中没有任何符合条件的废标情况,请返回 []。 如果文本中没有任何符合条件的废标情况请返回 []
# 示例输出,仅供格式参考 示例输出,仅供格式参考
# [1,3,4,6] [1,3,4,6]
# 文本内容:{full_text} 文本内容{full_text}
# """, """,
# os.path.join(output_dir, "temp2.txt"), os.path.join(output_dir, "temp2.txt"),
# "废标项" "废标项"
# ), ),
# ( (
# r'不\s*得(?!\s*(分|力))|禁\s*止\s*投\s*标', r'\s*得(?!\s*(分|力))|禁\s*止\s*投\s*标',
# """以下是从招标文件中摘取的内容,文本中序号分明,文本内的条款以'...............'分割。条款规定了各方不得存在的情形。请根据以下要求进行筛选: """以下是从招标文件中摘取的内容,文本中序号分明,文本内的条款以'...............'分割。条款规定了各方不得存在的情形。请根据以下要求进行筛选:
# **投标相关主体与非投标相关主体的定义** **投标相关主体与非投标相关主体的定义**
# 投标相关主体:包括但不限于“投标人”、“中标人”、“供应商”、“联合体投标各方”、“响应人”、“应答人”或其他描述投标方的词语。 投标相关主体包括但不限于投标人中标人供应商联合体投标各方响应人应答人或其他描述投标方的词语
# 非投标相关主体:包括但不限于“招标人”、“采购人”、“评标委员会”或其他描述非投标方的词语。 非投标相关主体包括但不限于招标人采购人评标委员会或其他描述非投标方的词语
# **筛选要求** **筛选要求**
# 1. **仅筛选**明确描述投标相关主体禁止情形或不得存在的情形的条款,不包含笼统或未具体说明情形的条款。例如: 1. **仅筛选**明确描述投标相关主体禁止情形或不得存在的情形的条款不包含笼统或未具体说明情形的条款例如
# 若条款内容包含'投标人不得存在的其他关联情形'这样的笼统描述,而未说明具体的情形,则无需添加该条款。 若条款内容包含'投标人不得存在的其他关联情形'这样的笼统描述而未说明具体的情形则无需添加该条款
# 2. **排除**仅描述非投标相关主体行为限制或禁止情形的条款,例如“招标人不得泄露信息”或“评标委员会不得收受贿赂”,则无需返回。 2. **排除**仅描述非投标相关主体行为限制或禁止情形的条款例如招标人不得泄露信息评标委员会不得收受贿赂则无需返回
# 3. 若条款同时描述了对投标相关主体与非投标相关主体的行为限制、禁止情形,也需返回。 3. 若条款同时描述了对投标相关主体与非投标相关主体的行为限制禁止情形也需返回
# 4. **特殊情况**:如果条款中包含“磋商小组”、”各方“等既能指代投标相关主体又能指代非投标相关主体的词汇: 4. **特殊情况**如果条款中包含磋商小组各方等既能指代投标相关主体又能指代非投标相关主体的词汇
# 若在语境中其指代或包含投标相关主体,则应将其考虑在内;否则,排除该条款。 若在语境中其指代或包含投标相关主体则应将其考虑在内否则排除该条款
#
# **输出格式** **输出格式**
# 返回结果以 [x, x, x] 的形式,其中 x 为符合条件的条款的序号,为自然数 返回结果以 [x, x, x] 的形式其中 x 为符合条件的条款的序号为自然数
# 如果没有符合条件的条款,返回 `[]`。 如果没有符合条件的条款返回 `[]`
# **示例** **示例**
# - **符合条件** - **符合条件**
# - `1. 投标人不得...` → 包含,返回序号 1 - `1. 投标人不得...` 包含返回序号 1
# - `3. 联合体投标各方不得...` → 包含,返回序号 3 - `3. 联合体投标各方不得...` 包含返回序号 3
# - **不符合条件** - **不符合条件**
# - `2. 采购人不得...` → 主语为“采购人”,排除 - `2. 采购人不得...` 主语为采购人排除
# -示例输出: [1,3] -示例输出: [1,3]
# 请根据上述筛选要求,阅读以下文本内容,并返回符合条件的条款序号 请根据上述筛选要求阅读以下文本内容并返回符合条件的条款序号
#
# 文本内容:{full_text} 文本内容{full_text}
# """, """,
# os.path.join(output_dir, "temp3.txt"), os.path.join(output_dir, "temp3.txt"),
# "不得存在的情形" "不得存在的情形"
# ) )
] ]
results = [] results = []
@ -617,13 +611,13 @@ if __name__ == '__main__':
# clause_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\77a48c63-f39f-419b-af2a-7b3dbf41b70b\\clause1.json" # clause_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\77a48c63-f39f-419b-af2a-7b3dbf41b70b\\clause1.json"
# doc_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfilesdocx\\磋商文件(1).docx" # doc_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfilesdocx\\磋商文件(1).docx"
# doc_path = r'C:\Users\Administrator\Desktop\new招标文件\tmp\2024-贵州-贵州省罗甸县 2024 年度广州市协作资金龙坪镇、边阳镇产业路硬化建设项目.docx' # doc_path = r'C:\Users\Administrator\Desktop\new招标文件\tmp\2024-贵州-贵州省罗甸县 2024 年度广州市协作资金龙坪镇、边阳镇产业路硬化建设项目.docx'
pdf_path = r'C:\Users\Administrator\Desktop\货物\test\磋商采购文件-恩施市森林火灾风险普查样品检测服务_invalid.pdf' pdf_path = r'C:\Users\Administrator\Desktop\货物\test5\磋商采购文件-恩施市森林火灾风险普查样品检测服务.pdf'
output_dir = r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\tmp" output_dir = r"C:\Users\Administrator\Desktop\货物\test5"
# invalid_added = insert_mark(pdf_path) # invalid_added = insert_mark(pdf_path)
# invalid_added_docx = pdf2docx(invalid_added) # invalid_added_docx = pdf2docx(invalid_added)
invalid_added_docx=r'D:\flask_project\flask_app\static\output\output1\8a662477-a954-4b84-b9c2-d68ebd4f537b\invalid_added.docx' invalid_added_docx=r'C:\Users\Administrator\Desktop\货物\test3\invalid_added.docx'
results = combine_find_invalid(invalid_added_docx, output_dir) results = combine_find_invalid(invalid_added_docx, output_dir)
end_time = time.time() # end_time = time.time()
print("Results:", json.dumps(results, ensure_ascii=False, indent=4)) # print("Results:", json.dumps(results, ensure_ascii=False, indent=4))
print("Elapsed time:", str(end_time - start_time)) # print("Elapsed time:", str(end_time - start_time))

View File

@ -129,11 +129,11 @@ def truncate_pdf_main_engineering(input_path, output_folder, selection, logger,
), ),
( (
regex.compile( regex.compile(
r'.*(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:招标公告|磋商公告|谈判公告|邀请书|邀请函|投标邀请|磋商邀请|谈判邀请)[\)]?\s*$', r'.*(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:招标公告|磋商公告|谈判公告|邀请书|邀请函|投标邀请|磋商邀请|谈判邀请)[\)]?\s*$',
regex.MULTILINE regex.MULTILINE
), ),
regex.compile( regex.compile(
r'.*(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:投标人?|磋商|供应商|谈判供应商|磋商供应商|应答人)须知(?:前附表)?\s*$', r'.*(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:投标人?|磋商|供应商|谈判供应商|磋商供应商|应答人)须知(?:前附表)?\s*$',
regex.MULTILINE regex.MULTILINE
) )
) )
@ -152,8 +152,8 @@ def truncate_pdf_main_engineering(input_path, output_folder, selection, logger,
# Alternative end pattern # Alternative end pattern
regex.compile( regex.compile(
r'^第[一二三四五六七八九十1-9]+(?:章|部分)\s*[\u4e00-\u9fff]+|[:]清标报告\s*$|' r'^第[一二三四五六七八九十1-9]+(?:章|部分)\s*[\u4e00-\u9fff]+|[:]清标报告\s*$|'
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)评标(方法|办法)正文\s*$|' r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)评标(方法|办法)正文\s*$|'
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:评标|定标)详细程序\s*$', r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:评标|定标)详细程序\s*$',
regex.MULTILINE regex.MULTILINE
) )
), ),
@ -180,13 +180,13 @@ def truncate_pdf_main_engineering(input_path, output_folder, selection, logger,
( (
regex.compile( regex.compile(
r'^(?:附录(?:[一1])?[:]|附件(?:[一1])?[:]|附表(?:[一1])?[:]).*(?:资质|资格).*$|' r'^(?:附录(?:[一1])?[:]|附件(?:[一1])?[:]|附表(?:[一1])?[:]).*(?:资质|资格).*$|'
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九1-9]+(?:章|部分)\s*' r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九1-9]+(?:章|部分)\s*'
r'[\u4e00-\u9fff、()]*资格[\u4e00-\u9fff、()]*\s*$', r'[\u4e00-\u9fff、()]*资格[\u4e00-\u9fff、()]*\s*$',
regex.MULTILINE regex.MULTILINE
), ),
regex.compile( regex.compile(
r'^(?:附录[一二三四五六七八九1-9]*[:]|附件[一二三四五六七八九1-9]*[:]|附表[一二三四五六七八九1-9]*[:])(?!.*(?:资质|资格)).*|' r'^(?:附录[一二三四五六七八九1-9]*[:]|附件[一二三四五六七八九1-9]*[:]|附表[一二三四五六七八九1-9]*[:])(?!.*(?:资质|资格)).*|'
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九1-9]+(?:章|部分)\s*[\u4e00-\u9fff]+', r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九1-9]+(?:章|部分)\s*[\u4e00-\u9fff]+',
regex.MULTILINE regex.MULTILINE
) )
) )

View File

@ -30,7 +30,7 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter
def get_patterns_for_procurement(): def get_patterns_for_procurement():
begin_pattern = regex.compile( begin_pattern = regex.compile(
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)' r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)'
r'第[一二三四五六七八九十1-9]+(?:章|部分)\s*(?!.*说明)' # 匹配“第X章”或“第X部分” r'第[一二三四五六七八九十1-9]+(?:章|部分)\s*(?!.*说明)' # 匹配“第X章”或“第X部分”
r'[\u4e00-\u9fff、()]*?' # 匹配允许的字符 r'[\u4e00-\u9fff、()]*?' # 匹配允许的字符
r'(?:(?:服务|项目|商务|技术|供货)[\u4e00-\u9fff、()]*?要求[\u4e00-\u9fff、()]*?\s*$|' # 匹配“服务”、“项目”、“商务”或“技术”后跟“要求” r'(?:(?:服务|项目|商务|技术|供货)[\u4e00-\u9fff、()]*?要求[\u4e00-\u9fff、()]*?\s*$|' # 匹配“服务”、“项目”、“商务”或“技术”后跟“要求”
@ -38,7 +38,7 @@ def get_patterns_for_procurement():
r'需求书[\u4e00-\u9fff、()]*?)\s*$', r'需求书[\u4e00-\u9fff、()]*?)\s*$',
regex.MULTILINE regex.MULTILINE
) )
end_pattern = regex.compile(r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九1-9]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$', regex.MULTILINE) end_pattern = regex.compile(r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九1-9]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$', regex.MULTILINE)
return begin_pattern, end_pattern return begin_pattern, end_pattern
# """ # """
@ -55,7 +55,7 @@ def get_patterns_for_evaluation_method():
regex.MULTILINE regex.MULTILINE
) )
end_pattern = regex.compile( end_pattern = regex.compile(
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九十1-9]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$', regex.MULTILINE) r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九十1-9]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$', regex.MULTILINE)
return begin_pattern, end_pattern return begin_pattern, end_pattern
@ -88,7 +88,7 @@ def extract_pages_qualification(pdf_document, begin_page, common_header):
) )
# 结束匹配模式 - 章节标题 # 结束匹配模式 - 章节标题
end_pattern_chapter = regex.compile( end_pattern_chapter = regex.compile(
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$', r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$',
regex.MULTILINE regex.MULTILINE
) )
@ -219,12 +219,12 @@ def truncate_pdf_main_goods(input_path, output_folder, selection,logger, output_
# 根据选择设置对应的模式和结束模式 # 根据选择设置对应的模式和结束模式
if selection == 1: if selection == 1:
begin_pattern = regex.compile( begin_pattern = regex.compile(
r'.*(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:招标公告|磋商公告|谈判公告|邀请书|邀请函|投标邀请|磋商邀请|谈判邀请|采购公告)[\)]?\s*$', r'.*(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:招标公告|磋商公告|谈判公告|邀请书|邀请函|投标邀请|磋商邀请|谈判邀请|采购公告)[\)]?\s*$',
regex.MULTILINE regex.MULTILINE
) )
end_pattern = regex.compile( end_pattern = regex.compile(
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九十1-9]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$|' r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)第[一二三四五六七八九十1-9]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$|'
r'(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:投标人?|磋商|供应商|谈判供应商|磋商供应商)须知前附表\s*$', r'(?<!对应\s*)(?<!根据\s*)(?<!按照\s*)(?<!\s*)(?<!与\s*)(?<!"\s*)(?<!“\s*)(?<!”\s*)(?:投标人?|磋商|供应商|谈判供应商|磋商供应商)须知前附表\s*$',
regex.MULTILINE regex.MULTILINE
) )
local_output_suffix = "notice" local_output_suffix = "notice"