806 lines
35 KiB
Python
806 lines
35 KiB
Python
from tkinter import EXCEPTION
|
|
from bson import RE_TYPE
|
|
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.action_chains import ActionChains
|
|
from selenium.webdriver.common.keys import Keys
|
|
# from google.api_core.exceptions import InternalServerError
|
|
# from utils import log
|
|
import time
|
|
import sys, re
|
|
import numpy as np
|
|
from naver_search import parse_naver_shopping
|
|
from edit.naver_code import find_naver_code
|
|
from edit.action_elements import click_element, return_element, click_and_confirm_tab
|
|
from img_trans.image_trans import image_trans
|
|
import logging
|
|
from bs4 import BeautifulSoup
|
|
import io
|
|
import win32clipboard
|
|
|
|
# 로거 인스턴스 가져오기
|
|
logger = logging.getLogger('default_logger')
|
|
|
|
avg_price = 0
|
|
|
|
def detail_ai(driver, textarea, aicontents, combined_value):
|
|
try:
|
|
logger.debug("detail_ai 맞추기")
|
|
|
|
aicontent_html = aicontents
|
|
time.sleep(0.5)
|
|
current_value = textarea.get_attribute("data-value")
|
|
logger.debug("current_value에서 기존 이미지 삭제")
|
|
deleted_original_html_tags = original_html(current_value)
|
|
|
|
ai_value = combined_value + aicontent_html + deleted_original_html_tags
|
|
# contents 변수의 값이 None이 아닐 때만 send_keys 메서드를 호출합니다.
|
|
if aicontent_html is not None:
|
|
# logger.debug("aicontent_html 입력시작")
|
|
|
|
# html_insert_btn_xpath="//div[@id='productMainContentContainerId']/div/div/div[2]/div[2]/div/div[1]/div[2]/div/div/button[7]"
|
|
# click_element(driver, 'XPATH', html_insert_btn_xpath, 5, 'ac')
|
|
# logger.debug("html_insert_btn_xpath 클릭")
|
|
|
|
# html_insert_TEXTAREA_xpath="/html/body/div[7]/div/div[3]/div/div[2]/div[1]/div/div/div[2]/div[2]/div[2]/div/div[2]/div[2]/textarea"
|
|
# click_element(driver, 'XPATH', html_insert_TEXTAREA_xpath, 5, 'ac')
|
|
# html_insert_TEXTAREA_element = return_element(driver, 'XPATH', 'html_insert_TEXTAREA_xpath', 10)
|
|
# html_insert_TEXTAREA_element.send_keys(aicontent_html)
|
|
# logger.debug("html 요소로 전송")
|
|
|
|
# html_insert_ok_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div[2]/div[2]/div/div[1]/div[2]/div/button[1]"
|
|
# click_element(driver, 'XPATH', html_insert_ok_xpath, 5, 'ac')
|
|
# logger.debug("html_insert_ok_xpath 클릭")
|
|
|
|
# logger.debug(f"AI 컨텐츠 입력완료")
|
|
|
|
driver.execute_script("arguments[0].setAttribute('data-value', arguments[1]);", textarea, ai_value)
|
|
|
|
logger.debug(f"AI 컨텐츠 입력완료")
|
|
else:
|
|
# contents 변수가 None일 때의 대체 처리
|
|
# 예: detail_content.send_keys("") 또는 아무 동작도 수행하지 않음
|
|
pass
|
|
except Exception as e:
|
|
logger.debug(f"AI 컨텐츠 수정 중 에러발생 : {e}", exc_info=True)
|
|
|
|
|
|
def detail_html(driver):
|
|
try:
|
|
logger.debug("HTML 수정 버튼 클릭 .")
|
|
html_btn_by_contains_xpath="//button[contains(.,'소스')]"
|
|
html_btn_css = "ck.ck-button.ck-source-editing-button.ck-off.ck-button_with-text"
|
|
html_btn_xpath="//div[@id='productMainContentContainerId']/div/div/div[2]/div[2]/div/div/div[2]/div/div/button[8]"
|
|
click_element(driver, 'XPATH', html_btn_xpath, 5, 'ac')
|
|
# html_btn = WebDriverWait(driver, 10).until(
|
|
# # EC.presence_of_element_located((By.CSS_SELECTOR, ".ck-source-editing-button"))
|
|
# EC.presence_of_element_located(By.CLASS_NAME, html_btn_xpath)
|
|
# # EC.presence_of_element_located((By.CSS_SELECTOR, "button.ck.ck-button.ck-source-editing-button.ck-off.ck-button_with-text"))
|
|
# )
|
|
# logger.debug("HTML 수정 버튼을 성공적으로 찾았습니다.")
|
|
# html_btn.click()
|
|
except Exception as e:
|
|
logger.debug(f"HTML 수정 버튼 요소를 찾을 수 없습니다. : {e}", exc_info=True)
|
|
|
|
|
|
def detail_textarea(driver):
|
|
try:
|
|
# textarea_css = "ck-source-editing-area"
|
|
# click_element(driver, 'CSS_SELECTOR', textarea_css, 10, 'js')
|
|
textarea = WebDriverWait(driver, 5).until(
|
|
EC.presence_of_element_located((By.CLASS_NAME, "ck-source-editing-area"))
|
|
)
|
|
logger.debug("textarea버튼을 성공적으로 찾았습니다.")
|
|
time.sleep(0.2)
|
|
actions = ActionChains(driver)
|
|
actions.move_to_element(textarea).perform() # 요소로 스크롤
|
|
logger.debug("'textarea' 요소로 스크롤 실행")
|
|
time.sleep(0.2)
|
|
actions.click(textarea).perform()
|
|
logger.debug("'textarea' 요소 클릭 실행")
|
|
|
|
return textarea
|
|
# textarea.click()
|
|
except Exception as e:
|
|
logger.debug(f"textarea버튼 요소를 찾을 수 없습니다. : {e}", exc_info=True)
|
|
|
|
|
|
def paste_image_in_editor(driver, editor_element):
|
|
action = ActionChains(driver)
|
|
action.click(editor_element) # 에디터를 클릭하여 포커스를 맞춥니다.
|
|
action.key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform() # CTRL+V를 보내 이미지를 붙여넣습니다.
|
|
|
|
def image_to_clipboard(image):
|
|
"""
|
|
이미지 객체(PIL.Image)를 받아 클립보드에 BMP 형식으로 복사합니다.
|
|
|
|
Args:
|
|
- image (PIL.Image): 이미지 객체.
|
|
"""
|
|
output = io.BytesIO()
|
|
# BMP 형식으로 이미지를 저장합니다. BMP 형식은 클립보드에 붙여넣기를 위해 필요합니다.
|
|
image.save(output, format="BMP")
|
|
data = output.getvalue()[14:] # BMP 파일 헤더를 제거합니다.
|
|
output.close()
|
|
|
|
win32clipboard.OpenClipboard() # 클립보드를 연다
|
|
win32clipboard.EmptyClipboard() # 클립보드를 비운다
|
|
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) # 클립보드에 이미지 데이터를 설정한다
|
|
win32clipboard.CloseClipboard() # 클립보드를 닫는다
|
|
|
|
def fetch_image_urls(html_content):
|
|
"""
|
|
HTML 콘텐츠에서 모든 <img> 태그의 URL을 "순서대로" 추출합니다.
|
|
이 함수는 class="image_resized"를 가진 <img> 태그와
|
|
<figure class="image"> 내부의 <img> 태그 모두를 포함합니다.
|
|
"""
|
|
soup = BeautifulSoup(html_content, 'html.parser')
|
|
image_urls = []
|
|
|
|
# class="image_resized"를 가진 모든 <img> 태그 찾기
|
|
images_resized = soup.find_all('img', class_='image_resized')
|
|
for img in images_resized:
|
|
if img and 'src' in img.attrs and img['src'] not in image_urls:
|
|
image_urls.append(img['src'])
|
|
|
|
# <figure class="image"> 내부의 모든 <img> 태그 찾기
|
|
figures = soup.find_all('figure', class_='image')
|
|
for figure in figures:
|
|
img_tag = figure.find('img')
|
|
if img_tag and 'src' in img_tag.attrs and img_tag['src'] not in image_urls:
|
|
image_urls.append(img_tag['src'])
|
|
|
|
return image_urls
|
|
|
|
def safe_generate_content(gemini, product_info, max_retries=3, initial_wait=1):
|
|
retry_count = 0
|
|
wait_time = initial_wait
|
|
|
|
while retry_count < max_retries:
|
|
try:
|
|
return gemini.generate_description(product_info)
|
|
# except InternalServerError as e:
|
|
# logger.debug(f"Retry {retry_count + 1}/{max_retries} for InternalServerError")
|
|
# time.sleep(wait_time)
|
|
# wait_time *= 2 # Exponential backoff
|
|
# retry_count += 1
|
|
except Exception as e:
|
|
logger.debug(f"예외 발생 : {e}", exc_info=True)
|
|
logger.debug(f"예외 발생으로 재시도 : {retry_count + 1}/{max_retries}")
|
|
time.sleep(wait_time)
|
|
wait_time *= 2 # Exponential backoff
|
|
retry_count += 1
|
|
|
|
def selling_price(tao_bao_price, shipping_fee=6500, target_margin_rate=0.29, market_fee_rate=0.13,
|
|
card_fee_rate=0.035, exchange_rate=200):
|
|
"""
|
|
판매가를 계산하는 함수
|
|
|
|
Args:
|
|
tao_bao_price: float, 타오바오 제품 원가
|
|
target_margin_rate: float, 목표 마진율
|
|
market_fee_rate: float, 마켓 수수료율
|
|
card_fee_rate: float, 카드 결제 수수료율
|
|
exchange_rate: float, 환율
|
|
shipping_fee: float, 배송비
|
|
|
|
Returns:
|
|
float, 판매가
|
|
"""
|
|
|
|
# 제품 원가 계산
|
|
product_cost = tao_bao_price * exchange_rate
|
|
|
|
# 목표 마진 계산
|
|
target_margin = product_cost * target_margin_rate
|
|
|
|
# 총 수수료율 계산
|
|
total_fee_rate = card_fee_rate + market_fee_rate
|
|
|
|
# 판매가 계산
|
|
selling_price = (product_cost + target_margin) / (1 - total_fee_rate - market_fee_rate) + shipping_fee
|
|
|
|
# 마켓 수수료 계산
|
|
market_fee = selling_price * market_fee_rate
|
|
|
|
# 마진 계산
|
|
margin = selling_price - product_cost - market_fee - shipping_fee
|
|
|
|
return selling_price, margin
|
|
|
|
def extract_weight(text):
|
|
"""
|
|
텍스트에서 무게 값만 추출하는 함수. 유효한 값이 없을 경우 기본값을 적용합니다.
|
|
|
|
Args:
|
|
text: str, 텍스트. None이거나 빈 문자열일 경우 기본값 반환.
|
|
|
|
Returns:
|
|
float, 추출된 무게 값 (kg) 또는 기본값.
|
|
"""
|
|
default_weight = 1.0 # 기본 무게값 설정
|
|
|
|
# 입력값 검증
|
|
if not text:
|
|
return default_weight
|
|
|
|
# 정규표현식을 사용하여 숫자와 단위를 추출합니다.
|
|
pattern = r'(\d+(\.\d+)?)\s*(kg|g)'
|
|
match = re.search(pattern, text)
|
|
|
|
if match:
|
|
weight = float(match.group(1)) # 숫자를 실수형으로 변환합니다.
|
|
unit = match.group(3) # 단위를 가져옵니다.
|
|
|
|
if unit == 'g':
|
|
weight /= 1000 # 그램을 킬로그램으로 변환합니다.
|
|
|
|
return weight if weight > 0 else default_weight
|
|
else:
|
|
return default_weight
|
|
|
|
# 무게배송비 추정
|
|
def find_delivery_fee(weight, delv_collection):
|
|
# 가장 가까운 무게값 찾기
|
|
cursor = delv_collection.find({}, {'_id': 0, 'weight': 1, 'fee': 1}).sort('weight', 1)
|
|
weights = [record['weight'] for record in cursor]
|
|
cursor.rewind()
|
|
fees = [int(record['fee']) for record in cursor] # 콤마 제거 후 정수로 변환
|
|
|
|
|
|
# fees = [record['fee'] for record in cursor]
|
|
|
|
|
|
# 입력된 무게와 가장 가까운 무게 찾기
|
|
closest_weight_index = np.argmin(np.abs(np.array(weights) - weight))
|
|
closest_weight = weights[closest_weight_index]
|
|
logger.debug(f"가장 가까운 무게 : {closest_weight}")
|
|
logger.debug(f"closest_weight_index: {closest_weight_index}")
|
|
logger.debug(f"fees 길이 : {len(fees)}")
|
|
logger.debug(f"weights 길이 : {len(weights)}")
|
|
|
|
|
|
# 가장 가까운 무게에 해당하는 요금 반환
|
|
return fees[closest_weight_index]
|
|
|
|
#네이버쇼핑 함수
|
|
# def NS_info(products):
|
|
# # 각 상품 정보에 접근하여 텍스트로 가공
|
|
# product_info_text = ""
|
|
# global avg_price
|
|
# avg_price = 0
|
|
# prices = []
|
|
|
|
# for index, product in enumerate(products):
|
|
# idx = index + 1
|
|
# product_info_text += '<div class="card">\n' # 카드 시작 태그
|
|
# product_info_text += f"=================={idx} 번째 네이버 상품========================"
|
|
# # 이미지 태그
|
|
# product_info_text += f'<img src="{product["imageUrl"]}" width="200" alt="상품 이미지">\n'
|
|
|
|
# # 상품 세부 정보 태그 시작
|
|
# product_info_text += '<div class="product-details">\n'
|
|
|
|
# # 상품명 태그
|
|
# # product_info_text += f'<div class="product-name"><strong>상품명 : {product["productTitle"]}</strong></div>\n'
|
|
# product_info_text += f'<div class="product-name"><h3>상품명 : {product["productTitle"]}</h3></div>\n'
|
|
# # 가격 태그
|
|
# # 상품 가격을 숫자로 변환하고 포맷
|
|
# if isinstance(product["price"], str):
|
|
# # 문자열인 경우 숫자로 변환
|
|
# price = int(product["price"].replace(",", "")) # 쉼표 제거 후 정수 변환
|
|
# else:
|
|
# price = product["price"] # 이미 숫자인 경우 변환 없음
|
|
|
|
# prices.append(price)
|
|
# formatted_price = format(price, ",") + "원" # 천 단위 구분으로 포맷
|
|
# product_info_text += f'<div class="price">가격: {formatted_price}</div>'
|
|
|
|
# # 순위 태그
|
|
# product_info_text += f'<div class="price">순위: {product["rank"]}</div>\n'
|
|
|
|
# product_info_text += "=================================================================="
|
|
|
|
# # 상품 세부 정보 태그 종료
|
|
# product_info_text += '</div>\n'
|
|
|
|
# product_info_text += '</div>\n' # 카드 종료 태그
|
|
# avg_price += int(product["price"])
|
|
|
|
# # avg_price = round(avg_price/len(products),0)
|
|
|
|
# if products: # products가 비어 있지 않은 경우에만 평균 가격 계산
|
|
# avg_price = round(avg_price / len(products), 0)
|
|
# low_price = min(prices)
|
|
# high_price = max(prices)
|
|
# product_info_text += f'네이버 상품의 최저 가격은 [{low_price}]'
|
|
# product_info_text += f'네이버 상품의 평균 가격은 [{avg_price}]'
|
|
# product_info_text += f'네이버 상품의 최고 가격은 [{high_price}]'
|
|
|
|
# return product_info_text
|
|
|
|
def original_html(current_html):
|
|
# BeautifulSoup 객체 생성
|
|
soup = BeautifulSoup(current_html, 'html.parser')
|
|
|
|
# "img class='image_resized'" 태그만 추출
|
|
resized_images = soup.find_all('img', class_='image_resized')
|
|
|
|
# 추출된 태그들로 새로운 HTML 문자열 생성
|
|
original_html_text = ''.join(str(tag) for tag in resized_images)
|
|
|
|
return original_html_text
|
|
|
|
def NS_info_with_HTML(products):
|
|
# 상품 정보를 담을 5x1 표 생성
|
|
product_info_table = "<table><tr>"
|
|
global avg_price
|
|
avg_price = 0
|
|
|
|
for product in products:
|
|
if isinstance(product["price"], str):
|
|
price = int(product["price"].replace(",", ""))
|
|
else:
|
|
price = product["price"]
|
|
formatted_price = format(price, ",") + "원"
|
|
|
|
# 각 상품 카드를 생성하고 테이블의 셀로 추가
|
|
product_card = create_product_card(product["productTitle"], product["imageUrl"], formatted_price, product["rank"], product["purchase"], product["review"])
|
|
product_info_table += f"<td style='width: 170px; height: 170px;'>{product_card}</td>"
|
|
avg_price += price
|
|
|
|
product_info_table += "</tr></table>"
|
|
|
|
if products:
|
|
avg_price = round(avg_price / len(products), 0)
|
|
product_info_table += f'<div><h3>네이버 상품의 평균 가격은 {avg_price:,.0f}원입니다.</h3></div>'
|
|
|
|
return product_info_table
|
|
|
|
|
|
def naver_prices(products):
|
|
prices = []
|
|
for product in products:
|
|
if product["price"] is None:
|
|
logger.debug("가격 정보가 없는 제품이 있습니다.")
|
|
continue # 가격 정보가 없으면, 이 제품을 건너뛰고 다음 제품으로 넘어갑니다.
|
|
|
|
if isinstance(product["price"], str):
|
|
price = int(product["price"].replace(",", ""))
|
|
logger.debug(f"정수변환 가격 : {price}")
|
|
else:
|
|
price = product["price"]
|
|
logger.debug(f"이미 정수인 가격 : {price}")
|
|
|
|
prices.append(price)
|
|
|
|
if not prices: # prices 리스트가 비어 있는 경우를 처리
|
|
logger.error("가격 정보가 없습니다.")
|
|
return None, None, None
|
|
|
|
low_price = min(prices)
|
|
avg_price = sum(prices) / len(prices)
|
|
high_price = max(prices)
|
|
|
|
return low_price, avg_price, high_price
|
|
|
|
|
|
def create_product_card(product_name, image_url, price, rank, purchase, review):
|
|
# 카드의 크기를 150x150px로 조정
|
|
product_card = f"""
|
|
<div style="width: 150px; height: 150px; overflow: hidden; text-align: center;">
|
|
<img src="{image_url}" style="width: 150px; height: 150px;" alt="상품 이미지">
|
|
<div style="font-size: 12px;"><strong>{product_name}</strong></div>
|
|
<div style="font-size: 12px;">가격: {price}</div>
|
|
<div style="font-size: 12px;">순위: {rank}</div>
|
|
<div style="font-size: 12px;">구매건수: {purchase}</div>
|
|
<div style="font-size: 12px;">리뷰수: {review}</div>
|
|
</div>
|
|
"""
|
|
return product_card
|
|
|
|
|
|
# #네이버쇼핑 함수 마크다운
|
|
# def NS_info_markdown(products):
|
|
# product_info_md = ""
|
|
# global avg_price
|
|
# avg_price = 0
|
|
|
|
# for index, product in enumerate(products):
|
|
# idx = index + 1
|
|
# # 제품명을 헤더로 표시
|
|
# product_info_md += f"### {idx}번상품. {product['productTitle']}\n"
|
|
|
|
# # 이미지 추가
|
|
# product_info_md += f"\n\n"
|
|
|
|
# # 가격과 순위 정보
|
|
# if isinstance(product["price"], str):
|
|
# price = int(product["price"].replace(",", "")) # 쉼표 제거 후 정수 변환
|
|
# else:
|
|
# price = product["price"]
|
|
# formatted_price = format(price, ",") + "원"
|
|
|
|
# product_info_md += f"- **가격:** {formatted_price}\n"
|
|
# product_info_md += f"- **순위:** {product['rank']}\n\n"
|
|
|
|
# avg_price += price # 평균 가격 계산을 위해 가격 추가
|
|
|
|
# if products: # products가 비어 있지 않은 경우에만 평균 가격 계산
|
|
# avg_price = round(avg_price / len(products), 0)
|
|
# product_info_md += f"**네이버 상품의 평균 가격은 {avg_price}원입니다.**\n"
|
|
|
|
# return product_info_md
|
|
|
|
def modify_detail_page(driver, product_info, gemini, translator, delv_collection, json_naver_codes, login_info):
|
|
simpleMode = login_info['whether_simpleMode']
|
|
|
|
detail_css = ".ant-tabs-tab:nth-child(6)"
|
|
|
|
thumb_data_note = "5"
|
|
click_and_confirm_tab(driver, thumb_data_note, 10)
|
|
|
|
product_info_card = None
|
|
product_keyword = product_info.keyword_title
|
|
# product_low_cost = product_info.tao_low_price
|
|
# product_high_cost = product_info.tao_high_price
|
|
product_low_cost = product_info.option_low_price
|
|
product_high_cost = product_info.option_high_price
|
|
image_src = product_info.main_image_url
|
|
|
|
try:
|
|
# cat = click_element(driver, 'XPATH', '//div[8]/div/div/div[2]/div/div/div/span[2]/div/div', 10, 'js')
|
|
# cat = return_element(driver, 'XPATH', '//div[8]/div/div/div[2]/div/div/div/span[2]/div/div', 10)
|
|
# logger.debug(f"퍼센티 등록 화면에 표시된 카테고리 텍스트 : {cat.text}")
|
|
# product_info.per_cat_code = cat.text
|
|
naver_code = find_naver_code(product_info.per_cat_code, json_naver_codes)
|
|
logger.debug(f"검색된 스스 캣코드 : {naver_code}")
|
|
product_info.naver_code = naver_code
|
|
except Exception as e:
|
|
logger.debug(f"카테고리 코드 검색 중 예외 발생 : {e}", exc_info=True)
|
|
naver_code = ""
|
|
|
|
|
|
if not simpleMode: # 심플모드일 경우 네이버 파싱 패스
|
|
try:
|
|
logger.debug("네이버쇼핑 파싱 중")
|
|
products = parse_naver_shopping(product_keyword, naver_code, isOverseas=1, sortcount=5)
|
|
logger.debug("네이버쇼핑 파싱 완료")
|
|
# logger.debug(f"네이버 파싱된 상품 리스트 \n {products}")
|
|
product_info.naver_products = products
|
|
|
|
# 네이버 상품가격 담기
|
|
naver_price = naver_prices(products)
|
|
|
|
if naver_price[0]: # 리턴값이 있는 경우 사용
|
|
product_info.naver_low_price = naver_price[0]
|
|
product_info.naver_avg_price = naver_price[1]
|
|
product_info.naver_high_price = naver_price[2]
|
|
else: # 리턴값이 None인 경우 기본값인 100원 사용
|
|
product_info.naver_low_price = 100
|
|
product_info.naver_avg_price = 100
|
|
product_info.naver_high_price = 100
|
|
|
|
# product_info_text = NS_info(products)
|
|
product_info_card = NS_info_with_HTML(products)
|
|
# logger.debug(f"수집된 정보 \n {product_info_card} \n")
|
|
logger.debug("네이버쇼핑 파싱 완료")
|
|
except Exception as e:
|
|
logger.debug(f"네이버쇼핑 파싱 중 에러발생 : {e}", exc_info=True)
|
|
|
|
|
|
|
|
# click_element('XPATH', '/html/body/div[7]/div/div[3]/div/div[2]/div[1]/div/div[2]/div[8]/div/div/div[2]/div[1]/div/div/span[2]/div/div')
|
|
|
|
# testt = find_delivery_fee(7,delv_collection)
|
|
# logger.debug(f"값 출력 : {testt}")
|
|
|
|
# 상세페이지 탭으로 이동
|
|
# detail_tab = driver.find_element(By.ID, "rc-tabs-0-tab-5")
|
|
|
|
|
|
try:
|
|
detail_tab_xpath = ".ant-tabs-tab:nth-child(6)"
|
|
detail_tab = WebDriverWait(driver, 10).until(
|
|
EC.presence_of_element_located((By.CSS_SELECTOR, ".ant-tabs-tab:nth-child(6)"))
|
|
)
|
|
save_button_xpath = "//button[contains(.,'저장하기')]"
|
|
save_button = WebDriverWait(driver, 10).until(
|
|
EC.presence_of_element_located((By.XPATH, "//button[contains(.,'저장하기')]"))
|
|
)
|
|
except Exception as e:
|
|
logger.debug(f"상세페이지 탭으로 이동 중 오류 발생: 요소를 찾을 수 없습니다. : {e}", exc_info=True)
|
|
# detail_tab = driver.find_element(By.CSS_SELECTOR, ".ant-tabs-tab:nth-child(6)")
|
|
# detail_tab.click()
|
|
time.sleep(0.2) # 페이지 로딩 대기
|
|
|
|
# 상세페이지 내용 수정
|
|
try:
|
|
detail_content = WebDriverWait(driver, 10).until(
|
|
EC.presence_of_element_located((By.XPATH, "//div[@id='productMainContentContainerId']/div/div/div[2]/div[2]/div[2]/div"))
|
|
)
|
|
except Exception as e:
|
|
logger.debug(f"상세페이지 xpath 요소 가져오기 오류 발생: 요소를 찾을 수 없습니다. : {e}", exc_info=True)
|
|
# detail_content = driver.find_element(By.XPATH, "//div[@id='productMainContentContainerId']/div/div/div[2]/div[2]/div[2]/div")
|
|
# detail_content = driver.find_element(By.ID, "rc-tabs-0-tab-5")
|
|
# if not simpleMode: # 심플모드일 경우 AI처리 하지않음
|
|
try:
|
|
ActionChains(driver).move_to_element(detail_content).click().perform()
|
|
time.sleep(1)
|
|
logger.debug("AI 이미지 처리중")
|
|
# contents = bard_img(image_src)
|
|
# contents = gemini.generate_description(image_src, product_title)
|
|
aicontents = []
|
|
aicontents = safe_generate_content(gemini, product_info)
|
|
logger.debug(f"{aicontents}")
|
|
product_info.ai_contents_html = aicontents
|
|
logger.debug("AI 이미지 처리 완료")
|
|
|
|
weight = extract_weight(aicontents)
|
|
logger.debug(f"무게 추정 : {weight}")
|
|
product_info.weight = weight
|
|
|
|
delv_fee = find_delivery_fee(weight, delv_collection)
|
|
logger.debug(f"무게배송비 추정 : {delv_fee}")
|
|
product_info.w_delv_fee = delv_fee
|
|
|
|
packing_fee = 0 # 추가포장비의 경우 크기와 소재에 따라 래핑, 우드포장등 세분화 필요. 기본은 0
|
|
logger.debug(f"추가포장비 추정 : {packing_fee}")
|
|
product_info.packing_fee = packing_fee
|
|
|
|
except Exception as e:
|
|
logger.debug(f"상세페이지 편집 중 에러발생 : {e}", exc_info=True)
|
|
|
|
# if not simpleMode: # 심플모드일 경우 가격계산 패스
|
|
try:
|
|
# 가격 정보 계산
|
|
low_cost, low_margin = selling_price(product_low_cost, delv_fee, 0.29)
|
|
high_cost, high_margin = selling_price(product_high_cost, delv_fee, 0.29)
|
|
low_delv_fee = avg_price - low_cost
|
|
high_delv_fee = avg_price - high_cost
|
|
|
|
# 마진율 계산
|
|
low_margin_r = round((low_margin / low_cost) * 100, 2)
|
|
high_margin_r = round((high_margin / high_cost) * 100, 2)
|
|
|
|
# 텍스트 조합
|
|
weight_text = f'''
|
|
해당 제품의 무게배송비는 <span style="font-size:16px;"><strong><u>{delv_fee:,.0f}</u></strong></span>원입니다.<br>
|
|
|
|
'''
|
|
cost_add_text = f"""
|
|
<h1>==== 가격과 마진율 ====</h1><br>
|
|
해당 제품의 네이버 평균가격은 <span style="font-size:16px;"><strong><u>{avg_price:,.0f}</u></strong></span>원입니다.<br>
|
|
해당 제품의 무게배송비는 <span style="font-size:16px;"><strong><u>{delv_fee:,.0f}</u></strong></span>원입니다.<br>
|
|
|
|
**마진율 확보를 위한 판매가 범위:** {low_cost:,.0f}원 ~ {high_cost:,.0f}원<br>
|
|
|
|
<h3>**저가 중심 계산:**</h3><br>
|
|
* 무게 배송비 대비: {low_delv_fee:,.0f}원 {('낮은' if low_delv_fee < 0 else '높은')} 편<br>
|
|
* 마진율: {low_margin_r:,.2f}%<br>
|
|
<h3>**고가중심 계산:**</h3><br>
|
|
* 무게 배송비 대비: {high_delv_fee:,.0f}원 {('낮은' if high_delv_fee < 0 else '높은')} 편<br>
|
|
* 마진율: {high_margin_r:,.2f}%<br>
|
|
|
|
<h3>**추가 정보:**</h3><br>
|
|
<h2>* 해당 제품의 파손 여부 및 무게 오차를 고려하여 더하기 마진을 설정하세요.</h2><br>
|
|
<h3>=======================================</h3><br><br>
|
|
"""
|
|
|
|
# time.sleep(2)
|
|
# detail_content.send_keys(cost_add_text)
|
|
|
|
except Exception as e:
|
|
logger.debug(f"가격정보 생성 중 에러 : {e}", exc_info=True)
|
|
|
|
|
|
try:
|
|
logger.debug("HTML 수정 버튼 클릭 .")
|
|
html_btn_by_contains_xpath="//button[contains(.,'소스')]"
|
|
html_btn_css = "ck.ck-button.ck-source-editing-button.ck-off.ck-button_with-text"
|
|
html_btn_xpath="//div[@id='productMainContentContainerId']/div/div/div[2]/div[2]/div/div/div[2]/div/div/button[8]"
|
|
click_element(driver, 'XPATH', html_btn_xpath, 5, 'ac')
|
|
# html_btn = WebDriverWait(driver, 10).until(
|
|
# # EC.presence_of_element_located((By.CSS_SELECTOR, ".ck-source-editing-button"))
|
|
# EC.presence_of_element_located(By.CLASS_NAME, html_btn_xpath)
|
|
# # EC.presence_of_element_located((By.CSS_SELECTOR, "button.ck.ck-button.ck-source-editing-button.ck-off.ck-button_with-text"))
|
|
# )
|
|
# logger.debug("HTML 수정 버튼을 성공적으로 찾았습니다.")
|
|
# html_btn.click()
|
|
except Exception as e:
|
|
logger.debug(f"HTML 수정 버튼 요소를 찾을 수 없습니다. : {e}", exc_info=True)
|
|
|
|
|
|
# html_btn = driver.find_element(By.CSS_SELECTOR, ".ck-source-editing-button")
|
|
|
|
try:
|
|
# textarea_css = "ck-source-editing-area"
|
|
# click_element(driver, 'CSS_SELECTOR', textarea_css, 10, 'js')
|
|
textarea = WebDriverWait(driver, 5).until(
|
|
EC.presence_of_element_located((By.CLASS_NAME, "ck-source-editing-area"))
|
|
)
|
|
logger.debug("textarea버튼을 성공적으로 찾았습니다.")
|
|
time.sleep(0.2)
|
|
actions = ActionChains(driver)
|
|
actions.move_to_element(textarea).perform() # 요소로 스크롤
|
|
logger.debug("'textarea' 요소로 스크롤 실행")
|
|
time.sleep(0.2)
|
|
actions.click(textarea).perform()
|
|
logger.debug("'textarea' 요소 클릭 실행")
|
|
|
|
# textarea.click()
|
|
except Exception as e:
|
|
logger.debug(f"textarea버튼 요소를 찾을 수 없습니다. : {e}", exc_info=True)
|
|
|
|
try:
|
|
# # data-value 속성 값을 가져옵니다.
|
|
current_value = textarea.get_attribute("data-value")
|
|
product_info.current_value = current_value
|
|
logger.debug("현재 속성값 수집 완료")
|
|
# original_html_tags = original_html(current_value)
|
|
|
|
detail_images = fetch_image_urls(current_value)
|
|
product_info.detail_image_urls = detail_images
|
|
logger.debug(f"원본 이미지 URLs [{len(detail_images)}]개 수집 완료")
|
|
# logger.debug(f"detail_images List : {detail_images}")
|
|
# logger.debug(f"현재 속성값 :{current_value}")
|
|
if not simpleMode: # 심플모드일 경우 상품카드 작성하지 않음
|
|
logger.debug("product_info_card 결합.")
|
|
# info_value = product_info_card + '<br><br>' + cost_add_text + '<br><br>'
|
|
info_value = product_info_card + '<br><br>' + weight_text + '<br><br>'
|
|
# textarea.send_keys(product_info_card)
|
|
time.sleep(0.2)
|
|
combined_value = info_value
|
|
|
|
logger.debug(f"{combined_value}")
|
|
|
|
# # data-value 속성을 새로운 값으로 설정합니다.
|
|
logger.debug("product_info_card + 기존 내용 입력.")
|
|
# driver.execute_script("arguments[0].setAttribute('data-value', arguments[1]);", textarea, combined_value)
|
|
|
|
logger.debug("HTML 수정 버튼 다시 클릭하여 기본편집페이지로 돌아감.")
|
|
click_element(driver, 'XPATH', html_btn_xpath, 5, 'ac')
|
|
|
|
time.sleep(0.2)
|
|
except Exception as e:
|
|
logger.debug(f"product_info_card 수정 중 에러발생 : {e}", exc_info=True)
|
|
|
|
# try:
|
|
# # 상세페이지 내용에 텍스트 추가
|
|
# logger.debug("커서위치 맞추기")
|
|
# detail_content.send_keys(Keys.LEFT)
|
|
# detail_content.send_keys(Keys.HOME)
|
|
# detail_content.send_keys(Keys.ENTER)
|
|
# detail_content.send_keys(Keys.ENTER)
|
|
# detail_content.send_keys(Keys.UP)
|
|
# detail_content.send_keys(Keys.UP)
|
|
# detail_content.send_keys(Keys.UP)
|
|
# detail_content.send_keys(Keys.UP)
|
|
# detail_content.send_keys(Keys.HOME)
|
|
# detail_content.send_keys(Keys.HOME)
|
|
# time.sleep(0.5)
|
|
|
|
|
|
# # contents 변수의 값이 None이 아닐 때만 send_keys 메서드를 호출합니다.
|
|
# if aicontents is not None:
|
|
# logger.debug("aicontents 입력시작")
|
|
|
|
# html_insert_btn_xpath="//div[@id='productMainContentContainerId']/div/div/div[2]/div[2]/div/div[1]/div[2]/div/div/button[7]"
|
|
# click_element(driver, 'XPATH', html_insert_btn_xpath, 5, 'ac')
|
|
# logger.debug("html_insert_btn_xpath 클릭")
|
|
# html_insert_TEXTAREA_xpath="/html/body/div[7]/div/div[3]/div/div[2]/div[1]/div/div/div[2]/div[2]/div[2]/div/div[2]/div[2]/textarea"
|
|
# click_element(driver, 'XPATH', html_insert_TEXTAREA_xpath, 5, 'ac')
|
|
# html_insert_TEXTAREA_element = return_element(driver, 'XPATH', 'html_insert_TEXTAREA_xpath', 10)
|
|
# html_insert_TEXTAREA_element.send_keys(aicontents)
|
|
# logger.debug("html 요소로 전송")
|
|
# html_insert_ok_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div[2]/div[2]/div/div[1]/div[2]/div/button[1]"
|
|
# click_element(driver, 'XPATH', html_insert_ok_xpath, 5, 'ac')
|
|
# logger.debug("html_insert_ok_xpath 클릭")
|
|
# logger.debug(f"AI 컨텐츠 입력완료")
|
|
# detail_content.send_keys(Keys.DOWN)
|
|
# else:
|
|
# # contents 변수가 None일 때의 대체 처리
|
|
# # 예: detail_content.send_keys("") 또는 아무 동작도 수행하지 않음
|
|
# pass
|
|
# except Exception as e:
|
|
# logger.debug(f"AI 컨텐츠 수정 중 에러발생 : {e}", exc_info=True)
|
|
|
|
|
|
trans_img_tag = login_info["whether_modifyImageTanslation"]
|
|
logger.debug(f"whether_modifyImageTanslation : {trans_img_tag}")
|
|
|
|
if trans_img_tag:
|
|
logger.debug("상세페이지 이미지 번역 시작")
|
|
for i in range(30):
|
|
detail_content.send_keys(Keys.PAGE_DOWN)
|
|
detail_content.send_keys(Keys.DOWN)
|
|
|
|
# detail_content.send_keys(Keys.HOME)
|
|
detail_content.send_keys(Keys.DOWN)
|
|
detail_content.send_keys(Keys.DOWN)
|
|
detail_content.send_keys(Keys.ENTER)
|
|
detail_content.send_keys(Keys.ENTER)
|
|
detail_content.send_keys(Keys.ENTER)
|
|
detail_content.send_keys(Keys.ENTER)
|
|
|
|
try:
|
|
for i, detail_image in enumerate(detail_images):
|
|
logger.debug(f"상세페이지 [{i+1}]/[{len(detail_images)}]번째 이미지 번역 시작")
|
|
logger.debug(f"이미지 타입 : {type(detail_image)}")
|
|
returned_img = image_trans(detail_image, translator, 'translate')
|
|
image_to_clipboard(returned_img)
|
|
logger.debug("번역 완료 및 부텨넣기")
|
|
detail_content.send_keys(Keys.CONTROL, 'v')
|
|
detail_content.send_keys(Keys.ENTER)
|
|
|
|
logger.debug(f"{i+1}번째 이미지 붙여넣기 완료")
|
|
|
|
# translated_image_urls.append(returned_img_path)
|
|
|
|
logger.debug("이미지 번역 완료")
|
|
detail_content.send_keys(Keys.ENTER)
|
|
time.sleep(0.5)
|
|
|
|
except Exception as e:
|
|
logger.error(f"이미지 번역 중 에러발생 : {e}", exc_info=True)
|
|
|
|
finally:
|
|
|
|
# num_detail_images = len(detail_images)
|
|
# logger.debug(f"====기존 이미지 삭제=====\n DEL키 [{num_detail_images}]번 보내기")
|
|
# # DELETE 키를 50번 보내기
|
|
# for _ in range(num_detail_images + 5):
|
|
# detail_content.send_keys(Keys.DELETE)
|
|
|
|
logger.debug("====번역이미지 붙여넣기 완료=====")
|
|
|
|
|
|
# try:
|
|
# logger.debug("aicontent_html 입력시작")
|
|
|
|
# html_insert_btn_xpath="//div[@id='productMainContentContainerId']/div/div/div[2]/div[2]/div/div[1]/div[2]/div/div/button[7]"
|
|
# click_element(driver, 'XPATH', html_insert_btn_xpath, 5, 'ac')
|
|
# logger.debug("html_insert_btn_xpath 클릭")
|
|
|
|
# html_insert_TEXTAREA_xpath="/html/body/div[7]/div/div[3]/div/div[2]/div[1]/div/div/div[2]/div[2]/div[2]/div/div[2]/div[2]/textarea"
|
|
# click_element(driver, 'XPATH', html_insert_TEXTAREA_xpath, 5, 'ac')
|
|
# html_insert_TEXTAREA_element = return_element(driver, 'XPATH', 'html_insert_TEXTAREA_xpath', 10)
|
|
# html_insert_TEXTAREA_element.send_keys(aicontents)
|
|
# logger.debug("html 요소로 전송")
|
|
|
|
# html_insert_ok_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div[2]/div[2]/div/div[1]/div[2]/div/button[1]"
|
|
# click_element(driver, 'XPATH', html_insert_ok_xpath, 5, 'ac')
|
|
# logger.debug("html_insert_ok_xpath 클릭")
|
|
|
|
# logger.debug(f"AI 컨텐츠 입력완료")
|
|
# except Exception as e:
|
|
# logger.error(f"AI 컨텐츠 입력 중 에러발생 : {e}", exc_info=True)
|
|
|
|
|
|
detail_html(driver)
|
|
time.sleep(0.5)
|
|
textarea = detail_textarea(driver)
|
|
time.sleep(0.5)
|
|
detail_ai(driver, textarea, aicontents, combined_value)
|
|
time.sleep(0.5)
|
|
detail_html(driver)
|
|
time.sleep(0.5)
|
|
|
|
detail_content.send_keys(Keys.ENTER)
|
|
|
|
# try:
|
|
# num_detail_images = len(detail_images)
|
|
# logger.debug(f"====기존 이미지 삭제=====\n DEL키 [{num_detail_images}]번 보내기")
|
|
# # DELETE 키를 50번 보내기
|
|
# for _ in range(num_detail_images):
|
|
# detail_content.send_keys(Keys.DELETE)
|
|
# except Exception as e:
|
|
# logger.error(f"기존 이미지 삭제 중 에러발생 : {e}", exc_info=True)
|
|
|
|
logger.debug("상세페이지 편집 저장")
|
|
|
|
click_element(driver, "XPATH", save_button_xpath, 5, 'js')
|
|
time.sleep(0.5)
|
|
|
|
logger.debug("상세페이지 편집 완료") |