import inspect import pymysql import os from selenium import webdriver from selenium.webdriver.ie.options import Options from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.common.exceptions import TimeoutException from bs4 import BeautifulSoup import lxml.html # from lxml.cssselect import CSSSelector import requests import json import time import re # from konfig import Config import urllib from datetime import datetime, timedelta import sys import pandas as pd import html.entities # safe_encode import uuid import random import logging # pluins from plugins.sql.hooks.mysql_hook import CommonHookMySQL #환경설정 파일 로드 # CONF = Config(os.path.dirname(__file__) + "\\conf.ini") # DB_CONF = CONF.get_map("DB") # DRIVER = CONF.get_map("DRIVER") # DRIVER["EXT_PATH"] = os.path.dirname(__file__) + DRIVER["EXT_PATH"] # DB 연결 ID DB_CONN_ID = "COLLECT_SERVER_DB" #=============================================================================================================================================== #데이터베이스 커넥터 #=============================================================================================================================================== class dbconn(CommonHookMySQL): def __init__(self): pass def change_DB(self, DB_CONF): self.__init__(host=DB_CONF["host"], user=DB_CONF["user"], passwd=DB_CONF["passwd"], db=DB_CONF["db"], cs=DB_CONF["cs"]) #DB 접속 def sql_exec(self, qry, type): #print(qry) if type == "TEST": return '' else: if (type == "DS" or type=="DS_one"): cur = self.conn.cursor(pymysql.cursors.DictCursor) else: cur = self.conn.cursor() cur.execute(qry) if (type=="S" or type=="DS"): rows = cur.fetchall() if (type == "S_one" or type == "DS_one"): rows = cur.fetchone() #cur.close() #conn.close() if(type=="S" or type=="DS"): return rows if (type == "S_one" or type == "DS_one"): if rows is not None: return rows[0] else: return '' #쿼리 문자열 클린징 def clean_param(self, _str): _str = Util.clean_str(self, _str, [["'","\\'"],]) return _str def Upsert_table(self, dataset, exec_mode="EXEC"): # del_col은 비교시 제외할 컬럼 리스트 # 샘플 데이터셋 # dataset = { # "key": {"n_num": last_nbbs_detail["n_num"], "biz_num": last_nbbs_detail["biz_num"]}, # "table": "last_nbbs_detail", # "value": last_nbbs_detail # "type": "update" ==> update : 없으면 입력 있으면 변경분 수정 , insert : 없으면 입력 있으면 아무것도 안함, updateonly : 없으면 입력 안함, insertonly : 입력만 함 # "del_col": ["code", "reg_date", "ipchal_date"] # "orderby": "" # } orderby = dataset["orderby"] if "orderby" in dataset else "" whereis = dataset["whereis"] if "whereis" in dataset else "" #키가 있는지 확인 if dataset["table"] != 'bid_content': row = self.get_one_row(dataset["key"], dataset["table"], orderby) elif dataset["table"] == 'bid_content': try: attchd_lnk = dataset["value"]['attchd_lnk'] if "attchd_lnk" in dataset['value'] else "" except: attchd_lnk = '' # bid_html로 오류가 날수잇으므로 발주처를 따로 관리, 나라장터 물품건 따로 HTML 넣어주기 Or 나라장터, 조달청 물품건 HTML 저장. # 나라장터 경우 첨부파일 없을 경우 bid_html 넣어주며, 공사/용역/물품 전부 포함. -> 차세대 나라장터 오픈 후 제거 # if whereis in ["17","03","96","81","91","44","21"] or ( whereis in ['01',"84","85","86"] and not attchd_lnk): if whereis in ["17","03","96","81","91","44","21"]: row = self.get_one_row(dataset["key"], dataset["table"], orderby,"bidid,bidcomment_mod,bidcomment,nbidcomment,bid_html, orign_lnk,s_orign_lnk,attchd_lnk,pur_lnk,bid_file,nbid_file,pur_goods,partition_seq") else: #bid_html로 오류가 날수잇으므로 오류가 나는 발주처는 빼준다 row = self.get_one_row(dataset["key"], dataset["table"], orderby,"bidid,bidcomment_mod,bidcomment,nbidcomment,orign_lnk,s_orign_lnk,attchd_lnk,pur_lnk,bid_file,nbid_file,pur_goods,partition_seq") #print("+++") #print(row) #print("+++") if row is None or dataset["type"] in ["insertonly", "insert"]: print("Upsert_table 입력모드 ==> dataset:", dataset) if dataset["type"] not in ["updateonly"]: print("self.Insert_table 실행") # ================================================================ # 인서트 처리부분 if exec_mode == "EXEC": self.Insert_table(dataset["value"], dataset["table"]) # ================================================================ else: print("updateonly 입력안함") else: if dataset["type"] in ["update", "updateonly"]: Etl = scraplib.Etl() #print("Upsert_table 수정모드 ==> row:", row) # 비교제외 컬럼 삭제 후 비교 diff = Etl.diff_array(Etl.del_key(row, dataset["del_col"]), Etl.del_key(dataset["value"], dataset["del_col"])) print("diff :", diff["MOD"]) if len(diff["MOD"]) > 0: _data = {} for _key, _value in diff["MOD"].items(): # if _value["NEW"] > _value["OLD"]: #새로운 데이터가 이전 데이터보다 클경우에만 수정 _data[_key] = _value["NEW"] if len(_data) > 0: print("self.Update_table 실행 [변경분 수정]=========>", _data) # ================================================================ # 업데이트 처리부분 if exec_mode == "EXEC": self.Update_table(_data, dataset["key"], dataset["table"]) # ================================================================ else: print("[insert 모드 입력 안함] ==> row:", row) def get_one_row(self, _where, tb_nm, orderby="", rtn_col="*"): where = '' for key, value in _where.items(): if value != None: if type(value) == dict: value_type = list(value.keys())[0] value_content = list(value.values())[0] if value_type == "KEY": where = where + " AND {key} = {value}".format(key=key, value=value_content) elif value_type == "LIKE": where = where + " AND {key} LIKE '{value}'".format(key=key, value=value_content) elif value_type == "IN": where = where + " AND {key} IN {value}".format(key=key, value=value_content) else: where = where + " AND {key} = '{value}'".format(key=key, value=value) query = "SELECT {rtn_col} FROM {tb_nm} WHERE 1 {where} {orderby} limit 1".format(tb_nm=tb_nm, where=where, orderby=orderby, rtn_col=rtn_col) print(query) row = self.select_sql_to_dict(query) #print("@@@") #print("get_one_row : ", row) #print("get_one_row2 : ") if( len(row) > 0 ): for key, value in row[0].items():#반환데이터 전처리 if type(value) == datetime:#날짜 형식은 포맷팅해서 텍스트로 변환 row[0][key] = str(value.strftime('%Y-%m-%d %H:%M:%S')) # print("=======================================================================>", key, row[0][key], type(row[0][key])) return row[0] else: return None def ck_Exist_one(self, _where, tb_nm): where = '' for key, value in _where.items(): if value != None: where = where + " AND {key} = '{value}'".format(key=key, value=value) query = "SELECT COUNT(*) FROM {tb_nm} WHERE 1 {where}".format(tb_nm=tb_nm, where=where) cnt = self.select_sql_to_dict(query)[0] if( cnt > 0 ): return True else: return False def ck_Exist_cnt(self, _where, tb_nm): where = '' for key, value in _where.items(): if value != None: where = where + " AND {key} = '{value}'".format(key=key, value=value) query = "SELECT COUNT(*) FROM {tb_nm} WHERE 1 {where}".format(tb_nm=tb_nm, where=where) cnt = self.select_sql_to_dict(query)[0] return cnt def ck_Exist_one_difftype(self, _where, tb_nm): where = '' for key, value in _where.items(): if value is not None: if value['type'] == 'equal': where = where + " AND {key} = '{value}'".format(key=key, value=value['data']) elif value['type'] == 'text': where = where + " AND {key} {value}".format(key=key, value=value['data']) query = "SELECT COUNT(*) FROM {tb_nm} WHERE 1 {where}".format(tb_nm=tb_nm, where=where) print(query) cnt = self.select_sql_to_dict(query) if( cnt > 0 ): return True else: return False def Insert_table(self, _data, tb_nm, rtn_fg=False): logging.info("[scraplib.Insert_table][쿼리실행] start") data_str = '' for key, value in _data.items(): if value != None: if data_str != '': data_str = data_str + " , " if value == "NOW()": data_str = data_str + "`{key}` = {value}".format(key=key, value=self.remove_quote(value)) else: data_str = data_str + "`{key}` = '{value}'".format(key=key, value=self.remove_quote(value)) query = "INSERT INTO {tb_nm} SET {data_str}".format(tb_nm=tb_nm, data_str=data_str) try: self.insert_sql(query) except: logging.error('insert query error : ', query) pass # print(query) if rtn_fg == True: try: row = self.select_sql_to_dict("SELECT LAST_INSERT_ID() AS seq") logging.info("=====================>row:", row) return row[0]["seq"] except: pass logging.info("[scraplib.Insert_table][쿼리실행] end") def remove_quote(self, _val): if type(_val) == str: return _val.replace("'", "\\'") else: return _val def Update_table(self, _data, _where, tb_nm): print("[scraplib.Update_table][쿼리실행] start") data_str = '' for key, value in _data.items(): if value != None: if data_str != '': data_str = data_str + " , " if value == "NOW()": data_str = data_str + "`{key}` = {value}".format(key=key, value=self.remove_quote(value)) else: data_str = data_str + "`{key}` = '{value}'".format(key=key, value=self.remove_quote(value)) where = '' for key, value in _where.items(): if where != '': where = where + " AND " where = where + "{key} = '{value}'".format(key=key, value=self.remove_quote(value)) if data_str != '': query = "UPDATE {tb_nm} SET {data_str} WHERE {where}".format(tb_nm=tb_nm, data_str=data_str, where=where) self.process_sql(query) # print(query) print("[scraplib.Update_table][쿼리실행] end") def Delete_table(self, key, value, tb_nm): print("[scraplib.Delete_table][쿼리실행] start") if key and value and tb_nm: query = "DELETE FROM {tb_nm} WHERE {key}='{value}'".format(tb_nm=tb_nm, key=key, value=value) self.process_sql(query) print("[scraplib.Delete_table][쿼리실행] end") #=============================================================================================================================================== # 셀레늄 크롬드라이버 기반 크롤러 #=============================================================================================================================================== class chrome_selenium: # chromedriver = os.path.dirname(__file__)+DRIVER["CHROMEDRIVER_PATH"] # iedriver = os.path.dirname(__file__) + DRIVER["IEDRIVER_PATH"] iedriver = '' chromedriver = '' # os.environ["webdriver.chrome.driver"] = chromedriver driver = "" exec_type = "exec" crxs=[] def __init__(self, _exec_type, _crxs=[]): stack = inspect.stack() caller_frame = stack[1] caller_filename = caller_frame.filename caller_function = caller_frame.function if(_exec_type=="IE"): ie_options = Options() ie_options.ignore_protected_mode_settings = True self.driver = webdriver.Ie(executable_path=self.iedriver, options=ie_options) else: chrome_options = webdriver.ChromeOptions() if(_exec_type=="exec"): chrome_options.add_argument("--headless") chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-gpu") chrome_options.set_capability('unhandledPromptBehavior', 'accept') if "www_kapt_go_kr" in caller_filename and caller_function == 'bid_collect_process': # kapt의 경우 첨부파일 경로를 가져오기위해 api 호출시 크로스도메인 오류를 뱉어내 재호출없이 네트워크 로그의 출력물을 판단하기 위해 아래 로직을 추가함 chrome_options.set_capability('goog:loggingPrefs', {"performance": "ALL"}) for crs in _crxs: chrome_options.add_extension(crs) #chrome_options.set_capability('unhandledPromptBehavior', 'accept') self.exec_type = _exec_type self.crxs = _crxs self.driver = webdriver.Chrome(self.chromedriver, options=chrome_options) # 해당 xpath 경로 프레임으로 포커스 이동 def switch_to_frame(self, XPATH): self.driver.switch_to_default_content() # 프레임 초기화 self.driver.switch_to.frame(self.driver.find_element_by_xpath(XPATH)) # 프레임 이동 # 최상위 프레임으로 포커스 복귀 def switch_to_default_frame(self): self.driver.switch_to_default_content() # 프레임 초기화 def rtn_elements_bs4(self, _selector): try: rtn = [] page_source = self.driver.page_source soup = BeautifulSoup(page_source, 'html.parser') listdata = soup.select(_selector) for i in listdata: rtn.append(Util.clean_str(self, i.text)) except Exception as e: print("rtn_elements_bs4 : ", e) rtn = [] return rtn def rtn_elements_bs4_request(self, _selector, _attr="TEXT"): try: rtn = [] soup = BeautifulSoup(self.page_source_request, 'html.parser') listdata = soup.select(_selector) for i in listdata: if _attr == "TEXT": _tmp = i.text else: _tmp = i.attrs[_attr] rtn.append(Util.clean_str(self, _tmp)) except Exception as e: print("chrome_selenium rtn_elements_bs4_request : ", e) rtn = [] return rtn def rtn_elements_lxml_request(self, _selector, _attr="TEXT"): try: rtn = [] tree = lxml.html.fromstring(self.page_source_request) # listdata = tree.findall(_selector) listdata = list(tree.xpath(_selector)) # print(listdata) for i in listdata: if _attr == "TEXT": _tmp = i.text_content().replace(" ", "") else: _tmp = i.attrib[_attr] rtn.append(Util.clean_str(self, _tmp)) except Exception as e: print("chrome_selenium rtn_elements_lxml_request : ", e) rtn = [] return rtn def rtn_elements_lxml_request_g2b(self, _selector, _type="XPATH", _attr="TEXT"): try: rtn = [] tree = lxml.html.fromstring(self.page_source_request) # listdata = tree.findall(_selector) listdata = list(tree.xpath(_selector)) if _attr == "TEXT_BR": print("rtn_elements_lxml_request_g2b listdata", listdata, type(listdata)) for _idx, i in enumerate(listdata): if _attr == "TEXT": _tmp = "".join(i.text_content()) elif _attr == "TEXT_BR": if _idx == 0: tmp_sub = [] tmp_sub.append(Util.clean_str(self, i.text)) # print("===>", Util.clean_str(self, i.text)) for a in i: tmp_sub.append(Util.clean_str(self, a.tail)) # print("===>", Util.clean_str(self, a.tail)) _tmp = "
".join(tmp_sub) print(_tmp) else: _tmp = i.attrib[_attr] rtn.append(Util.clean_str(self, _tmp)) if len(rtn) == 0: rtn.append("") except Exception as e: print("chrome_selenium rtn_elements_lxml_request : ", e) rtn = [] return rtn def rtn_elements_regex_g2b(self, _selector): try: rtn = re.findall(_selector, self.page_source_request) except Exception as e: print("chrome_selenium rtn_elements_regex_g2b : ", e) rtn = None return rtn # 페이지 소스에서 객체정보 추출 _type : CSS, XPATH def rtn_element(self, _selector, _type="CSS", _attr=""): try: if(_type=="CSS_CLICK"): rtn = self.driver.find_element_by_css_selector(_selector).click() elif(_type=="XPATH_CLICK"): rtn = self.driver.find_element_by_xpath(_selector).click() elif (_type == "CSS"): rtn = Util.clean_str(self, self.driver.find_element_by_css_selector(_selector).text) elif (_type == "CSS_TEXT"): self.driver.find_element_by_css_selector(_selector).clear() self.driver.find_element_by_css_selector(_selector).send_keys(_attr) rtn = "" elif (_type == "XPATH_TEXT"): self.driver.find_element_by_xpath(_selector).clear() self.driver.find_element_by_xpath(_selector).send_keys(_attr) rtn = "" else:# XPATH if (_attr == "getText"): rtn = self.driver.find_element_by_xpath(_selector).getText() elif (_attr != ""): rtn = self.driver.find_element_by_xpath(_selector).get_attribute(_attr) else: rtn = Util.clean_str(self, self.driver.find_element_by_xpath(_selector).text) except: print("_selector not found : ", _selector) rtn = "" #특수문자 print 할경우 오류방생으로 주석 #print("rtn_element [", _selector, "] :", rtn) return rtn def rtn_elements(self, _selector, _type="CSS", _attr=""): try: if(_type=="CSS"): tmp = self.driver.find_elements_by_css_selector(_selector) else: tmp = self.driver.find_elements_by_xpath(_selector) rtn = [] for t in tmp: if(_attr!="" and _type=="XPATH"): rtn.append(t.get_attribute(_attr)) # 2021-08-23 추가, 상단 XPATH 제거해도 되는지 재택끝나면 확인 elif (_attr != ""): rtn.append(t.get_attribute(_attr)) else: rtn.append(Util.clean_str(self, t.text)) except: print("_selector not found : ", _selector) rtn = [] return rtn def callback(self, callback=None,_lists=None, driver=None, _alert=False): if (driver == None): driver = chrome_selenium(self.exec_type, self.crxs) for list in _lists: print(list["url"]) try: if _alert == True: #alert 처리 driver.get_URL(list["url"], 5, '', True) else: driver.get_URL(list["url"]) callback(list["list"], driver) except Exception as e: print('Exception : callback오류 ', e) """ error_text = str(e) if error_text.find("WinError") != -1: os.exit(0) """ def callback_for_s2b(self, callback=None,_lists=None, driver=None): if (driver == None): driver = chrome_selenium(self.exec_type, self.crxs) for list in _lists: print(list["url"]) try: driver.get_URL(list["url"]) callback(list["list"], list["url"], driver) except Exception as e: print('Exception : callback오류 ', e) """ error_text = str(e) if error_text.find("WinError") != -1: os.exit(0) """ def callback_post(self, callback=None,_lists=None, driver=None): for list in _lists: print(list["url"],list["list"]["link_post"]) try: try: PARAMS = json.loads(list["list"]["link_post"]) except: PARAMS = {} driver.get_URL_request(list["url"], PARAMS, "POST", "TEXT") callback(list["list"], driver) except Exception as e: print('Exception : ', e) def callback_get(self, callback=None, _lists=None, driver=None): for _list in _lists: try: driver.get_URL_request(_list["url"], "", "GET", "TEXT") callback(_list["list"], driver) except Exception as e: print('Exception : ', e) def callback_xml(self, callback=None, _lists=None, driver=None, _alert=False): print("callback_xml") if (driver == None): driver = chrome_selenium(self.exec_type, self.crxs) for list in _lists: print(list) try: # driver.get_URL(list["url"]) headers = {'Content-Type': 'application/xml'} response = requests.post(list["payload_url"], data=list["payload_xml"], headers=headers) response_text = response.text callback(list, response_text, driver) except Exception as e: print('Exception : callback오류 ', e) """ error_text = str(e) if error_text.find("WinError") != -1: os.exit(0) """ def get_URL_request(self, URL, PARAMS, METHOD="GET", RTN_TYPE="JSON", HEADERS=None, COOKIES=None, TIMEOUT=30): self.page_source_request = Util.get_URL(self, URL, PARAMS, METHOD, RTN_TYPE, HEADERS, COOKIES, TIMEOUT) # url 의 경우 단일일경우 문자열 "", 경유가 필요한경우 ["","" ...] 문자열리스트 def get_URL(self, url, sec=5, id="", fg_alert=False): if (type(url) is str):#문자열일경우 try: self.driver.get(url) if(fg_alert==True): try: alert = self.driver.switch_to_alert() alert.accept() except Exception as e: pass # print('Exception : ', e) time.sleep(1) wait = WebDriverWait(self.driver, sec) if id != '': wait.until(EC.element_to_be_clickable((By.ID, id))) except: pass else:#리스트일경우 try: for _url in url: self.driver.get(_url) if(fg_alert==True): try: alert = self.driver.switch_to_alert() alert.accept() except Exception as e: print('Exception : ', e) time.sleep(1) except Exception as e: print('Exception : ', e) #마지막 페이지만 기다렸다 수집 wait = WebDriverWait(self.driver, sec) if id != '': wait.until(EC.element_to_be_clickable((By.ID, id))) pass body = self.driver.page_source return body def check_until_alert_show(self, sec=7) -> None: """ loop until every alert, confirm is dismissed in given second :param sec: second that dev can set :return: None """ wait = WebDriverWait(self.driver, sec) try: wait.until(EC.alert_is_present()) confirm = self.driver.switch_to.alert confirm.dismiss() self.check_until_alert_show() except Exception as TimeoutException: pass #=============================================================================================================================================== # 유틸리티 펑션 #=============================================================================================================================================== class Util: # 네이트온 메세지 발송 def send_msg(self, msg, id="LOG"): urls = { "ERROR": "https://teamroom.nate.com/api/webhook/6585e4a8/BGnMXKey8HUTwQROkmIaDiDK", "LOG": "https://teamroom.nate.com/api/webhook/6585e4a8/TapsrGOgqoSO8tfqCLwLpDqZ", # 토지주택공사 "LBC": "https://teamroom.nate.com/api/webhook/6585e4a8/a4OWV4umnnNJImGiZWtkCU6Z", "LBMC": "https://teamroom.nate.com/api/webhook/6585e4a8/a4OWV4umnnNJImGiZWtkCU6Z", # 한국철도공사 "KRL_B": "https://teamroom.nate.com/api/webhook/6585e4a8/IfAZig74jUBJWyZxtGRG2TML", "KRL_SB": "https://teamroom.nate.com/api/webhook/6585e4a8/IfAZig74jUBJWyZxtGRG2TML", # 국가철도공단 "KRBC": "https://teamroom.nate.com/api/webhook/6585e4a8/cFH2bM8GleIbHESCKUGZrT1i", # 국제협력단 "KOICA_B": "https://teamroom.nate.com/api/webhook/6585e4a8/M9ZTkpFS3TAtw9JqqOBIYvRk", # 한국마사회 "KRABA": "https://teamroom.nate.com/api/webhook/6585e4a8/MtTy4Weo6km2B18A4BMehh3e", # 전자통신연구원 "ETRI_B": "https://teamroom.nate.com/api/webhook/6585e4a8/lRNMkeMC8xSFjtzSr5Dq0UOy", #도로공사 "EBC": "https://teamroom.nate.com/api/webhook/6585e4a8/eug9PTbaJJJvXkvr8mhe2Mxv", "EBS": "https://teamroom.nate.com/api/webhook/6585e4a8/eug9PTbaJJJvXkvr8mhe2Mxv", "EBI": "https://teamroom.nate.com/api/webhook/6585e4a8/eug9PTbaJJJvXkvr8mhe2Mxv", #국토정보공사 "LX_B": "https://teamroom.nate.com/api/webhook/6585e4a8/KU8Np7Klx11XvNhnVZ0Sa52X", #강원랜드 "K_LAND_B": "https://teamroom.nate.com/api/webhook/6585e4a8/QgqK5vPDid0bzdSuS289Z70D", # 국방부 "D2B_B": "https://teamroom.nate.com/api/webhook/6585e4a8/s3FG0Tz2mk28nkp7jeNbAujZ", # 수자원공사 "KWATER_B": "https://teamroom.nate.com/api/webhook/6585e4a8/NwL7xpqYiVlHcutUn9fErMX8", # 나라장터 직접 "G2B_B": "https://teamroom.nate.com/api/webhook/6585e4a8/rSYzYnbAt3uCB5aambYDLDiZ", } try: try: url = urls[id] except: url = urls["LOG"] msg = u"content=" + msg + " | " + str(datetime.now().strftime('%Y-%m-%d %H:%M:%S')) txt = msg.encode('utf-8') payload = txt headers = { 'content-type': "application/x-www-form-urlencoded", 'cache-control': "no-cache", 'postman-token': "6176cbc2-3050-4c1f-2d2a-4c25ae984e3f" } response = requests.request("POST", url, data=payload, headers=headers) print(response.text) except Exception as ex: print("send_msg() ERROR :", ex) #============================================================================== ##텍스트 클린징 함수 _str 은 문자열("asdf") 또는 문자열리스트(["asdf","qwer","zxcv"])가 올수 있다 ## def clean_str(self, _str, cus_pattern=[]): patterns=[#여기에 리스트형태로 리플레이스 패턴 추가 ["원래패턴","변경패턴"], ["①",""], ["③",""], ["․","."], ["\xa0","-"], ["\u2024","."], ["\u2e31","."], ["\u25ef", ""], ["\u2027","."], ["\uff62","["], ["\uff63","]"], ["\u2013","-"], ["\u2610","□"], ["\u2205","Ø"], ["\r",""], ["\t",""], ["\n",""], ["・","."], ["\u25e6", ""], ["\xa9", ""], ["\ufeff", ""], ["\u200b", ""], ["\u2219", ""], ["\u274d", ""], ["\u2782", ""], ["\u2981", ""], ["\u3007", ""], ["\u25fc", ""], ["\ufffd", ""], ["\u25ba", ""], ["\u20de", ""], ["\u302c", ""], ["\u0223", ""], ["\u22c5", ""], ["\u1100", ""], ["\u1161", ""], ["\u11bc", ""], ["\u1102", ""], ["\u11b7", ""], ["\u110b", ""], ["\u116f", ""], ["\u11ab", ""], ["\u1112", ""], ["\u116d", ""], ["\u1109", ""], ["\u1165", ""], ["\u1175", ""], ["\u2022", ""], ["\u2613", ""], ["\u27a1", ""], ["\U00010a50",""], ["\u25b9",""], ["\u2023",""], ["\u0228",""], ["\xb5",""], ["\u0387", ""], ["\u3693", ""], ["\u2715", ""], ["\u2714", ""], ["\u25a2", ""], ["\u1110", ""], ["\u1169", ""], ["\u11a8", ""], ["\u1107", ""], ["\u1173", ""], ["\u1111", ""], ["\u1167", ""], ["\u11af", ""], ["\u110e", ""], ["\u1103", ""], ["\u1162", ""], ["\u110c", ""], ["\u116e", ""], ["\uff65", ""], ["\uf09e", ""], ["\u24c7", ""], ["\u2002", ""], ["\u26aa", ""], ["\U000f0854", ""], ["\u2e33", ""], ["\U000f02ea", ""], ["\u2218",""], ["\u0278",""], ["\u2022", ""], ["\u022f", ""], ["\u24fd", ""], ["\u302e", ""], ["\u0368", ""], ["\u301c", "~"], ["\u02d1", "~"], ["\u21e8", "->"], ["\u25a2", ""], ["\u231f", ""], ["\u2780", ""], ["\u119e", ""], ["\u20a9", "원"], ["\u25cc", ""], ["\uf022", ""], ["\u0301", ""], ["\u1168", ""], ["\u1163", ""], ["\u1166", ""], ["\u2215", ""], ["\u231c", ""], ["\U000f02ef", ""], ["\uf0a0", ""], ["\u2014", ""], ["\u301a", ""], ["\u301b", ""], ["\uf028", ""], ["\u30fb", ""], ["\uf076", ""], ["\u25aa", ""], ["\u1104", ""], ["\u2776", ""], ["\u2777", ""], ["\u2613", ""], ["\u2000", ""], ["\u25b8", ""], ["\u2219", ""], ["\u2012", ""], ["\u233d", ""], ["\u8f66", ""], ["\u65f6", ""], ["\u95f4", ""], ["\u27f6", ""], ["\uf0a6", ""], ["\u21db", ""], ["\u2783", ""], ["\u2784", ""], ["\u2785", ""], ["\u2010", "-"], ["\U0001d635", ""], ["\u206d", ""], ["\u279f", ""], ["\u2d41", ""], ["\u278a", ""], ["\u278b", ""], ["\u278c", ""], ["\u27f9", ""], ["\u2035", ""], ["\u02dc", ""], ["\u2053", ""], ["\u301e", ""], ] # ["원래패턴", "변경패턴"], if(len(cus_pattern)>0):#커스텀 패턴이 있는경우 커스텀패턴 적용 patterns = cus_pattern if (type(_str) is str):#문자열일경우 try: _str = _str.strip() for pattern in patterns: _str=_str.replace(pattern[0],pattern[1]) except: pass else:#리스트일경우 try: _tmp = [] for _s in _str: _s = _s.strip() for pattern in patterns: _s = _s.replace(pattern[0], pattern[1]) _tmp.append(_s) _str = _tmp except: pass return _str #============================================================================== ##None 을 공백으로 def ck_None(self, _str): if(_str is None): _str = "" return _str #============================================================================== ## 문서에서 패턴을 매칭하여 반환 ## 샘플 : aa = spilt_rex_doc(text, "(var\sbidRateCode\s=\s')((\d|\.){2,6})(';)", 2) ## idx 는 정규식 그룹중 추출할 인덱스 def spilt_rex_doc(self, _doc, _pattern, _idx): for para in _doc.splitlines(): para_tmp = para.strip() print("para_tmp:",para_tmp) line = re.match(_pattern, para_tmp) print("line:",line) if line is not None: rtn = re.sub(_pattern, r"\{idx}".format(idx=_idx), para_tmp) return rtn return None #============================================================================== ## URL 직접수집 ## URL : 수집주소, PARAMS : 파라미터, METHOD : 전송방법(POST,GET), RTN_TYPE : 리턴타입(JSON,TEXT,PLAIN) ==> 필수파라미터 ## HEADERS=None, COOKIES=None, TIMEOUT=3 ==> 선택파라미터 # response.content # 응답 데이터(binary형식 내용,이미지파일 등) # response.text # 응답 데이터(텍스트형식 내용, 텍스트 파일에 씀) # response.json # 응답 데이터 JSON형태 # response.url # 해당 url 반환 # response.status_code # 응답 상태코드 (200 이면 성공) # response.headers # 응답 헤더의 {Key:Value} 형식의 딕셔너리 자료반환 # response.encoding = 'utf-8' # 응답 객체에 인코딩 지정 # HEADERS = {'User-Agent' : ('Mozilla/5.0 (Windows NT 10.0;Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'),'Referer': 'http://www.igunsul.net/'} def get_URL(self, URL, PARAMS, METHOD="GET", RTN_TYPE="JSON", HEADERS=None, COOKIES=None, TIMEOUT=60): #print("URL:", re.findall("^https:", URL)) try: if len(re.findall("^https:", URL)) > 0 or len(re.findall("^http://www.d2b.go.kr", URL)) > 0:#ssl 통신일경우 옵션 변경, 2024.10.18 국방부 SSL인증 임시조치 VAL_verify = False else: VAL_verify = True except: VAL_verify = True if RTN_TYPE == "JSON": NORMAL_PARAMS = '' JSON_PARAMS = PARAMS else: NORMAL_PARAMS = PARAMS JSON_PARAMS = '' if(METHOD=="GET"): if(HEADERS is not None and COOKIES is not None): response = requests.get(URL, params=NORMAL_PARAMS, json=JSON_PARAMS, headers=HEADERS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify) elif(HEADERS is not None and COOKIES is None): response = requests.get(URL, params=NORMAL_PARAMS, json=JSON_PARAMS, headers=HEADERS, timeout=TIMEOUT, verify=VAL_verify) elif(HEADERS is None and COOKIES is not None): response = requests.get(URL, params=NORMAL_PARAMS, json=JSON_PARAMS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify) # 나라장터 API 수집시 json 수집임에도 param을 json으로 넘겨주면 데이터를 못가져온다. json이 아닌 param으로 호출하도록 함 elif len(re.findall("apis.data.go.kr", URL)) > 0: response = requests.get(URL, params=JSON_PARAMS, json='', timeout=TIMEOUT, verify=VAL_verify) else: response = requests.get(URL, params=NORMAL_PARAMS, json=JSON_PARAMS, timeout=TIMEOUT, verify=VAL_verify) else: #POST if(HEADERS is not None and COOKIES is not None): response = requests.post(URL, data=NORMAL_PARAMS, json=JSON_PARAMS, headers=HEADERS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify) elif(HEADERS is not None and COOKIES is None): response = requests.post(URL, data=NORMAL_PARAMS, json=JSON_PARAMS, headers=HEADERS, timeout=TIMEOUT, verify=VAL_verify) elif(HEADERS is None and COOKIES is not None): response = requests.post(URL, data=NORMAL_PARAMS, json=JSON_PARAMS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify) else: response = requests.post(URL, data=NORMAL_PARAMS, json=JSON_PARAMS, timeout=TIMEOUT, verify=VAL_verify) ''' if(METHOD=="GET"): if(HEADERS is not None and COOKIES is not None): response = requests.get(URL, params=PARAMS, headers=HEADERS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify) elif(HEADERS is not None and COOKIES is None): response = requests.get(URL, params=PARAMS, headers=HEADERS, timeout=TIMEOUT, verify=VAL_verify) elif(HEADERS is None and COOKIES is not None): response = requests.get(URL, params=PARAMS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify) else: response = requests.get(URL, params=PARAMS, timeout=TIMEOUT, verify=VAL_verify) else: #POST if(HEADERS is not None and COOKIES is not None): response = requests.post(URL, data=PARAMS, headers=HEADERS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify) elif(HEADERS is not None and COOKIES is None): response = requests.post(URL, data=PARAMS, headers=HEADERS, timeout=TIMEOUT, verify=VAL_verify) elif(HEADERS is None and COOKIES is not None): response = requests.post(URL, data=PARAMS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify) else: response = requests.post(URL, data=PARAMS, timeout=TIMEOUT, verify=VAL_verify) ''' #print("status_code:", response.status_code) #print("response.text:", response.text) #print("response.headers:", response.headers) if(response.status_code == 200): if(RTN_TYPE=="JSON"): RTN = json.loads(response.text) elif(RTN_TYPE=="TEXT"): RTN = response.text else: RTN = response else: RTN = response.status_code return RTN #=============================================================================================================================================== # ETL 펑션 #=============================================================================================================================================== class Etl: dbconn_BI = dbconn() Util = Util() array_diff_list = [] DIC_G2BPartCode = {} def g2b_init(self): self.DIC_G2BPartCode = self.G2BPartCode() def del_key(self, _dic, _keys):# 딕셔너리 컬럼 삭제 for _col in _keys: try: del _dic[_col] except: pass return _dic def array_diff(self, a, b, path=None): # a : 기준값, b : 변경값 딕셔너리간 다른값 찾기 keys = a.keys()# a 를 기준으로 확인 for key in keys: if self.getValue(a, key, {}) == self.getValue(b, key, {}):# 하위 값이 같으면 패스 pass else: #하위값이 다른경우 내용을 풀어 확인한다 _path = key if path == None else path + "|" + key if type(self.getValue(a, key, {})) == dict and type(self.getValue(b, key, {})) == dict: self.array_diff(self.getValue(a, key, {}), self.getValue(b, key, {}), _path) else: self.array_diff_list.append([_path, self.getValue(a, key, {}), self.getValue(b, key, {})]) _path = path def diff_array(self, a, b): # 변경데이터 교집합, 차집합 작성 try: RTN = {} a = a if type(a) == dict else json.loads(a, strict=False) b = b if type(b) == dict else json.loads(b, strict=False) self.array_diff_list = [] # 데이터 초기화 self.array_diff(a, b) A = self.array_diff_list self.array_diff_list = [] # 데이터 초기화 self.array_diff(b, a) B = self.array_diff_list dic_intersection = {} dic_Acomplement = {} # A - B 차집합 dic_Bcomplement = {} # B - A 차집합 for Aval in A: FG = False for Bval in B: if Aval[0]==Bval[0]: FG = True break else: FG = False if FG == True: dic_intersection[Aval[0]] = {"OLD": Aval[1], "NEW": Aval[2]} else: dic_Acomplement[Aval[0]] = {"OLD": Aval[1], "NEW": Aval[2]} for Bval in B: FG = False for Aval in A: if Bval[0]==Aval[0]: FG = True break else: FG = False if FG == True: pass else: dic_Bcomplement[Bval[0]] = {"NEW": Bval[1], "OLD": Bval[2]} RTN["MOD"] = dic_intersection # 데이터가 수정된 경우 RTN["REMOVE"] = dic_Acomplement # 키가 사라진 경우 RTN["ADD"] = dic_Bcomplement # 키가 추가된 경우 return RTN except Exception as e: print(e) def blank_none_zero_To_0(self, _param): try: rtn = float(_param) except: rtn = 0 return rtn def change_detect_DB(self, _diff, syscollect, new): #데이터 변경시 테이블 자동반영 # ================================================================================================================== # 기초금액, A값, 순공사원가 변경 허용할 발주처 세팅 # 기초금액 변경 확인 추가 -> ([53] 국가철도공산, 2021.7) # A값 변경 확인 추가 -> ([53] 국가철도공산, 2021.7) # 순공사원가 변경 확인 추가 -> ([05] 토지주택공사, 2021.7.21) # # ================================================================================================================== print("===========================================================================") print("=====================change_detect_DB===>start=============================") allow_basic = ["52","53","91","08","05","04","03"]#기초금액 변경 확인 발주처 whereis allow_basic_type2 = ["10"] # 기초금액 변경 확인시 투찰율+사정율+난이도계수 함께 업데이트 발주처 whereis allow_premiumList = ["01","53","08","10","04"]#A값 변경 확인 발주처 whereis allow_bid_const_cost_list = ["01","05","08","10","04"]#순공사원가 변경 확인 발주처 whereis allow_bidcomment = ["91"] #전자통신연구원 참가자격 서류 allow_notice = ["10"] #공지사항 업데이트 # ================================================================================================================== new = new if type(new) == dict else json.loads(new, strict=False) bidid = syscollect["bidid"] # 파이프라인 detail_list_select 에서 데이터 추가 if bidid == "" or bidid is None: print("===========================================================================") print("=====================change_detect_DB======================================") print("=====================bidid is None=========================================") print("===========================================================================") return None if syscollect["a.whereis"] is not None: whereis = syscollect["a.whereis"] # 파이프라인 detail_list_select 에서 데이터 추가 else: whereis = None # ============================================================== # 변경감지에서 쌓인 로그를 임시로 남긴다. 정상적으로 쌓이는지 확인차 # 코드와 변경내용은 하단에서 종류별로 선언 후 insert # ============================================================== bid_notify = {} bid_notify['case'] = 'Notify' bid_notify['bidid'] = bidid bid_notify['confirm'] = 'N' bid_notify['uptime'] = "NOW()" # ============================================================================================================= # A값 변경 확인 premiumList if whereis in allow_premiumList: ck_premiumList = ["cost1", "cost2", "cost3", "cost4", "cost5", "cost6", "cost7", "cost8", "cost_total"]# A값 확인 컬럼 리스트 mod_premiumList = list(filter(lambda x: x is not None, list(map(lambda x: x if "premiumList|" + x in _diff["MOD"] else None, ck_premiumList))))# 변경된 컬럼 리스트 if len(mod_premiumList) > 0:# 변경된 값에 A값이 있는지 확인 _premiumList = {} for key in mod_premiumList: _premiumList[key] = _diff["MOD"]["premiumList|" + key]["NEW"]# 변경된 값 딕셔너리 작성 query = "SELECT `cost1`,`cost2`,`cost3`,`cost4`,`cost5`,`cost6`,`cost7`,`cost8`,`cost_total` FROM `premiumList` WHERE bidid = '{bidid}' limit 1".format(bidid=bidid)# 기존 입력된 데이터 가져오기 row = self.dbconn_BI.sql_exec(query, "DS") if len(row) > 0:# 이미 입력된 건이 있는경우 변경 비교 pass #추후 자동변경될시에 작업 # _premiumList_mod = {} # for key in _premiumList: # if _premiumList[key] != row[0][key]:# 저장데이터와 수집데이터가 다른것만 업데이트 # _premiumList_mod[key] = _premiumList[key] #self.dbconn_BI.Update_table(_premiumList_mod, {'bidid': bidid}, 'premiumList') #변경완료 후 알림이 필요하면 알림 작성 # JB A값 점검페이지 로그 남기기 new_cost_total = new["premiumList"].get("cost_total", 0) if "premiumList" in new else 0 _data_change_log_cost_a = {} for key in _premiumList: if _premiumList[key] != row[0][key]: # 저장데이터와 수집데이터가 다른 경우 _data_change_log_cost_a['data_type'] = 'cost_a_change' _data_change_log_cost_a['bidid'] = bidid _data_change_log_cost_a['notinum'] = syscollect["dkey"] _data_change_log_cost_a['whereis'] = whereis _data_change_log_cost_a['prev_data'] = row[0]['cost_total'] _data_change_log_cost_a['change_data'] = new_cost_total _data_change_log_cost_a['writedt'] = 'NOW()' self.dbconn_BI.Insert_table(_data_change_log_cost_a, 'data_change_log') # A값 항목별로 쌓을 필요가 없어서 1개 항목만 달라도 쌓고 종료 break elif whereis != '01': # 입력된 건이 없는 경우 입력 - 나라장터는 하단에서 별도 처리 함 _premiumList["bidid"] = bidid _premiumList["writedt"] = "NOW()" self.dbconn_BI.Insert_table(_premiumList, 'premiumList') query = "SELECT `state_a` FROM bid_key WHERE bidid = '{bidid}'".format(bidid=bidid) row = self.dbconn_BI.sql_exec(query, "DS") if len(row) > 0: if row[0]["state_a"] == "I": update_query = "UPDATE bid_key SET state_a = 'Y' WHERE bidid = '{bidid}'".format(bidid=bidid) self.dbconn_BI.sql_exec(update_query, "U") bid_notify['code'] = '0102' bid_notify['note'] = '[{dcode}] {dkey} - A값이 업데이트 되었습니다.'.format(dcode=syscollect['dcode'], dkey=syscollect['dkey']) self.dbconn_BI.Insert_table(bid_notify, 'bid_notify') # ============================================================================================================= # 순공사원가 변경확인 bid_const_cost_list if whereis in allow_bid_const_cost_list: ck_bid_const_cost_list = ["const_cost", "material", "labor", "expense", "tax", "const_cost_nbid", "updatedt_nbid"] # 순공사원가 확인 컬럼 리스트 mod_bid_const_cost_list = list(filter(lambda x: x is not None, list(map(lambda x: x if "bid_const_cost_list|" + x in _diff["MOD"] else None, ck_bid_const_cost_list)))) # 변경된 컬럼 리스트 if len(mod_bid_const_cost_list) > 0: # 변경된 값에 순공사원가 있는지 확인 _bid_const_cost_list = {} for key in mod_bid_const_cost_list: _bid_const_cost_list[key] = _diff["MOD"]["bid_const_cost_list|" + key]["NEW"] # 변경된 값 딕셔너리 작성 query = "SELECT `const_cost`,`material`,`labor`,`expense`,`tax`,`const_cost_nbid`,`updatedt_nbid` FROM `bid_const_cost_list` WHERE bidid = '{bidid}' limit 1".format(bidid=bidid) # 기존 입력된 데이터 가져오기 row = self.dbconn_BI.sql_exec(query, "DS") if len(row) > 0: # 이미 입력된 건이 있는경우 변경 비교 pass # 추후 자동변경될시에 작업 # 순공사원가 점검페이지 로그 남기기 _data_change_log = {} for key in _bid_const_cost_list: if _bid_const_cost_list[key] != row[0][key]: # 저장데이터와 수집데이터가 다른 경우 _data_change_log['data_type'] = 'const_cost' _data_change_log['bidid'] = bidid _data_change_log['notinum'] = syscollect["dkey"] _data_change_log['whereis'] = whereis _data_change_log['prev_data'] = row[0]['const_cost'] _data_change_log['change_data'] = _bid_const_cost_list[key] _data_change_log['writedt'] = 'NOW()' self.dbconn_BI.Insert_table(_data_change_log, 'data_change_log') # 입력하는 부분 # _bid_const_cost_list_mod = {} # for key in _bid_const_cost_list: # if _bid_const_cost_list[key] != row[0][key]: # 저장데이터와 수집데이터가 다른것만 업데이트 # _bid_const_cost_list_mod[key] = _bid_const_cost_list[key] # _bid_const_cost_list_mod["updatedt"] = "NOW()" # self.dbconn_BI.Update_table(_bid_const_cost_list_mod, {'bidid': bidid}, 'bid_const_cost_list') else: # 입력된 건이 없는 경우 입력 _bid_const_cost_list["bidid"] = bidid _bid_const_cost_list["writedt"] = "NOW()" # self.dbconn_BI.Insert_table(_bid_const_cost_list, 'bid_const_cost_list') pass bid_notify['code'] = '0103' bid_notify['note'] = '[{dcode}] {dkey} - 순공사원가가 업데이트 되었습니다.'.format(dcode=syscollect['dcode'], dkey=syscollect['dkey']) # self.dbconn_BI.Insert_table(bid_notify, 'bid_notify') #============================================================================================================= # 기초금액 변경 확인 # print("=====================기초금액1=====================") # print(whereis) # print(syscollect) if whereis in allow_basic:#whereis 값이 리스트에 허용된 발주처만 적용 print("=====================기초금액2=====================") if "bid_key|basic" in _diff["MOD"]:# 변경된 값에 기초금액이 있는지 확인 basic = self.blank_none_zero_To_0(_diff["MOD"]["bid_key|basic"]["NEW"]) # 새로 수집된 기초금액 print("=====================기초금액3=====================") if basic > 0: print("=====================기초금액4=====================") query = "SELECT `basic`, `bidtype`, `opt` FROM bid_key WHERE bidid = '{bidid}' AND constdt > NOW() limit 1".format(bidid=bidid) row = self.dbconn_BI.sql_exec(query, "DS") print("=====================기초금액5=====================") if len(row) > 0:# 공고가 조건(constdt > NOW() AND state = 'Y' AND bidproc = 'B')에 부합할때만 처리 if self.blank_none_zero_To_0(row[0]["basic"]) == 0: #기존 입력된 기초금액이 공백이거나 0일때 수정 print("=====================기초금액6=====================") _data = {} _data["basic"] = basic _data["opt"] = row[0]["opt"] + 512 self.dbconn_BI.Update_table(_data, {'bidid': bidid}, 'bid_key') print("=====================기초금액7=====================") bid_notify['code'] = '0101' bid_notify['note'] = '[{dcode}] {dkey} - 기초금액이 업데이트 되었습니다.'.format(dcode=syscollect['dcode'], dkey=syscollect['dkey']) self.dbconn_BI.Insert_table(bid_notify, 'bid_notify') # ============================================================================================================= # 기초금액 변경 확인시 투찰율+사정율+난이도계수+A값+순공사원가 함께 업데이트 (국방부는 기초금액이 나올경우 해당항목들이 같이 나와서 같이 업데이트 해줘야 함) if whereis in allow_basic_type2: # whereis 값이 리스트에 허용된 발주처만 적용 print("국방부 기초금액 업데이트 체크") print(_diff) _diff["MOD"].update(_diff["ADD"]) if "bid_key|basic" in _diff["MOD"]: # 변경된 값에 기초금액이 있는지 확인 print("1") basic = self.blank_none_zero_To_0(_diff["MOD"]["bid_key|basic"]["NEW"]) # 새로 수집된 기초금액 new = new if type(new) == dict else json.loads(new, strict=False) pct = new["bid_key"]["pct"] if "pct" in new["bid_key"] else '' # 투찰율 yegarng = new["bid_value"]["yegarng"] if "yegarng" in new["bid_value"] else '' # 예가범위 lvcnt = new["bid_value"]["lvcnt"] if "lvcnt" in new["bid_value"] else '' # 난이도계수 bidcomment = new["bid_content"]["bidcomment"] if "bidcomment" in new["bid_content"] else '' # 토목기초금액, 건설기초금액이 나오면 자격조건에 텍스트로만 넣어준다. if basic > 0: print("2") query = "SELECT `basic`, `bidtype`, `opt` FROM bid_key WHERE bidid = '{bidid}' AND constdt > NOW() limit 1".format(bidid=bidid) row = self.dbconn_BI.sql_exec(query, "DS") if len(row) > 0: # 공고가 조건(constdt > NOW() AND state = 'Y' AND bidproc = 'B')에 부합할때만 처리 if self.blank_none_zero_To_0(row[0]["basic"]) == 0: # 기존 입력된 기초금액이 공백이거나 0일때 수정 _data = {} _data["basic"] = basic _data["opt"] = row[0]["opt"] + 512 if pct: _data["pct"] = pct self.dbconn_BI.Update_table(_data, {'bidid': bidid}, 'bid_key') # bid_value 업데이트 if yegarng or lvcnt: _data = {} if yegarng: _data["yegarng"] = yegarng if lvcnt: _data["lvcnt"] = lvcnt self.dbconn_BI.Update_table(_data, {'bidid': bidid}, 'bid_value') # bid_content 업데이트 if bidcomment.find('기초금액') >= 0: # 1. 토목기초금액, 건설기초금액 내용이 자격조건에 세팅되었을때 -> 해당금액들은 따로 컬럼이 없고 자격조건에 텍스트로만 넣어주고 있음 bidcomment_query = "SELECT `bidcomment` FROM bid_content WHERE bidid = '{bidid}'".format(bidid=bidid) bidcomment_row = self.dbconn_BI.sql_exec(bidcomment_query, "DS") if bidcomment_row[0]["bidcomment"].find('기초금액') < 0: # 2. 기존 자격조건에 토목기초금액, 건설기초금액 내용이 없을때 if bidcomment_row[0]["bidcomment"]: bidcomment = bidcomment_row[0]["bidcomment"] + '\n' + bidcomment _data = {} _data["bidcomment"] = bidcomment self.dbconn_BI.Update_table(_data, {'bidid': bidid}, 'bid_content') # 기초금액 공개시 A값 같이 나와서 업데이트 필요 cost1 = new["premiumList"]["cost1"] if "cost1" in new["premiumList"] else '' cost2 = new["premiumList"]["cost2"] if "cost2" in new["premiumList"] else '' cost3 = new["premiumList"]["cost3"] if "cost3" in new["premiumList"] else '' cost4 = new["premiumList"]["cost4"] if "cost4" in new["premiumList"] else '' cost5 = new["premiumList"]["cost5"] if "cost5" in new["premiumList"] else '' cost6 = new["premiumList"]["cost6"] if "cost6" in new["premiumList"] else '' cost8 = new["premiumList"]["cost8"] if "cost8" in new["premiumList"] else '' if cost1 or cost2 or cost3 or cost4 or cost5 or cost6 or cost8: premium_query = "SELECT * FROM premiumList WHERE bidid ='{bidid}' LIMIT 1".format(bidid=bidid) premium_check = self.dbconn_BI.sql_exec(premium_query, "S_one") if premium_check is None or premium_check == '': insert_query = "INSERT INTO premiumList SET cost1 = '{cost1}', cost2 = '{cost2}', cost3 = '{cost3}', cost4 = '{cost4}', cost5 = '{cost5}', cost6 = '{cost6}', cost8 = '{cost8}', bidid = '{bidid}'".format(cost1=cost1, cost2=cost2, cost3=cost3, cost4=cost4, cost5=cost5, cost6=cost6, cost8=cost8,bidid=bidid) self.dbconn_BI.sql_exec(insert_query, "I") if syscollect["ext_info2"] != "공개수의": update_query = "UPDATE bid_key SET state_a = 'Y' WHERE bidid = '{bidid}'".format(bidid=bidid) self.dbconn_BI.sql_exec(update_query, "U") else: update_query = "UPDATE premiumList SET cost1 = '{cost1}', cost2 = '{cost2}', cost3 = '{cost3}', cost4 = '{cost4}', cost5 = '{cost5}', cost6 = '{cost6}', cost8 = '{cost8}' WHERE bidid = '{bidid}'".format(cost1=cost1, cost2=cost2, cost3=cost3, cost4=cost4, cost5=cost5, cost6=cost6, cost8=cost8,bidid=bidid) self.dbconn_BI.sql_exec(update_query, "U") query = "SELECT `state_a` FROM bid_key WHERE bidid = '{bidid}'".format(bidid=bidid) row = self.dbconn_BI.sql_exec(query, "DS") if len(row) > 0: if row[0]["state_a"] == "I": update_query = "UPDATE bid_key SET state_a = 'Y' WHERE bidid = '{bidid}'".format(bidid=bidid) self.dbconn_BI.sql_exec(update_query, "U") # 기초금액 공개시 순공사원가 같이 나와서 업데이트 필요 const_cost = new["bid_const_cost_list"]["const_cost"] if "const_cost" in new["bid_const_cost_list"] else '' expense = new["bid_const_cost_list"]["expense"] if "expense" in new["bid_const_cost_list"] else '' labor = new["bid_const_cost_list"]["labor"] if "labor" in new["bid_const_cost_list"] else '' material = new["bid_const_cost_list"]["material"] if "material" in new["bid_const_cost_list"] else '' tax = new["bid_const_cost_list"]["tax"] if "tax" in new["bid_const_cost_list"] else '' if const_cost or expense or labor or material or tax: const_cost_query = "SELECT bidid FROM bid_const_cost_list WHERE bidid ='{bidid}' LIMIT 1".format(bidid=bidid) const_cost_check = self.dbconn_BI.sql_exec(const_cost_query, "S_one") if const_cost_check: update_query = "UPDATE bid_const_cost_list SET material = '{material}', labor = '{labor}', expense = '{expense}', tax = '{tax}', const_cost = '{const_cost}' WHERE bidid = '{bidid}'".format(material=material, labor=labor, expense=expense, tax=tax, const_cost=const_cost,bidid=bidid) self.dbconn_BI.sql_exec(update_query, "U") else: insert_query = "INSERT INTO bid_const_cost_list SET material = '{material}', labor = '{labor}', expense = '{expense}', tax = '{tax}', const_cost = '{const_cost}', bidid = '{bidid}'".format(material=material, labor=labor, expense=expense, tax=tax, const_cost=const_cost,bidid=bidid) self.dbconn_BI.sql_exec(insert_query, "I") # ============================================================================================================= # 공지사항 업데이트 if whereis in allow_notice: if "bid_notice_memo|notice_memo" in _diff["MOD"]: prev_notice_query = "SELECT notice_memo FROM bid_notice_memo WHERE bidid = '{bidid}' ORDER BY reg_date LIMIT 1".format(bidid=bidid) prev_notice = self.dbconn_BI.sql_exec(prev_notice_query, "S_one") updateCheck = False # 이전 공지사항이 있으면 비교하여 다를경우, 공지사항 입력된게 없을경우 if prev_notice is not None and prev_notice != '': # DB에 있는 공지사항내용과 수집된 공지사항 내용이 다르면 if prev_notice != _diff["MOD"]["bid_notice_memo|notice_memo"]["NEW"]: updateCheck = True else: updateCheck = True if updateCheck == True: _data = {} _data["notice_memo"] = _diff["MOD"]["bid_notice_memo|notice_memo"]["NEW"] _data["writer_name"] = _diff["MOD"]["bid_notice_memo|writer_name"]["NEW"] _data["status"] = _diff["MOD"]["bid_notice_memo|status"]["NEW"] _data["reg_date"] = 'NOW()' _data["bidid"] = bidid self.dbconn_BI.Insert_table(_data, 'bid_notice_memo') # ====================================================== # 추후에 통합하여 트리거로 대체후 제거해야함. # ====================================================== trigger_table = "log_bid_trigger_y" if syscollect["ext_info"] == 'pur': trigger_table = "log_bid_trigger_pur" self.trigger_insert(bidid, trigger_table) # insert_query = "INSERT INTO {trigger_table} SET bidid='{bidid}', uptime= NOW()".format(trigger_table=trigger_table, bidid=bidid) # self.dbconn_BI.sql_exec(insert_query, "I") # ====================================================== # 추후에 통합하여 트리거로 대체후 제거해야함. # ====================================================== # ============================================================================================================= # 조달청 기초금액, A값, 순공사원가, 공지사항 업데이트 if syscollect["dcode"] in ["G2B_B"]: print("=================>조달청 입찰 업데이트 start") print("_diff :", _diff) if "bidtype" in syscollect: trigger_flag = 'N' ck_bid_value_MOD = self.extract_diff(_diff["MOD"], "bid_value", "NEW") # bid_value _diff_bid_value_MOD = _diff["ADD"] if len(ck_bid_value_MOD) == 0 else _diff["MOD"] # 첫입력일경우 _diff["ADD"] 데이터을 입력 bid_value_dataset = self.extract_diff(_diff_bid_value_MOD, "bid_value", "NEW") # _diff 에서 해당 테이블 데이터만 추출 if len(bid_value_dataset) > 0: # yegarng 변경시 if 'yegarng' in bid_value_dataset and bid_value_dataset['yegarng'] is not None and bid_value_dataset['yegarng'] != '': _bid_value_dataset = { "key": {"bidid": bidid}, "table": "i2.bid_value", "value": {"yegarng": bid_value_dataset['yegarng']}, "type": "updateonly", "orderby": "", "del_col": [], } print("i2.bid_value yegarng :", bid_value_dataset['yegarng']) trigger_flag = 'Y' self.dbconn_BI.Upsert_table(_bid_value_dataset, "EXEC") if 'lvcnt' in bid_value_dataset and bid_value_dataset['lvcnt'] is not None and bid_value_dataset['lvcnt'] != '': _bid_value_dataset = { "key": {"bidid": bidid}, "table": "i2.bid_value", "value": {"lvcnt": bid_value_dataset['lvcnt']}, "type": "updateonly", "orderby": "", "del_col": [], } print("i2.bid_value lvcnt :", bid_value_dataset['lvcnt']) trigger_flag = 'Y' self.dbconn_BI.Upsert_table(_bid_value_dataset, "EXEC") ck_bid_key_MOD = self.extract_diff(_diff["MOD"], "bid_key", "NEW") #bid_key _diff_bid_key_MOD = _diff["ADD"] if len(ck_bid_key_MOD) == 0 else _diff["MOD"] # 첫입력일경우 _diff["ADD"] 데이터을 입력 bid_key_dataset = self.extract_diff(_diff_bid_key_MOD, "bid_key", "NEW") # _diff 에서 해당 테이블 데이터만 추출 if len(bid_key_dataset) > 0: #기초금액 변경시 if "basic" in bid_key_dataset and bid_key_dataset['basic'] is not None: bid_key_query = "SELECT * FROM bid_key WHERE bidid = '{bidid}' ORDER BY writedt LIMIT 1".format(bidid=bidid) bid_key_row = self.dbconn_BI.sql_exec(bid_key_query, "DS")[0] if self.blank_none_zero_To_0(bid_key_row["basic"]) == 0: #입력된 기초금액이 없을때 if bid_key_row["opt"] is not None: prev_opt_arr = self.conv_bin(bid_key_row["opt"]) else: prev_opt_arr = [] prev_opt_arr.append(9) mod_opt = self.pow_sum(list(set(prev_opt_arr))) _bid_key_dataset = { "key": {"bidid": bidid}, "table": "i2.bid_key", "value": {"basic": bid_key_dataset['basic'], "opt": mod_opt}, "type": "updateonly", "orderby": "", "del_col": [], } print("i2.bid_key basic :", bid_key_dataset['basic']) self.dbconn_BI.Upsert_table(_bid_key_dataset, "EXEC") else: if bid_key_dataset['basic'] is not None and bid_key_dataset['basic'] != '' and int(bid_key_row["basic"]) != int(bid_key_dataset['basic']): code_pattern = [ {"pattern": {"P1": "con", }, "value": "0031"}, {"pattern": {"P1": "ser", }, "value": "0032"}, {"pattern": {"P1": "pur", }, "value": "0033"}, ] notify_code=self.mapping_pattern_value(code_pattern, syscollect['bidtype'], '') #기초금액이 입력되있는데, 변경된경우 _notify_dataset = { "key": {"bidid": bidid, "code": notify_code}, "table": "i2.bid_notify", "value": { "case": "Notify", "code": notify_code, "bidid": bidid, "note": str(bid_key_row["basic"]) + "|" + str(bid_key_dataset['basic']), "confirm": "N", "uptime": "NOW()", }, "type": "update", "orderby": "", "del_col": [], } print("i2.notify basic :", bid_key_dataset['basic']) #잠ㅅㅣ빼놈 self.dbconn_BI.Upsert_table(_notify_dataset, "EXEC") ck_premium_MOD = self.extract_diff(_diff["MOD"], "premiumList", "NEW") #premiumList _diff_premium_MOD = _diff["ADD"] if len(ck_premium_MOD) == 0 else _diff["MOD"] # 첫입력일경우 _diff["ADD"] 데이터을 입력 premium_dataset = self.extract_diff(_diff_premium_MOD, "premiumList", "NEW") # _diff 에서 해당 테이블 데이터만 추출 if len(premium_dataset) > 0: #A값 변경시 print("여기") print(premium_dataset) if "cost1" in premium_dataset and premium_dataset['cost1'] is not None and premium_dataset['cost1'] != '' or \ "cost2" in premium_dataset and premium_dataset['cost2'] is not None and premium_dataset['cost2'] != '' or \ "cost3" in premium_dataset and premium_dataset['cost3'] is not None and premium_dataset['cost3'] != '' or \ "cost4" in premium_dataset and premium_dataset['cost4'] is not None and premium_dataset['cost4'] != '' or \ "cost5" in premium_dataset and premium_dataset['cost5'] is not None and premium_dataset['cost5'] != '' or \ "cost6" in premium_dataset and premium_dataset['cost6'] is not None and premium_dataset['cost6'] != '' or \ "cost7" in premium_dataset and premium_dataset['cost7'] is not None and premium_dataset['cost7'] != '' or \ "cost8" in premium_dataset and premium_dataset['cost8'] is not None and premium_dataset['cost8'] != '' or \ "cost_total" in premium_dataset and premium_dataset['cost_total'] is not None and premium_dataset['cost_total'] != '' or \ "direct_labor_cost" in premium_dataset and premium_dataset['direct_labor_cost'] is not None and premium_dataset['direct_labor_cost'] != '': trigger_flag = 'Y' premium_dataset['bidid'] = bidid premium_dataset['writedt'] = "NOW()" g2b_premium_total_cost_query = "SELECT `cost_total` FROM `premiumList` WHERE bidid = '{bidid}' limit 1".format(bidid=bidid) # 기존 입력된 데이터 가져오기 g2b_before_premium_total_cost = self.dbconn_BI.sql_exec(g2b_premium_total_cost_query, "S_one") _diff_premium_dataset = { "key": {"bidid": bidid}, "table": "i2.premiumList", "value": premium_dataset, "type": "update", "orderby": "", "del_col": ['writedt'], } print("i2.premiumList :", premium_dataset) self.dbconn_BI.Upsert_table(_diff_premium_dataset, "EXEC") #총값이 있을경우에만 Y로 업데이트 if syscollect["bidtype"] in ["con"] and premium_dataset['cost_total'] is not None and premium_dataset['cost_total'] != '': _diff_state_a_dataset = { "key": {"bidid": bidid}, "table": "i2.bid_key", "value": {"state_a": "Y"}, "type": "updateonly", "orderby": "", "del_col": [], } print("i2.bid_key state_a : Y") self.dbconn_BI.Upsert_table(_diff_state_a_dataset, "EXEC") g2b_after_premium_total_cost = self.dbconn_BI.sql_exec(g2b_premium_total_cost_query, "S_one") # 3. G2B A값(premiumList) 변경시 공지사항 등록 # 비포, 애프터값이 있고 서로 다르면 공지 Etl.change_cost_notice_memo(self, g2b_before_premium_total_cost, g2b_after_premium_total_cost, bidid, "cost_a", whereis) ck_const_cost_MOD = self.extract_diff(_diff["MOD"], "bid_const_cost_list", "NEW") # bid_const_cost_list _diff_const_cost_MOD = _diff["ADD"] if len(ck_const_cost_MOD) == 0 else _diff["MOD"] # 첫입력일경우 _diff["ADD"] 데이터을 입력 const_cost_dataset = self.extract_diff(_diff_const_cost_MOD, "bid_const_cost_list", "NEW") # _diff 에서 해당 테이블 데이터만 추출 if len(const_cost_dataset) > 0: # 순공사원가 변경시 trigger_flag = 'Y' bid_key_query = "SELECT * FROM bid_key WHERE bidid = '{bidid}' ORDER BY writedt LIMIT 1".format(bidid=bidid) bid_key_row = self.dbconn_BI.sql_exec(bid_key_query, "DS")[0] g2b_const_cost_query = "SELECT const_cost FROM bid_const_cost_list WHERE bidid ='{bidid}' LIMIT 1".format(bidid=bidid) g2b_before_const_cost = self.dbconn_BI.sql_exec(g2b_const_cost_query, "S_one") self.confirm_const_cost_list_new(bid_key_row, syscollect['bidproc'], const_cost_dataset, bidid) g2b_after_const_cost = self.dbconn_BI.sql_exec(g2b_const_cost_query, "S_one") # 4. G2B 순공사원가(bid_const_cost_list) 변경시 공지사항 등록 # 비포, 애프터값이 있고 서로 다르면 공지 Etl.change_cost_notice_memo(self, g2b_before_const_cost, g2b_after_const_cost, bidid, "const_cost", whereis) ck_bid_notice_memo_MOD = self.extract_diff(_diff["MOD"], "bid_notice_memo", "NEW") # bid_notice_memo _diff_bid_notice_memo_MOD = _diff["ADD"] if len(ck_bid_notice_memo_MOD) == 0 else _diff["MOD"] # 첫입력일경우 _diff["ADD"] 데이터을 입력 bid_notice_memo_dataset = self.extract_diff(_diff_bid_notice_memo_MOD, "bid_notice_memo", "NEW") # _diff 에서 해당 테이블 데이터만 추출 if len(bid_notice_memo_dataset) > 0: # 공지사항이 수집된 경우에만 실행한다. trigger_flag = 'Y' bid_notice_memo_dataset['bidid']=bidid dataset = { "key": {"bidid": bidid}, "table": "i2.bid_notice_memo", "value": bid_notice_memo_dataset, "type": "update", "orderby": "", "del_col": ["writedt"], } print("i2.bid_notice_memo :", dataset) self.dbconn_BI.Upsert_table(dataset, "EXEC") # ====================================================== # 추후에 통합하여 트리거로 대체후 제거해야함. # ===================================================== if trigger_flag == 'Y': if syscollect["bidtype"] in ["pur"]: self.trigger_insert(bidid, "log_bid_trigger_pur") else: self.trigger_insert(bidid, "log_bid_trigger_y") # ====================================================== # 추후에 통합하여 트리거로 대체후 제거해야함. # ====================================================== # ============================================================================================================= # 조달청 낙찰공지사항 업데이트 if syscollect["dcode"] in ["G2B_R"]: print("=================>조달청 낙찰공지사항 업데이트 start") print("_diff :", _diff) if "bidtype" in syscollect: ck_MOD = self.extract_diff(_diff["MOD"], "modify_nbbs", "NEW")# 이미 입력되어 있는 경우 수정사항이 있는지 확인. => 입력되지 않은 경우 {} 값을 리턴, _diff_modify_nbbs = _diff["ADD"] if len(ck_MOD) == 0 else _diff["MOD"]# 첫입력일경우 _diff["ADD"] 데이터을 입력 modify_nbbs_dataset = self.extract_diff(_diff_modify_nbbs, "modify_nbbs", "NEW")# _diff 에서 해당 테이블 데이터만 추출 print("modify_nbbs_dataset :", modify_nbbs_dataset) # if len(new["modify_nbbs"]) > 4:#공지사항이 수집된 경우에만 실행한다. if len(modify_nbbs_dataset) > 0: # 공지사항이 수집된 경우에만 실행한다. dataset = { "key": {"bidid": bidid}, "table": "i2.modify_nbbs", "value": modify_nbbs_dataset, "type": "update", "orderby": "", "del_col": ["writedt"], } print("i2.modify_nbbs :", dataset) self.dbconn_BI.Upsert_table(dataset, "TEST") # self.dbconn_BI.Upsert_table(dataset, "EXEC") # ====================================================== # 추후에 통합하여 트리거로 대체후 제거해야함. # ====================================================== # if syscollect["bidtype"] in ["pur"]: # self.trigger_insert(bidid, "log_bid_trigger_pur") # else: # self.trigger_insert(bidid, "log_bid_trigger_y") # ====================================================== # 추후에 통합하여 트리거로 대체후 제거해야함. # ====================================================== #============================================================================================================= #참가자격서류 변경 확인 allow_bidcomment if whereis in allow_bidcomment: #whereis 값이 리스트에 허용된 발주처만 적용 if "bid_content|bidcomment" in _diff["MOD"]:# 변경된 값에 기초금액이 있는지 확인 bidcomment = _diff["MOD"]["bid_content|bidcomment"]["NEW"].strip() # 새로 수집된 bidcomment query = "SELECT `bidcomment`,`bidid` FROM bid_content WHERE bidid = '{bidid}' limit 1".format(bidid=bidid) row = self.dbconn_BI.sql_exec(query, "DS") if bidcomment != '': if len(row) > 0: if row[0]["bidcomment"] is None or row[0]["bidcomment"].strip() == '' or row[0]["bidcomment"].strip() == '* 공고 원문을 참조하시기 바랍니다. *' : #기존 입력된 bidcomment가 없을떄 _data = {} _data["bidid"] = bidid _data["whereis"] = whereis _data["syscode"] = syscollect['dcode'] _data["notinum"] = syscollect['dkey'] if syscollect['dkey_ext']: _data["notinum_ex"] = syscollect['dkey_ext'] _data["tableName"] = 'bid_content' _data["fieldName"] = 'bidcomment' _data["state"] = 'N' _data["writedt"] = "NOW()" #self.dbconn_BI.Insert_table(_data, 'modification_request_list') print("===========================================================================") print("=====================change_detect_DB===>end===============================") ####################################################################### ########################순공사 원가 ##################################### ####################################################################### # 낙찰제외금액 분류함수 def confirm_const_cost_list_new(self, bid_key_arr, proc, const_cost_arr, bidid): print("여안탐?") iu_log_query = "" if proc == 'S' or proc == 'F': self.upsert_const_cost_nbid(const_cost_arr, bidid) else: print("입찰") presum = 0 if 'presum' in bid_key_arr and 'basic' in bid_key_arr: if bid_key_arr['presum'] is not None and bid_key_arr['presum'] != '': presum = bid_key_arr['presum'] if (bid_key_arr['presum'] is None or bid_key_arr['presum'] == '') and ( bid_key_arr['basic'] is not None and bid_key_arr['basic'] != ''): try: int_basic = int(bid_key_arr['basic']) except Exception as e: int_basic = 0 presum = int_basic * 0.9 if int(presum) <= 10000000000: self.upsert_const_cost_bid(bid_key_arr, const_cost_arr, bidid) def upsert_const_cost_bid(self, bid_key_arr, const_cost_arr, bidid): print("입찰 순공사 입력") if "material" not in const_cost_arr: const_cost_arr['material'] = None if "labor" not in const_cost_arr: const_cost_arr['labor'] = None if "expense" not in const_cost_arr: const_cost_arr['expense'] = None if "tax" not in const_cost_arr: const_cost_arr['tax'] = None if "const_cost" not in const_cost_arr: const_cost_arr['const_cost'] = None # 국방부 # 소스단쪽에서 따로 처리됨 """ if bid_key_arr['whereis'] == '10': material = 0 labor = 0 expense = 0 tax = 0 if const_cost_arr['material'] is not None: material = const_cost_arr['material'] if const_cost_arr['labor'] is not None: labor = const_cost_arr['labor'] if const_cost_arr['expense'] is not None: expense = const_cost_arr['expense'] if const_cost_arr['tax'] is not None: tax = const_cost_arr['tax'] self_const_cost = material + labor + expense + tax if self_const_cost != 0: const_cost_arr['const_cost'] = material + labor + expense + tax """ if const_cost_arr['material'] is not None or const_cost_arr['labor'] is not None or \ const_cost_arr['expense'] is not None or const_cost_arr['tax'] is not None or \ const_cost_arr['const_cost'] is not None: updsert_data = { "key": {"bidid": bidid}, "table": "bid_const_cost_list", "type": "update", "value": {"bidid": bidid, "material": const_cost_arr['material'], "labor": const_cost_arr['labor'], "expense": const_cost_arr['expense'], "tax": const_cost_arr['tax'], "const_cost": const_cost_arr['const_cost'], "writedt": "NOW()", "updatedt": "NOW()"}, "del_col": ["updatedt", "writedt", "updatedt_nbid"] } self.dbconn_BI.Upsert_table(updsert_data) def upsert_const_cost_nbid(self, const_cost_arr, bidid): if "const_cost_nbid" in const_cost_arr: if const_cost_arr['const_cost_nbid'] is not None and const_cost_arr['const_cost_nbid'] != '': updsert_data = { "key": {"bidid": bidid}, "table": "bid_const_cost_list", "type": "update", "value": {"bidid": bidid, "const_cost_nbid": const_cost_arr['const_cost_nbid'], "updatedt_nbid": "NOW()"}, "del_col": ["updatedt", "writedt", "updatedt_nbid"] } self.dbconn_BI.Upsert_table(updsert_data) print("@") # diff 딕셔너리에서 특정 테이블 정보만 추출하여 테이블컬럼을 키로 가지는 딕셔너리 반환 # _type 는 NEW, OLD 를 구분 def extract_diff(self, _diff, _table, _type="NEW"): RTN = {} # print("extract_diff _diff :", _diff) for key, val in _diff.items(): tmp = key.split("|") if tmp[0] == _table: RTN[tmp[1]] = val[_type] return RTN def trigger_insert(self, bidid, tablenm): qry = "INSERT INTO {tablenm} SET `bidid`='{bidid}', `uptime`=NOW()".format(bidid=bidid, tablenm=tablenm) self.dbconn_BI.sql_exec(qry, "I") def change_detect(self, old, new, syscollect): # old : 이전수집데이터(json str), new : 새수집데이터(json str), syscollect(dic) try: # ============================================== # 데이터 체크 및 업데이트 # ============================================== # 52 -> 기존 제외조건, 07 -> dkey_ext 에 텍스트가 들어가고 있어서 제외시킴 pass_whereis= ["52", "07", "05", "53", "03"] err_diff_flag = 'Y' if syscollect["dcode"] in ["G2B_R"]: logging.info("pass - 나라장터 낙찰건 - G2B_R")#나라장터 낙찰건은 일단빼논다. 나중에 일괄작업하여 입낙찰구분필요 elif syscollect["dcode"] in ["ERC"]: logging.info("pass - 나라장터 낙찰건 - ERC")#나라장터 낙찰건은 일단빼논다. 나중에 일괄작업하여 입낙찰구분필요 elif syscollect["dcode"] in ["ERS"]: logging.info("pass - 나라장터 낙찰건 - ERS")#나라장터 낙찰건은 일단빼논다. 나중에 일괄작업하여 입낙찰구분필요 elif syscollect["dcode"] in ["ERI"]: logging.info("pass - 나라장터 낙찰건 - ERI")#나라장터 낙찰건은 일단빼논다. 나중에 일괄작업하여 입낙찰구분필요 else: # 국방부데이터 데이터가 잘못 들어가는 현상이 있어 예외처리, 하면서 타 발주처도 같이 처리 # 오류있을때 로그에 쌓아준다. new_data = new if type(new) == dict else json.loads(new, strict=False) old_data = old if type(old) == dict else json.loads(old, strict=False) if new_data['bid_key']['whereis'] in pass_whereis: # print("pass52") pass else: if new_data['bid_key']['notinum_ex'] is None: new_data['bid_key']['notinum_ex'] = '' if old_data['bid_key']['notinum'] != new_data['bid_key']['notinum']: err_diff_flag = 'N' # print("잘못된 데이터") if syscollect["dkey_ext"] is not None and syscollect["dkey_ext"] != '': if old_data['bid_key']['notinum_ex'] != new_data['bid_key']['notinum_ex']: err_diff_flag = 'N' # print("잘못된 데이터2") if err_diff_flag == 'Y': # 국방부는 공고번호에 조달청공고번호까지 넣어준다. bcc_dkey = str(syscollect["dkey"]) + "|" + str(syscollect["dkey_ext"]) if syscollect["dcode"] == "D2B_B" else syscollect["dkey"] #bcc_dkey = syscollect["dkey"] #복수공고 공고번호 만들어주기 if syscollect["dcode"] == "G2B_B" and syscollect["dkey_ext"] is not None and syscollect["dkey_ext"] != "": bcc_dkey = str(syscollect["dkey"]) + "|" + str(syscollect["dkey_ext"]) query = "SELECT * FROM bid_change_check WHERE notinum = '{notinum}' AND syscode = '{syscode}' limit 1".format(notinum=bcc_dkey, syscode=syscollect["dcode"]) row = self.dbconn_BI.sql_exec(query, "DS") _diff = self.diff_array(old, new) RTN = _diff if len(row) > 0: # 비교 제외 데이터 삭제 try: #예외처리(불필요한 컬럼제외)함수 만들면 여기다 적용한다. 김영주 #국방부 if new_data['bid_key']['whereis'] == '10': if "bid_key|constnm" in _diff['MOD']: del _diff['MOD']['bid_key|constnm'] #공고명이 바뀜 del _diff['MOD']['bid_key|writedt'] except Exception as e: print("change_detect : ", e) # 변경분이 있는경우 if len(_diff['ADD'])+len(_diff['MOD'])+len(_diff['REMOVE']) > 0: print("변경분이 있는경우") print(row[0]) _diff_str = json.dumps(_diff, ensure_ascii=False, default=self.json_default, sort_keys=True) # dic to str update_data = {} update_data['prevdt'] = row[0]["updatedt"] update_data['updatedt'] = 'NOW()' update_data['state'] = 'Y' update_data['diff'] = _diff_str self.dbconn_BI.Update_table(update_data, {'seq': row[0]["seq"]}, 'bid_change_check') bcs_cnt_select_query = "SELECT count(*) as cnt FROM bid_change_source WHERE notinum = '{notinum}' AND syscode = '{syscode}' limit 1".format(notinum=bcc_dkey, syscode=syscollect["dcode"]) bcs_cnt_select = self.dbconn_BI.sql_exec(bcs_cnt_select_query, "DS") # 데이터가 오류가 있는듯, 계속 쌓이는 공고들로인해 조건 걸어둠, 같은공고번호로 3건이상은 안쌓이도록 if bcs_cnt_select[0]["cnt"] < 4: bcs_data = {} bcs_data['notinum'] = bcc_dkey bcs_data['syscode'] = syscollect["dcode"] bcs_data['content'] = new bcs_data['writedt'] = 'NOW()' self.dbconn_BI.Insert_table(bcs_data, 'bid_change_source') #================================================================================================================== # 변경처리 => A값, 순공사원가, 기초금액 비어 있는경우 자동입력 # ================================================================================================================== self.change_detect_DB(_diff, syscollect, new) # ================================================================================================================== # 변경내용 자동수정 (설정한 데이터 변경시 자동으로 공고 수정) # ================================================================================================================== self.change_detect_DB_etc(_diff, syscollect) msg ="[{dcode}] notinum : {notinum}, _diff : {_diff}".format(dcode=syscollect["dcode"], notinum=syscollect["dkey"], _diff=_diff_str.replace("&","_")) #발주처별 팀룸 (2023.10.27 사용하지 않는것으로 판단되어 우선 주석) #self.Util.send_msg(msg, syscollect["dcode"]) #전체 로그 팀룸 (2023.10.27 사용하지 않는것으로 판단되어 우선 주석) #self.Util.send_msg(msg) # ================================================================================================================== else: print("전과 동일") #첫 수집일 경우 else: print("첫 수집일 경우") insert_data = {} insert_data['notinum'] = bcc_dkey insert_data['syscode'] = syscollect["dcode"] insert_data['prevdt'] = 'NOW()' insert_data['updatedt'] = 'NOW()' insert_data['writedt'] = 'NOW()' insert_data['state'] = 'N' self.dbconn_BI.Insert_table(insert_data, 'bid_change_check') bcs_data = {} bcs_data['notinum'] = bcc_dkey bcs_data['syscode'] = syscollect["dcode"] bcs_data['content'] = new bcs_data['writedt'] = 'NOW()' self.dbconn_BI.Insert_table(bcs_data, 'bid_change_source') else: #공고번호 및 공고번호_ex 가 매칭이 안된경우 if syscollect['dkey_ext'] is None or syscollect['dkey_ext'] == '': syscollect['dkey_ext'] = '' _err_diff_data_dataset = { "key": {"whereis": new_data['bid_key']['whereis'], "dkey": syscollect['dkey'], "dkey_ext": syscollect['dkey_ext'], "notinum": new_data['bid_key']['notinum'], "notinum_ex": new_data['bid_key']['notinum_ex'], }, "table": "err_diff_data", "value": {"whereis": new_data['bid_key']['whereis'], "dkey": syscollect['dkey'], "dkey_ext": syscollect['dkey_ext'], "notinum": new_data['bid_key']['notinum'], "notinum_ex":new_data['bid_key']['notinum_ex'], #"crawl_data_view_old": syscollect['crawl_data_view'], #"crawl_data_view_old": new_data, "writedt": "NOW()", }, "type": "insertonly", "orderby": "", "del_col": [], } self.dbconn_BI.Upsert_table(_err_diff_data_dataset, "EXEC") RTN = {} except Exception as ex: RTN = {} print("change_detect Exception : ", ex) change_detect_error = {} change_detect_error['notinum'] = syscollect['dkey'] change_detect_error['notinum_ex'] = syscollect['dkey_ext'] change_detect_error['whereis'] = syscollect['whereis'] change_detect_error['err_code'] = ex change_detect_error['writedt'] = 'NOW()' self.dbconn_BI.Insert_table(change_detect_error, 'change_detect_error') return RTN def change_detect_DB_etc(self, _diff, syscollect): print("[change_detect_DB_etc] 들어옴") diff_arr = { # 91전자통신연구원 패턴 '91' : { 'bid_content|bidcomment': { 'tableName': 'bid_content', 'fieldName': 'bidcomment', 'action': "update", #update : 데이터 업데이트, modify : 정정데이터 쌓기 'field_empty' : 'N', #저장데이터 빈값 허용 여부 Y: 빈값체크x N: 빈값체크 'exlusion_pattern': [ #제외조건 데이터가 해당값일 경우엔 무시하고 저장 '^\* 공고 원문을 참조하시기 바랍니다. \*$', '^$', ], }, 'bid_key|constnm': { 'tableName': 'bid_key', 'fieldName': 'constnm', 'action': "modify", # update : 데이터 업데이트, modify : 정정데이터 쌓기 'field_empty': 'N', # 저장데이터 빈값 허용 여부 Y: 빈값체크x N: 빈값체크 'exlusion_pattern': [ # 제외조건 ], }, }, #08 도로공사 패턴 '08': { 'bid_key|constnm': { 'tableName': 'bid_key', 'fieldName': 'constnm', 'action': "modify", # update : 데이터 업데이트, modify : 정정데이터 쌓기 'field_empty': 'N', # 저장데이터 빈값 허용 여부 Y: 빈값체크x N: 빈값체크 'exlusion_pattern': [ # 제외조건 ], }, }, } whereis = syscollect['whereis'] bidid = syscollect["bidid"] if whereis in diff_arr: for key, value in enumerate(diff_arr[whereis]): if value in _diff["MOD"]: # 변경된 값에 키데이터가 있을때 updtate_data = _diff["MOD"][value]["NEW"].strip() if diff_arr[whereis][value]['field_empty'] == 'Y' or updtate_data != '': insert_yn = 'N' #저장은 기본적으로 하지 않는다. #제외조건이 있을경우 if len(diff_arr[whereis][value]['exlusion_pattern']) > 0: query = "SELECT {fieldName}, bidid FROM {tableName} WHERE bidid='{bidid}' limit 1 ".format(fieldName=diff_arr[whereis][value]['fieldName'], tableName=diff_arr[whereis][value]['tableName'], bidid=bidid) row = self.dbconn_BI.sql_exec(query, "DS") print(diff_arr[whereis][value]['exlusion_pattern']) #db데이터 확인 if len(row) > 0: # 저장된 데이터값 없으면 패스 for value2 in diff_arr[whereis][value]['exlusion_pattern']: field_tmp = row[0]["{fieldName}".format(fieldName=diff_arr[whereis][value]['fieldName'])] print("테이블값 : "+field_tmp) field_tmp = '' if field_tmp is None else field_tmp # table None 일때 공백처리 print("조건값 : "+value2) if len(re.findall(value2, field_tmp)) > 0: #기존데이터가 exlusion_pattern에 걸리는 패턴일경우 저장하지 않는다. print("저장") #테이블 데이터와 저장데이터 조건이 맞는경우 저장한다. insert_yn = 'Y' else: #제외조건이 없을경우 저장 insert_yn = 'Y' if insert_yn == 'Y': if diff_arr[whereis][value]['action'] == 'update': self.insert_modification_request_list(syscollect, diff_arr[whereis][value]['tableName'], diff_arr[whereis][value]['fieldName']) elif diff_arr[whereis][value]['action'] == 'modify': self.self_mod_module(syscollect, updtate_data) print("[change_detect_DB_etc] 나감") def insert_modification_request_list(self, data, tableName, fieldName): print("[insert_modification_request_list] 들어옴") _data = {} _data["bidid"] = data['bidid'] _data["whereis"] = data['whereis'] _data["syscode"] = data['dcode'] _data["notinum"] = data['dkey'] if data['dkey_ext']: _data["notinum_ex"] = data['dkey_ext'] else: _data["notinum_ex"] = '' _data["tableName"] = tableName _data["fieldName"] = fieldName _data["state"] = 'N' _data["writedt"] = "NOW()" self.dbconn_BI.Insert_table(_data, 'modification_request_list') def self_mod_module(self, syscollect,updtate_data): self.mod_table_insert(syscollect, updtate_data) self.mod_syscollect_insert(syscollect, updtate_data) def mod_table_insert(self, syscollect,updtate_data): # 김영주 whereis = syscollect['whereis'] dcode = syscollect["dcode"] notinum = syscollect["dkey"] notinum_ex = syscollect["dkey_ext"] if syscollect["dkey_ext"] is not None else None notinum_ex_q='' if notinum_ex is not None: notinum_ex_q="and dkey_ext='{dkey_ext}'".format(dkey_ext=notinum_ex) # mod_data_table 쌓기 tmp_sql = "SELECT * FROM mod_data_table WHERE dcode='{dcode}' and dkey='{dkey}' {notinum_ex_q} and mod_data='{mod_data}' and whereis='{whereis}' order by uptime desc limit 1" \ .format(dcode=dcode, dkey=notinum, mod_data=updtate_data, whereis=whereis, notinum_ex_q=notinum_ex_q) tmp_row = self.dbconn_BI.sql_exec(tmp_sql, "DS") if len(tmp_row) > 0: print("pass") else: # sys_collect 정보가져오기 sys_sql = "SELECT stats,rebid_no,seq FROM sys_collect WHERE dcode='{dcode}' and dkey='{dkey}' order by uptime desc limit 1".format( dcode=dcode, dkey=notinum) sys_row = self.dbconn_BI.sql_exec(sys_sql, "DS") # 기본 정정차수는 1 mod_chasu = 1 if len(tmp_row) > 0: # 이전 정정차수가 mod_data_table에 있을경우 정정차수를 업데이트 해준다 mod_chasu = int(tmp_row[0]['mod_chasu']) + 1 insert_data_arr = {} insert_data_arr['dkey'] = notinum insert_data_arr['dkey_ext'] = notinum_ex insert_data_arr['dcode'] = dcode insert_data_arr['stats'] = sys_row[0]['stats'] insert_data_arr['rebid_no'] = sys_row[0]['rebid_no'] insert_data_arr['mod_chasu'] = mod_chasu insert_data_arr['mod_text'] = updtate_data #insert_data_arr['prevcode'] = row['prevcode'] --사용x #insert_data_arr['updatecode'] = row['updatecode'] --사용x insert_data_arr['whereis'] = whereis self.dbconn_BI.Insert_table(insert_data_arr, "mod_data_table") update_data_arr = {} update_data_arr['state'] = 'D' update_data_arr['updatedt'] = "NOW()" self.dbconn_BI.Update_table(update_data_arr, {'seq': tmp_row['seq']}, 'bid_change_check') def mod_syscollect_insert(self, syscollect, updtate_data): whereis = syscollect['whereis'] dcode = syscollect["dcode"] notinum = syscollect["dkey"] notinum_ex = syscollect["dkey_ext"] if syscollect["dkey_ext"] is not None else None notinum_ex_q = '' if notinum_ex is not None: notinum_ex_q = 'and dkey_ext={dkey_ext}'.format(dkey_ext=notinum_ex) mod_sql = "SELECT * FROM mod_data_table WHERE dcode='{dcode}' and dkey='{dkey}' {notinum_ex_q} and mod_data='{mod_data}' whereis='{whereis}' and state ='N' "\ .format(dcode=dcode, dkey=notinum, mod_data=updtate_data, whereis=whereis, notinum_ex_q=notinum_ex_q) mod_row = self.dbconn_BI.sql_exec(mod_sql, "DS") if len(mod_row) > 0: sys_sql = "SELECT * FROM sys_collect WHERE dcode='{dcode}' and dkey='{dkey}' and stats='{stats}' and rebid_no ='{rebid_no}' order by uptime desc limit 1" \ .format(dcode=mod_row['dcode'], dkey=mod_row['dkey'], stats=mod_row['stats'], rebid_no=mod_row['rebid_no']) sys_res = self.dbconn_BI.sql_exec(sys_sql, "DS") insert_arr = {} insert_arr['dkey'] = mod_row['dkey'] insert_arr['dkey_ext'] = mod_row['dkey_ext'] insert_arr['dcode'] = mod_row['dcode'] insert_arr['stats'] = "자체정정" + mod_row['mod_chasu'] + "차" insert_arr['rebid_no'] = mod_row['rebid_no'] insert_arr['link'] = sys_res[0]['link'] insert_arr['ext_info1'] = sys_res[0]['ext_info1'] insert_arr['ext_info2'] = sys_res[0]['ext_info2'] insert_arr['proc'] = 'new' if dcode == 'KNOCBA': insert_arr['link_post'] = sys_res[0]['link_post'] self.dbconn_BI.Insert_table(insert_arr, "sys_collect") update_data_arr = {} update_data_arr['state'] = 'I' update_data_arr['updatedt'] = "NOW()" self.dbconn_BI.Update_table(update_data_arr, {'seq': mod_row['seq']}, 'bid_change_check') # ============================================================ # "CREATE TABLE `modification_request_list` ( # `seq` int(6) NOT NULL AUTO_INCREMENT, # `bidid` char(24) NOT NULL COMMENT '입찰번호(일련번호-차수-재입찰번호-분류번호)', # `whereis` char(2) DEFAULT NULL COMMENT '공고게시기관코드', # `syscode` char(12) DEFAULT NULL COMMENT '수집시스템코드', # `notinum` char(32) NOT NULL COMMENT '공고번호', # `notinum_ex` char(32) NOT NULL, # `tableName` varchar(30) NOT NULL COMMENT '테이블이름', # `fieldName` varchar(30) NOT NULL COMMENT '컬럼이름', # `writedt` datetime DEFAULT NULL COMMENT '요청등록일', # `updatedt` datetime DEFAULT NULL COMMENT '처리시간', # `state` char(1) DEFAULT NULL COMMENT '처리유무', # PRIMARY KEY (`seq`) # ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=euckr COMMENT='특정 데이터 업데이트 요청 리스트'" \ # 입력페이지에서 해당 테이블에 업데이트할 정보를 쌓는다. # 테이블이름과, 컬럼이름을 매칭해서 데이터를 업데이트 한다. # ============================================================ def modification_request_list(self, new, syscollect): print("[modification_request_list] start") tableName = syscollect["tableName"] fieldName = syscollect["fieldName"] seq = syscollect["modification_seq"] bidid = syscollect["bidid"] new_parser = new if type(new) == dict else json.loads(new, strict=False) self.dbconn_BI.Update_table({fieldName: new_parser[tableName][fieldName]}, {'bidid': bidid}, tableName) self.dbconn_BI.Update_table({'state': 'Y', 'updatedt': 'NOW()'}, {'seq': seq}, 'modification_request_list') print("[modification_request_list] end") def blank_None(self, _str): if _str == "": return None else: return _str def clean_page_source(self, _html): pattern = [ ["\r",""], ["\t",""], ["\n",""], ["\"", ""], ["\u25e6", ""], ["\xa9", ""], ["\xa0", ""], ["\uf0e8", ""], ["\uff62", ""], ["\uff63", ""], ["\ufeff", ""], ["\u200b", ""], ["\u2013", ""], ["\u2024", ""], ["\u2027",""], ["\u2219", ""], ["\u25ef", ""], ["\u274d", ""], ["\u2782", ""], ["\u2981", ""], ["\u3007", ""], ["\uff63", ""], ["\u25fc", ""], ["\u2003", ""], ["\u231c", ""], ["\u0223", ""], ["\u2023", ""], ["\u0228", ""], ["\u2011", ""], ["\u0387", ""], ["\u2781", ""], ["\U000f02ea", ""], ["\u0278", ""], ["\u2022", ""], ["\u22c5",""], ["\u022f", ""], ["\u24fd", ""], ["\u302e", ""], ["\u0368", ""], ["\u301c", "~"], ["\u02d1", "~"], ["\u21e8", "->"], ["\u25a2", ""], ["\u231f", ""], ["\xb5", ""], ["\u2780", ""], ["\u119e", ""], ["\u20a9", ""], ["\u25cc", ""], ["\uf022", ""], ["\uf09e", ""], ["\u0301", ""], ["\uff65", ""], ["\u1168", ""], ["\u1163", ""], ["\u1166", ""], ["\u2215", ""], ["\u231c", ""], ["\U000f02ef", ""], ["\uf0a0", ""], ["\u2014", ""], ["\u2205", "Ø"], ["\u301a", ""], ["\u301b", ""], ["\uf028", ""], ["\u30fb", ""], ["\uf076", ""], ["\u25aa", ""], ["\u1104", ""], ["\u2776", ""], ["\u2777", ""], ["\u2613", ""], ["\u2000", ""], ["\u25b8", ""], ["\u2219", ""], ["\u2012", ""], ["\u233d", ""], ["\u8f66", ""], ["\u65f6", ""], ["\u95f4", ""], ["\u27f6", ""], ["\uf0a6", ""], ["\u21db", ""], ["\u2783", ""], ["\u2784", ""], ["\u2785", ""], ["\u2010", "-"], ["\U0001d635", ""], ["\u206d", ""], ["\u279f", ""], ["\u2d41", ""], ["\ufffd", ""], ["\u278a", ""], ["\u278b", ""], ["\u278c", ""], ["\u27f9", ""], ["\u2035", ""], ["\u02dc", ""], ["\u2053", ""], ["\u301e", ""], ] return self.Util.clean_str(_html, pattern) def enc_UTF8(self, _str, encoding='utf-8'): return urllib.parse.unquote(_str, encoding=encoding) # 기관 코드 가져오기 def getOrg(self, tblNm, orgNm, data_code_org_i, data_code_org_y, data_order_name): # order_code 테이블에서 발주처명 변환이 필요한지 확인한다. orgTmp = orgNm if orgTmp: # order_code_q = "SELECT * FROM order_name WHERE REPLACE(`before`, ' ', '') = '{orgNm}'".format(orgNm=orgNm.replace(" ", "")) order_code_row = data_order_name[data_order_name['before'].str.replace(' ', '', regex=False) == orgNm.replace(" ", "")] order_code_row = order_code_row.to_dict(orient='records') # order_code_row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, order_code_q) if order_code_row and len(order_code_row) > 0 and order_code_row[0].get('after'): orgTmp = order_code_row[0]['after'] # print("order_name 추출 O") # else: # print("order_name 추출 X") # print("orgTmp >>> ", orgTmp) if tblNm == "code_org_y": # query ="select order_code, order_name from code_org_y where replace(order_name, ' ', '') = replace('{orgNm}', ' ', '')".format(orgNm=orgTmp) query_result = data_code_org_y[data_code_org_y['order_name'].str.replace(' ', '', regex=False) == orgTmp.replace(" ", "")] query_result = query_result[['order_code', 'order_name']] elif tblNm == "code_org_i": # query = "select org_Scode as order_code, result_name as order_name from code_org_i where org_name = '{orgNm}'".format(orgNm=orgTmp) query_result = data_code_org_i[data_code_org_i['org_name'].str.replace(' ', '', regex=False) == orgTmp.replace(" ", "")] query_result = query_result[['org_Scode', 'result_name']] query_result = query_result.rename(columns={'org_Scode': 'order_code', 'result_name': 'order_name'}) try: # row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) row = query_result.to_dict(orient='records') if len(row) > 0: return row[0] else: return {"order_code": None, "order_name": orgNm} except: return {"order_code": None, "order_name": None} #code_etc 가져오기 def getCodeEtc(self, kind, data_code_etc): if data_code_etc.empty: return None # query = "select * from code_etc where kind='{kind}'".format(kind=kind) query_result = data_code_etc[data_code_etc['kind'] == kind] if kind == "succls": #제한적 최저가가 먼저 체크되도록 # query = "select * from code_etc where kind='{kind}' ORDER BY CASE WHEN `code` = '03' THEN 0 ELSE 1 END, `code`;".format(kind=kind) # 1. 'kind'로 먼저 필터링 filtered_df = data_code_etc[data_code_etc['kind'] == kind].copy() # 원본 수정을 피하기 위해 .copy() 사용 # 2. 정렬 우선순위를 위한 임시 컬럼 생성 # 'code'가 '03'이면 0, 아니면 1을 할당 filtered_df['sort_priority'] = filtered_df['code'].apply(lambda x: 0 if x == '03' else 1) # 3. 'sort_priority'로 먼저 정렬하고, 그 다음 'code'로 정렬 # 두 컬럼 모두 오름차순으로 정렬 (ascending=True가 기본값) query_result = filtered_df.sort_values(by=['sort_priority', 'code']) # 4. 임시로 사용한 'sort_priority' 컬럼 제거 query_result = query_result.drop(columns=['sort_priority']) # row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) row = query_result.to_dict(orient='records') if len(row) > 0: return row else: return None # 종목코드 가져오기 def G2BPartCode(self): query = "SELECT g2b_code, i2_code FROM code_item_match WHERE g2b_code NOT IN('1459','1460') AND i2_code IS NOT NULL" row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) if len(row) > 0: rtn = {} for v in row: rtn[v["g2b_code"]] = v["i2_code"] return rtn else: return None # 종목코드 가져오기 def KEPCOPartCode(self): query = "SELECT bi_code, k_code FROM code_kepco WHERE state ='Y' AND bi_code IS NOT NULL" row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) if len(row) > 0: rtn = {} for v in row: rtn[v["k_code"]] = v["bi_code"] return rtn else: return None # 한전 종목코드 체크 후 없는 데이터일 경우 데이터삽입 def insertKEPCOPartCode(self,k_code,k_name): query = "SELECT bi_code, k_code FROM code_kepco WHERE k_code='{k_code}' ".format(k_code=k_code) row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) result = None if len(row) <= 0 : insert_query = "INSERT INTO code_kepco SET k_code = '{k_code}', k_name = '{k_name}', state = 'N', writedt = NOW() ".format(k_code=k_code, k_name=k_name) result = self.dbconn_BI.process_sql(DB_CONN_ID, insert_query) return result # 물품분류번호로 면허찍어지는 코드 가져오기 def G2BpurCodeChange(self, data_g2b_pur_code_change): # query = "SELECT *,LENGTH(g2b_code) AS codelength FROM g2b_pur_code_change ORDER BY codelength ASC" if data_g2b_pur_code_change.empty: return None data_g2b_pur_code_change_copy = data_g2b_pur_code_change.copy() data_g2b_pur_code_change_copy['codelength'] = data_g2b_pur_code_change_copy['g2b_code'].astype(str).str.len() query_result = data_g2b_pur_code_change_copy.sort_values(by='codelength', ascending=True) row = query_result.to_dict(orient='records') # row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) if len(row) > 0: rtn = {} for v in row: rtn[v["g2b_code"]] = v["change_code"] return rtn else: return None # 물품분류번호로 면허찍어지는 코드 가져오기 def G2BpurCodeLikeChange(self, gcode, data_g2b_pur_code_change): tmpcode = gcode[0:2] # 아래 항목일땐 2자리로 검색하지 않는다. likecode = gcode if tmpcode == '39' and gcode != '3911260201' and gcode != '3911260301' and gcode != '3911260302' and gcode != '3911260303' and gcode != '3911260401' and gcode != '3913170609' and gcode != '3913170610': rtn = {} rtn["39"] = 'C007' return rtn else: # query = "SELECT *,LENGTH(g2b_code) AS codelength FROM g2b_pur_code_change WHERE g2b_code Like '{likecode}%' ORDER BY codelength ASC".format(likecode=likecode) data_g2b_pur_code_change_copy = data_g2b_pur_code_change.copy() data_g2b_pur_code_change = data_g2b_pur_code_change_copy[data_g2b_pur_code_change_copy['g2b_code'].str.contains(likecode)] if data_g2b_pur_code_change.empty: return None data_g2b_pur_code_change_copy['codelength'] = data_g2b_pur_code_change_copy['g2b_code'].astype(str).str.len() query_result = data_g2b_pur_code_change_copy.sort_values(by='codelength', ascending=True) row = query_result.to_dict(orient='records') # row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) # print(row) if len(row) > 0: rtn = {} for v in row: rtn[v["g2b_code"]] = v["change_code"] return rtn else: return None def pluscode_set(self, concode, sercode, pluscode): rt = {} g2b_code = [] prev_code = [concode, sercode] for k, v in pluscode.items(): if len(re.findall("C", v)) > 0: if concode is None: concode = v elif len(re.findall(v, concode)) < 0: concode = "{code}|{pluscode}".format(code=concode, pluscode=v) if len(re.findall("S", v)) > 0: if sercode is None: sercode = v elif len(re.findall(v, sercode)) < 0: sercode = "{code}|{pluscode}".format(code=sercode, pluscode=v) g2b_code.append(k) change_code = [concode, sercode] g2b_pur_code_change_log = {} g2b_pur_code_change_log['g2b_code'] = "|".join(list(filter(lambda x: x is not None, g2b_code))) g2b_pur_code_change_log['prev_code'] = "|".join(list(filter(lambda x: x is not None, prev_code))) g2b_pur_code_change_log['change_code'] = "|".join(list(filter(lambda x: x is not None, change_code))) rt['concode'] = concode rt['sercode'] = sercode rt['g2b_pur_code_change_log'] = g2b_pur_code_change_log return rt # def G2BNmToYERAMCode(self, nm, data_code_item_match): # # query = "SELECT g2b_code, i2_code, g2b_code_nm FROM code_item_match WHERE REPLACE(REPLACE(g2b_code_nm,'.',''),'·','') = REPLACE(REPLACE('{nm}','.',''),'·','') ".format(nm=nm) # query = "SELECT g2b_code, i2_code, g2b_code_nm FROM code_item_match WHERE REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(g2b_code_nm,' ',''),'.',''),'·',''),',',''),'ㆍ', '') = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE('{nm}',' ',''),'.',''),'·',''),',',''),'ㆍ', ''),'기계설비가스공사업','기계가스설비공사업')".format(nm=nm) # row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) # rtn = row[0] if len(row) > 0 else {"g2b_code": "", "i2_code": "", "g2b_code_nm": "", } # return rtn def G2BNmToYERAMCode(self, nm: str, data_code_item_match: pd.DataFrame): """ 입력된 이름(nm)과 DataFrame(data_code_item_match)의 'g2b_code_nm'을 동일한 방식으로 전처리한 후, 정확히 일치하는 행을 찾아 관련 정보를 반환합니다. :param nm: 비교할 이름 문자열 :param data_code_item_match: 'g2b_code_nm', 'g2b_code', 'i2_code' 컬럼을 포함하는 DataFrame :return: 일치하는 행의 정보를 담은 딕셔너리 또는 기본값 딕셔너리 """ if not isinstance(nm, str): # logging.warning(f"입력값 nm이 문자열이 아닙니다: {type(nm)}. 빈 문자열로 처리합니다.") nm = "" if not isinstance(data_code_item_match, pd.DataFrame) or data_code_item_match.empty: # logging.warning("입력 DataFrame data_code_item_match가 비어있거나 DataFrame이 아닙니다.") return {"g2b_code": "", "i2_code": "", "g2b_code_nm": ""} # 원본 DataFrame 수정을 피하기 위해 복사본 사용 data_code_item_match_copy = data_code_item_match.copy() # 전처리할 문자들 chars_to_remove = [' ', '.', '·', ',', 'ㆍ'] # 특정 문자열 치환 (SQL의 마지막 REPLACE에 해당) specific_replacements = { '기계설비가스공사업': '기계가스설비공사업' # 필요시 다른 치환 규칙 추가 } # 1. DataFrame의 'g2b_code_nm' 컬럼 전처리 if 'g2b_code_nm' not in data_code_item_match_copy.columns: logging.error("'g2b_code_nm' 컬럼이 DataFrame에 존재하지 않습니다.") return {"g2b_code": "", "i2_code": "", "g2b_code_nm": ""} # NaN 값을 빈 문자열로 처리 후 문자열로 변환 processed_g2b_code_nm_col = data_code_item_match_copy['g2b_code_nm'].fillna('').astype(str) for char in chars_to_remove: processed_g2b_code_nm_col = processed_g2b_code_nm_col.str.replace(char, '', regex=False) for old, new in specific_replacements.items(): # 특정 문자열 치환 적용 processed_g2b_code_nm_col = processed_g2b_code_nm_col.str.replace(old, new, regex=False) # 전처리된 컬럼을 DataFrame에 임시로 저장하거나, 바로 비교에 사용 # data_code_item_match_copy['processed_g2b_code_nm'] = processed_g2b_code_nm_col # 2. 입력 문자열 'nm' 전처리 (DataFrame 컬럼과 동일한 방식으로) processed_nm = str(nm) # nm이 숫자로 들어올 경우를 대비해 문자열로 변환 for char in chars_to_remove: processed_nm = processed_nm.replace(char, '') for old, new in specific_replacements.items(): # 특정 문자열 치환 적용 processed_nm = processed_nm.replace(old, new) # logging.debug(f"전처리된 g2b_code_nm 컬럼 (일부): \n{processed_g2b_code_nm_col.head()}") # logging.debug(f"전처리된 입력 nm: '{processed_nm}'") # 3. 전처리된 값들로 정확히 일치하는 행 필터링 (str.contains 대신 == 사용) # data_code_item_match_copy = data_code_item_match_copy[processed_g2b_code_nm_col == processed_nm] # 필터링된 결과를 새로운 변수에 할당하여 원본 data_code_item_match_copy의 참조 문제를 피함 filtered_df = data_code_item_match_copy[processed_g2b_code_nm_col == processed_nm] # 결과가 여러 개일 경우 첫 번째 행을 사용할지, 아니면 다른 로직이 필요한지 결정해야 합니다. # 현재 코드는 to_dict 후 첫 번째 결과를 사용합니다. # 4. 결과를 딕셔너리 리스트로 변환 rows_list = filtered_df.to_dict(orient='records') # 5. 최종 결과 반환 if rows_list: # 일치하는 행이 있는 경우 # SQL 쿼리는 g2b_code, i2_code, g2b_code_nm 컬럼만 선택했으므로, 여기서도 해당 컬럼만 추출 first_match = rows_list[0] rtn = { "g2b_code": first_match.get("g2b_code", ""), "i2_code": first_match.get("i2_code", ""), "g2b_code_nm": first_match.get("g2b_code_nm", "") # 원본 g2b_code_nm 반환 } # logging.info(f"일치하는 데이터 찾음: {rtn}") else: rtn = {"g2b_code": "", "i2_code": "", "g2b_code_nm": ""} # logging.info(f"일치하는 데이터를 찾지 못함. 입력 nm: '{nm}', 전처리된 nm: '{processed_nm}'") return rtn # g2b 종목코드 예람코드로 변환 # code 는 list 형태의 g2b 코드 ['1232','1145',...], # 리턴값은 문자열 dic {'purcode': None, 'concode': 'C011|C013', 'sercode': None} def G2BCodeToYERAMCode(self, code, data_DIC_G2BPartCode): PartCode = data_DIC_G2BPartCode YeramCode = list(filter(lambda x: x is not None, list(map(lambda x: PartCode[x] if x in PartCode else None, code)))) YeramCode = [x for x in YeramCode if x != ''] #코드별 예람 종목추가 적용[ key => g2b code, value => 추가할 예람코드(리스트)로 세팅] add_code = { "0001": ["C003"], "0002": ["C003"], "0032": ["C030"], "0033": ["C030"], "1121": ["S001", "S054"], "1139": ["S031", "S032"], "1177": ["S080", "C053"], "1189": ["S072"], "1401": ["C035", "C078"], "3562": ["S999"], "3585": ["S019"], "3589": ["S037"], "4817": ["S001"], "4949": ["C030"], "4950": ["C030"], "5220": ["S057"], "6117": ["C011", "S999"], "6311": ["S031", "S032"], "6312": ["S031", "S032"], "6313": ["S031", "S032"], "6815": ["C020", "S999"], "4989": ["C021", "C034", "C032"], #B001 #지반조성.포장공사업 [토공|포장|보링.그라우팅] "4990": ["C020"], # B002 #실내건축공사업 [실내건축] "4991": ["C027", "C028"], # B003 #금속창호.지붕건축물조립공사업 [금속창호|지풍판금,건축물] "4992": ["C024", "C022", "C023"], # B004 #도장.습식.방수.석공사업 [도장|습식.방수|석공] "4993": ["C036", "C037"], # B005 # 조경식재.시설물공사업 [조경식재|조경시설물] "4994": ["C029"], # B006 # 철근.콘크리트공사업 [철콘] "4995": ["C025"], # B007 # 구조물해체.비계공사업 [비계구조] "4996": ["C031"], # B008 # 상.하수도설비공사업 [상,하수도] "4997": ["C033"], # B009 # 철도.궤도공사업 [철도,궤도] "4998": ["C039", "C038"], # B010 # 철강구조물공사업 [철강재|강구조물] "4999": ["C035", "C041"], # B011 # 수중.준설공사업 [수중|준설] "6201": ["C042", "C040"], # B012 # 승강기,삭도공사업 [승강기|삭도] "6202": ["C030", "C045"], # B013 # 기계가스설비공사업 [기계설비|가스1종] "6203": ["C046", "C047", "C049", "C050", "C051"], # B013 # 가스난방공사업 [가스2종|가스3종|난방1종|난방2종|난방3종] '토공사': ['C021','B001' ], # ========================= 여기서부터 주력공사 붙는 경우 해당 문구에 맞는 면허 반환될 수 있게 처리. '포장공사': ['C034','B001' ], '보링·그라우팅·파일공사': ['C032', 'B001'], # '실내건축공사': ['C020', 'B002'], # '금속구조물·창호·온실공사': ['C027','B003' ], # '지붕판금·건축물조립공사': ['C028', 'B003'], # '도장공사': ['C024','B004' ], # '습식·방수공사': ['C022','B004' ], # '석공사': ['C023','B004' ], # '조경식재공사': ['C036','B005'], '조경시설물설치공사': ['C037', 'B005'], '철근·콘크리트공사': ['C029', 'B006'], # '구조물해체·비계공사': ['C025', 'B007'], # '상하수도설비공사': ['C031', 'B008'], # '철도·궤도공사': ['C033', 'B009'], '철강구조물공사' : ['C039', 'C038', 'B010'], '수중공사': ['C035', 'B011'], '준설공사': ['C041', 'B011'], '승강기설치공사': ['C042', 'B012'], '삭도설치공사': ['C040', 'B012'], '기계설비공사': ['C030', 'B013'], # '가스시설공사(제1종)': ['C045', 'B013'], '가스시설공사(제2종)': ['C046', 'B014'], '가스시설공사(제3종)': ['C047', 'B014'], '난방공사(제1종)': ['C049', 'B014'], '난방공사(제2종)': ['C050', 'B014'], '난방공사(제3종)': ['C051', 'B014'], '토공사 와 포장공사': ['C021', 'C034', 'B001'], '토공사 와 보링·그라우팅·파일공사': ['C021', 'C032', 'B001'], '포장공사 와 토공사': ['C021', 'C034', 'B001'], '포장공사 와 보링·그라우팅·파일공사': ['C034', 'C032', 'B001'], '보링·그라우팅·파일공사 와 포장공사': ['C034', 'C032', 'B001'], '보링·그라우팅·파일공사 와 토공사': ['C021', 'C032', 'B001'], } AddYeramCode = list(filter(lambda x: x is not None, list(map(lambda x: add_code[x] if x in add_code else None, code)))) if len(AddYeramCode) > 0: AddYeramCodeList = [] list(map(lambda x: list(map(lambda y: AddYeramCodeList.append(y), x)), AddYeramCode)) if len(AddYeramCodeList) > 0: YeramCode = YeramCode + AddYeramCodeList #추가 코드 병합 YeramCode = list(set(YeramCode)) #중복제거 YeramCode.sort() #정렬 dic_partcode = {} dic_partcode['concode'] = "|".join(list(filter(lambda x: x[0] == "C", YeramCode))) if len(list(filter(lambda x: x[0] == "C", YeramCode))) > 0 else None # 공사 dic_partcode['sercode'] = "|".join(list(filter(lambda x: x[0] == "S", YeramCode))) if len(list(filter(lambda x: x[0] == "S", YeramCode))) > 0 else None # 용역 dic_partcode['purcode'] = "|".join(list(filter(lambda x: x[0] == "P", YeramCode))) if len(list(filter(lambda x: x[0] == "P", YeramCode))) > 0 else None # 물품 dic_partcode['big_part'] = "|".join(list(filter(lambda x: x[0] == "B", YeramCode))) if len(list(filter(lambda x: x[0] == "B", YeramCode))) > 0 else None # 물품 return dic_partcode def dicCodeEtc(self, kind, data_code_etc): rtn = [] rows = self.getCodeEtc(kind, data_code_etc) if rows is not None: for row in rows: rtn.append({"pattern": {"P1": row["val"], }, "value": row["code"]}) # 입찰방식 추가조건, 상단엔 DB데이터를 기반으로 세팅하기때문에 따로 넣어준다 if kind == 'bidcls': rtn.append({"pattern": {"P1": '수기', }, "value": '00'}) return rtn # xpath 로 구분한 테이블 시리얼 데이터를 줄단위 key value 데이터로 변환 리턴값 [{key:value,...},{key:value,...},{key:value,...}] def change_serial_to_dic(self, _data, _keys): _len = len(_data) key_len = len(_keys) _tmp = {} RTN = [] for _idx, key in enumerate(_keys): _tmp[key] = _data[_idx:_len:key_len] for _idx, row in enumerate(_tmp[_keys[0]]): list = {} for idx, key in enumerate(_keys): list[key] = _tmp[key][_idx] RTN.append(list) return RTN # [{key:value,...},{key:value,...},{key:value,...}] 형식의 데이터중 컬럼하나만 함수적용하여 데이터 변경시 사용 def column_func(self, _list, _key, func, err_init=None): for _idx, row in enumerate(_list): try: _list[_idx][_key] = func(row[_key]) except: _list[_idx][_key] = err_init return _list def getPartCode(self, partNm): try: query = "select * from code_item where i2_name = '{partNm}' and state = 'y'".format(partNm=partNm) row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) if len(row) > 0: return row[0] else: return None except: return None def getCodeLocal(self, locationNm, data_code_local): try: location_pattern = [ {"pattern": {"P1": "당진", }, "value": "충청남도 당진시"}, {"pattern": {"P1": "포항", }, "value": "경상북도 포항시"}, ] tmp = self.mapping_pattern_value(location_pattern, locationNm, None) if tmp is not None: locationNm = tmp # query = "select * from code_local where `name` like '%{locationNm}%'".format(locationNm=locationNm) query_result = data_code_local[data_code_local['name'].str.contains(locationNm)] # row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) row = query_result.to_dict(orient='records') if len(row) > 0: return row[0] else: return None except: return None # bid_local의 코드 앞자리 2개로 해당 지역 관내 개수 카운트 def count_code_local(self, code_value): try: query = "select count(*) as cnt from code_local where code like '{code_value}%'".format(code_value=code_value) row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query) return row[0]['cnt'] except: return False # 도내에 해당하는 관내가 모두 찍힌 경우 bid_local에서 제외하는 함수 def filter_bid_local(self, item_bid_local): divide_bid_local_code = {} filtered_bid_local = {} new_key = 0 # bid_local의 코드 앞자리 2개를 key / 코드 뒷자리 2개를 value로 가지는 dict 세팅 for key, value in item_bid_local.items(): # 25/01/24 nil 처리 안되어 있어 추가 if value and value.get('code') and value['code'] and len(value['code']) == 4: #if value['code'] and len(value['code']) == 4: code = value['code'] local_code_key = code[:2] local_code_value = code[2:] if local_code_key in divide_bid_local_code: divide_bid_local_code[local_code_key].append(local_code_value) # 키값 있으면 value append else: divide_bid_local_code[local_code_key] = [local_code_value] # 키값 없으면 키 세팅, value는 리스트형태로 # {'46': ['20', '14', '09', '17'], '20': ['16'], '49': ['03']} << 결과 예시 else: logging.info("bid_local 코드 값 비정상@@@") # 키 값 개수만큼 반복하여 bid_local_item+1과 DB의 지역 개수가 같은지 체크 (같으면 관내체크 안함) # +1은 DB의 도내 값과 맞춰주기 위함(ex 1100, 서울특별시) for key, value in divide_bid_local_code.items(): # 관내가 여러 지역이더라도 하나라도 관내면 관내이다. if self.count_code_local(key) == len(value)+1: continue else: for local_key, local_value in item_bid_local.items(): # 25/01/24 nil 처리 안되어 있어 추가 if local_value and local_value.get('code'): if local_value['code'][:2] == key: filtered_bid_local[new_key] = local_value new_key = new_key + 1 return filtered_bid_local # json => 문자열 변환시 value를 문자열로 변환하는 함수 def json_default(self, value): return str(value) # 딕셔너리에서 키 참조시 키가 존재하지 안는경우 에러가 나기 때문에 예외처리 및 키없는경우 초기값 지정 def getValue(self, arr, key, _init=""): try: return arr[key.strip()] except: return _init # 숫자 스트링 클린징 def amt(self, _str): _str = self.Util.clean_str(_str, [[",",""],[" ",""],["원",""],["\t",""],["₩",""],]) _str = _str if _str and self.Not_None_Zero(_str) else "" return _str # 코드(정수) 리스트 => 리스트 값을 승수로 한 2의 승수 구하여 sum 값 반환 def pow_sum(self, arr): rtn = 0 for i in arr: rtn = rtn + pow(2, i) return rtn def conv_bin(self, num): tmp = reversed(str(bin(num))[2:]) rtn = [] for _idx, row in enumerate(tmp): if row == "1": rtn.append(_idx) return rtn # _str 에서 정규식 패턴 리스트에 맞는 값 매칭 def mapping_rex_to_value(self, pattern_list, _str, _init=""): rtn = _init for pattern in pattern_list: if len(re.findall(pattern[0],_str))>0 : rtn=pattern[1] return rtn # 지역 리스트 => 지역코드 리스트 변환 def loction_to_code(self, arr): local_pattern_arr = [ #["rex pattern", "value"] ["전국", 0], ["서울", 1], ["부산", 2], ["광주", 3], ["대전", 4], ["인천", 5], ["대구", 6], ["울산", 7], ["경기", 8], ["강원", 9], ["충북|충청북도", 10], ["충남|충청남도", 11], ["경북|경상북도", 12], ["경남|경상남도", 13], ["전북|전라북도", 14], ["전남|전라남도", 15], ["제주", 16], ["세종", 17], ] rtn = [] for val in arr: tmp = self.mapping_rex_to_value(local_pattern_arr, val, None) if tmp is not None: rtn.append(tmp) rtn = list(set(rtn)) if len(rtn)>0: return rtn else: return [] #raise Exception("loction_to_code : 지역값이 없습니다.") #============================================================================== ## 문서에서 패턴을 매칭하여 반환 ## 샘플 : aa = spilt_rex_doc(text, "(var\sbidRateCode\s=\s')((\d|\.){2,6})(';)", 2) ## idx 는 정규식 그룹중 추출할 인덱스 def spilt_rex_doc(self, _doc, _pattern, _idx): try: _idx_str = "" if type(_idx) == list: _idx_str = '|#==#|\\'.join(_idx) _idx_str = "\{idx}".format(idx=_idx_str) #print(_idx_str) else: _idx_str = "\{idx}".format(idx=_idx) for para in _doc.splitlines(): para_tmp = para.strip() #print("para_tmp:", para_tmp) line = re.match(_pattern, para_tmp) #print("line:", line) if line is not None: rtn = re.sub(_pattern, _idx_str, para_tmp) if type(_idx)==list: rtn = rtn.split("|#==#|") return rtn except: pass return None def spilt_rex_doc_list(self, _list, _pattern, _idx): #print("_list, _pattern, _idx",_list, _pattern, _idx) rtn=[] for list in _list: rtn.append(self.spilt_rex_doc(list, _pattern, _idx)) return rtn #============================================================================== ## 샘플 {"pattern": {"P1": "공동도급.", "P2": ".혼합허용", "N1": "ZZZ",}, "value": "1"} ## P(n) => 포함 조건 , P1, P2 ... and, or 조건은 정규식으로, = 조건은 ^word$ ## N(n) => 제외 조건 , N1, N2 ... and, or 조건은 정규식으로, = 조건은 ^word$ def mapping_pattern_value(self, _patterns, _str, _init): for _row in _patterns: fg = True for key in _row["pattern"]: tmp = self.check_pattern(key, _row["pattern"][key], _str) #print(key, _row["pattern"][key], tmp) if (tmp != True): # 조건중 하나라도 참이 아니면 초기값 리턴 fg = False if fg == True: return _row["value"] return _init # K-APT 면허 매칭 def mapping_pattern_list(self, _patterns, _str): #매칭되는 모든 값을 리스트로 반환 rtn = [] for _row in _patterns: fg = True for key in _row["pattern"]: tmp = self.check_pattern(key, _row["pattern"][key].rstrip('|'), _str) # print(key, _row["pattern"][key], tmp) if (tmp != True): # 조건중 하나라도 참이 아니면 초기값 리턴 fg = False if fg == True: rtn.append(_row["value"]) return rtn def check_pattern(self, key, pattern, txt): try: if(key[0]=="P"): if(len(re.findall(pattern, txt))>0): rtn = True else: rtn = False else: if(len(re.findall(pattern, txt))<=0): rtn = True else: rtn = False except Exception as ex: # print("check_pattern :", ex) rtn = None return rtn def Not_None_Zero(self, _str): try: if float(_str.replace(",",""))>0: return True else: return False except: return False #JSON 변환전 딕셔너리 구조 정리 함수 def dict_reorganization(self, item): returnData = {} try: for item_key, item_value in item.items(): if type(item_value) == int or type(item_value) == str or type(item_value) == float or item_value is None: returnData[item_key] = item_value else: returnData[item_key] = self.dict_reorganization(item_value) return returnData except Exception as ex: print("dict_reorganization :", ex) def listTOdict(self, _lists): rtn = {} for _idx, _list in enumerate(_lists): rtn[_idx] = _list return rtn # sys_collect 다음차수 공고 있는지 체크 (리스트 수집시 같은공고정보가 다수일경우 전차수가 뒤에 수집되기때문에 전차수가 나중에 수집되면 pass하기위해) def prev_notinum_check(self, syscollect): rtn = 'new' dkey = syscollect['dkey'] # 현재 whereis 안들어가는 발주처가 많아서 dcode로 함 if syscollect['dcode'] == "D2B_B": dkey = syscollect['dkey'].split("-")[0] + "-" + str(int(syscollect['dkey'].split("-")[1]) + 1) where = {} where['dcode'] = syscollect['dcode'] where['dkey'] = dkey where['dkey_ext'] = syscollect['dkey_ext'] chk = self.dbconn_BI.ck_Exist_one(where, "sys_collect") if chk == True: print("다음차수 공고 있음") rtn = 'closed' else: pass return rtn def null_turn(self, data): if data is None: data = '' if len(re.findall('0000-00-00', str(data))) > 0: data = '' return data def filter_num(self, num): rt = 0 if num: numbers = re.findall(r'\d+', str(num)) if numbers: rt = numbers[0] return rt def crawl_error_list(self, site, spiders, error_type, error_log): logging.info("[crawl_error_list] start") _insert = {} _insert['site'] = site _insert['spiders'] = spiders _insert['error_type'] = error_type _insert['error_log'] = str(error_log) _insert['writedt'] = 'NOW()' self.dbconn_BI.Insert_table(_insert, 'crawl_error_list') logging.info("[crawl_error_list] end") def crawl_moniter(self, type, site, spiders, spiderType): print("[crawl_moniter] start") # 특정 데이터를 업데이트 하는 경우 수집프로그램 구동으로 보지 않는다. if spiderType != "modification": columnName = "" if type == "start": columnName = "startTime" elif type == "end": columnName = "endTime" _where = {} _where['site'] = site _where['spiders'] = spiders _where['spiderType'] = spiderType row = self.dbconn_BI.get_one_row(_where, "crawl_moniter") if row == None: _insert = {} _insert['site'] = site _insert['spiders'] = spiders _insert['spiderType'] = spiderType if columnName: _insert[columnName] = "NOW()" self.dbconn_BI.Insert_table(_insert, 'crawl_moniter') else: _update = {} if columnName: _update[columnName] = "NOW()" # 종료시간 저장시 이전 종료시간을 남겨두기 위해 기록한다. if type == "end": _update['prevTime'] = row['endTime'] self.dbconn_BI.Update_table(_update, _where, 'crawl_moniter') print("[crawl_moniter] end") return "" def safe_encode(self, input_str, encoding='euc_kr'): if input_str is None: return '' result = [] for char in input_str: try: char.encode(encoding) result.append(char) except UnicodeEncodeError: # HTML 엔티티로 대체 if char in html.entities.codepoint2name: # 공식 HTML 엔티티가 있으면 사용 name = html.entities.codepoint2name[ord(char)] result.append('&{0};'.format(name)) else: # HTML 엔티티가 없는 경우 숫자 참조를 사용 result.append('&#{0};'.format(ord(char))) return ''.join(result) def change_cost_notice_memo(self, before, after, bidid, type, whereis): try: # 국방부는 기초금액이 있을 때에만 수집되어서 변경에 대한 로그가 없을것으로 판단 제외함 print("change_cost_notice_memo") if whereis == '01': content1 = "g2b" elif whereis == '10': content1 = "d2b" if type == 'const_cost': type_kor = "순공사원가" elif type == 'cost_a': type_kor = "A값" #수집시각 crawl_date_time = str(datetime.now().strftime('%Y/%m/%d %H:%M')) if (before is not None and before != "" and int(before) > 0 and after is not None and after != "" and int(after) > 0 and before != after): notice_memo_premium = "[수집시각: {}] 본 공고의 {} 금액이 정정이나 공지없이 변경되었습니다. 입찰에 참고하시기 바랍니다.(변경 전: {:,}원 / 변경 후: {:,}원)".format(crawl_date_time, type_kor, int(before), int(after)) crawl_monitor_data = {"category": "bid_change_notice_ck", "error_detail": notice_memo_premium, "content1": content1, "content2": type, "bidid": bidid} Etl.crawl_monitor_error(self, crawl_monitor_data) notice_data = {} notice_data["notice_memo"] = notice_memo_premium notice_data["writer_name"] = "자동수집" notice_data["status"] = "done" notice_data["category_type"] = "" notice_data["reg_date"] = 'NOW()' notice_data["bidid"] = bidid self.dbconn_BI.Insert_table(notice_data, 'bid_notice_memo') except Exception as e: crawl_monitor_data = {"category": "bid_change_notice_ck", "error_detail": "TRY-CATCH - bidid({}), whereis({}) : {}".format(bidid, whereis, e), "content1": content1, "content2": type, "bidid": bidid} Etl.crawl_monitor_error(self, crawl_monitor_data) def crawl_monitor_error(self, data): # 'category' 컬럼이 존재하는지 확인 category_column = data.get('category', None) if category_column is not None: # 'category' 컬럼에 공백, None이 아닌 값이 있는지 확인 if all(item is not None and str(item).strip() != '' for item in category_column): # 에러 로그라 이후 구문에 문제 없도록 try catch try: data["reg_date"] = "NOW()" self.dbconn_BI.Insert_table(data, 'crawl_monitor_error') except Exception as e: # 오류가 발생하더라도 로그로만 남기고 계속 진행 error = { 'category': data['category'], 'error_detail': e, "reg_date": "NOW()" } self.dbconn_BI.Insert_table(error, 'crawl_monitor_error') print("crawl_monitor_error 오류: " + e) else: print("crawl_monitor_error: category 공백") # 자동 검수 코드 세팅 def auto_service_code_set(self, auto_service_code, item_bidkey, item_bidvalue, item_bidcontent): # print("[auto_service_code_set] 공고 미입력 자동 서비스 start") update_bid_key = {} update_bid_value = {} update_bid_content = {} ########################## # 추후 추가할 항목들 ########################## # [K아파트] 자동점검 # [물품] 자동점검 # [나라장터] 특정용역 자동점검 # [나라장터] 재입찰 자동점검 # [나라장터] 취소 자동점검 ################################################################################################ # [나라장터] 전자시담 자동점검 (start) ################################################################################################ e_sidam_whereis = {"01"} e_sidam_bidcls = {"06"} if item_bidkey["whereis"] in e_sidam_whereis and item_bidkey["bidcls"] in e_sidam_bidcls: ################################ # 1. state 세팅 ################################ update_bid_key['state'] = 'A' update_bid_key['inputer'] = '19' ################################ # 2. location 세팅 (시스템에서 수집한 면허가 없을 경우) ################################ if not item_bidkey["location"] or item_bidkey["location"] == '0': update_bid_key["location"] = 1 ################################ # 3. auto_service_code 세팅 ################################ # bid_key에 코드가 세팅되어 있을 경우 if 'auto_service_code' in item_bidkey and item_bidkey['auto_service_code']: # 세팅된 코드에 추가하려는 코드가 이미 있을 경우 if auto_service_code in item_bidkey['auto_service_code']: pass # 세팅된 코드에 추가하려는 코드가 없을 경우 else: update_bid_key['auto_service_code'] = item_bidkey['auto_service_code'] + '|' + auto_service_code # bid_key에 코드가 세팅되지 않은 경우 else: update_bid_key['auto_service_code'] = auto_service_code # 시스템에서 수집한 면허가 없을 경우 -> 시설 : 기타공사, 용역 : 기타용역, 물품 : 기타물품 if not item_bidkey["concode"] and not item_bidkey["sercode"] and not item_bidkey["purcode"]: if item_bidkey["bidtype"] == "con": update_bid_key["concode"] = "C999" elif item_bidkey["bidtype"] == "ser": update_bid_key["sercode"] = "S999" elif item_bidkey["bidtype"] == "pur": update_bid_key["purcode"] = "P501" ################################ # 공고명으로 텍스트 판단해서 면허 세팅 (시스템에서 수집한 면허가 없을 경우) # 해당조건 제거 -> 나중에 살릴수 있으니 코드 남겨 놓음 ################################ if False: update_part_tmp = {"concode": item_bidkey["concode"], "sercode": item_bidkey["sercode"], "purcode": item_bidkey["purcode"]} # 시스템에서 수집한 면허가 없을 경우 if not update_part_tmp["concode"] and not update_part_tmp["sercode"] and not update_part_tmp["purcode"]: auto_part_check_query = "SELECT * FROM auto_partcode_check WHERE whereis = 'e_sidam' AND state IN ('T', 'TT')" auto_part_check_data = self.dbconn_BI.sql_exec(auto_part_check_query, "DS") for item in auto_part_check_data: # 현재공고와 자동세팅된 정보의 기준이 동일할때 (시설, 용역, 물품) if item['bidtype'] == item_bidkey['bidtype']: # 매칭 조건만 있을 때 if item['state'] == 'T': pattern_value_dict = {"pattern": {"P1": item['searchWord']}, "value": item['checkCode']} # 매칭 + 제외 조건 있을 때 elif item['state'] == 'TT': split_search_word = item['searchWord'].split('","') pattern_value_dict = {"pattern": {"P1": split_search_word[0].replace('{"P":"', ''), "N1": split_search_word[1].replace('N":"', '').replace('"}', '')}, "value": item['checkCode']} # 공고명과 매칭해서 추가할 면허를 추출 mapping_part = self.mapping_pattern_list([pattern_value_dict], item_bidkey["constnm"].replace(" ", "")) # 추출된 면허가 있으면 if mapping_part: print("매핑", pattern_value_dict) # 기존에 면허가 없으면 면허만 세팅 if not update_part_tmp[item['codetype']]: update_part_tmp[item['codetype']] = mapping_part[0] update_bid_key[item['codetype']] = mapping_part[0] # 기존 면허정보에 추가할 면허가 없을 경우 elif mapping_part[0] not in update_part_tmp[item['codetype']]: # 기존에 면허가 있으면 면허 뒤에 | 붙여서 세팅 update_part_tmp[item['codetype']] = update_part_tmp[item['codetype']] + "|" + mapping_part[0] update_bid_key[item['codetype']] = update_bid_key[item['codetype']] + "|" + mapping_part[0] else: print("시스템 수집 면허 있음") ################################ # 5. 자격조건, 공고문 세팅 ################################ if not item_bidcontent['bidcomment']: update_bid_content['bidcomment'] = '[* 공고 원문을 참조하시기 바랍니다. *]' elif '원문' not in item_bidcontent['bidcomment']: update_bid_content['bidcomment'] = item_bidcontent['bidcomment'] + '[* 공고 원문을 참조하시기 바랍니다. *]' #else: update_bid_content['bidcomment'] = item_bidcontent['bidcomment'] + '[* 공고 원문을 참조하시기 바랍니다. *]' #if not item_bidcontent['bid_html']: update_bid_content['bid_html'] = '[* 공고 원문을 참조하시기 바랍니다. *]' #update_bid_content['bidcomment'] = '[* 공고 원문을 참조하시기 바랍니다. *]' update_bid_content['bid_html'] = '[* 공고 원문을 참조하시기 바랍니다. *]' ################################################################################################ # [나라장터] 전자시담 자동점검 (end) ################################################################################################ # print("[auto_service_code_set] 공고 미입력 자동 서비스 end") return update_bid_key, update_bid_value, update_bid_content class g2b_attchd_lnk: # make_guid 함수 정의 def make_guid(self, a=None): if hasattr(uuid, 'uuid4'): c = str(uuid.uuid4()).replace("-", "") else: c = ''.join([hex(random.randint(0, 65535))[2:] for _ in range(8)]) if a is not None: c = "{}-{}".format(a, c) return c # 문자열 삽입 함수 def insertAt(self, a, c, b): return a[:c] + b + a[c:] # UTF-8 인코딩 함수 def utf8_encode(self, b): b = b.replace('\r\n', '\n') c = "" for a in range(len(b)): d = ord(b[a]) if d < 128: c += chr(d) else: if 127 < d < 2048: c += chr((d >> 6) | 192) else: c += chr((d >> 12) | 224) c += chr(((d >> 6) & 63) | 128) c += chr((d & 63) | 128) return c # Base64 인코딩 함수 def base64_encode(self, a): keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" c = "" l = 0 a = self.utf8_encode(a) while l < len(a): b = ord(a[l]) l += 1 if l < len(a): e = ord(a[l]) l += 1 else: e = None if l < len(a): f = ord(a[l]) l += 1 else: f = None d = b >> 2 if e is not None: b_new = ((b & 3) << 4) | (e >> 4) if f is not None: h = ((e & 15) << 2) | (f >> 6) k = f & 63 else: h = ((e & 15) << 2) k = 64 # Padding index for '=' else: b_new = ((b & 3) << 4) h = k = 64 # Padding indices for '=' c += keyStr[d] c += keyStr[b_new] c += keyStr[h] c += keyStr[k] return c # 암호화 파라미터 생성 함수 def makeEncryptParam(self, a): #a = self.base64_encode(self.utf8_encode(a)) # 중복 사용이어서 수정 a = self.base64_encode(a) if len(a) >= 10: a = self.insertAt(a, 8, "r") a = self.insertAt(a, 6, "a") a = self.insertAt(a, 9, "o") a = self.insertAt(a, 7, "n") a = self.insertAt(a, 8, "w") a = self.insertAt(a, 6, "i") a = self.insertAt(a, 9, "z") else: a = self.insertAt(a, len(a) - 1, "$") a = self.insertAt(a, 0, "$") a = a.replace('+', '%2B') return a # 최종 암호화 파라미터 함수 def makeEncryptParamFinal(self, a, c): b = { 'name': "", 'value': "" } e = "1" f = "1" d = "2018.1548512.1555.33" if "1" >= e: if f == "0": b['name'] = "k10" b['value'] = self.makeEncryptParam(a) if c and c == 1 else d + self.makeEncryptParam(a) else: b['name'] = "k00" b['value'] = self.makeEncryptParam(a) return b def attchd_lnk_set(self, attchd_lnk_data): # 데이터 없을 경우 데이터 반환하지 않도록 처리 필요 # 첨부파일 여러개이면 여러개 데이터 세팅하도록 처리 필요 print("attchd_lnk_data > ", attchd_lnk_data) returnData = "" orgnlAtchFileNm = html.unescape(urllib.parse.unquote(attchd_lnk_data['orgnlAtchFileNm'])) atchFilePathNm = html.unescape(urllib.parse.unquote(attchd_lnk_data['atchFilePathNm'])) print("orgnlAtchFileNm > ", orgnlAtchFileNm) print("atchFilePathNm > ", atchFilePathNm) file_set_data = "kc\fc11\u000b" file_set_data += "k01\f1\u000b" file_set_data += "k12\f{}\u000b".format(self.make_guid()) file_set_data += "k26\f{}\u000b".format(atchFilePathNm) file_set_data += "k31\f{}\u000b".format(orgnlAtchFileNm) file_set_data += "k21\f{},{}\u000b".format(attchd_lnk_data['untyAtchFileNo'], attchd_lnk_data['atchFileSqno']) print("file_set_data > ", file_set_data) # 마지막 수직 탭 제거 (원하지 않으면 생략 가능) # file_set_data = file_set_data.rstrip("\u000b") # print("file_set_data 수직 탭 제거> ", file_set_data) # Now, process a using makeEncryptParamFinal file_set_data_result = self.makeEncryptParamFinal(file_set_data, c=1) file_set_data_name = file_set_data_result.get('name') file_set_data_value = file_set_data_result.get('value') returnData = "{orgnlAtchFileNm}#=====#https://nwww.g2b.go.kr/fs/fsc/fsca/fileUpload.do?{file_set_data_name}={file_set_data_value}".format(orgnlAtchFileNm=orgnlAtchFileNm, file_set_data_name=file_set_data_name, file_set_data_value=file_set_data_value) print("file_set_data_result > ", file_set_data_result) print("https://nwww.g2b.go.kr/fs/fsc/fsca/fileUpload.do?" + file_set_data_name + "=") print("file_set_data_value > ", file_set_data_value) return returnData