Initial Commit

This commit is contained in:
K.H.CHOI 2024-03-26 15:40:34 +09:00
commit 2008156df3
104 changed files with 15326 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
Lib/
Scripts/
__pycache__/
*.log
*.log.*
*.xlsx
pyvenv.cfg

BIN
NotoSans.ttf Normal file

Binary file not shown.

BIN
NotoSansKR-Bold.ttf Normal file

Binary file not shown.

BIN
NotoSansKR-ExtraBold.ttf Normal file

Binary file not shown.

BIN
NotoSansKR-Regular.ttf Normal file

Binary file not shown.

BIN
NotoSansKR-SemiBold.ttf Normal file

Binary file not shown.

4371
Percenty_SS_Code.json Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

227
SELENIUM_IDE/test1.side Normal file
View File

@ -0,0 +1,227 @@
{
"id": "30063f2a-106a-405f-91ab-b35d099d4e55",
"version": "2.0",
"name": "test1",
"url": "https://www.percenty.co.kr",
"tests": [{
"id": "767c39f9-96f2-45ab-ba8b-bd791d219e4e",
"name": "test1",
"commands": [{
"id": "2b3dcb84-c18d-4dbc-a30e-272e72ea958c",
"comment": "",
"command": "open",
"target": "/",
"targets": [],
"value": ""
}, {
"id": "17193eeb-fe07-47d5-815e-ef5bbe0ebae1",
"comment": "",
"command": "setWindowSize",
"target": "974x1032",
"targets": [],
"value": ""
}, {
"id": "8a1a14e5-22ae-4a78-9a58-2c11282ee55c",
"comment": "",
"command": "mouseOver",
"target": "css=.signList > .ant-btn-default > span",
"targets": [
["css=.signList > .ant-btn-default > span", "css:finder"],
["xpath=//div[@id='root']/div/div/header/div/div/div/button[2]/span", "xpath:idRelative"],
["xpath=//button[2]/span", "xpath:position"],
["xpath=//span[contains(.,'로그인')]", "xpath:innerText"]
],
"value": ""
}, {
"id": "73440b50-a5d0-4dc7-8592-a9636ed3f27d",
"comment": "",
"command": "click",
"target": "css=.signList > .ant-btn-default > span",
"targets": [
["css=.signList > .ant-btn-default > span", "css:finder"],
["xpath=//div[@id='root']/div/div/header/div/div/div/button[2]/span", "xpath:idRelative"],
["xpath=//button[2]/span", "xpath:position"],
["xpath=//span[contains(.,'로그인')]", "xpath:innerText"]
],
"value": ""
}, {
"id": "2a6ae23a-82f9-4d62-9eed-50c3fafd78b1",
"comment": "",
"command": "type",
"target": "css=.ant-input:nth-child(4)",
"targets": [
["css=.ant-input:nth-child(4)", "css:finder"],
["xpath=//input[@value='leensoo1nt@gmail.com']", "xpath:attributes"],
["xpath=//div[@id='root']/div/div/div/div[3]/input", "xpath:idRelative"],
["xpath=//input", "xpath:position"]
],
"value": "leensoo1nt@gmail.com"
}, {
"id": "a4b45e77-90f6-4517-a970-8f7005410129",
"comment": "",
"command": "click",
"target": "css=.ant-switch-handle",
"targets": [
["css=.ant-switch-handle", "css:finder"],
["xpath=//div[@id='root']/div/div/div/div[3]/div[3]/button/div", "xpath:idRelative"],
["xpath=//button/div", "xpath:position"]
],
"value": ""
}, {
"id": "03e10ed9-293c-4be5-ba11-fe96ce64a8cb",
"comment": "",
"command": "click",
"target": "css=.ant-input:nth-child(2)",
"targets": [
["css=.ant-input:nth-child(2)", "css:finder"],
["xpath=//input[@value='']", "xpath:attributes"],
["xpath=//div[@id='root']/div/div/div/div[4]/input", "xpath:idRelative"],
["xpath=//div[4]/input", "xpath:position"]
],
"value": ""
}, {
"id": "ad155faa-e5e3-4e83-8304-3070697f8c5d",
"comment": "",
"command": "type",
"target": "css=.ant-input:nth-child(2)",
"targets": [
["css=.ant-input:nth-child(2)", "css:finder"],
["xpath=//input[@value='송정윤']", "xpath:attributes"],
["xpath=//div[@id='root']/div/div/div/div[4]/input", "xpath:idRelative"],
["xpath=//div[4]/input", "xpath:position"]
],
"value": "송정윤"
}, {
"id": "32b19999-254f-4558-a52f-df9dd57489b7",
"comment": "",
"command": "click",
"target": "css=.ant-input:nth-child(1)",
"targets": [
["css=.ant-input:nth-child(1)", "css:finder"],
["xpath=//input[@type='password']", "xpath:attributes"],
["xpath=//div[@id='root']/div/div/div/div[5]/span[2]/input", "xpath:idRelative"],
["xpath=//span[2]/input", "xpath:position"]
],
"value": ""
}, {
"id": "d17b078a-fd39-4715-a592-23fb2816103a",
"comment": "",
"command": "type",
"target": "css=.ant-input:nth-child(1)",
"targets": [
["css=.ant-input:nth-child(1)", "css:finder"],
["xpath=//input[@type='password']", "xpath:attributes"],
["xpath=//div[@id='root']/div/div/div/div[5]/span[2]/input", "xpath:idRelative"],
["xpath=//span[2]/input", "xpath:position"]
],
"value": "asdf1234"
}, {
"id": "e2a17d06-36b6-4754-a02e-9da15fbf3f56",
"comment": "",
"command": "click",
"target": "css=.ant-btn-primary",
"targets": [
["css=.ant-btn-primary", "css:finder"],
["xpath=(//button[@type='button'])[2]", "xpath:attributes"],
["xpath=//div[@id='root']/div/div/div/div[7]/div/button", "xpath:idRelative"],
["xpath=//div[7]/div/button", "xpath:position"],
["xpath=//button[contains(.,'직원 로그인 하기')]", "xpath:innerText"]
],
"value": ""
}, {
"id": "21578e9c-aed8-4d4e-a80e-dee3d5945986",
"comment": "",
"command": "click",
"target": "xpath=//span[contains(.,'신규 상품 등록')]",
"targets": [
["css=.ant-menu-item-active > .ant-menu-title-content", "css:finder"],
["xpath=//ul[@id='rc-menu-uuid-97265-1-상품 관리-popup']/li[2]/span", "xpath:idRelative"],
["xpath=//li[3]/ul/li[2]/span", "xpath:position"],
["xpath=//span[contains(.,'신규 상품 등록')]", "xpath:innerText"]
],
"value": ""
}, {
"id": "7df01c7a-7ac6-4b4a-9036-e3235cdd86ef",
"comment": "",
"command": "click",
"target": "xpath=//div[2]/div/div/div[2]/div/div/button",
"targets": [
["css=#selected_background_id65a25a828e425f4b29ffdf53 .ant-col:nth-child(1) > .ant-btn", "css:finder"],
["xpath=(//button[@type='button'])[24]", "xpath:attributes"],
["xpath=//div[@id='selected_background_id65a25a828e425f4b29ffdf53']/div/div[2]/div/div/button", "xpath:idRelative"],
["xpath=//div[2]/div/div/div[2]/div/div/button", "xpath:position"],
["xpath=//button[contains(.,'세부사항 수정 및 업로드')]", "xpath:innerText"]
],
"value": ""
}, {
"id": "65a17317-76ca-4f4e-8018-dedcaa4146eb",
"comment": "",
"command": "click",
"target": "id=rc-tabs-0-tab-5",
"targets": [
["id=rc-tabs-0-tab-5", "id"],
["css=#rc-tabs-0-tab-5", "css:finder"],
["xpath=//div[@id='rc-tabs-0-tab-5']", "xpath:attributes"],
["xpath=//div/div/div/div/div/div/div/div/div[6]/div", "xpath:position"]
],
"value": ""
}, {
"id": "765a9fca-2e6e-4837-af48-64b8bdf7a2a2",
"comment": "",
"command": "click",
"target": "xpath=//button[contains(.,'브라우저 번역하기')]",
"targets": [
["css=.ant-tooltip-open", "css:finder"],
["xpath=(//button[@type='button'])[61]", "xpath:attributes"],
["xpath=//div[@id='productMainContentContainerId']/div/div/div/div/div/div[3]/button", "xpath:idRelative"],
["xpath=//div[3]/div/div/div[2]/div/div/div/div/div/div/div[3]/button", "xpath:position"],
["xpath=//button[contains(.,'브라우저 번역하기')]", "xpath:innerText"]
],
"value": ""
}, {
"id": "7113f07f-9c8d-4e56-a14c-236ea18e69e4",
"comment": "",
"command": "click",
"target": "css=.sc-gRfXlQ:nth-child(3) svg",
"targets": [
["css=.sc-gRfXlQ:nth-child(3) svg", "css:finder"]
],
"value": ""
}, {
"id": "032daeaa-852d-4a2e-9a9b-9cf7d89072e8",
"comment": "",
"command": "click",
"target": "css=.sc-gRfXlQ:nth-child(2) svg",
"targets": [
["css=.sc-gRfXlQ:nth-child(2) svg", "css:finder"]
],
"value": ""
}, {
"id": "8f6edc6b-1dda-4710-b697-b086b189431e",
"comment": "",
"command": "click",
"target": "css=.sc-gRfXlQ:nth-child(1) path",
"targets": [
["css=.sc-gRfXlQ:nth-child(1) path", "css:finder"]
],
"value": ""
}, {
"id": "a6439e5b-6450-4b79-8192-e507b154f514",
"comment": "",
"command": "close",
"target": "",
"targets": [],
"value": ""
}]
}],
"suites": [{
"id": "7df6f58a-0212-4d65-93d2-2adbee34b1f5",
"name": "Default Suite",
"persistSession": false,
"parallel": false,
"timeout": 300,
"tests": ["767c39f9-96f2-45ab-ba8b-bd791d219e4e"]
}],
"urls": ["https://www.percenty.co.kr/"],
"plugins": []
}

0
ai/__init__.py Normal file
View File

30
ai/bard.py Normal file
View File

@ -0,0 +1,30 @@
import requests
from bardapi.constants import SESSION_HEADERS
from bardapi import Bard
token = "fQjBOEA1p3Kll3HB0SXY_8RojD6d3csOZlixQq_k_n25aRIBg0ItfuWJv3GPYupJNHcbaA."
session = requests.Session()
session.headers = SESSION_HEADERS
session.cookies.set("__Secure-1PSID", token)
session.cookies.set("__Secure-1PSIDTS", "sidts-CjEBPVxjSvcFFGMWV_IsjKDzTXfK0x-AbSLBFEvPY1YgLs7dzByOTtUKkbh_6MSmb_leEAA")
session.cookies.set("__Secure-1PSIDCC", "ABTWhQEyG0iO-oFXI_5FuVIVBvQcoLz0Zy-Gi__w03M0VegcVLsBVE-xX_bQ9mIpd4CxVcAkvA")
bard = Bard(token=token, session=session)
#print(bard.get_answer("나와 내 동년배들이 좋아하는 뉴진스에 대해서 알려줘")['content'])
#print(bard.get_answer("나와 내 동년배들이 좋아하는 뉴진스에 대해서 알려줘"))
# 이미지 URL
image_url = 'https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/6575b191d39b6b71ccac77ce/a5ffe56b-3349-4dca-bc36-fa569a68c337.jpg'
# requests를 사용하여 이미지 다운로드
response = requests.get(image_url)
response.raise_for_status() # HTTP 요청 에러 체크
# 이미지 데이터를 Bard에 전달
image_data = response.content
bard_answer = bard.ask_about_image('이미지의 상품을 한국의 쇼핑몰에서 판매할때 적절한 홍보문구를 3줄로 만들어줘.', image_data)
print(bard_answer['content'])

76
ai/bard_img.py Normal file
View File

@ -0,0 +1,76 @@
# from ai.unlock_cookies import fetch_cookies
# from bardapi.constants import SESSION_HEADERS
# from bardapi import Bard
import requests
import re
# def get_cookies(domain_name):
# """특정 도메인의 쿠키를 가져오는 함수"""
# # unlock_cookies 함수를 호출하여 쿠키 파일의 잠금을 해제하고,
# # 특정 도메인에 대한 쿠키를 가져옵니다.
# cookies = fetch_cookies(domain_name)
# #cookies = new_fetch_cookies(domain_name)
# print(f"cookies = {cookies}")
# # 쿠키를 딕셔너리 형태로 변환합니다.
# cookies_dict = {}
# for cookie in cookies:
# cookies_dict[cookie.name] = cookie.value
# return cookies_dict
# bard.google.com 도메인의 쿠키를 가져옵니다.
cookies = get_cookies(".google.com")
# 필요한 쿠키를 추출합니다.
token1 = cookies.get("__Secure-1PSID")
print(f"token1 = {token1}")
token2 = cookies.get("__Secure-1PSIDTS")
print(f"token2 = {token2}")
token3 = cookies.get("__Secure-1PSIDCC")
print(f"token3 = {token3}")
session = requests.Session()
session.headers = SESSION_HEADERS
session.cookies.set("__Secure-1PSID", token1)
session.cookies.set("__Secure-1PSIDTS", token2)
session.cookies.set("__Secure-1PSIDCC", token3)
bard = Bard(token=token1, session=session)
#print(bard.get_answer("나와 내 동년배들이 좋아하는 뉴진스에 대해서 알려줘")['content'])
#print(bard.get_answer("나와 내 동년배들이 좋아하는 뉴진스에 대해서 알려줘"))
def extract_numbered_lines(text):
# 정규 표현식 패턴: 숫자 + 점 + 공백으로 시작하고, 줄바꿈 문자가 나올 때까지의 내용을 찾음
pattern = r'\d\.\s.+?(?=\n|$)'
matches = re.findall(pattern, text)
return "\n".join(matches)
def bard_img(image_url):
# 이미지 URL
#image_url = 'https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/6575b191d39b6b71ccac77ce/a5ffe56b-3349-4dca-bc36-fa569a68c337.jpg'
# requests를 사용하여 이미지 다운로드
response = requests.get(image_url)
response.raise_for_status() # HTTP 요청 에러 체크
# 이미지 데이터를 Bard에 전달
image_data = response.content
bard_answer = bard.ask_about_image('이미지의 상품을 한국의 쇼핑몰에서 판매할때 적절한 홍보문구를 3줄로 만들어줘.', image_data)
contents = extract_numbered_lines(bard_answer['content'])
print("이미지 분석 결과입니다")
print(contents)
result = extract_numbered_lines(contents)
print("분석 결과 파싱 데이터입니다")
print(result)
return result

55
ai/bard_test1.py Normal file
View File

@ -0,0 +1,55 @@
from ai.unlock_cookies import fetch_cookies
from bardapi.constants import SESSION_HEADERS
from bardapi import Bard
import requests
def get_cookies(domain_name):
"""특정 도메인의 쿠키를 가져오는 함수"""
# unlock_cookies 함수를 호출하여 쿠키 파일의 잠금을 해제하고,
# 특정 도메인에 대한 쿠키를 가져옵니다.
cookies = fetch_cookies(domain_name)
#cookies = new_fetch_cookies(domain_name)
print(f"cookies = {cookies}")
# 쿠키를 딕셔너리 형태로 변환합니다.
cookies_dict = {}
for cookie in cookies:
cookies_dict[cookie.name] = cookie.value
return cookies_dict
# bard.google.com 도메인의 쿠키를 가져옵니다.
cookies = get_cookies(".google.com")
# 필요한 쿠키를 추출합니다.
token1 = cookies.get("__Secure-1PSID")
print(f"token1 = {token1}")
token2 = cookies.get("__Secure-1PSIDTS")
print(f"token2 = {token2}")
token3 = cookies.get("__Secure-1PSIDCC")
print(f"token3 = {token3}")
session = requests.Session()
session.headers = SESSION_HEADERS
session.cookies.set("__Secure-1PSID", token1)
session.cookies.set("__Secure-1PSIDTS", token2)
session.cookies.set("__Secure-1PSIDCC", token3)
bard = Bard(token=token1, session=session)
#print(bard.get_answer("나와 내 동년배들이 좋아하는 뉴진스에 대해서 알려줘")['content'])
#print(bard.get_answer("나와 내 동년배들이 좋아하는 뉴진스에 대해서 알려줘"))
# 이미지 URL
image_url = 'https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/6575b191d39b6b71ccac77ce/a5ffe56b-3349-4dca-bc36-fa569a68c337.jpg'
# requests를 사용하여 이미지 다운로드
response = requests.get(image_url)
response.raise_for_status() # HTTP 요청 에러 체크
# 이미지 데이터를 Bard에 전달
image_data = response.content
bard_answer = bard.ask_about_image('이미지의 상품을 한국의 쇼핑몰에서 판매할때 적절한 홍보문구를 3줄로 만들어줘.', image_data)
print(bard_answer['content'])

150
ai/compare.py Normal file
View File

@ -0,0 +1,150 @@
# 필요한 라이브러리를 임포트합니다.
import torch
import torchvision
import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.models.detection as detection
from PIL import Image, ImageDraw
import requests
from io import BytesIO
import numpy as np
'''
import torch
import torchvision.models as models
# GPU 사용 가능 여부 확인
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# 모델을 device로 이동
model = torchvision.models.detection.maskrcnn_resnet50_fpn(weights=torchvision.models.detection.MaskRCNN_ResNet50_FPN_Weights.DEFAULT).to(device)
model.eval()
# 데이터도 GPU로 이동
# 예를 들어, 이미지를 모델에 입력하기 전에
image = image.to(device)
'''
# GPU 사용 설정
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 객체 탐지 모델 로드 (Mask R-CNN)
# detection_model = torchvision.models.detection.maskrcnn_resnet50_fpn(weights=torchvision.models.detection.MaskRCNN_ResNet50_FPN_Weights.DEFAULT).to(device)
# fastrcnn_model = torchvision.models.detection.fasterrcnn_resnet50_fpn(weights=torchvision.models.detection.FasterRCNN_ResNet50_FPN_Weights.DEFAULT).to(device)
# detection_model = torchvision.models.detection.retinanet_resnet50_fpn(weights=torchvision.models.detection.RetinaNet_ResNet50_FPN_Weights.DEFAULT).to(device)
# detection_model.eval()
# 사전 학습된 모델을 불러옵니다.
# feature_model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT).to(device)
# feature_model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT).to(device)
# feature_model = models.resnet101(weights=models.ResNet101_Weights.DEFAULT).to(device)
feature_model = models.vgg16(weights=models.VGG16_Weights.DEFAULT).to(device)
# feature_model = models.inception_v3(weights=models.Inception_V3_Weights.DEFAULT).to(device)
# feature_model = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.DEFAULT).to(device)
# feature_model = models.vit_b_16(weights=models.ViT_B_16_Weights.DEFAULT).to(device)
feature_model.eval()
# print("모델을 성공적으로 불러왔습니다.")
# model.eval() # 모델을 평가 모드로 설정합니다.
# InceptionV3 전처리 예시
# preprocess = transforms.Compose([
# transforms.Resize(299),
# transforms.CenterCrop(299),
# transforms.ToTensor(),
# transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
# ])
# # # 이미지 전처리를 위한 변환을 정의합니다.
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
# print("이미지 전처리 설정을 완료했습니다.")
# 이미지 URL로부터 이미지를 다운로드하고 전처리하는 함수를 정의합니다.
# 이미지 다운로드 및 전처리
def download_and_preprocess_image(url):
response = requests.get(url)
img = Image.open(BytesIO(response.content)).convert('RGB')
img_preprocessed = preprocess(img).to(device)
return img_preprocessed
# 객체 탐지 및 배경 제거
# def remove_background(image_tensor):
# with torch.no_grad():
# prediction = detection_model([image_tensor])[0]
# # 가장 확률이 높은 객체의 마스크 추출
# mask = prediction['masks'][0, 0] > 0.5
# image_tensor_masked = image_tensor * mask.float()
# return image_tensor_masked
# 이미지 특징을 추출하는 함수를 정의합니다.
def extract_features(image_tensor):
with torch.no_grad():
# feature_model의 출력을 조정해야 할 수도 있음 (여기서는 단순화를 위해 직접 사용)
features = feature_model(image_tensor.unsqueeze(0))
return features
# 코사인 유사도 계산
def cosine_similarity(feature1, feature2):
cos = torch.nn.CosineSimilarity(dim=1, eps=1e-6)
similarity = cos(feature1, feature2)
return similarity
# 이미지 비교 및 가장 비슷한 이미지 찾기
def find_most_similar_image(source_url, target_urls):
source_image = download_and_preprocess_image(source_url)
source_features = extract_features(source_image)
# source_image_masked = remove_background(source_image)
# source_features = extract_features(source_image_masked)
similarities = []
for url in target_urls:
target_image = download_and_preprocess_image(url)
# target_image_masked = remove_background(target_image)
# target_features = extract_features(target_image_masked)
target_features = extract_features(target_image)
similarity = cosine_similarity(source_features, target_features)
similarities.append(similarity.item())
print(f" 이미지 유사도 점수: {similarity.item()}")
most_similar_index = np.argmax(similarities)
most_similar_score = similarities[most_similar_index]
return most_similar_index + 1, most_similar_score
# 이미지 비교
def find_most_similar_image_by_one(source_url, target_urls):
source_image = download_and_preprocess_image(source_url)
source_features = extract_features(source_image)
target_image = download_and_preprocess_image(url)
target_features = extract_features(target_image)
similarity = cosine_similarity(source_features, target_features)
return similarity
# # 주어진 이미지 URL 예시와 비교를 시작합니다.
# try:
# source_image_url = "https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65a3cb4a8e425f4b290089a1/344e23cb-669c-45cd-8df1-42b85fbf0c93.jpg"
# target_image_urls = [
# "https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65a3cb4a8e425f4b290089a1/28f0dd02-21c2-4314-b863-7a6076c92612.jpg",
# "https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65a3cb4a8e425f4b290089a1/99033f24-61d4-4a0e-9ad1-5dea457c33c4.jpg",
# "https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65a3cb4a8e425f4b290089a1/ecb80f2f-bc95-4e64-801a-ce8f4cdf13a4.jpg",
# "https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65a3cb4a8e425f4b290089a1/a3b9a3c1-ac04-4e55-be6d-f56eaa4187bb.jpg",
# "https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65a3cb4a8e425f4b290089a1/7f42c677-0bc7-4e71-9a99-6bd14163d597.jpg",
# "https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65a3cb4a8e425f4b290089a1/a39c5c73-b568-42fe-a9de-f364e2d31139.jpg"
# ]
# most_similar_index, similarity_score = find_most_similar_image(source_image_url, target_image_urls)
# print(f"\n가장 비슷한 이미지: 이미지 {most_similar_index}\n유사도 점수: {similarity_score}")
# except Exception as e:
# print(f"에러 발생: {e}")

158
ai/deepl.py Normal file
View File

@ -0,0 +1,158 @@
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from tqdm import tqdm
from selenium_stealth import stealth
from fake_useragent import UserAgent
def trans(original_text):
# Start a Selenium driver
options = webdriver.ChromeOptions()
ua = UserAgent()
options.add_argument(f"--user-agent={ua.random}") # 랜덤 user_agent 사용
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument('--ignore-certificate-errors')
options.add_argument('--ssl-protocol=any')
options.add_argument('--disable-cache')
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
# selenium-stealth 설정 적용
stealth(driver,
languages=["en-US", "en"],
vendor="Google Inc.",
platform="Win32",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hairline=True,
)
# Reach the deepL website
deepl_url = 'https://www.deepl.com/ko/translator' # 한국어
driver.get(deepl_url)
# Define text to translate
# texts_to_translate = [
# 'Shuffle data after an epoch!',
# 'StaleElementReferenceException: Message: stale element reference: element is not attached to the page document',
# 'Hello python!',
# 'Using Selenium and deepL to automate the translation!'
# ]
# for text_to_translate in tqdm(original_text):
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".min-h-0:nth-child(1) > div:nth-child(1)"))).send_keys(original_text)
time.sleep(3) # Adjust sleep time based on network speed and response time
# Improved wait for translation to appear
try:
WebDriverWait(driver, 10).until(
lambda driver: driver.find_element(By.CSS_SELECTOR, '[data-testid="translator-target-input"]').text.strip() != "")
translation_text = driver.find_element(By.CSS_SELECTOR, '[data-testid="translator-target-input"]')
content = translation_text.text
except Exception as e:
print("Error fetching translation:", e)
content = "Translation failed"
# Display results
# print('#' * 50)
# print('Original :', original_text)
# print('Translation :', content)
# print('#' * 50)
# Clear button
# try:
# # button = driver.find_element(By.CSS_SELECTOR, 'div.lmt__clear_text_button_wrapper button')
# # button = driver.find_element(By.CSS_SELECTOR, '#translator-source-clear-button path')
# button = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-testid="translator-source-clear-button"]')))
# button.click()
# except Exception as e:
# print("Error clicking clear button:", e)
# Close the driver
driver.quit()
return content
# result = trans("全铜芯卧式电机不锈钢底座厚")
# print(f"입력 : 全铜芯卧式电机不锈钢底座厚")
# print(f"결과 : {result}")
# ===========================================
# import time
# from selenium import webdriver
# from selenium.webdriver.chrome.options import Options
# from selenium.webdriver.chrome.service import Service
# from selenium.webdriver.common.by import By
# from selenium.webdriver.support.ui import WebDriverWait
# from selenium.webdriver.support import expected_conditions as EC
# from tqdm import tqdm
# from selenium_stealth import stealth
# from fake_useragent import UserAgent
# def trans(original_text):
# # Start a Selenium driver
# options = webdriver.ChromeOptions()
# ua = UserAgent()
# options.add_argument(f"--user-agent={ua.random}") # 랜덤 user_agent 사용
# options.add_argument("--disable-blink-features=AutomationControlled")
# options.add_argument('--ignore-certificate-errors')
# options.add_argument('--ssl-protocol=any')
# options.add_argument('--disable-cache')
# driver = webdriver.Chrome(options=options)
# # selenium-stealth 설정 적용
# stealth(driver,
# languages=["en-US", "en"],
# vendor="Google Inc.",
# platform="Win32",
# webgl_vendor="Intel Inc.",
# renderer="Intel Iris OpenGL Engine",
# fix_hairline=True,
# )
# # DeepL 웹사이트 접속
# deepl_url = 'https://www.deepl.com/ko/translator'
# driver.get(deepl_url)
# # 번역할 텍스트 입력
# input_area = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".min-h-0:nth-child(1) > div:nth-child(1)")))
# input_area.send_keys(original_text)
# time.sleep(3) # 네트워크 속도에 따라 조정
# # 번역 결과 대기 및 추출
# try:
# WebDriverWait(driver, 10).until(
# lambda driver: driver.find_element(By.CSS_SELECTOR, '[data-testid="translator-target-input"]').text.strip() != "")
# translation_text = driver.find_element(By.CSS_SELECTOR, '[data-testid="translator-target-input"]')
# translated_content = translation_text.text
# except Exception as e:
# print("Error fetching translation:", e)
# translated_content = "Translation failed"
# # # Clear 버튼 클릭
# # try:
# # clear_button = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-testid="translator-source-clear-button"]')))
# # clear_button.click()
# # except Exception as e:
# # print("Error clicking clear button:", e)
# # 드라이버 종료
# driver.quit()
# return translated_content
# trans("微博登录")

51
ai/gemini.py Normal file
View File

@ -0,0 +1,51 @@
import requests
import google.generativeai as genai
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class ImageDescriptionGenerator:
def __init__(self, api_key):
"""API 키를 사용하여 객체를 초기화합니다."""
self.api_key = api_key
genai.configure(api_key=self.api_key)
self.model = genai.GenerativeModel('gemini-pro-vision')
def download_image(self, image_url):
"""이미지 URL에서 이미지를 다운로드하고 바이트 데이터를 반환합니다."""
response = requests.get(image_url)
response.raise_for_status() # 에러가 발생했을 경우 예외를 발생시킵니다.
return response.content
def generate_description(self, image_url, title, top_k=3):
"""이미지 URL을 받아 해당 이미지에 대한 설명을 생성하고 반환합니다."""
# 'gemini-pro-vision' 모델을 사용하여 이미지 설명 생성
# prompt = f"이미지의 상품명은 {title} 1. 이 상품이 무엇인지, 어떤 용도로 사용하는지 설명해줘 2. 이 상품의 배송비 산정을 위해 대략적인 무게를 kg으로 단답형으로 추정해줘. 3. 이 이미지의 상품을 한국의 온라인 쇼핑몰에서 팔기위한 홍보문구를 상품이미지에 맞게 3줄 ~ 4줄 정도 만들어줘. 4.대답하는 방식은 [상품의 홍보문구] 여러줄, [상품의 용도] 자세히, [상품의 무게] 형식과 순서로 해줘."
prompt = f"이미지의 상품명은 {title} 1. 이 상품이 무엇인지, 어떤 용도로 사용하는지 설명해줘 2. 이 상품의 배송비 산정을 위해 대략적인 무게를 kg으로 단답형으로 추정해줘. 3. 이 이미지의 상품을 한국의 온라인 쇼핑몰에서 팔기위한 홍보문구를 상품이미지에 맞게 3줄 ~ 4줄 정도 만들어줘."
try:
image_data = self.download_image(image_url)
response = self.model.generate_content([prompt, {"mime_type": "image/jpeg", "data": image_data}])
# 후보 결과 확인 및 처리
if response.candidates and len(response.candidates) > 0:
logger.debug(f"{response.text}")
return response.text
else:
# 후보 결과가 없거나 유효하지 않을 경우 기본 응답 반환
return '''
### 상품의 용도\n\n
- 에러 발생: 유효한 상품 설명을 제공할 없습니다.\n\n
### 상품의 홍보문구\n\n
- 에러 발생: 유효한 홍보문구를 제공할 없습니다.\n\n
### 상품의 무게\n\n
- 에러 발생: 유효한 상품 무게를 제공할 없습니다.
'''
except ValueError as e:
logger.debug(f"오류 발생: {e}")
return "오류 발생: 유효한 응답을 생성할 수 없습니다."
except Exception as e:
logger.debug(f"예상치 못한 오류 발생: {e}")
return "예상치 못한 오류 발생: 처리할 수 없습니다."

38
ai/gpt4.py Normal file
View File

@ -0,0 +1,38 @@
import openai
import requests
from PIL import Image
from io import BytesIO
# OpenAI API 키 설정
openai.api_key = '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'
def download_image(image_url):
"""주어진 URL에서 이미지를 다운로드하고 PIL 이미지 객체로 반환합니다."""
response = requests.get(image_url)
image = Image.open(BytesIO(response.content))
return image
def generate_product_description(image_url, product_name):
"""이미지와 상품명을 사용하여 상품 설명을 생성합니다."""
# 이미지 다운로드 (이 예시에서는 사용하지 않음, 실제 API 요청에 이미지 직접 사용 불가)
image = download_image(image_url)
# 상품명을 사용하여 챗GPT에 설명 요청
prompt = f"상품명: {product_name}\n상품의 용도, 설명, 무게, 홍보 문구를 작성해주세요."
response = openai.Completion.create(
model="text-davinci-003", # 모델 버전에 따라 변경 가능
prompt=prompt,
temperature=0.7,
max_tokens=150
)
# 생성된 텍스트 반환
return response.choices[0].text.strip()
# 예시 사용
image_url = "https://example.com/your-image.jpg"
product_name = "예시 상품명"
description = generate_product_description(image_url, product_name)
print(description)

91
ai/test_gen.py Normal file
View File

@ -0,0 +1,91 @@
import requests
import google.generativeai as genai
def download_image(image_url):
"""이미지 URL에서 이미지를 다운로드하고 바이트 데이터를 반환합니다."""
response = requests.get(image_url)
response.raise_for_status() # 에러가 발생했을 경우 예외를 발생시킵니다.
return response.content
def main(image_url='https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65df13cf5860eb317389438b/1d439045-b47b-4c4a-994b-11bb2862a6d4.jpg', title='동축케이블압착기', top_k=3):
api_key = 'AIzaSyCER9mD617P5OGaoHCK7drsTkmXUIzFn4U' # 실제 API 키로 교체하세요.
genai.configure(api_key=api_key)
model = genai.GenerativeModel('gemini-pro-vision')
prompt = f"이미지의 상품명은 {title} 1. 이 상품이 무엇인지, 어떤 용도로 사용하는지 설명해줘 2. 이 상품의 배송비 산정을 위해 대략적인 무게를 kg으로 단답형으로 추정해줘. 3. 이 이미지의 상품을 한국의 온라인 쇼핑몰에서 팔기위한 홍보문구를 상품이미지에 맞게 3줄 ~ 4줄 정도 만들어줘. 4.대답하는 방식은 [상품의 홍보문구] 여러줄, [상품의 용도] 자세히, [상품의 무게] 형식과 순서로 해줘."
try:
image_data = download_image(image_url)
response = model.generate_content([prompt, {"mime_type": "image/jpeg", "data": image_data}])
# 후보 결과 확인 및 처리
if response.candidates and len(response.candidates) > 0:
print(f"{response.text}")
return response.text
else:
# 후보 결과가 없거나 유효하지 않을 경우 기본 응답 반환
return '''
### 상품의 용도\n\n
- 에러 발생: 유효한 상품 설명을 제공할 없습니다.\n\n
### 상품의 홍보문구\n\n
- 에러 발생: 유효한 홍보문구를 제공할 없습니다.\n\n
### 상품의 무게\n\n
- 에러 발생: 유효한 상품 무게를 제공할 없습니다.
'''
except ValueError as e:
print(f"오류 발생: {e}")
return "오류 발생: 유효한 응답을 생성할 수 없습니다."
except Exception as e:
print(f"예상치 못한 오류 발생: {e}")
return "예상치 못한 오류 발생: 처리할 수 없습니다."
def _process_description(description):
"""
Gemini에서 추출한 설명을 필요한 형식으로 가공합니다.
Args:
description: Gemini에서 추출한 설명
Returns:
필요한 형식으로 가공된 설명
"""
processed_description = description
# # 불필요한 문자열 제거
# processed_description = description.replace("**", "").replace("**", "")
# # 줄 바꿈 문자 변경
# processed_description = processed_description.replace("\n", "<br>")
# 필요한 답변 형식에 맞게 정렬
answer = "### 상품의 용도\n\n"
answer += processed_description.split("###")[1]
answer += "\n\n### 상품의 홍보문구\n\n"
answer += processed_description.split("###")[2]
answer += "\n\n### 상품의 무게\n\n"
answer += processed_description.split("###")[3]
return answer
def _validate_description(description):
"""
Gemini에서 추출한 설명을 검사하여 유효한지 확인합니다.
Args:
    description: Gemini에서 추출한 설명
Returns:
    True - 유효한 설명, False - 유효하지 않은 설명
"""
# 필요한 정보가 모두 포함되어 있는지 확인
if not all(x in description for x in ["상품의 홍보문구", "상품의 용도", "상품의 무게"]):
return False
# ... 추가적인 유효성 검사 코드
return True
if __name__ == "__main__":
main()

77
ai/unlock_cookies.py Normal file
View File

@ -0,0 +1,77 @@
'''
Proof of concept way to get cookies from chrome on Windows.. even when they're locked.
Does not require admin rights.
Includes a pure-python version of release_file_lock from:
https://github.com/thewh1teagle/rookie/blob/02995bbbb692f775e12368e7fb2b728775c88ddd/rookie-rs/src/winapi.rs#L63
(C) - MIT License 2023 - Charles Machalow
'''
import os
from ctypes import windll, byref, create_unicode_buffer, pointer, WINFUNCTYPE
from ctypes.wintypes import DWORD, WCHAR, UINT
import browser_cookie3 # pip install browser-cookie3
import backoff # pip install backoff
ERROR_SUCCESS = 0
ERROR_MORE_DATA = 234
RmForceShutdown = 1
cookies_path = os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\User Data\Default\Network\Cookies")
rstrtmgr = windll.LoadLibrary("Rstrtmgr")
@WINFUNCTYPE(None, UINT)
def callback(percent_complete: UINT) -> None:
print(f"Unlocking file status: {percent_complete}% done")
def unlock_cookies():
session_handle = DWORD(0)
session_flags = DWORD(0)
session_key = (WCHAR * 256)()
result = DWORD(rstrtmgr.RmStartSession(byref(session_handle), session_flags, session_key)).value
if result != ERROR_SUCCESS:
raise RuntimeError(f"RmStartSession returned non-zero result: {result}")
try:
result = DWORD(rstrtmgr.RmRegisterResources(session_handle, 1, byref(pointer(create_unicode_buffer(cookies_path))), 0, None, 0, None)).value
if result != ERROR_SUCCESS:
raise RuntimeError(f"RmRegisterResources returned non-zero result: {result}")
proc_info_needed = DWORD(0)
proc_info = DWORD(0)
reboot_reasons = DWORD(0)
result = DWORD(rstrtmgr.RmGetList(session_handle, byref(proc_info_needed), byref(proc_info), None, byref(reboot_reasons))).value
if result not in (ERROR_SUCCESS, ERROR_MORE_DATA):
raise RuntimeError(f"RmGetList returned non-successful result: {result}")
if proc_info_needed.value:
result = DWORD(rstrtmgr.RmShutdown(session_handle, RmForceShutdown, callback)).value
if result != ERROR_SUCCESS:
raise RuntimeError(f"RmShutdown returned non-successful result: {result}")
else:
print("File is not locked")
finally:
result = DWORD(rstrtmgr.RmEndSession(session_handle)).value
if result != ERROR_SUCCESS:
raise RuntimeError(f"RmEndSession returned non-successful result: {result}")
# Use backoff here since there is a race condition between unlocking the file and reading it.
# Technically we're killing a process within chrome that holds the lock. Chrome can/will restart it,
# .. so we have to fetch cookies before it re-locks the file. Generally on my system we get them
# .... though one time we didn't. I think it has to do with opening a new tab.. idk. Maybe not necessary?
@backoff.on_exception(backoff.constant, PermissionError, max_tries=5)
def fetch_cookies(domain_name):
unlock_cookies()
return browser_cookie3.chrome(domain_name=domain_name)

25
cfg_update.py Normal file
View File

@ -0,0 +1,25 @@
import os
# 환경 변수에서 정보 가져오기
python_home = os.environ.get('MY_PYTHON_HOME')
python_executable = os.environ.get('MY_PYTHON_EXECUTABLE')
pyvenv_cfg_path = os.path.join(python_home, '..', 'pyvenv.cfg')
# pyvenv.cfg 파일 수정
with open(pyvenv_cfg_path, 'r') as file:
cfg_content = file.readlines()
with open(pyvenv_cfg_path, 'w') as file:
for line in cfg_content:
if line.startswith('home ='):
file.write(f'home = {python_home}\n')
elif line.startswith('executable ='):
file.write(f'executable = {python_executable}\n')
else:
file.write(line)
# Qt 플러그인 경로 설정
qt_plugin_path = os.path.join(python_home, '../Lib/site-packages/PyQt5/Qt5/plugins', 'QtPlugins') # 예제 경로, 실제 경로로 조정 필요
os.environ['QT_PLUGIN_PATH'] = qt_plugin_path
print("pyvenv.cfg 파일이 업데이트 되었습니다.")

70
com.py Normal file
View File

@ -0,0 +1,70 @@
import torch
import torchvision.models as models
import torchvision.transforms as transforms
from PIL import Image
import requests
from io import BytesIO
import numpy as np
# 모델 불러오기
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
model.eval()
# 이미지 전처리 변환 정의
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
# 이미지 다운로드 및 전처리 함수
def download_and_preprocess_image(url):
try:
response = requests.get(url)
img = Image.open(BytesIO(response.content)).convert('RGB')
img_preprocessed = preprocess(img)
return img_preprocessed
except Exception as e:
print(f"이미지 다운로드 및 전처리 실패: {e}")
return None
# 이미지 특징 추출 함수
def extract_features(image):
with torch.no_grad():
features = model(image.unsqueeze(0))
return features
# 코사인 유사도 계산 함수
def cosine_similarity(feature1, feature2):
cos = torch.nn.CosineSimilarity(dim=1, eps=1e-6)
similarity = cos(feature1, feature2)
return similarity
# 가장 비슷한 이미지 찾기 함수
def find_most_similar_image(source_url, target_urls):
source_image = download_and_preprocess_image(source_url)
source_features = extract_features(source_image)
similarities = []
for url in target_urls:
target_image = download_and_preprocess_image(url)
if target_image is None:
continue
target_features = extract_features(target_image)
similarity = cosine_similarity(source_features, target_features)
similarities.append(similarity.item())
most_similar_index = np.argmax(similarities)
most_similar_url = target_urls[most_similar_index]
most_similar_score = similarities[most_similar_index]
return most_similar_url, most_similar_score
# 실제 이미지 URL 사용
source_image_url = "https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65e9ca5a8941d454f61ecb75/7a287e6a-b7cf-4d2d-97fc-5317be80ede1.jpg"
target_image_urls = [
"https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65e9ca5a8941d454f61ecb75/73709918-af96-4e51-b488-8bfb67c2eaf3.jpg",
"https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65e9ca5a8941d454f61ecb75/c6c92af6-23eb-4dec-ad33-ad4718200835.jpg",
"https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/65e9ca5a8941d454f61ecb75/2d5148ac-082c-4539-a901-4a37b909b8c8.jpg",
]
most_similar_url, similarity_score = find_most_similar_image(source_image_url, target_image_urls)
print(f"가장 비슷한 이미지 URL: {most_similar_url}, 유사도 점수: {similarity_score}")

275
compare.py Normal file
View File

@ -0,0 +1,275 @@
import cv2
import numpy as np
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QPushButton, QFileDialog, QLineEdit
import requests
from PIL import Image
import os
import tempfile
class ImageThread(QThread):
finished = pyqtSignal(list)
def __init__(self, original_img_path, target_img_paths):
super().__init__()
self.original_img_path = original_img_path
self.target_img_paths = target_img_paths
def run(self):
# 원본 이미지 읽기
original_img = cv2.imread(self.original_img_path)
# 대상 이미지 연관도 분석
similarities = []
for target_img_path in self.target_img_paths:
target_img = cv2.imread(target_img_path)
similarity = self.calculate_similarity(original_img, target_img)
similarities.append(similarity)
print(f"{target_img_path} 의 연관도 : {similarity}")
# 연관도가 높은 순으로 정렬 및 결과 추출
# sorted_indices = np.argsort(similarities)[::-1]
sorted_indices = np.argsort(similarities)
sorted_target_img_paths = [self.target_img_paths[i] for i in sorted_indices]
self.finished.emit(sorted_target_img_paths)
def calculate_similarity(self, img1, img2):
# 이미지 특징 추출 (ORB)
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# 특징 매칭 (BFMatcher)
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
# 좋은 매칭만 추출
good_matches = [m for m in matches if m.distance < 0.75]
# 연관도 계산
similarity = len(good_matches) / len(kp1)
return similarity
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Image Similarity Analysis")
self.main_widget = QWidget()
self.setCentralWidget(self.main_widget)
# 레이아웃 설정
layout = QVBoxLayout()
self.main_widget.setLayout(layout)
# 원본 이미지 선택
self.original_img_label = QLabel("원본 이미지")
layout.addWidget(self.original_img_label)
self.original_img_path_edit = QLineEdit()
self.original_img_path_edit.setPlaceholderText("원본 이미지 경로를 입력하세요")
layout.addWidget(self.original_img_path_edit)
# 대상 이미지 선택
self.target_img_labels = []
self.target_img_path_edits = []
for i in range(5):
label = QLabel(f"대상 이미지 {i+1}")
layout.addWidget(label)
self.target_img_labels.append(label)
edit = QLineEdit()
edit.setPlaceholderText(f"대상 이미지 {i+1} 경로를 입력하세요")
layout.addWidget(edit)
self.target_img_path_edits.append(edit)
# 비교 버튼
self.compare_button = QPushButton("비교")
self.compare_button.clicked.connect(self.compare_images)
layout.addWidget(self.compare_button)
# 팝업 창 설정
self.popup_window = QWidget()
self.popup_window.setWindowTitle("비교 결과")
self.popup_layout = QVBoxLayout()
self.popup_window.setLayout(self.popup_layout)
# 팝업 창에 원본 이미지 표시
self.original_img_popup_label = QLabel()
self.popup_layout.addWidget(self.original_img_popup_label)
# 팝업 창에 결과 표시
self.target_img_popup_labels = []
for i in range(3):
label = QLabel()
self.popup_layout.addWidget(label)
self.target_img_popup_labels.append(label)
def download_and_process_images(self, urls):
"""
URL 리스트에서 이미지를 다운받아 'img' 폴더에 저장합니다.
Args:
urls (list): 이미지 URL 리스트
Returns:
list: 로컬 파일 경로 리스트
"""
# 'img' 폴더 경로 확인 및 생성
img_folder_path = os.path.join(os.getcwd(), 'img')
if not os.path.exists(img_folder_path):
os.makedirs(img_folder_path)
local_paths = []
for i, url in enumerate(urls):
# if i == 0:
# # filename = "original.jpg"
# else:
filename = f"target{i}.jpg"
local_path = os.path.join(img_folder_path, filename)
# 이미지 다운로드
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"DNT": "1", # Do Not Track 요청 헤더 (사용자의 추적을 거부)
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1", # https로의 업그레이드를 요청
"Cache-Control": "max-age=0", # 캐시된 콘텐츠를 재사용하지 않도록 요청
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
with open(local_path, "wb") as f:
f.write(response.content)
# 이미지 처리 로직 (필요한 경우)
# 예: 썸네일 생성, 이미지 사이즈 조정 등
local_paths.append(local_path)
else:
print(f"Error downloading image: {url}")
return local_paths
def download_and_process_image(self, urls):
"""
URL 리스트에서 이미지를 다운받아 'img' 폴더에 저장합니다.
Args:
urls (list): 이미지 URL 리스트
Returns:
list: 로컬 파일 경로 리스트
"""
# 'img' 폴더 경로 확인 및 생성
img_folder_path = os.path.join(os.getcwd(), 'ori_img')
if not os.path.exists(img_folder_path):
os.makedirs(img_folder_path)
local_paths = []
for i, url in enumerate(urls):
if i == 0:
filename = "original.jpg"
local_path = os.path.join(img_folder_path, filename)
# 이미지 다운로드
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"DNT": "1", # Do Not Track 요청 헤더 (사용자의 추적을 거부)
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1", # https로의 업그레이드를 요청
"Cache-Control": "max-age=0", # 캐시된 콘텐츠를 재사용하지 않도록 요청
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
with open(local_path, "wb") as f:
f.write(response.content)
# 이미지 처리 로직 (필요한 경우)
# 예: 썸네일 생성, 이미지 사이즈 조정 등
local_paths.append(local_path)
else:
print(f"Error downloading image: {url}")
return local_paths
def compare_images(self):
"""
입력받은 URL 이미지들을 로컬에 저장하고, 비교 스레드를 시작합니다.
"""
# 입력 경로 확인
original_img_url = self.original_img_path_edit.text()
if not original_img_url:
return
# 원본 이미지 로컬 저장 및 경로
local_paths = self.download_and_process_image([original_img_url])
if not local_paths:
return
original_img_path = local_paths[0]
self.original_img_path = local_paths[0] # 클래스 변수 업데이트
# 대상 이미지 로컬 저장 및 경로 리스트
target_img_urls = [edit.text() for edit in self.target_img_path_edits]
target_img_paths = self.download_and_process_images(target_img_urls)
if not target_img_paths:
return
# 이미지 비교 스레드 시작
self.thread = ImageThread(original_img_path, target_img_paths)
self.thread.finished.connect(self.show_results)
self.thread.start()
def add_text_to_image(self, img, text):
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
font_color = (255, 0, 0) # 빨간색
font_thickness = 2
cv2.putText(img, text, (10, 30), font, font_scale, font_color, font_thickness)
def show_results(self, sorted_target_img_paths):
# 원본 이미지 팝업
original_img = cv2.imread(self.original_img_path)
original_img = cv2.resize(original_img, (200, 200))
self.add_text_to_image(original_img, "Original") # 원본 이미지에 텍스트 추가
qimage = QImage(original_img.data, original_img.shape[1], original_img.shape[0], QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(qimage)
self.original_img_popup_label.setPixmap(pixmap)
# 대상 이미지 팝업 업데이트
for i, label in enumerate(self.target_img_popup_labels):
if i < len(sorted_target_img_paths):
target_img_path = sorted_target_img_paths[i]
target_img = cv2.imread(target_img_path)
target_img = cv2.resize(target_img, (200, 200))
self.add_text_to_image(target_img, f"Target {i+1}") # 대상 이미지에 텍스트 추가
qimage = QImage(target_img.data, target_img.shape[1], target_img.shape[0], QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(qimage)
label.setPixmap(pixmap)
else:
label.setPixmap(QPixmap()) # 이미지가 없는 경우 비워둠
# 팝업 창 보여주기
self.popup_window.show()
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()

6
config.ini Normal file
View File

@ -0,0 +1,6 @@
[MongoDB]
address = cckb9998.synology.me
port = 27017
user = root
password = 1234

3
config.py Normal file
View File

@ -0,0 +1,3 @@
# 여기에 전역 설정을 저장합니다.
PRODUCTS_DB_PATH = 'products.db'
WEBSITE_URL = 'https://www.percenty.co.kr'

50
credentials.py Normal file
View File

@ -0,0 +1,50 @@
from cryptography.fernet import Fernet
import os
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
# 암호화 키 생성 또는 불러오기
def load_or_create_key():
key_file = 'secret.key'
if os.path.exists(key_file):
with open(key_file, 'rb') as file:
key = file.read()
else:
key = Fernet.generate_key()
with open(key_file, 'wb') as file:
file.write(key)
return key
# 사용자 정보 암호화
def encrypt_data(data):
key = load_or_create_key()
fernet = Fernet(key)
encrypted_data = fernet.encrypt(data.encode())
return encrypted_data
# 사용자 정보 복호화
def decrypt_data(encrypted_data):
key = load_or_create_key()
fernet = Fernet(key)
decrypted_data = fernet.decrypt(encrypted_data).decode()
return decrypted_data
# 사용자 정보 저장
def save_credentials(username, password, employeeID):
encrypted_username = encrypt_data(username)
encrypted_password = encrypt_data(password)
encrypted_employeeID = encrypt_data(employeeID)
with open('credentials.txt', 'wb') as file:
file.write(encrypted_username + b'\n' + encrypted_password + b'\n' + encrypted_employeeID)
# 저장된 사용자 정보 불러오기
def load_credentials():
if os.path.exists('credentials.txt'):
with open ('credentials.txt', 'rb') as file:
encrypted_username, encrypted_password, encrypted_employeeID= file.read().splitlines()
return decrypt_data(encrypted_username), decrypt_data(encrypted_password), decrypt_data(encrypted_employeeID)
return None, None, None

3
credentials.txt Normal file
View File

@ -0,0 +1,3 @@
gAAAAABl3cZvJaa8qtHCsbyKR8dCiWPDqZCmW8XIgv57PgzlOvzw4d3cdv65xdtB90dZPmuHOvdMIwxJMNxFODWggc9SZkAjSo0iweIRO0Hn-yv8sNo-0Ik=
gAAAAABl3cZvY5fdhQovlMY-rIafgiz6UCXCpA8NFoY5O8vAUM0Yvanv34mtoSZ9xOy0TwqGROPbCC5P2KBaLvvwxAh7aVAtaw==
gAAAAABl3cZvPcYXh-gvlrXW6F9unAj20lFwsBdCjFJEGi4oaHjwvjPzW9MwNUAZsY4UBB_C9L1uYvXWmMKdQemnptVKzH6sDg==

24
database.py Normal file
View File

@ -0,0 +1,24 @@
import sqlite3
from config import PRODUCTS_DB_PATH
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
conn = sqlite3.connect(PRODUCTS_DB_PATH)
cursor = conn.cursor()
def setup_database():
"""데이터베이스 테이블을 설정합니다."""
cursor.execute('CREATE TABLE IF NOT EXISTS processed_products (product_id TEXT PRIMARY KEY)')
conn.commit()
def is_product_processed(product_id):
"""상품이 이미 처리되었는지 확인합니다."""
cursor.execute('SELECT product_id FROM processed_products WHERE product_id = ?', (product_id,))
return cursor.fetchone() is not None
def mark_product_processed(product_id):
"""상품을 처리된 것으로 표시합니다."""
cursor.execute('INSERT INTO processed_products (product_id) VALUES (?)', (product_id,))
conn.commit()

132
dbConnect.py Normal file
View File

@ -0,0 +1,132 @@
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton, QMessageBox, QCheckBox
from pymongo import MongoClient
from modules.db.mongo_config import MongoConfig
from configparser import ConfigParser
from pymongo.errors import ConnectionFailure
from PyQt5.QtCore import Qt
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class MongoConnectionWidget(QWidget):
def __init__(self):
super().__init__()
self.mongoConfig = MongoConfig()
self.initUI()
def initUI(self):
self.setGeometry(780, 420, 360, 240)
layout = QVBoxLayout()
self.infoLabel = QLabel("사용자 로그인 정보를 입력하세요")
self.infoLabel = QLabel("서버 정보를 입력하세요:")
self.addressInput = QLineEdit("qcy2.duckdns.org")
self.addressInput.setPlaceholderText("서버 주소 (예: www.naver.com)")
self.addressInput.returnPressed.connect(self.focus_to_port_input)
self.portInput = QLineEdit("27017")
self.portInput.setPlaceholderText("서버 포트 (예: 27017)")
self.portInput.returnPressed.connect(self.focus_to_user_input)
self.userInput = QLineEdit("root")
self.userInput.setPlaceholderText("사용자 이름")
self.userInput.returnPressed.connect(self.focus_to_password_input)
self.passwordInput = QLineEdit("1234")
self.passwordInput.setPlaceholderText("비밀번호")
self.passwordInput.setEchoMode(QLineEdit.Password)
self.passwordInput.returnPressed.connect(self.try_connect)
# 비밀번호 표시 토글 버튼
self.showPasswordCheckBox = QCheckBox("비밀번호 표시")
self.showPasswordCheckBox.stateChanged.connect(self.togglePasswordVisibility)
self.connectButton = QPushButton("접속하기")
self.connectButton.clicked.connect(self.try_connect)
layout.addWidget(self.infoLabel)
layout.addWidget(self.addressInput)
layout.addWidget(self.portInput)
layout.addWidget(self.userInput)
layout.addWidget(self.passwordInput)
layout.addWidget(self.showPasswordCheckBox)
layout.addWidget(self.connectButton)
self.setLayout(layout)
self.setWindowTitle('데이터베이스 연결 설정')
def focus_to_user_input(self):
self.userInput.setFocus()
def focus_to_port_input(self):
self.portInput.setFocus()
def focus_to_password_input(self):
self.passwordInput.setFocus()
def togglePasswordVisibility(self, state):
if state == Qt.Checked:
self.passwordInput.setEchoMode(QLineEdit.Normal)
else:
self.passwordInput.setEchoMode(QLineEdit.Password)
def load_config_and_try_connect(self):
config = ConfigParser()
config.read('config.ini')
if config.has_section('MongoDB'):
address = config.get('MongoDB', 'address')
port = config.get('MongoDB', 'port')
user = config.get('MongoDB', 'user')
password = config.get('MongoDB', 'password')
return self.try_connect_with_config(address, port, user, password)
return False
def try_connect_with_config(self, address, port, user, password):
try:
client = MongoClient(f'mongodb://{user}:{password}@{address}:{port}/')
client.admin.command('ping') # 접속 확인
return True, "접속 성공"
except ConnectionFailure as e:
# ConnectionFailure 예외 발생 시 에러 메시지 포착
error_message = str(e.details) if hasattr(e, 'details') else str(e)
return False, f"접속 실패: {error_message}"
except Exception as e:
# 기타 예외 처리
return False, f"접속 실패: {str(e)}"
def try_connect(self):
address = self.addressInput.text()
port = self.portInput.text()
user = self.userInput.text()
password = self.passwordInput.text()
success, message = self.try_connect_with_config(address, port, user, password)
if success:
QMessageBox.information(self, "성공", message)
self.save_config(address, port, user, password)
self.proceed_to_main_program()
else:
QMessageBox.warning(self, "실패", message)
def save_config(self, address, port, user, password):
config = ConfigParser()
config['MongoDB'] = {
'address': address,
'port': port,
'user': user,
'password': password
}
with open('config.ini', 'w') as configfile:
config.write(configfile)
#def main():
# app = QApplication(sys.argv)
# ex = MongoConnectionWidget()
# ex.show()
# sys.exit(app.exec_())
#if __name__ == '__main__':
# main()

4371
edit/Percenty_SS_Code.json Normal file

File diff suppressed because it is too large Load Diff

0
edit/__init__.py Normal file
View File

65
edit/action_elements.py Normal file
View File

@ -0,0 +1,65 @@
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.common.exceptions import TimeoutException
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def click_element(driver, element_type, element_identifier, wait_time=10, click_type='normal'):
"""
주어진 요소를 찾아 클릭합니다.
Parameters:
- driver: WebDriver 인스턴스
- element_type: 요소를 찾는 방법 ('CSS_SELECTOR', 'CLASS_NAME', 'XPATH', 'ID')
- element_identifier: 요소의 식별자
- wait_time: 요소가 나타날 때까지 대기하는 시간 ()
- click_type: 클릭 방식 ('normal' 또는 'js')
- visibility_of_element_located : 사용자가 실제로 있고, 상호작용할 있는 요소에 대한 작업을 진행하기 전에 요소의 가시성을 보장
- presence_of_element_located : 페이지 로딩이 완료되었는지 확인하거나, 특정 요소가 DOM에 추가되었는지를 확인할
"""
try:
element = WebDriverWait(driver, wait_time).until(
EC.visibility_of_element_located((getattr(By, element_type), element_identifier))
# EC.presence_of_element_located((getattr(By, element_type), element_identifier))
)
if click_type == 'js':
driver.execute_script("arguments[0].click();", element)
else:
element.click()
logger.debug(f"'{element_identifier}' 요소를 성공적으로 클릭함.")
except TimeoutException:
logger.debug(f"'{element_identifier}' 요소를 {wait_time} 초 이내에 찾지 못함.")
except Exception as e:
logger.debug(f"예상치 못한 오류 발생: {e}")
def return_element(driver, element_type, element_identifier, wait_time=10):
"""
주어진 요소를 찾아 클릭합니다.
Parameters:
- driver: WebDriver 인스턴스
- element_type: 요소를 찾는 방법 ('CSS_SELECTOR', 'CLASS_NAME', 'XPATH', 'ID')
- element_identifier: 요소의 식별자
- wait_time: 요소가 나타날 때까지 대기하는 시간 ()
- visibility_of_element_located : 사용자가 실제로 있고, 상호작용할 있는 요소에 대한 작업을 진행하기 전에 요소의 가시성을 보장
- presence_of_element_located : 페이지 로딩이 완료되었는지 확인하거나, 특정 요소가 DOM에 추가되었는지를 확인할
"""
try:
element = WebDriverWait(driver, wait_time).until(
EC.visibility_of_element_located((getattr(By, element_type), element_identifier))
)
logger.debug(f"'{element}' 요소를 성공적으로 찾음.")
return element
except TimeoutException:
logger.debug(f"'{element_identifier}' 요소를 {wait_time} 초 이내에 찾지 못함.")
except Exception as e:
logger.debug(f"예상치 못한 오류 발생: {e}")

69
edit/compare.py Normal file
View File

@ -0,0 +1,69 @@
# 필요한 라이브러리를 임포트합니다.
import torch
import torchvision.models as models
import torchvision.transforms as transforms
from PIL import Image
import requests
from io import BytesIO
import numpy as np
# 사전 학습된 모델(예: ResNet)을 불러옵니다.
model = models.resnet18(pretrained=True)
model.eval() # 모델을 평가 모드로 설정합니다.
# 이미지 전처리를 위한 변환을 정의합니다.
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
# 이미지 URL로부터 이미지를 다운로드하고 전처리하는 함수를 정의합니다.
def download_and_preprocess_image(url):
response = requests.get(url)
img = Image.open(BytesIO(response.content)).convert('RGB')
img_preprocessed = preprocess(img)
return img_preprocessed
# 이미지 특징을 추출하는 함수를 정의합니다.
def extract_features(image):
with torch.no_grad():
features = model(image.unsqueeze(0))
return features
# 두 이미지 특징 간의 코사인 유사도를 계산하는 함수를 정의합니다.
def cosine_similarity(feature1, feature2):
cos = torch.nn.CosineSimilarity(dim=1, eps=1e-6)
similarity = cos(feature1, feature2)
return similarity
# 주어진 '원본이미지' URL과 여러 '대상이미지' URL을 비교하여 가장 비슷한 이미지를 찾는 함수를 정의합니다.
def find_most_similar_image(source_url, target_urls):
source_image = download_and_preprocess_image(source_url)
source_features = extract_features(source_image)
similarities = []
for url in target_urls:
target_image = download_and_preprocess_image(url)
target_features = extract_features(target_image)
similarity = cosine_similarity(source_features, target_features)
similarities.append(similarity.item())
# 가장 높은 유사도를 가진 이미지의 인덱스를 찾습니다.
most_similar_index = np.argmax(similarities)
most_similar_score = similarities[most_similar_index]
return target_urls[most_similar_index], most_similar_score
# 주어진 이미지 URL 예시 (실제 실행 시 주석 처리할 부분)
#source_image_url = "https://example.com/source.jpg"
#target_image_urls = [
# "https://example.com/target1.jpg",
# "https://example.com/target2.jpg",
# "https://example.com/target3.jpg",
#]
# 가장 비슷한 이미지를 찾습니다. (실제 실행 시 주석 처리할 부분)
#most_similar_url, similarity_score = find_most_similar_image(source_image_url, target_image_urls)
#print(f"Most similar image URL: {most_similar_url}, Similarity score: {similarity_score}")

652
edit/detail1.py Normal file
View File

@ -0,0 +1,652 @@
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
from img_trans.image_trans import image_trans
import logging
from bs4 import BeautifulSoup
# import pyperclip
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
avg_price = 0
from bs4 import BeautifulSoup
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:
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:
image_urls.append(img_tag['src'])
# 중복 제거
image_urls = list(set(image_urls))
return image_urls
def safe_generate_content(gemini, image_src, product_title, max_retries=3, initial_wait=1):
retry_count = 0
wait_time = initial_wait
while retry_count < max_retries:
try:
return gemini.generate_description(image_src, product_title)
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
# Final attempt without catching the exception
return gemini.generate_content(image_src, product_title)
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 NS_info_with_HTML(products):
# 상품 정보를 담을 5x1 표 생성
product_info_text = "<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_text += f"<td style='width: 170px; height: 170px;'>{product_card}</td>"
avg_price += price
product_info_text += "</tr></table>"
if products:
avg_price = round(avg_price / len(products), 0)
product_info_text += f'<div><h3>네이버 상품의 평균 가격은 {avg_price:,.0f}원입니다.</h3></div>'
return product_info_text
#네이버쇼핑 함수 HTML
def NS_info_with_HTML_ori(products):
product_info_text = ""
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_info_text += f"<td>{product_card}</td>"
avg_price += price
product_info_text += "</tr></table>"
if products:
avg_price = round(avg_price / len(products), 0)
product_info_text += f'<div>네이버 상품의 평균 가격은 {avg_price}원입니다.</div>'
return product_info_text
def naver_prices(products):
prices = []
for product in products:
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)
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 create_product_card_ori(product_name, image_url, price, rank):
# 이미지 크기를 직접 지정
html_text = f"""
<table style="width: 400px; border-collapse: collapse;">
<tr>
<td rowspan="3" style="height: 400px; vertical-align: top;"><img src="{image_url}" style="width: 100%; max-width: 400px; height: auto;" alt="상품 이미지"></td>
<td style="border: 1px solid #dddddd; text-align: left; padding: 8px;">가격: {price}</td>
</tr>
<tr>
<td style="border: 1px solid #dddddd; text-align: left; padding: 8px;">순위: {rank}</td>
</tr>
<tr>
<td></td>
</tr>
<tr>
<td colspan="2" style="border: 1px solid #dddddd; text-align: center; vertical-align: middle; font-weight: bold;">{product_name}</td>
</tr>
</table>
"""
return html_text
#네이버쇼핑 함수 마크다운
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"![상품 이미지]({product['imageUrl']})\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, gemini, product_info, delv_collection, json_naver_codes):
product_title = product_info.init_title
product_low_cost = product_info.tao_low_price
product_high_cost = product_info.tao_high_price
image_src = product_info.main_image_url
# 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(cat.text, json_naver_codes)
logger.debug(f"검색된 스스 캣코드 : {naver_code}")
product_info.naver_code = naver_code
# 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 = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".ant-tabs-tab:nth-child(6)"))
)
save_button = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//button[contains(.,'저장하기')]"))
)
except Exception as e:
logger.debug(f"상세페이지 탭으로 이동 중 오류 발생: 요소를 찾을 수 없습니다. : {e}")
# detail_tab = driver.find_element(By.CSS_SELECTOR, ".ant-tabs-tab:nth-child(6)")
detail_tab.click()
time.sleep(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}")
# 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")
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)
contents = safe_generate_content(gemini, image_src, product_title)
logger.debug(f"{contents}")
product_info.ai_contents = contents
logger.debug("AI 이미지 처리 완료")
weight = extract_weight(contents)
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
logger.debug("네이버쇼핑 파싱 중")
products = parse_naver_shopping(product_title, naver_code, isOverseas=1, sortcount=5)
logger.debug(f"네이버 파싱된 상품 리스트 \n {products}")
product_info.naver_products = products
# 네이버 상품가격 담기
naver_price = naver_prices(products)
product_info.naver_low_price = naver_price[0]
product_info.naver_avg_price = naver_price[1]
product_info.naver_high_price = naver_price[2]
# product_info_text = NS_info(products)
product_info_text = NS_info_with_HTML(products)
# logger.debug(f"수집된 정보 \n {product_info_text} \n")
logger.debug("네이버쇼핑 파싱 완료")
try:
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, "ck.ck-button.ck-source-editing-button.ck-off.ck-button_with-text"))
# 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()
logger.debug("HTML 수정 버튼 클릭 .")
except Exception as e:
logger.debug(f"HTML 수정 버튼 요소를 찾을 수 없습니다. : {e}")
# html_btn = driver.find_element(By.CSS_SELECTOR, ".ck-source-editing-button")
try:
textarea = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "ck-source-editing-area"))
)
logger.debug("textarea버튼을 성공적으로 찾았습니다.")
sys.stdout.flush()
textarea.click()
except Exception as e:
logger.debug(f"textarea버튼 요소를 찾을 수 없습니다. : {e}")
# # data-value 속성 값을 가져옵니다.
current_value = textarea.get_attribute("data-value")
# logger.debug(f"현재 속성값 :{current_value}")
product_info.current_value = current_value
logger.debug("현재 속성값 수집 완료")
# 이미지 Url 수집
translated_image_urls = [] # 번역된 이미지 URL들을 저장할 리스트
detail_images = fetch_image_urls(current_value)
logger.debug(f"detail_images URLs \n {detail_images}")
product_info.detail_image_urls = detail_images
# 원본 URL을 빈문자열로 대체
logger.debug("원본 URL을 빈 문자열로번역된 이미지로 대체")
for original_url in detail_images:
current_value = current_value.replace(original_url, "")
# logger.debug("상세페이지 이미지 번역 시작")
# for i, detail_image in enumerate(detail_images):
# logger.debug(f"상세페이지 {i}번째 이미지 번역 시작")
# returned_img = image_trans(detail_image, 'translate')
# pyperclip.copy(returned_img)
# detail_content.send_keys(img_url)
# detail_content.send_keys(Keys.ENTER)
# logger.debug(f"{i}번째 이미지 붙여넣기 완료")
# translated_image_urls.append(returned_img_base)
# logger.debug("이미지 번역 완료")
# # 원본 URL을 번역된 이미지로 대체
# logger.debug("원본 URL을 번역된 이미지로 대체")
# for original_url, translated_img_base in zip(detail_images, translated_image_urls):
# current_value = current_value.replace(original_url, translated_img_base)
# logger.debug(f"current_value \n {current_value}")
# # 새로운 data-value 값을 생성합니다.
new_value = product_info_text + "\n" + current_value
# logger.debug(f"new_value \n {new_value}")
product_info.new_value = new_value
logger.debug("새로운 data-value 값 생성")
# # data-value 속성을 새로운 값으로 설정합니다.
driver.execute_script("arguments[0].setAttribute('data-value', arguments[1]);", textarea, new_value)
# driver.execute_script("arguments[0].innerHTML = arguments[1]", textarea, new_value)
time.sleep(0.2)
# driver.execute_script("arguments[0].innerHTML = arguments[1]", detail_content, new_value);
logger.debug("새로운 data-value 값으로 설정되었습니다.")
save_button.click()
time.sleep(0.2)
# textarea.send_keys(product_info_text)
# logger.debug("detail_content 전송.")
# textarea.send_keys(Keys.ENTER)
# logger.debug("엔터키 전송.")
html_btn.click()
logger.debug("HTML 수정 버튼 다시 클릭하여 기본편집페이지로 돌아감.")
time.sleep(0.2)
# 상세페이지 내용에 텍스트 추가
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)
# logger.debug("상세페이지 이미지 번역 시작")
# for i, detail_image in enumerate(detail_images):
# logger.debug(f"상세페이지 {i}번째 이미지 번역 시작")
# returned_img_path = image_trans(detail_image, 'translate')
# logger.debug("번역 완료 및 부텨넣기")
# detail_content.send_keys(Keys.CONTROL, 'v')
# detail_content.send_keys(Keys.ENTER)
# logger.debug(f"{i}번째 이미지 붙여넣기 완료")
# translated_image_urls.append(returned_img_path)
# logger.debug("이미지 번역 완료")
logger.debug("====번역이미지 붙여넣기 완료=====")
# 인용버튼 클릭
# quote_button = driver.find_element(By.XPATH, "//div[@id='productMainContentContainerId']/div/div/div[2]/div[2]/div/div/div[2]/div/div/button[6]")
# quote_button = driver.find_element(By.CSS_SELECTOR, ".ck:nth-child(16)")
# try:
# quote_button = WebDriverWait(driver, 10).until(
# EC.presence_of_element_located((By.CSS_SELECTOR, ".ck:nth-child(16)"))
# )
# logger.debug("인용버튼을 성공적으로 찾았습니다.")
# except:
# logger.debug("인용버튼 요소를 찾을 수 없습니다.")
# quote_button.click()
# time.sleep(0.5)
# contents 변수의 값이 None이 아닐 때만 send_keys 메서드를 호출합니다.
if contents is not None:
detail_content.send_keys(contents)
else:
# contents 변수가 None일 때의 대체 처리
# 예: detail_content.send_keys("") 또는 아무 동작도 수행하지 않음
pass
time.sleep(0.5)
# 가격 정보 계산
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)
# 텍스트 조합
add_text = f"""
============= 가격과 마진율 =============
해당 제품의 네이버 평균가격은 {avg_price:,.0f}원입니다.
해당 제품의 무게배송비는 {delv_fee:,.0f}원입니다.
**마진율 확보를 위한 판매가 범위:** {low_cost:,.0f} ~ {high_cost:,.0f}
**저가:**
* 무게 배송비 대비: {low_delv_fee:,.0f} {('낮은' if low_delv_fee < 0 else '높은')}
* 마진율: {low_margin_r:,.2f}%
**고가:**
* 무게 배송비 대비: {high_delv_fee:,.0f} {('낮은' if high_delv_fee < 0 else '높은')}
* 마진율: {high_margin_r:,.2f}%
**적정 배송비:** {low_delv_fee:,.0f} ~ {high_delv_fee:,.0f}
**추가 정보:**
* 해당 제품의 파손 여부 무게 오차를 고려하여 더하기 마진을 설정하세요.
=======================================
"""
detail_content.send_keys(add_text)
time.sleep(2)
# 저장 버튼 클릭
try:
save_button = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//button[contains(.,'저장하기')]"))
)
logger.debug("저장 버튼을 성공적으로 찾았습니다.")
except Exception as e:
logger.debug(f"저장 버튼 요소를 찾을 수 없습니다. : {e}")
# save_button = driver.find_element(By.XPATH, "//button[contains(.,'저장하기')]")
save_button.click()
time.sleep(0.5)

15
edit/json_code.py Normal file
View File

@ -0,0 +1,15 @@
import json
def load_naver_codes(filename="Percenty_SS_code.json"):
codes = []
with open(filename, "r", encoding='utf-8') as file:
for line in file:
try:
item = json.loads(line)
codes.append(item)
except json.JSONDecodeError as e:
logger.error(f"Error decoding JSON: {e}")
return codes
# 한 번만 호출하여 메모리에 로드
naver_codes = load_naver_codes()

60
edit/margin.py Normal file
View File

@ -0,0 +1,60 @@
def calculate_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, market_fee, margin
# 예시 코드
tao_bao_price = 128 # 타오바오 제품 원가
shipping_fee = 12000
selling_price, market_fee, margin = calculate_selling_price(tao_bao_price, shipping_fee)
print(f"판매가: {selling_price:,.0f}")
print(f"마켓 수수료: {market_fee:,.0f}")
print(f"마진: {margin:,.0f}")
market_fee_rate = round((market_fee/selling_price)*100,2)
margin_rate = round((margin/selling_price)*100,2)
print(f"수수료율: {market_fee_rate:,.0f}%")
print(f"마진율: {margin_rate:,.0f}%")
# tao_bao_price = 200 # 타오바오 제품 원가
# selling_price, market_fee, margin = calculate_selling_price(tao_bao_price)
# market_fee_rate = market_fee/selling_price
# margin_rate = margin/selling_price
# print(f"수수료율: {market_fee_rate}")
# print(f"마진율: {margin_rate}")

59
edit/naver_code.py Normal file
View File

@ -0,0 +1,59 @@
import re, json
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def find_naver_code_ori(base_category):
logger.debug("네이버 카테고리 코드 찾기")
logger.debug(f"주어진 카테고리 : {base_category}")
category_parts = re.split(r'>|-', base_category)
logger.debug(f"카테고리 분리 : {category_parts}")
base_category1Name, base_category2Name, base_category3Name, base_category4Name = (category_parts + [None]*4)[:4]
with open("Percenty_SS_code.json", "r", encoding='utf-8') as file:
for line in file:
try:
item = json.loads(line)
if (item.get('category1Name') == base_category1Name and
item.get('category2Name') == base_category2Name and
item.get('category3Name') == base_category3Name and
item.get('category4Name') == base_category4Name):
logger.debug(f"찾은 카테고리 코드 : {item['Naver_code']}")
print(f"찾은 카테고리 코드 : {item['Naver_code']}")
return item['Naver_code'] # 조건에 맞는 첫 번째 코드를 반환하고 함수 종료
except json.JSONDecodeError as e:
logger.debug(f"Error decoding JSON: {e}")
continue
return None # 조건에 맞는 코드를 찾지 못한 경우
def find_naver_code(base_category, json_naver_codes):
logger.debug("네이버 카테고리 코드 찾기")
logger.debug(f"주어진 카테고리 : {base_category}")
category_parts = re.split(r'>|-', base_category)
logger.debug(f"카테고리 분리 : {category_parts}")
base_category1Name, base_category2Name, base_category3Name, base_category4Name = (category_parts + [None]*4)[:4]
for item in json_naver_codes:
try:
# category3Name과 category4Name가 없거나 NaN인 경우를 처리합니다.
category3Name_item = item.get('category3Name')
if isinstance(category3Name_item, dict) and '$numberDouble' in category3Name_item:
category3Name_item = None # NaN 값 처리
category4Name_item = item.get('category4Name')
if isinstance(category4Name_item, dict) and '$numberDouble' in category4Name_item:
category4Name_item = None # NaN 값 처리
# 조건에 따라 검색합니다.
if (item.get('category1Name') == base_category1Name and
item.get('category2Name') == base_category2Name and
(category3Name_item == base_category3Name or base_category3Name is None) and
(category4Name_item == base_category4Name or base_category4Name is None)):
logger.debug(f"찾은 카테고리 코드 : {item['Naver_code']}")
return item['Naver_code'] # 조건에 맞는 첫 번째 코드를 반환하고 함수 종료
except Exception as e:
logger.error(f"Error processing item: {e}")
continue
return None # 조건에 맞는 코드를 찾지 못한 경우

145
edit/options.py Normal file
View File

@ -0,0 +1,145 @@
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
import time
from edit.action_elements import click_element, return_element
from ai.deepl import trans
from ai.compare import find_most_similar_image_by_one
import re
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def modify_option_page(driver, original_img):
# 옵션 탭으로 이동
logger.debug("옵션탭으로 이동")
option_tab_XPATH = "//div[@id='rc-tabs-0-tab-1']"
click_element(driver, 'XPATH', option_tab_XPATH, 10, 'js')
logger.debug("옵션탭으로 이동 완료")
logger.debug("페이지 로딩 대기")
time.sleep(2) # 페이지 로딩 대기.
# 옵션타입 갯수 체크
# 옵션타입2가 존재할 경우 옵션타입2의 옵션갯수에 따라 (1개면 작업하지 않고, 2개이상일 경우 작업)
# 일단 보류
logger.debug("옵션타입 체크")
option_type_1_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[1]/div/div/div/span/div/div/span"
# 옵션타입이 1개일 경우 옵션타입2를 가져올 경우 오류발생할 것. 적절한 예외처리필요
option_type_2_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[2]/div/div/div/span/div/div/span"
option_type_3_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[3]/div/div/div/span/div/div/span"
option_type1 = return_element(driver, 'XPATH', option_type_1_xpath, 10)
logger.debug(f"옵션 타입 : {option_type1.text}")
# 현재 전체 옵션갯수 가져오기
# 가져온 옵션갯수에 따라, 2개이하면 수정하지 않고, 3개이상일 경우 이미지 유사도에 따라 선택
logger.debug("옵션갯수 가져오기")
option_num_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[5]/div/div/label/span[2]"
option_num_element = return_element(driver, 'XPATH', option_num_xpath, 10)
option_number = int(re.search(r'\d+', option_num_element.text).group())
# 가격 낮은 순으로 정렬
logger.debug("가격 낮은 순으로 정렬")
low_price_order_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[5]/div/div[3]/button/span"
click_element(driver, 'XPATH', low_price_order_xpath, 10, 'js')
# 이미지 비교
logger.debug("이미지 비교로 유사도 판단 후 옵션 선택")
# 옵션 이미지 URL을 저장할 리스트 초기화
option_images_urls = []
selected_option_indexes = []
for i in range(1, option_number + 1):
# 옵션 이미지의 XPath 경로 동적 생성
opt_img_xpath = f"//li[{i}]/div/div/div/div[2]/div/img"
try:
# 해당 XPath를 이용하여 웹 요소 찾기
opt_img_element = driver.find_element(By.XPATH, opt_img_xpath)
except Exception as e:
logger.debug(f"옵션 이미지 xpath를 찾을 수 없음 {e}")
opt_img_element = None
# 옵션이미지가 없을 경우 처리
if not opt_img_element:
logger.debug(f"{i}번째 옵션이미지가 없음")
# 아래의 2가지 상황에 맞추어 추가코드 필요
# 1. 전체 옵션의 이미지가 없는 경우 처리
# 2. 일부 옵션만 이미지가 없는 경우 처리
else:
# 웹 요소의 src 속성에서 이미지 URL 추출
img_url = opt_img_element.get_attribute('src')
# 추출한 이미지 URL을 리스트에 추가
option_images_urls.append(img_url)
# 유사도 비교로 적합한 이미지를 가진 옵션번호 저장
similarity = find_most_similar_image_by_one(original_img, img_url)
if similarity > 0.6:
selected_option_indexes.append(i)
logger.debug(f"옵션번호 {i}번이 이미지 유사도 {similarity}로 선택되었습니다.")
# # 선택된 옵션인덱스로 실제 옵션 체크
# # 옵션타입1의 옵션전체 선택&해제 버튼 xpath=(//input[@type='checkbox'])[22]
# # 옵션타입1의 1번 옵션 선택&해제 버튼 xpath=(//input[@type='checkbox'])[23]
# # 옵션타입1의 2번 옵션 선택&해제 버튼 xpath=(//input[@type='checkbox'])[24]
# # 주의점은 해당 옵션을 체크해제할 경우 이미지 주소를 가져올 수 없으므로, 체크 해제 전 모든 옵션에 해당하는 이미지 주소를 가져올 것.
# # 만약 이미지 주소가 none이거나 없으면 이미지 비교를 하지말고 옵션명으로 비교할 것.
logger.debug("옵션갯수 다시 가져오기")
selected_option_num_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[5]/div/div/label/span[2]"
selected_option_num_element = return_element(driver, 'XPATH', selected_option_num_xpath, 10)
selected_options_num = int(re.search(r'\d+', selected_option_num_element.text).group())
logger.debug("선택된 옵션 인덱스로 옵션박스 체크 실행")
all_checkbox_xpath=("//input[@type='checkbox'])[22]") # 전체옵션 체크해제를 위한 준비
# all_checkbox_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[5]/div/div/label/span/input"
# selected_options_num = len(selected_option_indexes)
selected_options_num = selected_option_indexes
max_options_num = min(7, selected_options_num)
if option_number > 2: # 선택가능한 옵션갯수가 2개이하일 경우 모든옵션을 선택하기 위함.
logger.debug("전체옵션 선택해제 실행")
click_element(driver, 'XPATH', all_checkbox_xpath, 10, 'js')
for i in range(1, max_options_num + 1): # 최대 선택가능한 옵션은 아무리 많아도 (7개 또는 selected_options_num 중 작은 숫자) 까지만
selected_option_number = selected_option_indexes[i]
option_checkbox_xpath = f"(//input[@type='checkbox'])[{22 + selected_option_number}]"
logger.debug(f"{i}번째 옵션 체크")
click_element(driver,'XPATH', option_checkbox_xpath, 10, 'js')
logger.debug("선택된 옵션명 DeepL로 번역 후 새로운 옵션명 입력")
logger.debug("선택된 옵션갯수 새로 가져오기")
selected_option_num_element = return_element(driver, 'XPATH', option_num_xpath, 10)
selected_option_number = int(re.search(r'\d+', selected_option_num_element).group())
for i in range(1, selected_option_number + 1):
# 원본 옵션이름의 XPath 경로 동적 생성으로 원본옵션명을 번역 생성
ori_optionName_xpath = f"/html/body/div[28]/div/div[3]/div/div[2]/div[1]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[6]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[3]/div[3]/span"
ori_optionName_element = driver.find_element(By.XPATH, ori_optionName_xpath)
ori_optionName = ori_optionName_element.get_attribute('text')
trans_option_name = trans(ori_optionName)
# 옵션이름입력칸의 XPath 경로 동적 생성으로 번역된 옵션명을 새로 입력
optionName_input_xpath = f"//li[{i}]/div/div/div/div[3]/div[2]/div/span/input"
optionName_input_element = driver.find_element(By.XPATH, optionName_input_xpath)
optionName_input_element.setText(trans_option_name)
logger.debug("옵션선택과 옵션명 수정 완료 후 옵션명 정리")
# A-Z
# xpath=//span[contains(.,'A-Z')]
# xpath=//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[4]/div/div/div/div/button/span
# 1-99
# xpath=//span[contains(.,'1-99')]
# xpath=//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[4]/div/div/div/div[2]/button/span
# 빈칸제거
# xpath=//span[contains(.,'빈칸 제거')]
# xpath=//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[4]/div/div/div/div[3]/button/span

128
edit/price.py Normal file
View File

@ -0,0 +1,128 @@
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
import time
from edit.action_elements import click_element, return_element
from edit.price_cal import calculate_margin_and_price
# from ai.compare import find_most_similar_image_by_one
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def modify_price_page(driver, product_infos):
# tao_high_price = product_infos.option_high_price
# tao_low_price = product_infos.option_low_price
tao_high_price = product_infos.tao_high_price # 디버깅을 위해 선택된 옵션가격이 아닌 기존의 원래가격을 가져옴
tao_low_price = product_infos.tao_low_price # 디버깅을 위해 선택된 옵션가격이 아닌 기존의 원래가격을 가져옴
naver_low_price = product_infos.naver_low_price
naver_avg_price = product_infos.naver_avg_price
naver_high_price = product_infos.naver_high_price
w_delv_fee = product_infos.w_delv_fee
packing_fee = product_infos.packing_fee
inputs = {
"tao_low_price": tao_high_price,
"tao_high_price": tao_low_price,
"delv_fee": w_delv_fee,
"packing_fee": packing_fee,
"ns_low_price": naver_low_price,
"ns_high_price": naver_high_price
}
outputs = calculate_margin_and_price(inputs)
selling_price = outputs["selling_price"]
final_margin_rate = outputs["final_margin_rate"]
seller_cost = outputs["seller_cost"]
plus_margin = outputs["plus_margin"]
ns_avg_price = outputs["ns_avg_price"]
return_fee = selling_price/2
init_delv_fee = seller_cost
exchange_fee = return_fee + init_delv_fee
product_infos.plus_fee = plus_margin
product_infos.return_fee = return_fee
product_infos.init_delv_fee = init_delv_fee
product_infos.exchange_fee = exchange_fee
print(f"결정된 상품가격: {selling_price}원, 최종 마진율: {final_margin_rate:.2f}%")
print(f"판매자 원가: {seller_cost}")
print(f"더하기 마진: {plus_margin}")
print(f"네이버 평균가: {ns_avg_price}")
# 가격 탭으로 이동
logger.debug("내부함수 가격 탭으로 이동")
thumb_tab_CSS = '.ant-tabs-tab:nth-child(3)'
click_element(driver, 'CSS_SELECTOR', thumb_tab_CSS, 10, 'js')
logger.debug("내부함수 가격 탭으로 이동 완료")
logger.debug("페이지 로딩 대기")
time.sleep(2) # 페이지 로딩 대기.
logger.debug("더하기마진 수정")
plus_fee_xpath="//div[@id='productMainContentContainerId']/div/div/div/div/div[2]/div/div/div[8]/div/div/div[3]/div/div/div/div/div[2]/input"
plus_fee_element = return_element(driver, 'XPATH', plus_fee_xpath, 10)
plus_fee_element.send_keys(Keys.CLEAR) # 기존 가격 삭제
driver.execute_script("arguments[0].value = '';", plus_fee_element) # 기존 가격 삭제 JS
logger.debug("기존가격 삭제")
plus_fee_element.send_keys(plus_margin) # 새 가격 입력
logger.debug(f"더하기마진 수정 완료 : {plus_margin}")
logger.debug("해외배송비 수정")
fore_delv_fee_xpath="//div[@id='productMainContentContainerId']/div/div/div/div/div[2]/div/div/div[10]/div/div/div/div/div[2]/input"
fore_delv_fee_element = return_element(driver, 'XPATH', fore_delv_fee_xpath, 10)
fore_delv_fee_element.send_keys(Keys.CLEAR) # 기존 가격 삭제
driver.execute_script("arguments[0].value = '';", fore_delv_fee_element) # 기존 가격 삭제 JS
logger.debug("기존가격 삭제")
fore_delv_fee_element.send_keys(w_delv_fee) # 새 가격 입력
logger.debug(f"해외배송비 수정 완료 : {w_delv_fee}")
logger.debug("반품비 수정")
return_fee_xpath = "//div[@id='productMainContentContainerId']/div/div/div/div/div[4]/div/div/div[3]/div/div/div/div/div[2]/input"
return_fee_element = return_element(driver, 'XPATH', return_fee_xpath, 10)
return_fee_element.send_keys(Keys.CLEAR) # 기존 가격 삭제
driver.execute_script("arguments[0].value = '';", return_fee_element) # 기존 가격 삭제 JS
logger.debug("기존가격 삭제")
return_fee_element.send_keys(return_fee) # 새 가격 입력
logger.debug(f"반품비 수정 완료 : {return_fee}")
logger.debug("초도배송비 수정")
first_delv_fee_xpath = "//div[@id='productMainContentContainerId']/div/div/div/div/div[4]/div/div/div[4]/div/div[2]/div/div/div[2]/input"
first_delv_fee_element = return_element(driver, 'XPATH', first_delv_fee_xpath, 10)
first_delv_fee_element.send_keys(Keys.CLEAR) # 기존 가격 삭제
driver.execute_script("arguments[0].value = '';", first_delv_fee_element) # 기존 가격 삭제 JS
logger.debug("기존가격 삭제")
first_delv_fee_element.send_keys(init_delv_fee) # 새 가격 입력
logger.debug(f"초도배송비 수정 완료 : {init_delv_fee}")
logger.debug("교환비 수정")
exchange_fee_xpath = "//div[@id='productMainContentContainerId']/div/div/div/div/div[4]/div/div/div[5]/div/div/div/div/div[2]/input"
exchange_fee_element = return_element(driver, 'XPATH', exchange_fee_xpath, 10)
exchange_fee_element.send_keys(Keys.CLEAR) # 기존 가격 삭제
driver.execute_script("arguments[0].value = '';", exchange_fee_element) # 기존 가격 삭제 JS
logger.debug("기존가격 삭제")
exchange_fee_element.send_keys(exchange_fee) # 새 가격 입력
logger.debug(f"교환비 수정 완료 : {exchange_fee}")
# logger.debug("기본마진율 수정")
# earning_rate_xpath = "//div[@id='productMainContentContainerId']/div/div/div/div/div[2]/div/div/div[8]/div/div/div/div/div/div/div/div[2]/input"
# earning_rate_element = return_element(driver, 'XPATH', earning_rate_xpath, 10, 'js')
# earning_rate_element.send_keys(Keys.CLEAR) # 기존 가격 삭제
# driver.execute_script("arguments[0].value = '';", earning_rate_element) # 기존 가격 삭제 JS
# earning_rate_element.send_keys('new_Price') # 새 가격 입력
# background: rgb(240, 240, 240); box-shadow: none; color: rgba(0, 0, 0, 0.85);
# 백그라운드 컬러로 빨간 가격탭 찾기
# background: rgb(255, 77, 79); box-shadow: none; color: rgb(255, 255, 255);
save_xpath="//button[contains(.,'저장하기')]"
click_element(driver, 'XPATH', save_xpath, 10)
logger.debug("옵션 정리 후 저장버튼 클릭 완료")

45
edit/price_cal.py Normal file
View File

@ -0,0 +1,45 @@
def calculate_margin_and_price(inputs):
# 입력값 처리
tao_low_price = inputs["tao_low_price"]
tao_high_price = inputs["tao_high_price"]
delv_fee = inputs["delv_fee"]
packing_fee = inputs["packing_fee"]
ns_low_price = inputs["ns_low_price"]
ns_high_price = inputs["ns_high_price"]
plus_margin = 5000 # 기본값
# 계산 로직
ns_avg_price = (ns_low_price + ns_high_price) / 2
tao_cost = lambda price: price * 190 * 1.035
base_margin = lambda cost: cost * 0.12
def adjust_plus_margin_for_min_margin(seller_cost, mall_cost, selling_price):
nonlocal plus_margin
target_margin_rate = 22
while True:
final_margin = selling_price - (seller_cost + mall_cost)
final_margin_rate = (final_margin / selling_price) * 100
if final_margin_rate < target_margin_rate:
plus_margin += 100 # 더하기 마진 증가
selling_price = seller_cost + plus_margin + mall_cost
mall_cost = selling_price * 0.13
else:
break
return selling_price, final_margin_rate
seller_cost = tao_cost(tao_low_price) + delv_fee + packing_fee
mall_cost = seller_cost * 0.13
selling_price = seller_cost + plus_margin + mall_cost
selling_price, final_margin_rate = adjust_plus_margin_for_min_margin(seller_cost, mall_cost, selling_price)
# 결과 반환
results = {
"selling_price": selling_price,
"final_margin_rate": final_margin_rate,
"seller_cost": seller_cost,
"base_margin": base_margin(tao_cost(tao_low_price)),
"plus_margin": plus_margin,
"ns_avg_price": ns_avg_price
}
return results

48
edit/product_info.py Normal file
View File

@ -0,0 +1,48 @@
class ProductInfo:
def __init__(self):
self.details = {}
self.id = None #상품 아이디
self.init_title = None # 키워드 상품명
self.modify_title = None # 수정 상품명
self.trans_title = None # 번역 상품명
self.tao_high_price = None # 타오바오의 최고 가격
self.tao_low_price = None # 타오바오의 최저 가격
self.option_high_price = None # 선택된 옵션 최고 가격
self.option_low_price = None # 선택된 옵션 최저 가격
self.main_image_url = None # 상품 메인썸네일 이미지 URL
self.per_cat_code = None # 퍼센티에 등록된 카테고리 텍스트
self.naver_code = None # 네이버 카테고리 코드
self.naver_low_price = None # 네이버 최저가격
self.naver_avg_price = None # 네이버 평균가격
self.naver_high_price = None # 네이버 최고가격
self.weight = None # 상품 무게
self.w_delv_fee = None # 상품 무게배송비
self.packing_fee = None # 상품 포장비
self.plus_fee = None # 더하기마진
self.return_fee = None # 반품비
self.init_delv_fee = None # 초기반품비
self.exchange_fee = None # 교환배송비
self.ai_contents = None # ai가 생성한 컨텐츠 결과
self.current_value = None # 상세페이지의 현재 내용
self.new_value = None # 상세페이지의 바뀐 내용
self.naver_products = [] # 네이버 파싱된 상품들
self.option_names = [] # 상품 옵션 이름 리스트
self.trans_option_names = [] # 번역된 상품 옵션 이름 리스트
self.trans_option_name_parts = [] # 번역된 상품 옵션 이름 리스트의 단어별 리스트 저장
self.option_image_urls = [] # 상품 옵션 이미지 URL 리스트
self.trans_option_image_urls = [] # 상품 옵션 이미지 URL 리스트
self.detail_image_urls = [] # 상품 상세페이지 이미지 URL 리스트
self.trans_detail_image_urls = [] # 상품 상세페이지 이미지 URL 리스트
self.thumb_image_urls = [] # 상품 썸네일 이미지 URL 리스트
self.trans_thumb_image_urls = [] # 상품 썸네일 이미지 URL 리스트
def update_prices(self, high_price, low_price):
self.high_price = high_price
self.low_price = low_price
def add_detail(self, key, value):
self.details[key] = value

90
edit/tag.py Normal file
View File

@ -0,0 +1,90 @@
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.keys import Keys
import time
from edit.action_elements import click_element, return_element
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def edit_tag(driver, product_infos):
product_title = product_infos.init_title
# 키워드 탭으로 이동
# try:
# keyword_tab = WebDriverWait(driver, 10).until(
# EC.presence_of_element_located((By.CSS_SELECTOR, ".ant-tabs-tab:nth-child(4)"))
# )
# except Exception as e:
# logger.debug(f"키워드 탭으로 이동 중 오류 발생: 요소를 찾을 수 없습니다. : {e}")
# # keyword_tab = driver.find_element(By.CSS_SELECTOR, "#rc-tabs-0-tab-3")
# keyword_tab.click()
keyword_tab_CSS = ".ant-tabs-tab:nth-child(4)"
click_element(driver, 'CSS_SELECTOR', keyword_tab_CSS, 10, 'js')
time.sleep(2) # 탭 전환 대기
# 키워드 입력
# try:
# keyword_input = WebDriverWait(driver, 10).until(
# EC.presence_of_element_located((By.XPATH, "(//input[@value=''])[4]"))
# )
# except Exception as e:
# logger.debug(f"키워드 입력 중 오류 발생: 요소를 찾을 수 없습니다. : {e}")
# # keyword_input = driver.find_element(By.XPATH, "(//input[@value=''])[4]")
# keyword_input.click()
# keyword_input.send_keys(product_title)
# time.sleep(0.5)
# 키워드 입력
# keyword_input_xpath = "//div[6]/div/div[3]/div/div[2]/div[1]/div/div/div[1]/div[2]/div/div/div[3]/div[1]/input"
keyword_input_xpath = "(//input[@value=''])[4]"
# keyword_input_xpath="//div[@id='productMainContentContainerId']/div/div/div/div[2]/div/div/div[3]/div/input"
keyword_input_element = return_element(driver, 'XPATH', keyword_input_xpath, 10)
keyword_input_element.send_keys(product_title)
time.sleep(0.5)
# 검색 버튼 클릭
#search_button = driver.find_element(By.XPATH, "//div[@id='productMainContentContainerId']/div/div/div/div[2]/div/div/div[3]/div[2]/button/span")
# search_button = driver.find_element(By.CSS_SELECTOR, ".sc-hpGnlu > .ant-row:nth-child(3) span")
# try:
# search_button = WebDriverWait(driver, 10).until(
# # EC.presence_of_element_located((By.CSS_SELECTOR, ".sc-hpGnlu > .ant-row:nth-child(3) span"))
# EC.presence_of_element_located((By.CSS_SELECTOR, ".sc-bizigk > .ant-row:nth-child(3) span"))
# )
# except Exception as e:
# logger.debug(f"검색 버튼 클릭 중 오류 발생: 요소를 찾을 수 없습니다. : {e}")
# search_button.click()
# 검색 버튼 클릭
search_button_xpath = "//div[@id='productMainContentContainerId']/div/div/div/div[2]/div/div/div[3]/div[2]/button"
click_element(driver, 'XPATH', search_button_xpath, 10, 'js')
time.sleep(0.5)
# 모든 키워드 추가 버튼 클릭
# add_all_keywords_button = driver.find_element(By.CSS_SELECTOR, ".sc-hpGnlu > .ant-row:nth-child(1) .ant-btn > span")
# add_all_keywords_button = driver.find_element(By.CSS_SELECTOR, ".sc-bizigk > .ant-row:nth-child(1) .ant-btn > span")
# add_all_keywords_button.click()
add_all_keywords_button_xpath = "//div[@id='productMainContentContainerId']/div/div/div/div[2]/div/div/div/div[4]/button"
click_element(driver, 'XPATH', add_all_keywords_button_xpath, 10, 'js')
time.sleep(0.5)
# 경고 키워드 삭제 버튼 클릭
# delete_warning_keywords_button = driver.find_element(By.CSS_SELECTOR, ".sc-etlCFv .ant-col:nth-child(5) span")
# delete_warning_keywords_button = driver.find_element(By.CSS_SELECTOR, ".sc-cPrPEB .ant-col:nth-child(5) span")
# delete_warning_keywords_button.click()
delete_warning_keywords_button_xpath = "//div[@id='productMainContentContainerId']/div/div/div[2]/div/div/div[5]/button"
click_element(driver, 'XPATH', delete_warning_keywords_button_xpath, 10, 'js')
time.sleep(0.5)

35
edit/test_op.py Normal file
View File

@ -0,0 +1,35 @@
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from options import modify_option_page
def main():
# 크롬 옵션 설정
chrome_options = Options()
#chrome_options.binary_location = "C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe" # 네이버 웨일 브라우저의 설치 경로
chrome_options.add_argument("--disable-notifications") # 팝업 알림 비활성화
chrome_options.add_argument("--disable-popup-blocking") # 팝업 차단 비활성화
chrome_options.add_experimental_option("prefs", {
"credentials_enable_service": False, # 자격 증명 서비스 비활성화
"profile.password_manager_enabled": False # 암호 저장 기능 비활성화
})
# 크롬 드라이버에 옵션 추가
driver = webdriver.Chrome(options=chrome_options)
driver.get('https://www.percenty.co.kr')
driver.set_window_size(1280, 800)
modify_option_page(driver)
driver.quit()
if __name__ == "__main__":
main()

125
edit/test_price.py Normal file
View File

@ -0,0 +1,125 @@
def validate_input(input_value):
try:
value = float(input_value)
if value < 0:
print("값은 0보다 커야 합니다. 기본값 0을 사용합니다.")
return 0
return value
except ValueError:
print("유효하지 않은 입력입니다. 기본값 0을 사용합니다.")
return 0
# 입력 검증 예시
tao_low_price = validate_input(input("타오바오 상품의 옵션 최저가(위안): "))
tao_high_price = validate_input(input("타오바오 상품의 옵션 최고가(위안): "))
delv_fee = validate_input(input("상품의 무게에 따른 배송비(원): "))
packing_fee = validate_input(input("포장비(원): "))
ns_low_price = validate_input(input("네이버쇼핑 상품의 최저가(원): "))
ns_high_price = validate_input(input("네이버쇼핑 상품의 최고가(원): "))
# 변수 계산
ns_avg_price = (ns_low_price + ns_high_price) / 2
plus_margin = 5000 # 기본값
# 상수 계산
tao_cost = lambda price: price * 190 * 1.035
base_margin = lambda cost: cost * 0.12
# 판매자 원가와 마진율 계산 함수
def calculate_cost_and_margin(tao_price):
tao_cost_val = tao_cost(tao_price)
seller_cost = tao_cost_val + delv_fee + packing_fee
return_fee = max(seller_cost / 2, seller_cost)
selling_price = seller_cost + plus_margin
mall_cost = selling_price * 0.13
final_cost = seller_cost + mall_cost
final_margin = selling_price - final_cost
final_margin_rate = (final_margin / selling_price) * 100
return seller_cost, final_margin, final_margin_rate, selling_price, mall_cost
# 네이버 평균가에 근접하도록 상품가 조정 로직
# 초기 plus_margin으로 판매자 원가, 최종 마진, 최종 마진율, 판매가, 마켓 수수료 계산
seller_cost, final_margin, final_margin_rate, selling_price, mall_cost = calculate_cost_and_margin(tao_low_price)
# 조건에 따라 plus_margin 조정
# 조건 A, B, C에 따른 로직 추가 예정
# 결과 출력
print(f"결정된 상품가격: {selling_price}")
print(f"그때의 최종마진율: {final_margin_rate:.2f}%")
print(f"판매자원가: {seller_cost}")
print(f"기본마진: {base_margin(tao_cost(tao_low_price))}")
print(f"더하기마진: {plus_margin}")
print(f"최종마진(기본마진+더하기마진): {final_margin}")
print(f"네이버평균가: {ns_avg_price}")
# 조정 정밀도 및 반복 횟수 제한을 포함한 마진율 조정 로직
def adjust_plus_margin_improved(tao_price):
global plus_margin
iteration_limit = 100 # 반복 횟수 제한
current_iteration = 0
while current_iteration < iteration_limit:
seller_cost, final_margin, final_margin_rate, selling_price, mall_cost = calculate_cost_and_margin(tao_price)
# 네이버 평균가격의 허용 오차범위 내 조정 목표 확인
if selling_price < ns_avg_price * 0.9:
plus_margin += 500 # 오차 범위 미만일 경우, plus_margin 증가
elif selling_price > ns_avg_price * 1.1:
plus_margin -= 500 # 오차 범위 초과일 경우, plus_margin 감소
else:
# 목표 마진율 범위 내에 있는지 확인
if final_margin_rate < 22:
plus_margin += 500 # 마진율이 목표 미만일 경우, plus_margin 증가
elif final_margin_rate > 29:
plus_margin -= 500 # 마진율이 목표를 초과할 경우, plus_margin 감소
else:
break # 목표 마진율 범위 내에 있으면 반복 종료
current_iteration += 1
return calculate_cost_and_margin(tao_price)
# 마진율 조정 실행
seller_cost, final_margin, final_margin_rate, selling_price, mall_cost = adjust_plus_margin_improved(tao_low_price)
# 결과 출력
print(f"결정된 상품가격: {selling_price}원, 최종 마진율: {final_margin_rate:.2f}%")
print(f"판매자 원가: {seller_cost}원, 기본 마진: {base_margin(tao_cost(tao_low_price))}")
print(f"더하기 마진: {plus_margin}원, 최종 마진(기본 마진+더하기 마진): {final_margin}")
print(f"네이버 평균가: {ns_avg_price}")
def adjust_plus_margin_to_ns_lower_bound(tao_price):
global plus_margin
iteration_limit = 100 # 반복 횟수 제한
current_iteration = 0
while current_iteration < iteration_limit:
seller_cost, final_margin, final_margin_rate, selling_price, mall_cost = calculate_cost_and_margin(tao_price)
ns_avg_price_lower_bound = ns_avg_price * 0.9 # 네이버 평균가의 하한
# 판매가가 네이버 평균가의 하한보다 낮을 경우, plus_margin을 증가
if selling_price < ns_avg_price_lower_bound:
plus_margin_adjustment = (ns_avg_price_lower_bound - selling_price) / (1 - 0.13) # 마켓 수수료 고려
plus_margin += plus_margin_adjustment
seller_cost, final_margin, final_margin_rate, selling_price, mall_cost = calculate_cost_and_margin(tao_price)
# 조정 후에도 네이버 평균가의 하한보다 낮지 않도록 체크
if selling_price >= ns_avg_price_lower_bound:
break
else:
break # 이미 네이버 평균가의 하한 이상인 경우 반복 종료
current_iteration += 1
return seller_cost, final_margin, final_margin_rate, selling_price, mall_cost
# 마진율 조정 실행
seller_cost, final_margin, final_margin_rate, selling_price, mall_cost = adjust_plus_margin_to_ns_lower_bound(tao_low_price)
# 결과 출력
print(f"결정된 상품가격: {selling_price}원, 최종 마진율: {final_margin_rate:.2f}%")
print(f"판매자 원가: {seller_cost}원, 기본 마진: {base_margin(tao_cost(tao_low_price))}")
print(f"더하기 마진: {plus_margin}원, 최종 마진(기본 마진+더하기 마진): {final_margin}")
print(f"네이버 평균가: {ns_avg_price}")

305
edit/test_price_g.py Normal file
View File

@ -0,0 +1,305 @@
import math
import numpy
# 환율
EXCHANGE_RATE = 190
# 카드 결제 수수료
CARD_FEE = 0.035
# 마켓 수수료
MARKET_FEE = 0.13
# 최종 마진율 범위
FINAL_MARGIN_RANGE = (0.22, 0.29)
# 허용 오차 범위
PRICE_TOLERANCE = (0.1, -0.1)
# 기본 마진
BASE_MARGIN = 0.22
# 더하기 마진 기본값
PLUS_MARGIN_DEFAULT = 5000
# 반품 비용
RETURN_FEE = 0
# 네이버 상품 정보
ns_low_price = 90000 # 최저가
ns_avg_price = 150000 # 평균가
ns_high_price = 220000 # 최고가
# 타오바오 상품 정보
tao_low_price = 200 # 최저가 (위안)
tao_high_price = 300 # 최고가 (위안)
delv_fee = 30000 # 배송비
# 포장비
packing_fee = 5000
# 상품 무게
weight = 1 # kg
# 최종 판매 가격
my_price = None
# 최종 마진율
final_margin = None
# 더하기 마진
plus_margin = None
# 상품 원가
product_cost = None
# 마켓 수수료
market_fee_amount = None
# 판매 마진
margin = None
def calculate_product_cost(tao_price, delv_fee, packing_fee, plus_margin=0):
"""
상품 원가를 계산합니다.
Args:
tao_price: 타오바오 상품 가격 (위안)
delv_fee: 배송비
packing_fee: 포장비
plus_margin: 더하기 마진
Returns:
상품 원가
"""
return (
tao_price * EXCHANGE_RATE * (1 + CARD_FEE) * (1 + BASE_MARGIN + plus_margin)
+ delv_fee
+ packing_fee
)
def calculate_minimum_plus_margin(product_cost, return_fee):
"""
최소 더하기 마진을 계산합니다.
Args:
product_cost: 상품 원가
return_fee: 반품 비용
Returns:
최소 더하기 마진
"""
return max(PLUS_MARGIN_DEFAULT, product_cost + delv_fee - return_fee)
def calculate_final_margin(my_price, product_cost):
"""
최종 마진율을 계산합니다.
Args:
my_price: 판매 가격
product_cost: 상품 원가
Returns:
최종 마진율
"""
return(my_price - product_cost) / my_price
def adjust_plus_margin(my_price, ns_avg_price, price_tolerance, final_margin_range):
"""
더하기 마진을 조정합니다.
Args:
my_price: 판매 가격
ns_avg_price: 네이버 평균 가격
price_tolerance: 허용 오차 범위
final_margin_range: 최종 마진율 범위
Returns:
조정된 더하기 마진
"""
min_price = ns_avg_price * (1 + price_tolerance[0])
max_price = ns_avg_price * (1 + price_tolerance[1])
if my_price < min_price:
return my_price - product_cost - market_fee_amount
if my_price > max_price:
return max(PLUS_MARGIN_DEFAULT, calculate_minimum_plus_margin(product_cost, return_fee))
min_plus_margin = calculate_minimum_plus_margin(product_cost, return_fee)
max_plus_margin = (
max_price
- product_cost
- market_fee_amount
- delv_fee
- packing_fee
)
for plus_margin in numpy.arange(min_plus_margin, max_plus_margin + 1):
final_margin = calculate_final_margin(
my_price, calculate_product_cost(tao_low_price, delv_fee, packing_fee, plus_margin)
)
if final_margin_range[0] <= final_margin <= final_margin_range[1]:
return plus_margin
return None
def calculate_optimal_price(
tao_low_price,
tao_high_price,
delv_fee,
packing_fee,
ns_low_price,
ns_avg_price,
ns_high_price,
):
"""
최적의 더하기 마진을 찾아 최종 판매 가격을 계산합니다.
Args:
tao_low_price: 타오바오 상품 최저가 (위안)
tao_high_price: 타오바오 상품 최고가 (위안)
delv_fee: 배송비
packing_fee: 포장비
ns_low_price: 네이버 최저가
ns_avg_price: 네이버 평균가
ns_high_price: 네이버 최고가
Returns:
상품 가격 정보
"""
min_product_cost = calculate_product_cost(tao_low_price, delv_fee, packing_fee)
max_product_cost = calculate_product_cost(tao_high_price, delv_fee, packing_fee)
min_avg_price = ns_avg_price * (1 - price_tolerance[1])
max_avg_price = ns_avg_price * (1 + price_tolerance[0])
# 1천원 단위로 더하기 마진 조정
for plus_margin in range(PLUS_MARGIN_DEFAULT, max_product_cost + 1000, 1000):
my_price = calculate_product_cost(tao_low_price, delv_fee, packing_fee, plus_margin)
market_fee_amount = my_price * MARKET_FEE
final_margin = calculate_final_margin(my_price, product_cost)
# 최종 마진율 범위에 포함되면 결과 출력
if FINAL_MARGIN_RANGE[0] <= final_margin <= FINAL_MARGIN_RANGE[1]:
return {
"min_product_cost": min_product_cost,
"max_product_cost": max_product_cost,
"min_avg_price": min_avg_price,
"max_avg_price": max_avg_price,
"my_price": my_price,
"plus_margin": plus_margin,
"market_fee_amount": market_fee_amount,
"margin": my_price - product_cost - market_fee_amount,
"final_margin": final_margin,
}
# 최종 마진율 범위에 포함되는 더하기 마진을 찾지 못하면 오류 메시지 출력
raise ValueError(
f"최종 마진율 범위({FINAL_MARGIN_RANGE[0]:.2%} ~ {FINAL_MARGIN_RANGE[1]:.2%})에 "
f"포함되는 더하기 마진을 찾지 못했습니다."
)
def calculate_price(
tao_low_price,
tao_high_price,
delv_fee,
packing_fee,
ns_low_price,
ns_avg_price,
ns_high_price,
):
"""
상품 가격을 계산합니다.
Args:
tao_low_price: 타오바오 상품 최저가 (위안)
tao_high_price: 타오바오 상품 최고가 (위안)
delv_fee: 배송비
packing_fee: 포장비
ns_low_price: 네이버 최저가
ns_avg_price: 네이버 평균가
ns_high_price: 네이버 최고가
Returns:
상품 가격 정보
"""
# 1. 상품 원가 계산
min_product_cost = calculate_product_cost(tao_low_price, delv_fee, packing_fee)
max_product_cost = calculate_product_cost(tao_high_price, delv_fee, packing_fee)
# 2. 네이버 평균 가격 기준 내 상품 가격 범위 계산
min_avg_price = ns_avg_price * (1 - price_tolerance[1])
max_avg_price = ns_avg_price * (1 + price_tolerance[0])
plus_margin = PLUS_MARGIN_DEFAULT
my_price = calculate_product_cost(
tao_low_price, delv_fee, packing_fee, plus_margin
) + market_fee_amount * my_price
# **오류 해결**: `final_margin` 변수 선언
final_margin = calculate_final_margin(my_price, product_cost)
if final_margin < FINAL_MARGIN_RANGE[0]:
plus_margin = adjust_plus_margin(
my_price, ns_avg_price, price_tolerance, final_margin_range
)
my_price = calculate_product_cost(
tao_low_price, delv_fee, packing_fee, plus_margin
) + market_fee_amount * my_price
final_margin = calculate_final_margin(my_price, product_cost)
elif final_margin > FINAL_MARGIN_RANGE[1]:
plus_margin = adjust_plus_margin(
my_price, ns_avg_price, price_tolerance, final_margin_range
)
my_price = calculate_product_cost(
tao_low_price, delv_fee, packing_fee, plus_margin
) + market_fee_amount * my_price
final_margin = calculate_final_margin(my_price, product_cost)
return {
"min_product_cost": min_product_cost,
"max_product_cost": max_product_cost,
"min_avg_price": min_avg_price,
"max_avg_price": max_avg_price,
"my_price": my_price,
"plus_margin": plus_margin,
"final_margin": final_margin,
}
tao_low_price = float(input("타오바오 최저가 (위안): "))
tao_high_price = float(input("타오바오 최고가 (위안): "))
delv_fee = int(input("배송비: "))
packing_fee = int(input("포장비: "))
ns_low_price = int(input("네이버 최저가: "))
ns_high_price = int(input("네이버 최고가: "))
ns_avg_price = (ns_low_price + ns_high_price)/2
# 최적의 더하기 마진 계산
result = calculate_optimal_price(
tao_low_price,
tao_high_price,
delv_fee,
packing_fee,
ns_low_price,
ns_avg_price,
ns_high_price,
)
# 결과 출력
for key, value in result.items():
print(f"{key}: {value}")

43
edit/texet_test.py Normal file
View File

@ -0,0 +1,43 @@
# from konlpy.tag import Okt
# import re
# # KoNLPy의 Okt 형태소 분석기 인스턴스화
# okt = Okt()
# # 옵션명 예시
# option_names = [
# "카키 - 트레이 및 컵홀더 없음 - 좌식 및 리클라이닝 가능",
# "그린 그린-트레이 없음-컵 홀더 없음-좌석 리클라이닝 가능",
# "옵시디언 블랙-접시 없음-컵 홀더 없음-좌식 및 리클라이닝 가능",
# "블랙 및 화이트-접시 없음-컵홀더 없음-좌식 및 리클라이닝 가능",
# "아리스토크랫 화이트-식판 탈착식-좌식 및 리클라이닝 가능"
# ]
# # 옵션명에서 명사 추출
# nouns = []
# for option_name in option_names:
# extracted_nouns = okt.nouns(option_name) # 명사 추출
# nouns.extend(extracted_nouns)
# # 추출된 명사 중복 제거
# unique_nouns = list(set(nouns))
# print("추출된 고유 명사:", unique_nouns)
# print(okt.pos(option_name)) # 품사 태깅
from sejong.tokenizer import Khaiii
# Khaiii 형태소 분석기 초기화
tokenizer = Khaiii()
# 분석할 텍스트
text = "안녕하세요, Sejong 라이브러리입니다."
# 형태소 분석 수행
tokens = tokenizer.tokenize(text)
# 결과 출력
for token in tokens:
print(token)

68
edit/thumbnail.py Normal file
View File

@ -0,0 +1,68 @@
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
import time
from edit.action_elements import click_element, return_element
# from ai.compare import find_most_similar_image_by_one
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def modify_thumb_page(driver):
# 옵션 탭으로 이동
logger.debug("내부함수 썸네일 탭으로 이동")
thumb_tab_CSS = '.ant-tabs-tab:nth-child(5)'
click_element(driver, 'CSS_SELECTOR', thumb_tab_CSS, 10, 'js')
logger.debug("내부함수 썸네일 탭으로 이동 완료")
logger.debug("페이지 로딩 대기")
time.sleep(2) # 페이지 로딩 대기.
logger.debug("전체 썸네일 상세페이지로 복사 버튼 클릭")
# xpath=//div[@id='productMainContentContainerId']/div/div/div/div[2]/div/button
# all_copy_detail_tab_btn_xpath= "//button[contains(.,'전체 상세페이지로 복사')]"
all_copy_detail_tab_btn_xpath= "//div[@id='productMainContentContainerId']/div/div/div/div[2]/div/button"
click_element(driver, 'XPATH', all_copy_detail_tab_btn_xpath, 10, 'js')
logger.debug("1번째 이미지 편집버튼 클릭")
first_img_edit_btn_xpath="//div[@id='productMainContentContainerId']/div/div/div[4]/div/div/div/div[3]/div[2]/div"
click_element(driver, 'XPATH', first_img_edit_btn_xpath, 10, 'js')
logger.debug("원클릭 배경 삭제 전송")
# 편집화면 요소에 'M' 키 전송
active_element = driver.switch_to.active_element
active_element.send_keys('m')
time.sleep(0.5)
logger.debug("편집내용 저장버튼 전송")
active_element.send_keys(Keys.CONTROL, 's')
time.sleep(0.5)
logger.debug("편집모드 나가기 전송")
active_element.send_keys(Keys.ESCAPE)
time.sleep(0.5)
logger.debug("2번째 이미지 편집버튼 클릭")
second_img_edit_btn_xpath="//div[@id='productMainContentContainerId']/div/div/div[4]/div/div[2]/div/div[3]/div[2]/div"
click_element(driver, 'XPATH', second_img_edit_btn_xpath, 10, 'js')
logger.debug("원클릭 배경 삭제 전송")
# 편집화면 요소에 'M' 키 전송
active_element = driver.switch_to.active_element
active_element.send_keys('m')
time.sleep(0.5)
logger.debug("편집내용 저장버튼 전송")
active_element.send_keys(Keys.CONTROL, 's')
time.sleep(0.5)
logger.debug("편집모드 나가기 전송")
active_element.send_keys(Keys.ESCAPE)
time.sleep(0.5)
save_xpath="//button[contains(.,'저장하기')]"
click_element(driver, 'XPATH', save_xpath, 10)
logger.debug("옵션 정리 후 저장버튼 클릭 완료")

74
edit/title.py Normal file
View File

@ -0,0 +1,74 @@
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
import time, re, random
from edit.action_elements import click_element, return_element
from edit.price_cal import calculate_margin_and_price
# from ai.compare import find_most_similar_image_by_one
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def modify_product_title(driver, product_infos):
# 가격 탭으로 이동
logger.debug("내부함수 상품명 탭으로 이동")
title_tab_CSS = '.ant-tabs-tab:nth-child(1)'
click_element(driver, 'CSS_SELECTOR', title_tab_CSS, 10, 'js')
logger.debug("내부함수 상품명 탭으로 이동 완료")
logger.debug("페이지 로딩 대기")
time.sleep(2) # 페이지 로딩 대기.
#상품명 복사
product_title_element = driver.find_element(By.XPATH, "//div[@id='productMainContentContainerId']/div/div/div/div/div[5]/div/span/input")
try:
product_title_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//div[5]/div/span/input"))
)
except Exception as e:
logger.debug(f"상품명 복사 중 오류 발생: 요소를 찾을 수 없습니다. : {e}")
# product_infos에 저장된 최초 상품명과 옵션명들을 이용해 새로운 상품명을 조합
new_title = set_title(product_infos)
product_title_element.set_attribute(new_title)
save_xpath="//button[contains(.,'저장하기')]"
click_element(driver, 'XPATH', save_xpath, 10)
logger.debug("옵션 정리 후 저장버튼 클릭 완료")
def set_title(product_infos):
current_title = product_infos.init_title
new_title = ""
# 상품명을 공백 기준으로 분리
current_title_parts = re.split(r' ', current_title)
# 숫자 포함 단어 제외, 영어와 숫자로만 이루어진 단어 제외
filtered_parts = [part for part in current_title_parts if not re.search(r'\d', part) and not re.fullmatch(r'[A-Za-z0-9]+', part)]
# 세 번째 단어부터 나머지 단어 섞기
if len(filtered_parts) >= 3:
shuffled_parts = filtered_parts[2:]
random.shuffle(shuffled_parts)
new_title_parts = filtered_parts[:2] + shuffled_parts
else:
new_title_parts = filtered_parts
# 옵션명에서 중요 단어 추출 후 상품명에 추가
option_keywords = [keyword for part in product_infos.trans_option_name_parts for keyword in part if keyword in ['색상', '크기', '용량']]
option_keywords = option_keywords[:2] # 대표하는 단어 2개만 선택
# 최종 상품명 조합
new_title = ' '.join(new_title_parts + option_keywords)
return new_title

BIN
fonts/NotoSans.ttf Normal file

Binary file not shown.

BIN
fonts/NotoSansKR-Bold.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
fonts/simfang.ttf Normal file

Binary file not shown.

BIN
img_trans/NotoSans.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

81
img_trans/image_trans.py Normal file
View File

@ -0,0 +1,81 @@
import cv2
from io import BytesIO
import base64
from PIL import Image
import urllib, requests
import numpy as np
from img_trans.src.text_detection_with_paddle import detect_text
from img_trans.src.removal import process_image_based_on_text_area
from img_trans.src.translation import translate_texts_translatepy
from img_trans.src.text_insertion import insert_text
from img_trans.src.mini_crop_process import resize_and_crop_image
from img_trans.src.rotate_img import flip_and_rotate_image
from img_trans.src.inpainting import inpaint_text # 기존 인페인팅 메서드
def image_trans(image_url, convert_type):
# 이미지를 URL로부터 읽어옵니다.
image = read_image_from_url(image_url)
# 이미지에서 텍스트 인식
detected_texts = detect_text(image)
print("Detected texts:", detected_texts)
# 처리된 이미지 경로 설정
img_name = "output"
thumb_image_path = f'img/{img_name}_thumbnail.jpg'
translated_image_path = f'img/{img_name}_translated.jpg'
# 처리된 이미지 경로 설정
Thumb_image_path = f'img/{img_name}_Thumb.jpg'
processed_image_path = f'img/{img_name}_processed.jpg'
translated_image_path = f'img/{img_name}_translated.jpg'
inpainted_image_path = f'img/{img_name}_inpainted.jpg'
if convert_type == 'thumbnail':
# 썸네일 생성 로직
print("썸네일 생성 처리 시작")
# 크롭과 인페인팅 결정 및 처리 함수 호출
removal_img = process_image_based_on_text_area(image, detected_texts, processed_image_path, threshold = 0.1)
# 추가 처리: 이미지 회전 및 좌우 반전
# removal_img = flip_and_rotate_image(removal_img)
cv2.imwrite(Thumb_image_path, removal_img)
print(f"썸네일 이미지 생성완료: {Thumb_image_path}")
# 저장된 이미지 파일의 경로를 반환합니다.
return Thumb_image_path
elif convert_type == 'translate':
# 2단계: 인페이팅 기법으로 텍스트 제거
inpainted_image = inpaint_text(image, detected_texts)
cv2.imwrite(inpainted_image_path, inpainted_image)
# 이미지 번역 처리
print("이미지 번역 처리 시작")
# 번역된 텍스트 추출 및 삽입
texts_to_translate = [text for _, text, _, _, _ in detected_texts]
print(f"원본 텍스트: {texts_to_translate}")
translated_texts = translate_texts_translatepy(texts_to_translate)
print(f"번역 텍스트: {texts_to_translate}")
translated_image = insert_text(inpainted_image_path, translated_texts, detected_texts)
cv2.imwrite(translated_image_path, translated_image)
print(f"번역된 이미지 생성완료 : {translated_image_path}")
# 번역된 이미지를 HTML에 삽입 가능한 Base64 인코딩 문자열로 변환합니다.
pil_img = Image.fromarray(cv2.cvtColor(translated_image, cv2.COLOR_BGR2RGB))
buffered = BytesIO()
pil_img.save(buffered, format="JPEG")
img_str = base64.b64encode(buffered.getvalue()).decode()
# HTML 코드를 생성하여 반환합니다.
img_html = f'<img src="data:image/jpeg;base64,{img_str}" />'
return img_html
# URL로부터 이미지를 읽어오는 함수
def read_image_from_url(image_url):
resp = urllib.request.urlopen(image_url)
image = np.asarray(bytearray(resp.read()), dtype="uint8")
image = cv2.imdecode(image, cv2.IMREAD_COLOR)
return image
# 외부에서 함수를 사용하는 예
# 이미지 URL과 원하는 변환 유형('thumbnail' 또는 'translate')을 인자로 넘깁니다.
result = image_trans('http://example.com/image.jpg', 'thumbnail')

55
img_trans/main.py Normal file
View File

@ -0,0 +1,55 @@
import cv2
from src.text_detection_with_paddle import detect_text
from src.removal import process_image_based_on_text_area
from src.translation import translate_texts_translatepy
from src.text_insertion import insert_text
from src.mini_crop_process import resize_and_crop_image
from src.rotate_img import flip_and_rotate_image
from src.inpainting import inpaint_text # 기존 인페인팅 메서드
def main(image_path, img_name, thumb_or_trans):
# 이미지에서 텍스트 인식
detected_texts = detect_text(image_path)
print("Detected texts:", detected_texts)
# 처리된 이미지 경로 설정
Thumb_image_path = f'img/{img_name}_Thumb.jpg'
processed_image_path = f'img/{img_name}_processed.jpg'
translated_image_path = f'img/{img_name}_translated.jpg'
inpainted_image_path = f'img/{img_name}_inpainted.jpg'
# 텍스트 영역에 따라 이미지 처리: 크롭 또는 인페이팅
# crop_and_inpaint(image_path, detected_texts, processed_image_path)
# print(f"Image processed and saved to {processed_image_path}")
if thumb_or_trans: # 썸네일 처리
# 썸네일 생성 로직
print("썸네일 생성 처리 시작")
# 크롭과 인페인팅 결정 및 처리 함수 호출
removal_img = process_image_based_on_text_area(image_path, detected_texts, processed_image_path, threshold = 0.1)
# 추가 처리: 이미지 회전 및 좌우 반전
# removal_img = flip_and_rotate_image(removal_img)
cv2.imwrite(Thumb_image_path, removal_img)
print(f"썸네일 이미지 생성완료: {Thumb_image_path}")
else: # 이미지 번역
# 2단계: 인페이팅 기법으로 텍스트 제거
image = cv2.imread(image_path)
inpainted_image = inpaint_text(image, detected_texts)
cv2.imwrite(inpainted_image_path, inpainted_image)
# 이미지 번역 처리
print("이미지 번역 처리 시작")
# 번역된 텍스트 추출 및 삽입
texts_to_translate = [text for _, text, _, _, _ in detected_texts]
print(f"원본 텍스트: {texts_to_translate}")
translated_texts = translate_texts_translatepy(texts_to_translate)
print(f"번역 텍스트: {texts_to_translate}")
translated_image = insert_text(inpainted_image_path, translated_texts, detected_texts)
cv2.imwrite(translated_image_path, translated_image)
print(f"번역된 이미지 생성완료 : {translated_image_path}")
if __name__ == "__main__":
img_name = "h"
image_path = f'img/{img_name}.png'
thumb_or_trans = False # True for thumbnail, False for translation
main(image_path, img_name, thumb_or_trans)

BIN
img_trans/simfang.ttf Normal file

Binary file not shown.

View File

View File

@ -0,0 +1,59 @@
# background_modification.py
import cv2
import numpy as np
def convert_background_to_white(image_path, output_path):
"""
이미지 배경을 흰색으로 변환합니다.
Parameters:
- image_path: str, 원본 이미지의 경로입니다.
- output_path: str, 배경이 수정된 이미지를 저장할 경로입니다.
Returns:
- None
"""
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
# 마스크 생성 및 마스크의 반전
mask = cv2.bitwise_not(thresh)
# 배경을 흰색으로 설정
background = np.full(image.shape, 255, dtype=np.uint8)
bk_image = cv2.bitwise_or(background, background, mask=mask)
# 원본 이미지에서 전경 추출
foreground = cv2.bitwise_and(image, image, mask=thresh)
# 최종 이미지: 전경 + 흰색 배경
final_image = cv2.bitwise_or(foreground, bk_image)
cv2.imwrite(output_path, final_image)
def remove_background(image_path, output_path):
"""
딥러닝 기반의 모델을 사용하여 이미지에서 배경을 제거합니다.
Note: 함수는 배경 제거 모델의 예시 구현을 위한 가상 코드입니다.
실제 모델 적용 시에는 모델의 구체적인 사용 방법에 따라 코드를 조정해야 합니다.
Parameters:
- image_path: str, 원본 이미지의 경로입니다.
- output_path: str, 배경이 제거된 이미지를 저장할 경로입니다.
Returns:
- None
"""
# 예시로, 여기서는 OpenCV를 사용한 간단한 배경 제거 방법을 보여줍니다.
# 실제 딥러닝 기반 모델을 사용할 때는 해당 모델의 API를 사용하여 구현해야 합니다.
image = cv2.imread(image_path)
# 배경 제거 로직 적용 (딥러닝 모델 사용 예정)
# ...
# 가상의 배경 제거 처리 후
cv2.imwrite(output_path, image) # 배경 제거 처리된 이미지 저장
# 예제 사용
# convert_background_to_white("path/to/your/image.jpg", "path/to/output/white_background_image.jpg")
# remove_background("path/to/your/image.jpg", "path/to/output/no_background_image.jpg")

View File

@ -0,0 +1,32 @@
import torch
from model import InpaintingModel # 모델과 관련된 클래스를 포함하는 가상의 파일
class DeepInpainter:
def __init__(self, model_path):
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
self.model = InpaintingModel().to(self.device)
self.model.load_state_dict(torch.load(model_path, map_location=self.device))
def inpaint(self, image, mask):
# 이미지와 마스크를 모델이 처리할 수 있는 형태로 변환합니다.
image_tensor = self._preprocess_image(image)
mask_tensor = self._preprocess_mask(mask)
# 모델을 이용하여 인페이팅을 수행합니다.
with torch.no_grad():
inpainted_image = self.model(image_tensor, mask_tensor)
# 결과를 이미지 형태로 변환하여 반환합니다.
return self._postprocess_image(inpainted_image)
def _preprocess_image(self, image):
# 이미지를 텐서로 변환하는 전처리 로직
pass
def _preprocess_mask(self, mask):
# 마스크를 텐서로 변환하는 전처리 로직
pass
def _postprocess_image(self, inpainted_image):
# 텐서를 이미지로 변환하는 후처리 로직
pass

66
img_trans/src/deepl.py Normal file
View File

@ -0,0 +1,66 @@
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# from tqdm import tqdm
from selenium_stealth import stealth
from fake_useragent import UserAgent
def trans(original_text):
# Start a Selenium driver
options = webdriver.ChromeOptions()
ua = UserAgent()
options.add_argument(f"--user-agent={ua.random}") # 랜덤 user_agent 사용
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument('--ignore-certificate-errors')
options.add_argument('--ssl-protocol=any')
options.add_argument('--disable-cache')
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
# selenium-stealth 설정 적용
stealth(driver,
languages=["en-US", "en"],
vendor="Google Inc.",
platform="Win32",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hairline=True,
)
# Reach the deepL website
deepl_url = 'https://www.deepl.com/ko/translator' # 한국어
driver.get(deepl_url)
# for text_to_translate in tqdm(original_text):
try:
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".min-h-0:nth-child(1) > div:nth-child(1)"))).send_keys(original_text)
except Exception as e:
print(f"예외발생 DeepL 웹 요소 에러. {e}")
print("JS 전환.")
driver.execute_script('arguments[0].value = arguments[1];', ".min-h-0:nth-child(1) > div:nth-child(1)", original_text)
time.sleep(3) # Adjust sleep time based on network speed and response time
# Improved wait for translation to appear
try:
WebDriverWait(driver, 10).until(
lambda driver: driver.find_element(By.CSS_SELECTOR, '[data-testid="translator-target-input"]').text.strip() != "")
translation_text = driver.find_element(By.CSS_SELECTOR, '[data-testid="translator-target-input"]')
content = translation_text.text
except Exception as e:
print("Error fetching translation:", e)
content = "Translation failed"
driver.quit()
print(f"번역된 텍스트 by DeepL \n {content}")
return content
# result = trans("全铜芯卧式电机不锈钢底座厚")
# print(f"입력 : 全铜芯卧式电机不锈钢底座厚")
# print(f"결과 : {result}")

View File

@ -0,0 +1,51 @@
#import cv2
#import numpy as np
#def inpaint_text(image_path, detected_texts):
#"""인페이팅 기법을 사용하여 이미지 내 텍스트 제거"""
## 이미지 로드
#image = cv2.imread(image_path)
## 마스크 생성 (처음에는 전부 0으로 채워진 마스크)
#mask = np.zeros(image.shape[:2], dtype=np.uint8)
# 인식된 텍스트 위치에 따라 마스크 업데이트
# for text in detected_texts:
#vertices = np.array([text[0]], dtype=np.int32)
#cv2.fillPoly(mask, vertices, 255)
# # 인페이팅 적용
#inpainted_image = cv2.inpaint(image, mask, inpaintRadius=3, flags=cv2.INPAINT_TELEA)
## 결과 이미지 반환
#return inpainted_image
#################################
import cv2
import numpy as np
def inpaint_text(image, detected_texts, expansion=5):
"""
이미지 텍스트 제거를 위해 인페이팅 기법을 사용합니다.
expansion 파라미터는 텍스트 주변의 마스크 확장 크기를 지정합니다.
"""
# image = cv2.imread(image_path)
mask = np.zeros(image.shape[:2], dtype=np.uint8)
# detected_texts에서 언패킹할 때 추가 정보를 포함시킵니다.
for box, text, confidence, *_ in detected_texts:
# 바운딩 박스 확장 로직을 추가합니다.
x_min, y_min = np.min(box, axis=0) - expansion
x_max, y_max = np.max(box, axis=0) + expansion
x_min, y_min = np.clip([x_min, y_min], a_min=0, a_max=None)
x_max, y_max = np.clip([x_max, y_max], a_min=None, a_max=[mask.shape[1], mask.shape[0]])
# 마스크 업데이트
cv2.rectangle(mask, (int(x_min), int(y_min)), (int(x_max), int(y_max)), 255, thickness=-1)
# 여기에서 딥러닝 기반 인페인팅 모델을 적용할 수 있습니다.
# 인페이팅 알고리즘 적용 (TELEA 또는 NS 방식 중 선택)
inpainted_image = cv2.inpaint(image, mask, inpaintRadius=3, flags=cv2.INPAINT_TELEA)
# 결과 이미지 반환
return inpainted_image

View File

@ -0,0 +1,44 @@
# preprocess.py
import cv2
import numpy as np
def resize_and_crop_image(image_path, output_path, crop_percentage=0.05):
"""
이미지를 중앙을 기준으로 축소 크롭합니다.
Parameters:
- image_path: str, 원본 이미지의 경로입니다.
- output_path: str, 수정된 이미지를 저장할 경로입니다.
- crop_percentage: float, 이미지를 얼마나 축소할지 결정하는 비율입니다.
Returns:
- None
"""
# 이미지 로드
image = cv2.imread(image_path)
if image is None:
print(f"Error: '{image_path}' cannot be loaded.")
return
# 이미지의 크기 계산
height, width = image.shape[:2]
# 크롭할 크기 계산
crop_height = int(height * crop_percentage)
crop_width = int(width * crop_percentage)
# 중앙을 기준으로 크롭 범위 계산
start_row, start_col = crop_height // 2, crop_width // 2
end_row, end_col = height - start_row, width - start_col
# 이미지 크롭
cropped_image = image[start_row:end_row, start_col:end_col]
# 크롭된 이미지 저장
cv2.imwrite(output_path, cropped_image)
# print(f"Image saved to {output_path}")
# 예제 사용
# resize_and_crop_image("path/to/input/image.jpg", "path/to/output/cropped_image.jpg")

207
img_trans/src/removal.py Normal file
View File

@ -0,0 +1,207 @@
# removal.py
import cv2
import numpy as np
from img_trans.src.inpainting import inpaint_text # 기존 인페인팅 메서드
# def get_combined_box_ori(boxes):
# """ 주어진 박스 리스트에서 x_min, y_min, x_max, y_max 값을 계산합니다. """
# x_min = min([box[0] for box in boxes])
# y_min = min([box[1] for box in boxes])
# x_max = max([box[2] for box in boxes])
# y_max = max([box[3] for box in boxes])
# return (x_min, y_min, x_max, y_max)
def get_combined_box(boxes):
"""주어진 박스 리스트에서 x_min, y_min, x_max, y_max 값을 계산합니다."""
x_min = min(box[0] for box in boxes)
y_min = min(box[1] for box in boxes)
x_max = max(box[2] for box in boxes)
y_max = max(box[3] for box in boxes)
return x_min, y_min, x_max, y_max
def calculate_area_ratio(box, image_area):
# box의 x 좌표와 y 좌표를 분리하여 각각의 최소값과 최대값을 구합니다.
x_coords = [point[0] for point in box]
y_coords = [point[1] for point in box]
x_min = min(x_coords)
x_max = max(x_coords)
y_min = min(y_coords)
y_max = max(y_coords)
# 상자의 너비와 높이를 계산합니다.
box_width = x_max - x_min
box_height = y_max - y_min
# 상자의 면적을 계산하고, 이미지 전체 면적에 대한 비율을 구합니다.
box_area = box_width * box_height
area_ratio = box_area / image_area
return area_ratio
def crop_out_text_area(image, box, image_height, upper):
"""
이미지에서 텍스트가 포함된 영역을 잘라냅니다.
upper 변수를 기준으로 상단 영역 또는 하단 영역을 잘라냅니다.
Parameters:
image : 원본 이미지
box : 텍스트 박스 ([x_min, y_min, x_max, y_max])
image_height : 이미지의 높이
upper : 상단 영역을 잘라낼지(1), 하단 영역을 잘라낼지(0) 결정
"""
_, y_min, _, y_max = box
if upper:
# 상단 영역을 잘라냅니다. y_max를 기준으로 아래쪽을 유지
cropped_image = image[y_max:, :]
else:
# 하단 영역을 잘라냅니다. y_min을 기준으로 위쪽을 유지
cropped_image = image[:y_min, :]
return cropped_image
def crop_out_text_area_ori(image, box, image_height, upper):
# box 변수에서 직접 y_min, y_max 값을 언팩킹
x_min, y_min, x_max, y_max = box
# y_min = int(y_min*1.1)
# y_max = int(y_max*0.9)
print(f"y_min : {y_min}")
print(f"y_max : {y_max}")
if upper == 1:
print(f"upper : {upper}")
cropped_image = image[:image_height-y_max, :]
elif upper == 0:
print(f"upper : {upper}")
cropped_image = image[image_height-y_min:, :]
# # 이미지를 y_min과 y_max 기준으로 분할하여 재조합
# upper_part = image[:y_min, :]
# lower_part = image[y_max:, :]
# # 위쪽 부분과 아래쪽 부분을 연결
# cropped_image = np.vstack((upper_part, lower_part))
# cropped_image = np.vstack((upper_part, lower_part))
# return cropped_image
return cropped_image
def inpaint_expanded_area(image, boxes, detected_texts, expansion=20):
mask = np.zeros(image.shape[:2], dtype=np.uint8)
for box, text, confidence, *_ in detected_texts:
# 바운딩 박스 확장 로직을 추가합니다.
x_min, y_min = np.min(box, axis=0) - expansion
x_max, y_max = np.max(box, axis=0) + expansion
x_min, y_min = np.clip([x_min, y_min], a_min=0, a_max=None)
x_max, y_max = np.clip([x_max, y_max], a_min=None, a_max=[mask.shape[1], mask.shape[0]])
# 마스크 업데이트
cv2.rectangle(mask, (int(x_min), int(y_min)), (int(x_max), int(y_max)), 255, thickness=-1)
inpainted_image = cv2.inpaint(image, mask, 3, cv2.INPAINT_TELEA)
return inpainted_image
def crop_and_inpaint(image, box, detected_texts, image_height, upper):
"""
이미지를 크롭하고 남은 텍스트를 인페인팅합니다.
"""
# 박스 좌표를 정수로 변환하기 위해 수정된 로직
x_coords = [coord[0] for coord in box]
y_coords = [coord[1] for coord in box]
x_min, x_max = int(min(x_coords)), int(max(x_coords))
y_min, y_max = int(min(y_coords)), int(max(y_coords))
new_crop_box = [x_min, y_min, x_max, y_max]
print(f"new_crop_box : {new_crop_box}")
# 이미지 크롭
cropped_image = crop_out_text_area(image, new_crop_box, image_height, upper)
# 크롭된 이미지에 대해 텍스트 박스 위치 재계산
new_detected_texts = []
for det_text in detected_texts:
new_box = [(x - x_min, y - y_min) for (x, y) in det_text[0]]
new_detected_texts.append((new_box, det_text[1], det_text[2], det_text[3], det_text[4]))
# 남은 텍스트 인페인팅 처리
inpainted_image = inpaint_text(cropped_image, new_detected_texts)
return inpainted_image
# cv2.imwrite(image_path, inpainted_image)
def separate_texts_by_region(detected_texts, image_height):
# 이미지 높이를 기준으로 상단과 하단 영역의 경계를 정의합니다.
# 상단 영역과 하단 영역을 전체 이미지 높이의 15%로 설정
upper_limit = image_height * 0.15
lower_limit = image_height * 0.85
upper_texts = [] # 상단 영역의 텍스트 정보를 담을 리스트
lower_texts = [] # 하단 영역의 텍스트 정보를 담을 리스트
for text_info in detected_texts:
box, text, confidence, text_width, text_height = text_info
# box 좌표에서 y 좌표만 추출하여 최소값과 최대값을 계산합니다.
y_coords = [point[1] for point in box]
y_min = min(y_coords)
y_max = max(y_coords)
# 텍스트 박스의 y 최소값이 상단 영역의 한계보다 작으면 상단 영역으로 분류
if y_min < upper_limit:
upper_texts.append(text_info)
# 텍스트 박스의 y 최대값이 하단 영역의 한계보다 크면 하단 영역으로 분류
elif y_max > lower_limit:
lower_texts.append(text_info)
# 그 외의 텍스트 박스는 중간 영역으로 간주하고 별도 처리하지 않음
return upper_texts, lower_texts
def process_image_based_on_text_area(image, detected_texts, output_path, threshold = 0.3):
""" 이미지 내 텍스트 면적에 따라 크롭 또는 인페인팅을 결정하고 처리합니다. """
# 임계값 설정 (예: 이미지의 40% 이상을 차지하는 경우 크롭)
# threshold = 0.3
print(f"텍스트박스의 임계값 설정 : {threshold}")
# image = cv2.imread(image_path)
image_area = image.shape[0] * image.shape[1] # 이미지 면적 계산
image_height = image.shape[0] # 이미지 높이 계산
upper_texts, lower_texts = separate_texts_by_region(detected_texts, image_height)
# # 각 영역별로 최대 텍스트 박스 결합
# upper_box = get_combined_box_ori([box for box, _, _, _, _ in upper_texts])
# lower_box = get_combined_box_ori([box for box, _, _, _, _ in lower_texts])
# 각 영역별로 최대 텍스트 박스 결합
upper_box = get_combined_box([text_info[0] for text_info in upper_texts])
print(f"upper_box \n {upper_box}")
lower_box = get_combined_box([text_info[0] for text_info in lower_texts])
print(f"lower_box \n {lower_box}")
# 각 영역별로 면적 비율 계산
upper_box_ratio = calculate_area_ratio(upper_box, image_area)
lower_box_ratio = calculate_area_ratio(lower_box, image_area)
print(f"upper_box_ratio : {upper_box_ratio}")
print(f"lower_box_ratio : {lower_box_ratio}")
# 상단 또는 하단 영역이 크롭 기준을 충족하는지 확인
if upper_box_ratio > threshold or lower_box_ratio > threshold:
print("텍스트 박스로 인한 이미지 크롭 진행")
if upper_box_ratio >= lower_box_ratio:
print("upper_box 진행")
removal_img = crop_and_inpaint(image, upper_box, upper_texts, image_height, upper = 1) # 박스에 대한 크롭
# removal_img = inpaint_expanded_area(image, upper_box, detected_texts) # 박스에 대한 인페인팅
else:
print("lower_box 진행")
removal_img = crop_and_inpaint(image, lower_box, lower_texts, image_height, upper = 0) # 박스에 대한 크롭
# removal_img = inpaint_expanded_area(image, lower_box, detected_texts) # 박스에 대한 인페인팅
else:
print("이미지 크롭 없이 인페인팅 진행")
# 전체 인페인팅
removal_img = inpaint_text(image, detected_texts)
return removal_img
# cv2.imwrite(output_path, removal_img)

View File

@ -0,0 +1,64 @@
import cv2
import numpy as np
def crop_by_boxline(image, crop_percent):
# 이미지의 크기 계산
height, width = image.shape[:2]
# 크롭할 크기 계산
crop_height = int(height * crop_percent)
crop_width = int(width * crop_percent)
# 중앙을 기준으로 크롭 범위 계산
start_row, start_col = crop_height // 2, crop_width // 2
end_row, end_col = height - start_row, width - start_col
# 이미지 크롭
cropped_image = image[start_row:end_row, start_col:end_col]
return cropped_image
def rotate_by_angle(image, angle, color):
# 이미지 중심점을 기준으로 15도 회전
height, width = image.shape[:2]
center = (width // 2, height // 2)
# 회전을 위한 변환 행렬 생성
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
if color == "white":
# 변환 행렬을 사용하여 흰색 배경으로 이미지 회전
rotated_image = cv2.warpAffine(image, rotation_matrix, (width, height), borderValue=(255, 255, 255))
return rotated_image
elif color == "transperent":
# 변환 행렬을 사용하여 투명 배경으로 이미지 회전
rotated_image = cv2.warpAffine(image, rotation_matrix, (width, height), borderMode=cv2.BORDER_CONSTANT, borderValue=(0, 0, 0, 0))
return rotated_image
def mirror_by_image(image):
flipped_image = cv2.flip(image, 1)
return flipped_image
def flip_and_rotate_image(image):
"""
이미지를 좌우 반전시키고, 중심을 기준으로 15 기울입니다.
Parameters:
- image_path: str, 원본 이미지 파일의 경로입니다.
- output_path: str, 변환된 이미지를 저장할 경로입니다.
"""
# 이미지 테두리 제거
cropped_image = crop_by_boxline(image, crop_percent = 0.05)
# 이미지를 좌우 반전
mirror_image = mirror_by_image(cropped_image)
# 이미지 중심점을 기준으로 7도 회전
rotated_image = rotate_by_angle(mirror_image, angle = 7, color="white")
# 결과 이미지 저장
return rotated_image
# cv2.imwrite(output_path, rotated_image)

View File

@ -0,0 +1,20 @@
import keras_ocr
import cv2
# keras-ocr 파이프라인 설정
pipeline = keras_ocr.pipeline.Pipeline()
def detect_text(image_path):
"""이미지 내 텍스트 인식 및 위치 탐지"""
# 이미지 읽기
image = cv2.imread(image_path)
# 이미지에서 텍스트 탐지
prediction_groups = pipeline.recognize([image])
# 탐지된 텍스트와 바운딩 박스 반환
return prediction_groups[0]
if __name__ == "__main__":
image_path = 'data/input_images/sample.jpg'
detected_texts = detect_text(image_path)
print(detected_texts)

View File

@ -0,0 +1,59 @@
from paddleocr import PaddleOCR, draw_ocr
from PIL import Image # PIL 라이브러리가 필요합니다.
import cv2
import matplotlib.pyplot as plt
import numpy as np
import os
# 현재 작업 디렉터리를 동적으로 받아오기
current_path = os.getcwd()
# 모델 파일을 저장할 경로 설정
det_model_path = os.path.join(current_path, "models", "det")
rec_model_path = os.path.join(current_path, "models", "rec")
cls_model_path = os.path.join(current_path, "models", "cls") # 분류 모델 경로 추가
# models 폴더가 없으면 자동으로 생성
os.makedirs(det_model_path, exist_ok=True)
os.makedirs(rec_model_path, exist_ok=True)
os.makedirs(cls_model_path, exist_ok=True) # 분류 모델 폴더 생성
# PaddleOCR 인스턴스 생성, 모델 경로 지정
ocr = PaddleOCR(use_angle_cls=True, lang="ch",
det_model_dir=det_model_path,
rec_model_dir=rec_model_path,
cls_model_dir=cls_model_path) # 분류 모델 경로 지정
# PaddleOCR 객체 생성
ocr = PaddleOCR(use_angle_cls=True, lang='ch') # 'ch'는 중국어, 'en'은 영어
def detect_text(image):
# 이미지에서 텍스트 감지 및 인식
# result = ocr.ocr(image_path, cls=True)
# OpenCV 이미지 배열을 PaddleOCR이 사용할 수 있는 형태로 변환합니다.
height, width, _ = image.shape
image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
# PaddleOCR로 이미지에서 텍스트 감지 및 인식
result = ocr.ocr(image_pil, cls=True)
detected_texts = []
for line in result:
for element in line:
box = element[0]
text = element[1][0]
# 신뢰도 점수 추출 방식 개선
confidence_raw = element[1][1]
confidence = 0
if isinstance(confidence_raw, tuple) and len(confidence_raw) > 0:
confidence = float(confidence_raw[0]) if confidence_raw[0].replace('.', '', 1).isdigit() else 0
elif isinstance(confidence_raw, float):
confidence = confidence_raw
elif isinstance(confidence_raw, str) and confidence_raw.replace('.', '', 1).isdigit():
confidence = float(confidence_raw)
text_width = np.linalg.norm(np.array(box[0]) - np.array(box[1]))
text_height = np.linalg.norm(np.array(box[1]) - np.array(box[2]))
detected_texts.append((box, text, confidence, text_width, text_height))
return detected_texts

View File

@ -0,0 +1,119 @@
from PIL import Image, ImageDraw, ImageFont
import numpy as np
import cv2
from colorsys import rgb_to_hsv, hsv_to_rgb
def get_complementary_color_ori(image, box):
"""주어진 박스 영역 내의 평균 색상의 보색을 계산합니다."""
x_min, y_min, x_max, y_max = box
cropped_image = image.crop((x_min, y_min, x_max, y_max))
np_image = np.array(cropped_image)
average_color = np_image.mean(axis=(0, 1))
# RGB에서 HSV로 변환
hsv = rgb_to_hsv(*(average_color/255))
# H 채널을 조정하여 보색 구하기 (Hue를 반전)
hsv = ((hsv[0] + 0.5) % 1.0, hsv[1], hsv[2])
# HSV에서 RGB로 다시 변환
complementary_color = hsv_to_rgb(*hsv)
return tuple(int(c * 255) for c in complementary_color)
def get_dominant_color(image, box, expand=10):
"""이미지의 주어진 영역에서 지배적인 색상을 찾습니다."""
x1, y1, x2, y2 = box
cropped_image = image.crop((max(0, x1-expand), max(0, y1-expand), x2+expand, y2+expand))
np_image = np.array(cropped_image)
colors, count = np.unique(np_image.reshape(-1, np_image.shape[-1]), axis=0, return_counts=True)
dominant_color = tuple(colors[count.argmax()])
return dominant_color
def get_complementary_color(color):
"""주어진 RGB 색상의 보색을 계산합니다."""
# RGB에서 보색 계산: 255 - 현재 값
complementary = (255 - color[0], 255 - color[1], 255 - color[2])
return complementary
def calculate_font_size(text, font_path="NotoSansKR-Bold.ttf", desired_width=None, desired_height=None):
font_size = 10 # 초기 폰트 크기
font = ImageFont.truetype(font_path, font_size)
img = Image.new('RGB', (1, 1)) # 실제 이미지 크기는 중요하지 않음
draw = ImageDraw.Draw(img)
# textbbox를 사용하여 텍스트 바운딩 박스 계산
bbox = draw.textbbox((0, 0), text, font=font)
text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1]
while (desired_width is not None and text_width < desired_width) or \
(desired_height is not None and text_height < desired_height):
font_size += 1 # 폰트 크기를 조금씩 증가
font = ImageFont.truetype(font_path, font_size)
bbox = draw.textbbox((0, 0), text, font=font)
text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1]
font_size = font_size*0.6
return font_size
def insert_text(image_path, translated_texts, detected_texts):
# # PIL 이미지 객체로 변환합니다. (OpenCV 이미지가 아닐 경우)
# if isinstance(inpainted_image, np.ndarray):
# # OpenCV 이미지를 PIL 포맷으로 변환합니다.
# image = Image.fromarray(cv2.cvtColor(inpainted_image, cv2.COLOR_BGR2RGB)).convert("RGBA")
# else:
# # 이미 PIL 이미지 객체인 경우 그대로 사용합니다.
# image = inpainted_image.convert("RGBA")
image = Image.open(image_path).convert("RGBA")
draw = ImageDraw.Draw(image)
for detected_text, translated_text in zip(detected_texts, translated_texts):
box = detected_text[0] # (box, text, confidence) 형태의 detected_text에서 box를 추출
# box 형태: [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]
# 이미지 영역에서 지배적인 색상을 찾기 위해 x, y 좌표 최소값과 최대값을 사용
x_coords = [coord[0] for coord in box]
y_coords = [coord[1] for coord in box]
x_min, x_max = min(x_coords), max(x_coords)
y_min, y_max = min(y_coords), max(y_coords)
# 수정된 부분: 올바른 x, y 좌표를 사용하여 dominant_color 계산
dominant_color = get_dominant_color(image, (x_min, y_min, x_max, y_max))
complementary_color = get_complementary_color(dominant_color)
# translated_text = detected_text[1] # 번역된 텍스트
print(f"translated_text : {translated_text}")
text_width = np.linalg.norm(np.array(box[0]) - np.array(box[1]))
text_height = np.linalg.norm(np.array(box[1]) - np.array(box[2]))
font_size = calculate_font_size(translated_text, "NotoSansKR-Bold.ttf", text_width, text_height)
font_background = ImageFont.truetype("NotoSansKR-Bold.ttf", font_size)
font = ImageFont.truetype("NotoSansKR-Bold.ttf", font_size)
draw.text((x_min+2, y_min+2), translated_text, fill="white", font=font_background)
draw.text((x_min-2, y_min-2), translated_text, fill="white", font=font_background)
draw.text((x_min, y_min), translated_text, fill="black", font=font)
cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGBA2BGR)
return cv_image
# def insert_text(image_path, translated_texts, detected_texts):
# image = Image.open(image_path).convert("RGBA")
# draw = ImageDraw.Draw(image)
# for detected_text, translated_text in zip(detected_texts, translated_texts):
# box, _, _, _, _ = detected_text # detected_texts에서 추가 정보가 있다면 여기서 언패킹
# text_width = np.linalg.norm(np.array(box[0]) - np.array(box[1]))
# text_height = np.linalg.norm(np.array(box[1]) - np.array(box[2]))
# desired_width = text_width # 폰트 크기 계산을 위한 desired_width
# desired_height = text_height # 폰트 크기 계산을 위한 desired_height
# # 지배적인 색상 계산
# dominant_color = get_dominant_color(image, box)
# # font_size = calculate_font_size(text_width, text_height, translated_text, "NotoSansKR-Bold.ttf", desired_width, desired_height)
# font_size = calculate_font_size(translated_text, "NotoSansKR-Bold.ttf", desired_width, desired_height)
# font = ImageFont.truetype("NotoSansKR-Bold.ttf", font_size)
# x_min, y_min = min(box[0][0], box[3][0]), min(box[0][1], box[1][1])
# draw.text((x_min, y_min), translated_text, fill=dominant_color, font=font)
# cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGBA2BGR)
# return cv_image

View File

@ -0,0 +1,64 @@
from googletrans import Translator
from img_trans.src.deepl import trans
from translatepy import Translator
def translate_texts_google(texts, src_lang='zh-cn', dest_lang='ko'):
"""텍스트 리스트를 지정된 언어에서 다른 언어로 번역"""
translated_texts = []
translator = Translator()
print(f"texts ======== {texts}")
print(f"texts number ======== {len(texts)}")
try:
translations = translator.translate(texts, src=src_lang, dest=dest_lang)
translated_texts = [translation.text for translation in translations]
except AttributeError as e:
print(f"번역 실패: Google 번역 API의 내부 변경으로 인해 번역을 수행할 수 없습니다. : {e}")
return translated_texts
def translate_texts_deepl(texts):
"""텍스트 리스트를 지정된 언어에서 다른 언어로 번역"""
# 텍스트 리스트를 엔터로 구분된 하나의 문자열로 합침
combined_text = "\n".join(texts)
# DeepL로 전체 텍스트 번역
try:
translated_combined_text = trans(combined_text)
for translation in translated_combined_text:
# "가격"이 포함된 경우 빈 문자열로 대체
translated_texts.append(translation.text if "가격" not in translation.text else "")
# 번역된 전체 텍스트를 엔터를 기준으로 분리하여 리스트로 만듦
translated_texts = translated_combined_text.split("\n")
except AttributeError as e:
print(f"번역 실패: {e}")
translated_texts = []
return translated_texts
def translate_texts_translatepy(texts, src_lang='zh-cn', dest_lang='ko'):
"""translatepy 라이브러리를 사용하여 텍스트 리스트를 번역합니다."""
translator = Translator()
translated_texts = []
print(f"번역대상 텍스트 숫자 ======== {len(texts)}")
print(f"번역대상 텍스트 ======== {texts}")
for text in texts:
result = translator.translate(text, source_language=src_lang, destination_language=dest_lang)
translation = str(result)
# "가격"이 포함된 경우 빈 문자열로 대체
translated_texts.append(translation if "가격" not in translation else "")
print(f"번역된 감지 텍스트 \n {translated_texts}")
return translated_texts
# # 사용 예
# texts = ["Hello, world!", "This is a test translation."]
# translated_texts = translate_texts_translatepy(texts, src_lang='en', dest_lang='ko')
# for text, translated in zip(texts, translated_texts):
# print(f'{text} => {translated}')
# text='老顾客多次回购'
# trans_ted = translate_texts_deepl(text)
# print(f"trans_ted : {trans_ted}")

View File

@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "igneous-primacy-409723",
"private_key_id": "9a9816ba7d7bcde45bc1f0f0f984586ad753022d",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCzx0uJieV+r8PU\ntdVrWrbpbMOXR0SIhCVKkM1GWEOF+p6c5Hd3WZz09ALdBWtIQkGNIodzwO4nKh3h\n9F0dNviY6zB86co6UkpdivOm+w6EOBA0qQSF5dCQsvsJ6FyWqZgNPBfZonlk96Hy\nCNtNbkUqxTEPXJQ751WiH0Ke9e6jhPQ9g2ORmDrW1ANNGD4r1dz9GUEegWkFouGE\nsjPgwXEO62i3cFxUuRNMFEE/bvIB/VNLYMqbo4osGfTXopl9N+lW0MJ0sINVnZaI\nImnU6I6F9NMRb1PjkXD/FsLl6lbE82dbZDTdq11rjA7KQqBNXne58DVwE3/dmowi\n3NaGdqkjAgMBAAECggEAAZbnNTXPeaBoxR5kKBbUFyw93Cv4kEoj82g/w8wGQ6og\nKE5hYATCz6MK8ScHrzhv4snX75hYgrAiWlvtltNkc9o75vEcgJwSyfWBr7BBsb4k\nAffBKDJ6P9+MFl3EIYMpxiHlT4Pag3sob/Wq8Y6ZK6sQXkOLR2VzD80G9AxEBZTt\n1Asa9lBXSnK5NCYLsRRX765YysrsQXd8tk6oqsLmcigWCr75nKgbELxiszOTKtLT\npb50y8cb0kDfMUXOltWojGIkhOKetbzmWA3EgXIIa75s9z8tUVE+vO6kSq3TuQXH\nwL+IwatWRlrco3G1LGsHtw2nPR2mljcCVZjvT/Z0gQKBgQDmOJIeobqku/bVDxcq\neLYrMV1/X7zTihox33C7w+ruVXB/edJxR+gROWVO9ufTIgfPTzkDkxZYvTiHA9hC\nz56vu1b2RNLgiGT9ASOvfR00xRSCE/FfaaN5VlWzGskuFUyWDthkbld78wEFLf19\nutsaV/9+RCGZNoGtRjjw4symSQKBgQDH6MI+wXejhAJyZrr2S5jvIlSKtY12QdU6\n+JhD+3OEBl+OndyfucD5HgjSJnMjnzRMML+mPFlcwqU1VmDeeTqSE/mmvyRAr9rm\nG6Xdh+dOngpqwpq9OGsqc+JZ8JF0bdn6V/g26LjkQgNnRCIFnARQ2UHBDoS2/wHn\n72ShlP9kCwKBgGlinADJp9ag9Gyza7dVao57GoGkIZv0K+mIjuJk3LYdBlJUQbD5\naZH45Bcxjw1nFowfh8nLGv+kHqwvZl+vCsUGzNgOyTlfNltamitK6oOtc6XX2zYB\n9YMlsjU6nb0qotROF2Bh4korAtyMIO3dC08T2TDDn12zRck7y/T43RWBAoGALEEO\nny3c+knC8OhlAxkBJg8HgB1oz4ELXx6hNot3qwZuKPgxWvqYCY3ojf0NCBm6ThOM\nmZRKhApi4Efa8eUMXkIlxhASSm+jmcUNFtl7DyBVVgT2lGTk9GTq+tYSnR+kXZMT\n07P5Gi6y6i1fCrbbDbrKn55DKu+Q0HNiZ5LAZrkCgYAp7Aa1rlbXbyoSXNWTA4Nc\nTZIRKj9Ra2hD2Y2EKjLWqLa9RVK+D9a9I/v1gW59PeRpUY9w674IEZXqOq5jx0D9\nFmwL00Omtfv96q+syq7pqSUmI7hDSd1CfLDaxCGHzGykI98GkjQDz6xPEgTAKRIL\njcB1KaEd55AuovwONS3qKA==\n-----END PRIVATE KEY-----\n",
"client_email": "service-account@igneous-primacy-409723.iam.gserviceaccount.com",
"client_id": "102875157826238718143",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-account%40igneous-primacy-409723.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

113
logger_module.py Normal file
View File

@ -0,0 +1,113 @@
import logging
import os
from PyQt5.QtCore import pyqtSignal, QObject
from logging.handlers import RotatingFileHandler
def setup_logger(name, log_file, level=logging.DEBUG, max_bytes=10*1024*1024, backup_count=5):
"""로거 설정을 위한 함수
DEBUG: 프로그램의 내부 상황을 상세하게 알고 싶을 사용합니다. 가장 낮은 레벨입니다.
INFO: 프로그램의 정상적인 작동 정보를 알릴 사용합니다.
WARNING: 예상치 못한 일이 발생했거나, 문제가 가능성이 있는 상황에 대해 경고할 사용합니다.
ERROR: 프로그램이 일부 문제로 인해 정상적으로 작동하지 않을 사용합니다.
CRITICAL: 매우 심각한 문제가 발생하여 프로그램이 실행을 계속할 없을 사용합니다.
main.py파일
------------------------------------------------------------
from logger_module import setup_logger
import logging
# 로그 레벨을 DEBUG로 설정하여 로거를 초기화합니다.
logger = setup_logger('default_logger', 'application.log', level=logging.DEBUG)
# 사용 예시
logger.debug('디버그 메시지입니다.')
logger.info('정보 메시지입니다.')
------------------------------------------------------------
main.py에 import된 파일
------------------------------------------------------------
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def login_function():
# 로깅 예시
logger.info("로그인 시도 중...")
try:
# 로그인 성공 로직
logger.info("로그인 성공")
except Exception as e:
logger.error(f"로그인 실패: {e}")
------------------------------------------------------------
"""
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# RotatingFileHandler를 사용하여 로그 파일 설정
handler = RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count, encoding='utf-8')
handler.setFormatter(formatter)
logger = logging.getLogger(name)
logger.setLevel(level)
logger.addHandler(handler)
# 콘솔 로그 출력을 위한 핸들러가 이미 추가되었는지 확인
if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
console_handler.setLevel(level)
logger.addHandler(console_handler)
# logger.propagate = False # 로그 이벤트가 루트 로거로 전파되지 않도록 설정
return logger
# # 기본 로거 설정
# log_directory = "logs"
# if not os.path.exists(log_directory):
# os.makedirs(log_directory)
# default_logger = setup_logger('default_logger', os.path.join(log_directory, 'application.log'))
class QTextEditLogger(logging.Handler, QObject):
#appendPlainText = pyqtSignal(str) # 로그 메시지를 전달할 시그널
appendHtml = pyqtSignal(str) # HTML 메시지를 전달할 시그널 정의
scrollToBottom = pyqtSignal() # 스크롤을 최하단으로 이동시키는 시그널
def __init__(self):
logging.Handler.__init__(self)
QObject.__init__(self)
def emit(self, record):
msg = self.format(record) # 로그 레코드를 문자열로 포매팅
if record.levelno == logging.DEBUG:
color = "black"
elif record.levelno == logging.INFO:
color = "grey"
elif record.levelno == logging.WARNING:
color = "orange"
elif record.levelno == logging.ERROR:
color = "red"
elif record.levelno == logging.CRITICAL:
color = "purple"
else:
color = "black"
# HTML 스타일을 적용한 메시지 생성
message = f"<span style=\"color:{color};\">{msg}</span><br/>"
self.appendHtml.emit(message) # HTML 메시지로 변경
self.scrollToBottom.emit() # 스크롤 시그널 발생
def close(self):
# 핸들러 종료 시 필요한 작업 구현
self.flush()
logging.Handler.close(self)
def flush(self):
# 커스텀 flush 구현
# 필요한 경우 안전한 정리 작업을 수행
pass

101
login.py Normal file
View File

@ -0,0 +1,101 @@
# from selenium import webdriver
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.common.exceptions import TimeoutException
# from config import WEBSITE_URL
from utils import log
import time
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def login(driver, login_info):
"""로그인 프로세스를 수행합니다."""
# login_info에서 로그인 정보 추출
per_email = login_info["per_email"]
per_password = login_info["per_password"]
per_em_email = login_info["per_em_email"]
per_em_password = login_info["per_em_password"]
per_mode = login_info["per_mode"]
logger.debug("웹사이트에 접속 중...")
driver.get("https://www.percenty.co.kr")
# time.sleep(1)
logger.debug("로그인 버튼 클릭...")
# login_button = driver.find_element(By.CSS_SELECTOR, ".signList > .ant-btn-default > span")
# login_button.click()
try:
login_button = WebDriverWait(driver, 30).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".signList > .ant-btn-default > span"))
)
login_button.click()
except TimeoutException:
logger.debug("로그인 버튼을 찾는데 실패했습니다.")
# try:
# login_button = WebDriverWait(driver, 30).until(
# EC.presence_of_element_located((By.CSS_SELECTOR, ".signList > .ant-btn-default > span"))
# # EC.presence_of_element_located(By.XPATH, "//div[@id='root']/div/div/header/div/div/div/button[2]/span")
# )
# login_button.click()
# except:
# logger.debug("로그인 중 오류 발생: 요소를 찾을 수 없습니다.")
time.sleep(1)
logger.debug("이메일 주소 입력...")
try:
email_input = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".ant-input:nth-child(4)"))
)
email_input.send_keys(per_email)
except:
logger.debug("이메일 주소 찾는 중 오류 발생: 요소를 찾을 수 없습니다.")
# email_input = driver.find_element(By.CSS_SELECTOR, ".ant-input:nth-child(4)")
time.sleep(0.1)
if per_mode:
logger.debug("사용자 패스워드 입력...")
password_input = driver.find_element(By.CSS_SELECTOR, ".ant-input:nth-child(1)")
password_input.send_keys(per_password)
time.sleep(0.1)
else:
logger.debug("스위치 버튼 클릭...")
switch_button = driver.find_element(By.CSS_SELECTOR, ".ant-switch-handle")
switch_button.click()
time.sleep(0.1)
logger.debug("직원 아이디 입력...")
employee_id_input = driver.find_element(By.CSS_SELECTOR, ".ant-input:nth-child(2)")
employee_id_input.send_keys(per_em_email)
time.sleep(0.1)
logger.debug("직원 패스워드 입력...")
password_input = driver.find_element(By.CSS_SELECTOR, ".ant-input:nth-child(1)")
password_input.send_keys(per_em_password)
time.sleep(0.1)
logger.debug("로그인 버튼 클릭...")
login_button = driver.find_element(By.CSS_SELECTOR, ".ant-btn-primary")
login_button.click()
time.sleep(2)
# 신규 상품 등록 페이지로이동
logger.debug("신규상품등록 버튼 클릭...")
try:
new_product_button = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//span[contains(.,'신규 상품 등록')]"))
)
except:
logger.debug("신규상품등록 버튼 찾는 중 오류 발생: 요소를 찾을 수 없습니다.")
# new_product_button = driver.find_element(By.XPATH, "//span[contains(.,'신규 상품 등록')]")
new_product_button.click()
time.sleep(2)

325
login_widget.py Normal file
View File

@ -0,0 +1,325 @@
from werkzeug.security import check_password_hash
from PyQt5.QtCore import Qt, QSettings
from PyQt5 import QtWidgets, QtCore, QtWidgets
from datetime import datetime
from credentials import load_credentials, save_credentials
import sys
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class LoginWidget(QtWidgets.QWidget):
def __init__(self, mongo_config):
super().__init__()
self.mongoConfig = mongo_config
self.client = mongo_config.client
# self.login_db = self.mongoConfig.get_db() # mongo_config 인스턴스를 통해 데이터베이스 객체를 가져옴
self.login_db = self.client['taobao_project']
self.initUI()
# Initialize MongoDB client and select database
self.loadSettings()
self.master_email = "로그인 하세요"
self.result = False
self.per_email = None
self.per_password = None
self.per_em_email = None
self.per_em_password = None
self.per_mode = False
self.set_num_modify = 0
def initUI(self):
self.setGeometry(780, 420, 240, 200)
layout = QtWidgets.QVBoxLayout()
self.signupButton = QtWidgets.QPushButton("회원 가입")
self.signupButton.clicked.connect(self.showSignUpWindow)
self.infoLabel = QtWidgets.QLabel("로그인 정보를 입력하세요")
self.emailInput = QtWidgets.QLineEdit()
self.emailInput.setPlaceholderText("서버인증 사용자 이름(이메일형식)")
self.emailInput.returnPressed.connect(self.focusToPassword) # 사용자 이름에서 엔터 -> 암호칸으로 포커스
self.passwordInput = QtWidgets.QLineEdit()
self.passwordInput.setPlaceholderText("서버인증 비밀번호")
self.passwordInput.setEchoMode(QtWidgets.QLineEdit.Password)
self.passwordInput.returnPressed.connect(self.login) # 암호에서 엔터 -> 로그인 시도
self.infoLabel2 = QtWidgets.QLabel("========퍼센티 로그인 정보 입력========")
self.per_emailInput = QtWidgets.QLineEdit()
self.per_emailInput.setPlaceholderText("퍼센티 사용자 이름(이메일형식)")
# self.per_emailInput.returnPressed.connect(self.focusToPassword) # 사용자 이름에서 엔터 -> 암호칸으로 포커스
self.per_em_emailInput = QtWidgets.QLineEdit()
self.per_em_emailInput.setPlaceholderText("퍼센티 직원 아이디")
# self.per_em_emailInput.returnPressed.connect(self.focusToPassword) # 사용자 이름에서 엔터 -> 암호칸으로 포커스
self.per_em_passwordInput = QtWidgets.QLineEdit()
self.per_em_passwordInput.setPlaceholderText("퍼센티 직원 비밀번호")
self.per_em_passwordInput.setEchoMode(QtWidgets.QLineEdit.Password)
# self.per_passwordInput.returnPressed.connect(self.login) # 암호에서 엔터 -> 로그인 시도
self.per_passwordInput = QtWidgets.QLineEdit()
self.per_passwordInput.setPlaceholderText("퍼센티 비밀번호")
self.per_passwordInput.setEchoMode(QtWidgets.QLineEdit.Password)
# self.per_passwordInput.returnPressed.connect(self.login) # 암호에서 엔터 -> 로그인 시도
self.infoLabel3 = QtWidgets.QLabel("=====================================")
# 토글 버튼 추가
self.changeModeButton = QtWidgets.QPushButton("관리자용용모드")
self.changeModeButton.setCheckable(True)
self.changeModeButton.clicked.connect(self.toggleMode)
# 초기 토글 상태 설정
self.toggleMode(False)
self.set_num_modifyInput = QtWidgets.QLineEdit()
self.set_num_modifyInput.setPlaceholderText("수정갯수 : 0이면 등록된 모든 신규상품 수정")
# self.per_em_emailInput.returnPressed.connect(self.focusToPassword) # 사용자 이름에서 엔터 -> 암호칸으로 포커스
self.infoLabel5 = QtWidgets.QLabel("[수정갯수가 0이면 등록된 모든 신규상품 수정]")
# 비밀번호 표시 토글 버튼
self.showPasswordCheckBox = QtWidgets.QCheckBox("비밀번호 표시")
self.showPasswordCheckBox.stateChanged.connect(self.togglePasswordVisibility)
self.loginButton = QtWidgets.QPushButton("로그인")
self.loginButton.clicked.connect(self.login)
self.infoLabel4 = QtWidgets.QLabel(" 퍼센티 자동화 by 리앤수Int.")
layout.addWidget(self.signupButton)
layout.addWidget(self.infoLabel)
layout.addWidget(self.emailInput)
layout.addWidget(self.passwordInput)
layout.addWidget(self.infoLabel2)
layout.addWidget(self.per_emailInput)
layout.addWidget(self.per_em_emailInput)
layout.addWidget(self.per_em_passwordInput)
layout.addWidget(self.per_passwordInput)
layout.addWidget(self.changeModeButton)
layout.addWidget(self.set_num_modifyInput)
layout.addWidget(self.infoLabel5)
layout.addWidget(self.infoLabel3)
layout.addWidget(self.showPasswordCheckBox)
layout.addWidget(self.loginButton)
layout.addWidget(self.infoLabel4)
self.setLayout(layout)
self.setWindowTitle('AutoPercenty')
def togglePasswordVisibility(self):
if self.showPasswordCheckBox.isChecked():
self.passwordInput.setEchoMode(QtWidgets.QLineEdit.Normal)
else:
self.passwordInput.setEchoMode(QtWidgets.QLineEdit.Password)
def focusToPassword(self):
self.passwordInput.setFocus()
def toggleMode(self, checked):
if checked:
self.changeModeButton.setText("직원용모드")
self.per_em_emailInput.setVisible(True)
self.per_em_passwordInput.setVisible(True)
self.per_passwordInput.setVisible(False)
self.per_mode = False # False일 경우 관리자
else:
self.changeModeButton.setText("관리자용모드")
self.per_em_emailInput.setVisible(False)
self.per_em_passwordInput.setVisible(False)
self.per_passwordInput.setVisible(True)
self.per_mode = True # True일 경우 직원용
# def checkToggleButtonState(self):
# if self.changeModeButton.isChecked():
# logger.debug("직원용모드 활성화")
# else:
# logger.debug("관리자용모드 활성화")
def saveSettings(self, email, password):
settings = QSettings("리앤수", "퍼센티자동화")
settings.setValue("email", self.emailInput.text())
settings.setValue("password", self.passwordInput.text())
settings.setValue("per_email", self.per_email)
settings.setValue("per_password", self.per_password)
settings.setValue("per_em_email", self.per_em_email)
settings.setValue("per_em_password", self.per_em_password)
settings.setValue("per_mode", self.per_mode)
settings.setValue("set_num_modify", self.set_num_modify)
def loadSettings(self):
settings = QSettings("리앤수", "퍼센티자동화")
self.emailInput.setText(settings.value("email", ""))
self.passwordInput.setText(settings.value("password", ""))
self.per_emailInput.setText(settings.value("per_email", ""))
self.per_passwordInput.setText(settings.value("per_password", ""))
self.per_em_emailInput.setText(settings.value("per_em_email", ""))
self.per_em_passwordInput.setText(settings.value("per_em_password", ""))
self.set_num_modifyInput.setText(settings.value("set_num_modify", "0"))
toggle_state = settings.value("per_mode", "false").lower() in ('true', '1', 'yes')
self.toggleMode(toggle_state)
def login(self):
email = self.emailInput.text()
password = self.passwordInput.text()
self.per_email = self.per_emailInput.text()
self.per_password = self.per_passwordInput.text()
self.per_em_email = self.per_em_emailInput.text()
self.per_em_password = self.per_em_passwordInput.text()
self.set_num_modify = self.set_num_modifyInput.text()
# MongoDB에서 사용자 문서 조회
user_doc = self.login_db.users.find_one({"email": email})
if user_doc and check_password_hash(user_doc['password'], password):
# 비밀번호 검증 성공
self.email = email # 여기에서 사용자 이메일을 self.email 속성에 저장
# 마지막 로그인 시간, 총 작업량, 주별 및 월별 성과 조회
last_login = user_doc.get('lastLoginTime', '기록 없음')
total_workload = user_doc.get('totalWorkload', 0)
weekly_performance = user_doc.get('weeklyPerformance', {})
monthly_performance = user_doc.get('monthlyPerformance', {})
performance_msg = f"\n마지막 로그인 시간: {last_login}\n마지막 작업량: {total_workload}\n주간성과: {weekly_performance}\n월간성과: {monthly_performance}"
#QtWidgets.QMessageBox.information(self, "로그인 성공", performance_msg)
if user_doc['userType'] in ['slave', 'manager']:
self.master_email = user_doc.get('master', None)
logger.debug(f"설정된 마스터 이메일 : {self.master_email}")
QtWidgets.QMessageBox.information(self, "로그인 성공", f"{self.master_email} 데이터베이스를 사용합니다.\n{performance_msg}")
else:
QtWidgets.QMessageBox.information(self, "로그인 성공", f"{email} 데이터베이스를 사용합니다.\n{performance_msg}")
self.saveSettings(email, password) # 로그인 성공 시 설정 저장
now = datetime.now()
self.login_db.users.update_one(
{"email": email},
{"$set": {"lastLoginTime": now.strftime('%Y-%m-%d %H:%M:%S')}}
)
self.on_login_success()
else:
# 로그인 실패
QtWidgets.QMessageBox.warning(self, "로그인 실패", "이메일 또는 비밀번호가 잘못되었습니다.")
def on_login_success(self):
# MongoDB에서 사용자 문서 조회
user_doc = self.login_db.users.find_one({"email": self.email})
if user_doc['userType'] == 'master':
# 마스터 사용자 정보 표시
userInfo = f"등급: {user_doc['userGrade']}, 플랜 종료일: {user_doc['planEndDate']}"
# 종속된 사용자 정보 조회 및 표시
dependent_users = self.login_db.users.find({"master": user_doc['email']})
logger.debug(dependent_users)
#dependent_users = self.login_db.users.find({"master": self.email}) # 수정된 부분: user_doc['email'] 대신 self.email 사용
for user in dependent_users:
last_login = user.get('lastLoginTime', '로그인 기록 없음')
total_workload = user.get('totalWorkload', 0)
userInfo += f"\n종속 사용자: {user['email']}, 마지막 로그인: {last_login}, 총 작업량: {total_workload}"
elif user_doc['userType'] in ['slave', 'manager']:
userInfo = "해당 정보는 마스터 사용자만 볼 수 있습니다."
logger.debug(f"on_login_succes의 마스터 이메일 : {self.master_email}")
self.result = True
logger.debug(f"login_widget의 self.result : {self.result}")
# 로그인 시간 업데이트
now = datetime.now()
self.login_db.users.update_one(
{"email": self.email},
{"$set": {"lastLoginTime": now.strftime('%Y-%m-%d %H:%M:%S')}}
)
logger.debug(f"사용자 {self.email}의 로그인 시간이 기록되었습니다.")
self.close() # 로그인 창 닫기
# sys.exit()
# self.close() # 로그인 창 닫기
def find_master_username(self, slave_username):
# 실제 애플리케이션에서는 여기서 MongoDB를 조회하여 master 사용자를 찾아야 합니다.
# 이 예시에서는 단순화를 위해 'master' 문자열을 반환합니다.
return self.master_email
def login_user(self, email, password):
# 로그인 검증 로직 (생략)
now = datetime.now()
self.login_db.users.update_one(
{"email": email},
{"$set": {"lastLoginTime": now.strftime('%Y-%m-%d %H:%M:%S')}}
)
# 로그인 성공 메시지와 마지막 로그아웃 시간 표시 (구현 필요)
def logout(self, email):
logger.debug("로그아웃 프로세스 시작")
now = datetime.now()
# 로그아웃 시간 업데이트
self.login_db.users.update_one(
{"email": email},
{"$set": {"lastLogoutTime": now.strftime('%Y-%m-%d %H:%M:%S')}}
)
logger.debug(f"사용자 {email}의 로그아웃 시간이 기록되었습니다.")
# 변경된 문서 다시 조회
user_doc = self.login_db.users.find_one({"email": email})
# 성과 메시지 생성
performance_msg = self.create_performance_message(user_doc)
# 로그아웃 메시지 박스 표시 및 5초 후 자동 닫기
msgBox = QtWidgets.QMessageBox(self)
msgBox.setWindowTitle("로그아웃")
msgBox.setText("성공적으로 로그아웃되었습니다.\n" + performance_msg)
msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok)
msgBox.buttonClicked.connect(msgBox.close)
QTimer = QtCore.QTimer(self)
QTimer.singleShot(3000, msgBox.close) # 5초 후 자동 닫기
msgBox.exec_()
def show_user_performance(self, email):
user_doc = self.login_db.users.find_one({"email": email}, {"totalWorkload": 1, "weeklyPerformance": 1, "monthlyPerformance": 1})
if user_doc:
logger.debug(f"총 작업량: {user_doc.get('totalWorkload', 0)}")
logger.debug("주별 성과:")
for week, workload in user_doc.get("weeklyPerformance", {}).items():
logger.debug(f"{week}: {workload}")
logger.debug("월별 성과:")
for month, workload in user_doc.get("monthlyPerformance", {}).items():
logger.debug(f"{month}: {workload}")
def create_performance_message(self, user_doc):
# 마지막 로그인 시간, 총 작업량, 주별 및 월별 성과 조회
last_login = user_doc.get('lastLoginTime', '기록 없음')
total_workload = user_doc.get('totalWorkload', 0)
weekly_performance = user_doc.get('weeklyPerformance', {})
monthly_performance = user_doc.get('monthlyPerformance', {})
performance_msg = f"마지막 로그인 시간: {last_login}\n총 작업량: {total_workload}\n"
for week, workload in weekly_performance.items():
performance_msg += f"주별 성과 ({week}): {workload}\n"
for month, workload in monthly_performance.items():
performance_msg += f"월별 성과 ({month}): {workload}\n"
return performance_msg
def showSignUpWindow(self):
self.signUpWidget = SignUpWidget()
self.signUpWidget.show()

111
main.py Normal file
View File

@ -0,0 +1,111 @@
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service #웨일
# from webdriver_manager.chrome import ChromeDriverManager #웨일
import time
from login import login
import sys
from navigate import navigate_to_new_product_registration
from modify_products import modify_products
from database import setup_database
from utils import log
from config import WEBSITE_URL
from credentials import load_credentials, save_credentials
from ai.gemini import ImageDescriptionGenerator
from login_widget import LoginWidget
from mongo_config import MongoConfig
from PyQt5 import QtCore, QtWidgets
from logger_module import setup_logger
import logging
mongo_config = MongoConfig() # MongoDB 설정 관리 인스턴스 생성
mongo_config.try_connect(*mongo_config.load_config()) # MongoDB에 연결 시도
logger = setup_logger('default_logger', 'application.log', level=logging.DEBUG)
def DB_setting():
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
app = QtWidgets.QApplication(sys.argv)
login_widget = LoginWidget(mongo_config) # MongoDB 설정을 인자로 전달하려면 여기에 추가하세요.
login_widget.show()
result = app.exec_() # 이벤트 루프를 실행하고 종료 코드를 반환
# login_widget에서 필요한 값을 딕셔너리로 패킹하여 반환
login_info = {
"per_email": login_widget.per_email,
"per_password": login_widget.per_password,
"per_em_email": login_widget.per_em_email,
"per_em_password": login_widget.per_em_password,
"per_mode": login_widget.per_mode,
"set_num_modify": login_widget.set_num_modify,
"login_result": login_widget.result # 로그인 결과도 함께 반환
}
return login_info, result
def main():
login_info, app_result = DB_setting() # 로그인 정보와 QApplication 실행 결과를 받음
if not login_info["login_result"]:
logger.debug("사용자 인증 실패로 프로그램을 종료합니다.")
sys.exit(app_result) # QApplication의 종료 코드와 함께 프로그램 종료
# 로그인 정보 사용
per_email = login_info["per_email"]
logger.debug(f'per_email : {per_email}')
per_password = login_info["per_password"]
logger.debug(f'per_password : {per_password}')
per_em_email = login_info["per_em_email"]
logger.debug(f'per_em_email : {per_em_email}')
per_em_password = login_info["per_em_password"]
logger.debug(f'per_em_password : {per_em_password}')
per_mode = login_info["per_mode"]
logger.debug(f'per_mode : {per_mode}')
set_num_modify = login_info["set_num_modify"]
logger.debug(f'set_num_modify : {set_num_modify}')
# 사용자로부터 로그인 정보를 받거나 저장된 정보 사용
gemini = ImageDescriptionGenerator('AIzaSyCER9mD617P5OGaoHCK7drsTkmXUIzFn4U')
# # username, password, employeeID = load_credentials()
# if not username or not password or not employeeID:
# username = input("Username: ")
# password = input("Password: ")
# employeeID = input("employeeID: ")
# save_credentials(username, password, employeeID)
# 크롬 옵션 설정
chrome_options = Options()
#chrome_options.binary_location = "C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe" # 네이버 웨일 브라우저의 설치 경로
chrome_options.add_argument("--disable-notifications") # 팝업 알림 비활성화
chrome_options.add_argument("--disable-popup-blocking") # 팝업 차단 비활성화
chrome_options.add_experimental_option("prefs", {
"credentials_enable_service": False, # 자격 증명 서비스 비활성화
"profile.password_manager_enabled": False # 암호 저장 기능 비활성화
})
# 크롬 드라이버에 옵션 추가
driver = webdriver.Chrome(options=chrome_options)
# 웨일 WebDriver 서비스
#service = Service(ChromeDriverManager().install())
# 웨일 WebDriver를 사용하여 네이버 웨일 브라우저 실행
#driver = webdriver.Chrome(service=service, options=chrome_options)
# 데이터베이스 설정
setup_database()
# 로그인
driver.get(WEBSITE_URL)
driver.set_window_size(1280, 800)
login(driver, login_info) # login 함수에 로그인 정보를 전달하여 호출
#상품 수정 작업 수행
modify_products(driver, gemini, mongo_config, set_num_modify)
#웹 드라이버 종료
driver.quit()
if __name__ == "__main__":
main()

43
main.spec Normal file
View File

@ -0,0 +1,43 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='main',
)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

336
modify_products.py Normal file
View File

@ -0,0 +1,336 @@
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.common.exceptions import TimeoutException
import time, re, math
import json
# from utils import log
from database import is_product_processed, mark_product_processed
from edit.detail1 import modify_detail_page
from edit.tag import edit_tag
from edit.options import modify_option_page
from edit.price import modify_price_page
from edit.title import modify_product_title
from edit.action_elements import click_element, return_element
import logging
from edit.product_info import ProductInfo
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def set_product_info():
'''
상품 정보 product_info 딕셔너리 초기화
'''
product_info = {
'id': None, #상품 아이디
'keyword_title': None, # 키워드 상품명
'product_title': None, # 수정 상품명
'trans_title': None, # 번역 상품명
'tao_high_price': None, # 타오바오의 최고 가격
'tao_low_price': None, # 타오바오의 최저 가격
'option_high_price': None, # 선택된 옵션 최고 가격
'option_low_price': None, # 선택된 옵션 최저 가격
'main_image_url': None, # 상품 메인썸네일 이미지 URL 리스트
'image_urls': [], # 상품 옵션 이미지 URL 리스트
'detail_image_urls': [], # 상품 상세페이지 이미지 URL 리스트
'thumb_image_urls': [], # 상품 썸네일 이미지 URL 리스트
'naver_code': None, # 네이버 카테고리 코드
'naver_avg_price': None, # 네이버 평균가격
'weight': None, # 상품 무게
'w_delv_fee': None, # 상품 무게배송비
'plus_fee': None, # 더하기마진
'return_fee': None, # 반품비
'init_delv_fee': None, # 초기반품비
'exchange_fee': None, # 교환배송비
}
return product_info
def load_json_naver_codes(filename):
codes = []
with open(filename, "r", encoding='utf-8') as file:
for line in file:
try:
item = json.loads(line)
codes.append(item)
except json.JSONDecodeError as e:
logger.error(f"Error decoding JSON: {e}")
return codes
def wait_for_javascript(driver, timeout=20):
try:
WebDriverWait(driver, timeout).until(lambda d: d.execute_script('return document.readyState') == 'complete')
except TimeoutException:
logger.debug("페이지 로딩 실패: 시간 초과")
def modify_products(driver, gemini, mongo_config, set_num_modify):
# product_info = set_product_info() # 상품정보리스트 생성
# MongoDB에 연결
client = mongo_config.client
# self.login_db = self.mongoConfig.get_db() # mongo_config 인스턴스를 통해 데이터베이스 객체를 가져옴
db = client['taobao_project']
delv_collection = db['delv_fee']
set_num_modify = int(set_num_modify)
# 한 번만 호출하여 메모리에 로드
json_naver_codes = load_json_naver_codes("Percenty_SS_code.json")
# 총 상품 수 확인을 위해 모든 자바스크립트가 로드 될때까지 기다리기
logger.debug("모든 JS 로드 대기")
wait_for_javascript(driver)
logger.debug("총 상품 수 확인을 위한 JS 로드 완료")
# 총 상품 수 확인
try:
total_products_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//span[contains(.,'')]"))
)
total_products_text = total_products_element.text
total_products = int(''.join(filter(str.isdigit, total_products_text)))
logger.debug(f"총 상품수 : {total_products}")
total_pages = (total_products + 19) // 20
except Exception as e:
logger.debug(f"총 상품 수 확인 중 오류 발생: 요소를 찾을 수 없습니다.{e}")
if set_num_modify == 0:
pass
else:
total_products = set_num_modify
logger.debug(f"수정 대상 상품수 재설정 : {total_products}")
current_page = 1
completed_products = 0
product_infos = []
while current_page <= total_pages:
# 현재 페이지의 상품 수 계산
products_on_page = min(20, total_products - completed_products)
for i in range(1, products_on_page + 1):
# try:
logger.debug(f"{current_page}페이지-{i}번 상품 수정시작")
product_infos.append(ProductInfo())
# 1번 상품과 나머지 상품들에 대한 XPATH 처리
# 상품ID 복사
try:
product_id_element = return_element(driver,"XPATH",f"//div[{i}]/li/div/div/div[2]/div/div/div/div[3]/div[3]/span[2]/span",10)
product_id = product_id_element.get_attribute('innerText')
product_id_set = f"ID:{product_id}"
product_infos[i-1].id = product_id_set
logger.debug(f"상품ID : {product_id}")
except Exception as e:
logger.debug(f"상품ID 복사 중 오류 발생: 요소를 찾을 수 없습니다. : {e}")
# 상품ID 처리 여부 판단
if is_product_processed(product_id):
logger.debug(f"상품 {product_id}는 이미 처리됨.")
# close_button = driver.find_element(By.CSS_SELECTOR, ".anticon-close path")
# close_button.click()
continue
try:
p_main_title_xpath = f"//div[{i}]/li/div/div/div[2]/div/div/div[1]/div[1]/span[2]"
# p_main_title_xpath = f"//div[{i}]/li/div/div/div[2]/div/div/div/div/span"
p_main_title_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, p_main_title_xpath))
)
p_main_title_text = p_main_title_element.text
product_infos[i-1].init_title = p_main_title_text
logger.debug(f"상품명 확인 : {p_main_title_text}")
if p_main_title_text == "수집 오류 발생":
logger.debug("수집 오류 발생한 상품은 건너뜁니다.")
continue # 다음 상품으로 넘어감
# 여기서부터는 "상품명"이 확인되었거나, 다른 조건에 해당하는 경우 상품 수정 로직을 계속 진행합니다.
except Exception as e:
logger.debug(f"상품명 확인 중 오류 발생: {e}")
continue # 해당 상품을 건너뛰고 다음 상품으로 넘어감
# if i == 1:
# xpath_pattern = f"//div[3]/div/span[2]"
# # 1번상품 xpath=//div[3]/div/span[2]
# # 2번상품 xpath=//div[2]/li/div/div/div[2]/div/div/div/div[3]/div/span[2]
# # 3번상품 xpath=//div[3]/li/div/div/div[2]/div/div/div/div[3]/div/span[2]
# # 20번상품 xpath=//div[20]/li/div/div/div[2]/div/div/div/div[3]/div/span[2]
# else:
# # xpath_pattern = f"//div[2]/li[{i}]/div/div/div[2]/div/div/div/div[3]/div/span[2]"
# xpath_pattern = f"//div[{i}]/li/div/div/div[2]/div/div/div/div[3]/div/span[2]"
try:
xpath_pattern = f"//div[{i}]/li/div/div/div[2]/div/div/div[1]/div[3]/div[1]/span[2]"
product_tao_price_element = driver.find_element(By.XPATH, xpath_pattern)
product_tao_price = product_tao_price_element.text
logger.debug(f"product_tao_price : {product_tao_price}")
# pattern = r'¥(\d+)~(\d+)'
# pattern = r'¥(\d{1,3}(?:,\d{3})*)~(\d{1,3}(?:,\d{3})*)'
# pattern = r'¥(\d{1,3}(?:,\d{3})*|\d+\.\d+)~(\d{1,3}(?:,\d{3})*|\d+\.\d+)'
# 범위 또는 단일 값을 추출하기 위한 패턴 수정
pattern = r'¥(\d{1,3}(?:,\d{3})*|\d+\.\d+)(?:~(\d{1,3}(?:,\d{3})*|\d+\.\d+))?'
match = re.search(pattern, product_tao_price)
if match:
if match.group(2): # 범위의 두 번째 값이 존재하는 경우
# 쉼표를 제거하고, 첫 번째 값 올림 처리
product_low_cost = math.ceil(float(match.group(1).replace(',', '')))
# 쉼표를 제거하고, 두 번째 값 올림 처리
product_high_cost = math.ceil(float(match.group(2).replace(',', '')))
else: # 단일 값만 존재하는 경우
product_low_cost = math.ceil(float(match.group(1).replace(',', '')))
product_high_cost = product_low_cost # 저가와 고가 모두 같은 값 할당
logger.debug(f"낮은 원가 : {product_low_cost}")
logger.debug(f"높은 원가 : {product_high_cost}")
else:
logger.debug("상품 원가 수집 오류 발생: 요소를 찾을 수 없습니다.")
logger.debug("원가를 기본값으로 할당합니다.")
product_low_cost = 100
product_high_cost = 100
product_infos[i-1].tao_low_price = product_low_cost
product_infos[i-1].tao_high_price = product_high_cost
except Exception as e:
logger.debug(f"상품 상세 정보 수집 중 오류 발생: {e}")
# 상품 이미지 복사
try:
product_image_element = return_element(driver,"XPATH",f"//div[{i}]/li/div/div/div[1]/div/div[2]/div/div/img",10)
except Exception as e:
logger.debug(f"상품 이미지 URL 복사 중 오류 발생: 요소를 찾을 수 없습니다. : {e}")
product_image_url = product_image_element.get_attribute('src')
product_infos[i-1].main_image_url = product_image_url
logger.debug(f"product_image_url : {product_image_url}")
# 상품 수정 시작
# try:
# product_title_element = WebDriverWait(driver, 10).until(
# EC.presence_of_element_located((By.XPATH, (f"//div[{i}]/li/div/div/div[2]/div/div/div/div")))
# # EC.presence_of_element_located((By.CSS_SELECTOR, ".signList > .ant-btn-default > span"))
# )
# except Exception as e:
# logger.debug(f"상품 수정시작 중 오류 발생: 요소를 찾을 수 없습니다.{e}")
# # product_title_element = driver.find_element(By.XPATH, (f"//div[{i}]/li/div/div/div[2]/div/div/div/div"))
# driver.execute_script("arguments[0].click();", product_title_element)# 상품 수정 페이지 자바스크립트로 열기
click_to_product_title_for_modify_xpath = f"//div[{i}]/li/div/div/div[2]/div/div/div/div"
click_element(driver, 'XPATH', click_to_product_title_for_modify_xpath, 10, 'js')
logger.debug("상품 수정 페이지 클릭")
time.sleep(2)
# # 이미지 주소 복사
# # image_element = driver.find_element(By.XPATH, "//div[@id='productMainContentContainerId']/div/div/div/div/div[2]/div/img")
# try:
# image_element = WebDriverWait(driver, 10).until(
# # EC.presence_of_element_located((By.CSS_SELECTOR, ".sc-gFnajm"))
# EC.presence_of_element_located((By.CSS_SELECTOR, ".sc-iaJaUu"))
# )
# except Exception as e:
# logger.debug(f"상품 이미지 주소 중 오류 발생: 요소를 찾을 수 없습니다. : {e}")
# # image_element = driver.find_element(By.CSS_SELECTOR, ".sc-gFnajm")
# image_src = image_element.get_attribute("src")
# logger.debug(f"상품 이미지 주소 : {image_src}")
# #상품명 복사
# product_title_element = driver.find_element(By.XPATH, "//div[@id='productMainContentContainerId']/div/div/div/div/div[5]/div/span/input")
# # product_title_element = driver.find_element(By.CSS_SELECTOR, ".ant-input-affix-wrapper-focused > .ant-input")
# try:
# product_title_element = WebDriverWait(driver, 10).until(
# EC.presence_of_element_located((By.XPATH, "//div[5]/div/span/input"))
# )
# except Exception as e:
# logger.debug(f"상품명 복사 중 오류 발생: 요소를 찾을 수 없습니다. : {e}")
# # product_title_element = driver.find_element(By.XPATH, "//div[5]/div/span/input")
# product_title = product_title_element.get_attribute("value")
product_infos[i-1].main_image_url = product_image_url
# 상품 수정 작업 수행
logger.debug("상세페이지 수정작업 시작")
modify_detail_page(driver, gemini, product_infos[i-1], delv_collection, json_naver_codes)
logger.debug("상세페이지 수정작업 완료")
# 키워드 버튼 클릭
# click_element(driver, 'CSS_SELECTOR', '#rc-tabs-0-tab-3', wait_time=10, click_type='normal')
click_element(driver, 'CSS_SELECTOR', '.ant-tabs-tab:nth-child(4)', wait_time=10, click_type='normal')
logger.debug("키워드 수정작업 시작")
# edit_tag(driver, product_infos[i-1])
logger.debug("키워드 수정작업 완료")
# 여기에 상품 수정 관련 코드 추가
logger.debug("옵션 수정작업 시작")
# modify_option_page(driver, product_infos[i-1])
logger.debug("옵션 수정작업 완료")
logger.debug("가격 수정작업 시작")
modify_price_page(driver, product_infos[i-1])
logger.debug("가격 수정작업 완료")
logger.debug("상품명 수정작업 시작")
modify_product_title(driver, product_infos[i-1])
logger.debug("상품명 수정작업 완료")
# 상품수정페이지 저장 버튼 클릭
click_element(driver, 'CSS_SELECTOR', '.ant-col:nth-child(9) > .ant-btn', wait_time=10, click_type='normal')
# 상품수정페이지 닫기 버튼 클릭
click_element(driver, 'CSS_SELECTOR', '.anticon-close > svg', wait_time=10, click_type='normal')
# try:
# # 'close' 버튼이 나타날 때까지 최대 10초간 대기
# close_button = WebDriverWait(driver, 10).until(
# EC.visibility_of_element_located((By.CSS_SELECTOR, ".anticon-close > svg"))
# )
# # 'close' 버튼이 나타나면 클릭
# close_button.click()
# logger.debug("Close button was clicked.")
# except TimeoutException:
# # 지정된 시간 내에 'close' 버튼이 나타나지 않으면
# logger.debug("Close button was not found within the given time.")
mark_product_processed(product_id)
logger.debug(f"상품 {product_id} 처리 완료.")
completed_products += 1
logger.debug(f"진행 상황: {completed_products}/{total_products} ({(completed_products/total_products)*100:.2f}%)")
# except Exception as e:
# logger.debug(f"상품 {i} 확인 중 오류 발생: {e}")
# 다음 페이지로 이동
if current_page < total_pages:
try:
next_page_button = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".ant-pagination-next > .ant-pagination-item-link"))
)
# next_page_button = driver.find_element(By.CSS_SELECTOR, ".ant-pagination-next > .ant-pagination-item-link")
# next_page_button.click()
driver.execute_script("arguments[0].click();", next_page_button)
time.sleep(2)
current_page += 1
except Exception as e:
logger.debug(f"다음 페이지로 이동하는 중 오류 발생: {e}")
break
else:
logger.debug("모든 페이지 처리 완료. 작업 종료.")
break
logger.debug("상품 수정 작업이 완료되었습니다.")

68
mongo_config.py Normal file
View File

@ -0,0 +1,68 @@
from pymongo import MongoClient
from configparser import ConfigParser
import os
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class MongoConfig:
_instance = None # 클래스 레벨의 인스턴스 변수
def __new__(cls):
if cls._instance is None:
cls._instance = super(MongoConfig, cls).__new__(cls)
cls._instance.init_config()
return cls._instance
def init_config(self):
self.config = ConfigParser()
config_path = os.path.join(os.path.dirname(__file__), 'config.ini')
self.config.read(config_path)
logger.debug(f"{config_path}")
self.client = None # MongoDB 클라이언트 초기화
self.load_config()
def load_config(self):
if not self.config.has_section('MongoDB'):
raise Exception('서버 설정파일이 없습니다.config.ini 파일을 체크하세요.')
if self.config.has_section('MongoDB'):
address = self.config.get('MongoDB', 'address', fallback='localhost')
port = self.config.get('MongoDB', 'port', fallback='27017')
user = self.config.get('MongoDB', 'user', fallback='')
password = self.config.get('MongoDB', 'password', fallback='')
if not all([address, port, user, password]):
raise Exception('Incomplete MongoDB configuration. Please check your config.ini file.')
return address, port, user, password
# def connect(self):
# self.client = MongoClient(f'mongodb://{self.user}:{self.password}@{self.address}:{self.port}/')
# self.db = self.client['taobao_project']
@staticmethod
def get_db():
return MongoConfig()._instance.db
def save_config(self, address, port, user, password):
self.config['MongoDB'] = {
'address': address,
'port': port,
'user': user,
'password': password
}
with open('config.ini', 'w') as configfile:
self.config.write(configfile)
def try_connect(self, address, port, user, password):
address, port, user, password = self.load_config()
# logger.debug(f"{address},{port},{user},{password}")
if not all([address, port, user, password]):
logger.debug("Configuration missing. Please check your config.ini file.")
return False
try:
self.client = MongoClient(f'mongodb://{user}:{password}@{address}:{port}/')
self.db = self.client['taobao_project'] # 여기에서 추가 연결 확인 작업을 수행할 수 있습니다.
logger.debug("MongoDB 연결 성공.")
return True
except Exception as e:
logger.debug(f"MongoDB 연결 실패: {e}")
return False

18
mongo_input.py Normal file
View File

@ -0,0 +1,18 @@
import pandas as pd
from pymongo import MongoClient
from logger_module import default_logger as logger
client = MongoClient('mongodb://root:1234@cckb9998.synology.me:27017/')
logger.debug("DB생성")
db = client['taobao_project']
collection = db['delv_fee']
# 엑셀 파일 읽기
excel_file = 'delv.xlsx'
df = pd.read_excel(excel_file)
# DataFrame을 MongoDB에 저장
records = df.to_dict(orient='records')
collection.insert_many(records)
logger.debug("데이터가 MongoDB에 저장되었습니다.")

202
naver_search.py Normal file
View File

@ -0,0 +1,202 @@
import requests
from bs4 import BeautifulSoup
# import sqlite3
import json
# from datetime import datetime
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def parse_naver_shopping(keyword, naver_code, isOverseas=1, sortcount=10):
# 네이버 쇼핑 URL 설정
urlBase = "https://search.shopping.naver.com/search/all?query="
urlEnd = f"&cat_id={naver_code}&frm=NVSHATC&pagingIndex=1&pagingSize=40&&productSet=overseas&sort=rel&timestamp=&viewType=list"
url = urlBase + keyword + urlEnd
logger.debug(f"네이버 카테코드는 [{naver_code}] 입니다.")
logger.debug(f"대상키워드는 [{keyword}] 입니다.")
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"DNT": "1", # Do Not Track 요청 헤더 (사용자의 추적을 거부)
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1", # https로의 업그레이드를 요청
"Cache-Control": "max-age=0", # 캐시된 콘텐츠를 재사용하지 않도록 요청
}
# # 네이버 쇼핑에 접속하여 HTML 받아오기
# response = requests.get(url, headers=headers)
# soup = BeautifulSoup(response.text, 'html.parser')
# 네이버 쇼핑에 접속하여 HTML 받아오기
try:
response = requests.get(url, headers=headers)
response.raise_for_status() # 만약 요청이 실패하면 예외 발생
except requests.exceptions.RequestException as e:
logger.debug(f"네이버 쇼핑 HTML을 가져오는 중 오류 발생: {e}")
# 예외 처리 코드 추가
soup = BeautifulSoup(response.text, 'html.parser')
# "__NEXT_DATA__" 파싱하기
logger.debug("NEXT_DATA 파싱")
try:
next_data_str = soup.find("script", {"id": "__NEXT_DATA__"}).string
except AttributeError as e:
logger.debug("NEXT_DATA 스크립트를 찾을 수 없습니다.")
# 예외 처리 코드 추가
else:
try:
next_data_json = json.loads(next_data_str)
except json.JSONDecodeError as e:
logger.debug("NEXT_DATA JSON을 파싱하는 중 오류 발생:", e)
# 예외 처리 코드 추가
# "__NEXT_DATA__" 파싱하기
logger.debug("NEXT_DATA 파싱 완료")
next_data_str = soup.find("script", {"id": "__NEXT_DATA__"}).string
next_data_json = json.loads(next_data_str)
# products 리스트 가져오기
logger.debug("products 리스트 가져오기")
products_list = next_data_json["props"]["pageProps"]["initialState"]["products"]["list"]
logger.debug(f"products 리스트 [{len(products_list)}] 개 입니다.")
filtered_products = products_list
#logger.debug(products_list)
# isOverseas 값에 따라 필터링
#filtered_products = [product for product in products_list if product.get("item", {}).get("overseaTp", 0) == isOverseas]
# isOverseas 값에 따라 필터링
# isOverseas = int(isOverseas)
# if isOverseas == 1:
# filtered_products = [product for product in products_list if int(product.get("item", {}).get("overseaTp")) == 1]
# else:
# filtered_products = products_list # 모든 제품 포함
#logger.debug(filtered_products)
# purchaseCnt, reviewCount, keepCnt를 기준으로 정렬
logger.debug(f"해외배송여부는 [{isOverseas}] 입니다.")
if len(filtered_products) >= 4:
# 상품 정렬: 낮은 가격 순
sorted_products_by_price = sorted(filtered_products, key=lambda p: int(p.get("item", {}).get("price", 0)))
logger.debug(f"상품 정렬 {len(sorted_products_by_price)}개 선택")
# 가격이 높은 상품 3개와 낮은 상품 2개를 제외한 나머지 상품 추출
remaining_products = sorted_products_by_price[1:-1]
logger.debug(f"낮은가격 2개, 높은가격 3개제외 한 나머지 상품 정렬 {len(remaining_products)}개 선택")
else:
sorted_products_by_price = sorted(filtered_products, key=lambda p: int(p.get("item", {}).get("price", 0)))
logger.debug(f"총 상품 4개이하 | 상품 정렬 {len(sorted_products_by_price)}개 선택")
remaining_products = filtered_products
logger.debug(f"총 상품 4개이하로 상하위 상품제외 로직 pass {len(remaining_products)}개 선택")
# 추출된 상품을 rank 순서대로 정렬
top5_products = sorted(remaining_products, key=lambda p: int(p.get("item", {}).get("rank", 0)))
logger.debug(f"추출된 상품을 rank 순서대로 정렬 : {len(top5_products)}개 선택")
final_top_5_products = top5_products[:5]
logger.debug(f"RANK 정렬상품 중 마지막으로 상위 제품 [{len(final_top_5_products)}]개 선택")
# pro_num = len(top5_products)
# logger.debug(f"정렬상품 중 상위 5[{pro_num}]개 선택")
#logger.debug(f"해외배송여부필터링된 상위제품들은 [{filtered_products}] 입니다.")
# def sort_key(p):
# # 값을 가져오고, 문자열이면 정수로 변환
# def to_int(value):
# try:
# return int(value)
# except (ValueError, TypeError):
# return 0 # 숫자로 변환할 수 없는 경우 0을 반환
# rank = to_int(p.get("item", {}).get("rank", 0))
# purchaseCnt = to_int(p.get("item", {}).get("purchaseCnt", 0))
# reviewCount = to_int(p.get("item", {}).get("reviewCount", 0))
# keepCnt = to_int(p.get("item", {}).get("keepCnt", 0))
# # return rank, purchaseCnt, reviewCount, keepCnt
# return rank
# # 정렬 부분
# sorted_products = sorted(filtered_products, key=sort_key, reverse=False)
# logger.debug(f"정렬된 상품 {len(sorted_products)}개")
# # 낮은가격이 위로 올라갈 경우 메인키워드와 일치하지않는 부분이 발생함 ex) 유압도끼 검색결과가 정렬 후 수동도끼로 바뀜.
# #logger.debug(f"3가지조건으로 정렬된 상품들 : [{sorted_products}]")
# #logger.debug(sorted_products)
# # 상위 5개 제품 선택
# top_5_products = sorted_products[:int(sortcount)]
# logger.debug(f"정렬된 상품 {len(top_5_products)}개")
# # 낮은가격이 위로 올라갈 경우 메인키워드와 일치하지않는 부분이 발생함 ex) 유압도끼 검색결과가 정렬 후 수동도끼로 바뀜.
# # top_5_products = filtered_products[:int(sortcount)]
# #logger.debug(f"정렬상품 중 상위 5개 선택 : [{top_5_products}]")
# # SQLite DB에 연결하기
# #conn = sqlite3.connect(db_name)
# # c = conn.cursor()
# original relatedTags 리스트 가져오기
related_tags_ori = next_data_json["props"]["pageProps"]["relatedTags"]
# 상품 정보를 담을 리스트 초기화
products_info = []
# relatedTags 리스트 생성
related_tags = [tag.strip() for tag in related_tags_ori if tag]
logger.debug(f"현재 키워드인 [{keyword}]에 대한 연관검색어는 [{related_tags}] 입니다.")
for product in final_top_5_products:
price = product.get("item", {}).get("price")
productTitle = product.get("item", {}).get("productTitle")
category1Name = product.get("item", {}).get("category1Name")
category2Name = product.get("item", {}).get("category2Name")
category3Name = product.get("item", {}).get("category3Name")
category4Name = product.get("item", {}).get("category4Name")
openDate = product.get("item", {}).get("openDate")
mallCount = product.get("item", {}).get("mallCount")
keepCnt = product.get("item", {}).get("keepCnt")
overseaTp = product.get("item", {}).get("overseaTp")
reviewCount = product.get("item", {}).get("reviewCount")
reviewCountSum = product.get("item", {}).get("reviewCountSum")
scoreInfo = product.get("item", {}).get("scoreInfo")
naverPayAdAccumulatedDisplayValue = product.get("item", {}).get("naverPayAdAccumulatedDisplayValue")
mobileLowPrice = product.get("item", {}).get("mobileLowPrice")
lowPrice = product.get("item", {}).get("lowPrice")
deliveryFeeContent = product.get("item", {}).get("deliveryFeeContent")
dlvryLowPrice = product.get("item", {}).get("dlvryLowPrice")
imageUrl = product.get("item", {}).get("imageUrl")
imgSz = product.get("item", {}).get("imgSz")
searchKeyword = product.get("item", {}).get("searchKeyword")
mallProductUrl = product.get("item", {}).get("mallProductUrl")
mallPcUrl = product.get("item", {}).get("mallPcUrl")
mallName = product.get("item", {}).get("mallName")
manuTag = product.get("item", {}).get("manuTag")
#mallInfoCache = product.get("item", {}).get("mallInfoCache")
purchaseCnt = product.get("item", {}).get("purchaseCnt")
rank = product.get("item", {}).get("rank")
# 상품 정보를 딕셔너리로 만들어 리스트에 추가
product_info = {
"productTitle": productTitle,
"price": price,
"imageUrl": imageUrl,
"rank": rank,
"purchase" : purchaseCnt,
"review" : reviewCountSum
}
products_info.append(product_info)
logger.debug(f"키워드 검색 결과 상품 [{keyword}]에 대한 [{len(products_info)}]개의 상품정보수집 완료")
return products_info

105
navigate.py Normal file
View File

@ -0,0 +1,105 @@
from utils import log
import time
from database import is_product_processed, mark_product_processed
def modify_products(driver):
# 총 상품 수 확인
total_products_text = driver.find_element_by_xpath("//span[contains(.,'')]").text
total_products = int(''.join(filter(str.isdigit, total_products_text)))
total_pages = (total_products + 19) // 20
current_page = 1
completed_products = 0
while current_page <= total_pages:
# 현재 페이지의 상품 수 계산
products_on_page = min(20, total_products - completed_products)
for i in range(1, products_on_page + 1):
try:
product_title_element = driver.find_element_by_xpath(f"//div[{i}]/li/div/div/div[2]/div/div/div/div")
product_title_element.click() # 상품 수정 페이지 열기
time.sleep(2)
product_id_element = driver.find_element_by_xpath("//div[3]/div/div[3]/div/div[3]/div/div/div")
product_id = product_id_element.text
if is_product_processed(product_id):
log(f"상품 {product_id}는 이미 처리됨.")
close_button = driver.find_element_by_css_selector(".anticon-close path")
close_button.click()
continue
# 상품 수정 작업 수행
# 여기에 상품 수정 관련 코드 추가
mark_product_processed(product_id)
log(f"상품 {product_id} 처리 완료.")
completed_products += 1
log(f"진행 상황: {completed_products}/{total_products} ({(completed_products/total_products)*100:.2f}%)")
except Exception as e:
log(f"상품 {i} 확인 중 오류 발생: {e}")
# 다음 페이지로 이동
if current_page < total_pages:
try:
next_page_button = driver.find_element_by_css_selector(".ant-pagination-next > .ant-pagination-item-link")
next_page_button.click()
time.sleep(5)
current_page += 1
except Exception as e:
log(f"다음 페이지로 이동하는 중 오류 발생: {e}")
break
else:
log("모든 페이지 처리 완료. 작업 종료.")
break
log("상품 수정 작업이 완료되었습니다.")
def navigate_to_new_product_registration(driver):
"""신규 상품 등록 페이지로 이동합니다."""
log("신규상품등록 버튼 클릭...")
new_product_button = driver.find_element_by_xpath("//span[contains(.,'신규 상품 등록')]")
new_product_button.click()
time.sleep(1)
# 각 페이지의 상품들을 순회하며 "수정작업" 수행
while True:
for i in range(1, 21): # 각 페이지에 최대 20개의 상품
try:
product_title_element = driver.find_element_by_xpath(f"//div[{i}]/li/div/div/div[2]/div/div/div/div")
product_id = product_title_element.text
log(f"상품 {product_id} 확인 중...")
if is_product_processed(product_id):
log(f"상품 {product_id}는 이미 처리됨.")
continue
# 여기에 "수정작업" 로직 추가
# 예:
product_title_element.click()
time.sleep(2)
#python Copy code
# 여기에 "수정작업" 세부 과정을 추가하세요. 예를 들어, 상품명 변경, 키워드 추가 등
# 상품 처리 완료 후, SQLite DB에 상품 ID 저장
mark_product_processed(product_id)
log(f"상품 {product_id} 처리 완료.")
except Exception as e:
log(f"상품 {i} 확인 중 오류 발생: {e}")
continue
# 다음 페이지로 이동
try:
next_page_button = driver.find_element_by_css_selector(".ant-pagination-next > .ant-pagination-item-link")
if not next_page_button.is_enabled():
log("더 이상 페이지가 없음. 작업 종료.")
break
next_page_button.click()
time.sleep(5)
except Exception as e:
log(f"다음 페이지로 이동하는 중 오류 발생: {e}")
break

95
per1.py Normal file
View File

@ -0,0 +1,95 @@
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from element_manager import *
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome(ChromeDriverManager().install())
# to click on the element(로그인) found
driver.find_element(By.XPATH,get_xpath(driver,'qmHvCUkmNT8mWle')).click()
# to click on the element found
driver.find_element(By.XPATH,get_xpath(driver,'WvGb1578ZvSWxf5')).click()
# to click on input field
driver.find_element(By.XPATH,get_xpath(driver,'3zIk6s6r8fjcKsQ')).click()
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'Pj1IEE2JLbfAdBx')).send_keys('Meta')
# to click on input field
driver.find_element(By.XPATH,get_xpath(driver,'Pl06owTF4K2qJ0M')).click()
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'fiJ8gMcakfeIXoD')).send_keys('t')
# press Backspace key
driver.switch_to.active_element.send_keys(Keys.BACK_SPACE)
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'MRpBBHVstWW4CGp')).send_keys('Process')
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'adw0IfyGuD8sTGi')).send_keys('Process')
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'JVNgNIZyTnXc1qW')).send_keys('Process')
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'IK6D19jz4htaIFp')).send_keys('Process')
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'Flft0kgJSTqrRlA')).send_keys('Process')
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'WuHrsOQ3yG0G6CR')).send_keys('Process')
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'MO_GJrIl8JdleES')).send_keys('Process')
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'fFPm0jRfhu4zMtg')).send_keys('Process')
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'LqgaR8rEAAetIki')).send_keys('Process')
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'uJIldt3OunIzcDH')).send_keys('Process')
# press Tab key
driver.switch_to.active_element.send_keys(Keys.TAB)
# to type content in input field
driver.find_element(By.XPATH,get_xpath(driver,'DVuY8WMalvL7t8C')).send_keys('asdf1234')
# to click on the element(직원 로그인 하기) found
driver.find_element(By.XPATH,get_xpath(driver,'yWReVy0DDOGqbBM')).click()
# to click on the element(신규 상품 등록) found
driver.find_element(By.XPATH,get_xpath(driver,'pixT6SFoLewNdtk')).click()
# to click on the element(판매가141,900~141,900원배...) found
driver.find_element(By.XPATH,get_xpath(driver,'DUuaybQEkYFD06p')).click()
# to click on the element(브라우저 번역하기) found
driver.find_element(By.XPATH,get_xpath(driver,'i47RYNqzZaSJcUt')).click()
# to click on the element found
driver.find_element(By.XPATH,get_xpath(driver,'sObEp8_QS4Cb10O')).click()
# to click on the element found
driver.find_element(By.XPATH,get_xpath(driver,'Be1LX5OzlybIBPQ')).click()
# to click on the element found
driver.find_element(By.XPATH,get_xpath(driver,'nfgPtsLds_xhvdZ')).click()
# to click on the element(전체 적용하기) found
driver.find_element(By.XPATH,get_xpath(driver,'hijklMMifSdEe93')).click()
# to click on the element(저장하기) found
driver.find_element(By.XPATH,get_xpath(driver,'QuARrBpzkXBcnM1')).click()
# to click on the element found
driver.find_element(By.XPATH,get_xpath(driver,'u4DCLfetYhREDph')).click()

BIN
products.db Normal file

Binary file not shown.

Binary file not shown.

BIN
r.txt Normal file

Binary file not shown.

BIN
requirements.txt Normal file

Binary file not shown.

1
secret.key Normal file
View File

@ -0,0 +1 @@
M6f9lUPrey_bZo78mZabJcDJEvu5C6zFsrkhacc1Y54=

188
share/man/man1/isympy.1 Normal file
View File

@ -0,0 +1,188 @@
'\" -*- coding: us-ascii -*-
.if \n(.g .ds T< \\FC
.if \n(.g .ds T> \\F[\n[.fam]]
.de URL
\\$2 \(la\\$1\(ra\\$3
..
.if \n(.g .mso www.tmac
.TH isympy 1 2007-10-8 "" ""
.SH NAME
isympy \- interactive shell for SymPy
.SH SYNOPSIS
'nh
.fi
.ad l
\fBisympy\fR \kx
.if (\nx>(\n(.l/2)) .nr x (\n(.l/5)
'in \n(.iu+\nxu
[\fB-c\fR | \fB--console\fR] [\fB-p\fR ENCODING | \fB--pretty\fR ENCODING] [\fB-t\fR TYPE | \fB--types\fR TYPE] [\fB-o\fR ORDER | \fB--order\fR ORDER] [\fB-q\fR | \fB--quiet\fR] [\fB-d\fR | \fB--doctest\fR] [\fB-C\fR | \fB--no-cache\fR] [\fB-a\fR | \fB--auto\fR] [\fB-D\fR | \fB--debug\fR] [
-- | PYTHONOPTIONS]
'in \n(.iu-\nxu
.ad b
'hy
'nh
.fi
.ad l
\fBisympy\fR \kx
.if (\nx>(\n(.l/2)) .nr x (\n(.l/5)
'in \n(.iu+\nxu
[
{\fB-h\fR | \fB--help\fR}
|
{\fB-v\fR | \fB--version\fR}
]
'in \n(.iu-\nxu
.ad b
'hy
.SH DESCRIPTION
isympy is a Python shell for SymPy. It is just a normal python shell
(ipython shell if you have the ipython package installed) that executes
the following commands so that you don't have to:
.PP
.nf
\*(T<
>>> from __future__ import division
>>> from sympy import *
>>> x, y, z = symbols("x,y,z")
>>> k, m, n = symbols("k,m,n", integer=True)
\*(T>
.fi
.PP
So starting isympy is equivalent to starting python (or ipython) and
executing the above commands by hand. It is intended for easy and quick
experimentation with SymPy. For more complicated programs, it is recommended
to write a script and import things explicitly (using the "from sympy
import sin, log, Symbol, ..." idiom).
.SH OPTIONS
.TP
\*(T<\fB\-c \fR\*(T>\fISHELL\fR, \*(T<\fB\-\-console=\fR\*(T>\fISHELL\fR
Use the specified shell (python or ipython) as
console backend instead of the default one (ipython
if present or python otherwise).
Example: isympy -c python
\fISHELL\fR could be either
\&'ipython' or 'python'
.TP
\*(T<\fB\-p \fR\*(T>\fIENCODING\fR, \*(T<\fB\-\-pretty=\fR\*(T>\fIENCODING\fR
Setup pretty printing in SymPy. By default, the most pretty, unicode
printing is enabled (if the terminal supports it). You can use less
pretty ASCII printing instead or no pretty printing at all.
Example: isympy -p no
\fIENCODING\fR must be one of 'unicode',
\&'ascii' or 'no'.
.TP
\*(T<\fB\-t \fR\*(T>\fITYPE\fR, \*(T<\fB\-\-types=\fR\*(T>\fITYPE\fR
Setup the ground types for the polys. By default, gmpy ground types
are used if gmpy2 or gmpy is installed, otherwise it falls back to python
ground types, which are a little bit slower. You can manually
choose python ground types even if gmpy is installed (e.g., for testing purposes).
Note that sympy ground types are not supported, and should be used
only for experimental purposes.
Note that the gmpy1 ground type is primarily intended for testing; it the
use of gmpy even if gmpy2 is available.
This is the same as setting the environment variable
SYMPY_GROUND_TYPES to the given ground type (e.g.,
SYMPY_GROUND_TYPES='gmpy')
The ground types can be determined interactively from the variable
sympy.polys.domains.GROUND_TYPES inside the isympy shell itself.
Example: isympy -t python
\fITYPE\fR must be one of 'gmpy',
\&'gmpy1' or 'python'.
.TP
\*(T<\fB\-o \fR\*(T>\fIORDER\fR, \*(T<\fB\-\-order=\fR\*(T>\fIORDER\fR
Setup the ordering of terms for printing. The default is lex, which
orders terms lexicographically (e.g., x**2 + x + 1). You can choose
other orderings, such as rev-lex, which will use reverse
lexicographic ordering (e.g., 1 + x + x**2).
Note that for very large expressions, ORDER='none' may speed up
printing considerably, with the tradeoff that the order of the terms
in the printed expression will have no canonical order
Example: isympy -o rev-lax
\fIORDER\fR must be one of 'lex', 'rev-lex', 'grlex',
\&'rev-grlex', 'grevlex', 'rev-grevlex', 'old', or 'none'.
.TP
\*(T<\fB\-q\fR\*(T>, \*(T<\fB\-\-quiet\fR\*(T>
Print only Python's and SymPy's versions to stdout at startup, and nothing else.
.TP
\*(T<\fB\-d\fR\*(T>, \*(T<\fB\-\-doctest\fR\*(T>
Use the same format that should be used for doctests. This is
equivalent to '\fIisympy -c python -p no\fR'.
.TP
\*(T<\fB\-C\fR\*(T>, \*(T<\fB\-\-no\-cache\fR\*(T>
Disable the caching mechanism. Disabling the cache may slow certain
operations down considerably. This is useful for testing the cache,
or for benchmarking, as the cache can result in deceptive benchmark timings.
This is the same as setting the environment variable SYMPY_USE_CACHE
to 'no'.
.TP
\*(T<\fB\-a\fR\*(T>, \*(T<\fB\-\-auto\fR\*(T>
Automatically create missing symbols. Normally, typing a name of a
Symbol that has not been instantiated first would raise NameError,
but with this option enabled, any undefined name will be
automatically created as a Symbol. This only works in IPython 0.11.
Note that this is intended only for interactive, calculator style
usage. In a script that uses SymPy, Symbols should be instantiated
at the top, so that it's clear what they are.
This will not override any names that are already defined, which
includes the single character letters represented by the mnemonic
QCOSINE (see the "Gotchas and Pitfalls" document in the
documentation). You can delete existing names by executing "del
name" in the shell itself. You can see if a name is defined by typing
"'name' in globals()".
The Symbols that are created using this have default assumptions.
If you want to place assumptions on symbols, you should create them
using symbols() or var().
Finally, this only works in the top level namespace. So, for
example, if you define a function in isympy with an undefined
Symbol, it will not work.
.TP
\*(T<\fB\-D\fR\*(T>, \*(T<\fB\-\-debug\fR\*(T>
Enable debugging output. This is the same as setting the
environment variable SYMPY_DEBUG to 'True'. The debug status is set
in the variable SYMPY_DEBUG within isympy.
.TP
-- \fIPYTHONOPTIONS\fR
These options will be passed on to \fIipython (1)\fR shell.
Only supported when ipython is being used (standard python shell not supported).
Two dashes (--) are required to separate \fIPYTHONOPTIONS\fR
from the other isympy options.
For example, to run iSymPy without startup banner and colors:
isympy -q -c ipython -- --colors=NoColor
.TP
\*(T<\fB\-h\fR\*(T>, \*(T<\fB\-\-help\fR\*(T>
Print help output and exit.
.TP
\*(T<\fB\-v\fR\*(T>, \*(T<\fB\-\-version\fR\*(T>
Print isympy version information and exit.
.SH FILES
.TP
\*(T<\fI${HOME}/.sympy\-history\fR\*(T>
Saves the history of commands when using the python
shell as backend.
.SH BUGS
The upstreams BTS can be found at \(lahttps://github.com/sympy/sympy/issues\(ra
Please report all bugs that you find in there, this will help improve
the overall quality of SymPy.
.SH "SEE ALSO"
\fBipython\fR(1), \fBpython\fR(1)

225
share/man/man1/ttx.1 Normal file
View File

@ -0,0 +1,225 @@
.Dd May 18, 2004
.\" ttx is not specific to any OS, but contrary to what groff_mdoc(7)
.\" seems to imply, entirely omitting the .Os macro causes 'BSD' to
.\" be used, so I give a zero-width space as its argument.
.Os \&
.\" The "FontTools Manual" argument apparently has no effect in
.\" groff 1.18.1. I think it is a bug in the -mdoc groff package.
.Dt TTX 1 "FontTools Manual"
.Sh NAME
.Nm ttx
.Nd tool for manipulating TrueType and OpenType fonts
.Sh SYNOPSIS
.Nm
.Bk
.Op Ar option ...
.Ek
.Bk
.Ar file ...
.Ek
.Sh DESCRIPTION
.Nm
is a tool for manipulating TrueType and OpenType fonts. It can convert
TrueType and OpenType fonts to and from an
.Tn XML Ns -based format called
.Tn TTX .
.Tn TTX
files have a
.Ql .ttx
extension.
.Pp
For each
.Ar file
argument it is given,
.Nm
detects whether it is a
.Ql .ttf ,
.Ql .otf
or
.Ql .ttx
file and acts accordingly: if it is a
.Ql .ttf
or
.Ql .otf
file, it generates a
.Ql .ttx
file; if it is a
.Ql .ttx
file, it generates a
.Ql .ttf
or
.Ql .otf
file.
.Pp
By default, every output file is created in the same directory as the
corresponding input file and with the same name except for the
extension, which is substituted appropriately.
.Nm
never overwrites existing files; if necessary, it appends a suffix to
the output file name before the extension, as in
.Pa Arial#1.ttf .
.Ss "General options"
.Bl -tag -width ".Fl t Ar table"
.It Fl h
Display usage information.
.It Fl d Ar dir
Write the output files to directory
.Ar dir
instead of writing every output file to the same directory as the
corresponding input file.
.It Fl o Ar file
Write the output to
.Ar file
instead of writing it to the same directory as the
corresponding input file.
.It Fl v
Be verbose. Write more messages to the standard output describing what
is being done.
.It Fl a
Allow virtual glyphs ID's on compile or decompile.
.El
.Ss "Dump options"
The following options control the process of dumping font files
(TrueType or OpenType) to
.Tn TTX
files.
.Bl -tag -width ".Fl t Ar table"
.It Fl l
List table information. Instead of dumping the font to a
.Tn TTX
file, display minimal information about each table.
.It Fl t Ar table
Dump table
.Ar table .
This option may be given multiple times to dump several tables at
once. When not specified, all tables are dumped.
.It Fl x Ar table
Exclude table
.Ar table
from the list of tables to dump. This option may be given multiple
times to exclude several tables from the dump. The
.Fl t
and
.Fl x
options are mutually exclusive.
.It Fl s
Split tables. Dump each table to a separate
.Tn TTX
file and write (under the name that would have been used for the output
file if the
.Fl s
option had not been given) one small
.Tn TTX
file containing references to the individual table dump files. This
file can be used as input to
.Nm
as long as the referenced files can be found in the same directory.
.It Fl i
.\" XXX: I suppose OpenType programs (exist and) are also affected.
Don't disassemble TrueType instructions. When this option is specified,
all TrueType programs (glyph programs, the font program and the
pre-program) are written to the
.Tn TTX
file as hexadecimal data instead of
assembly. This saves some time and results in smaller
.Tn TTX
files.
.It Fl y Ar n
When decompiling a TrueType Collection (TTC) file,
decompile font number
.Ar n ,
starting from 0.
.El
.Ss "Compilation options"
The following options control the process of compiling
.Tn TTX
files into font files (TrueType or OpenType):
.Bl -tag -width ".Fl t Ar table"
.It Fl m Ar fontfile
Merge the input
.Tn TTX
file
.Ar file
with
.Ar fontfile .
No more than one
.Ar file
argument can be specified when this option is used.
.It Fl b
Don't recalculate glyph bounding boxes. Use the values in the
.Tn TTX
file as is.
.El
.Sh "THE TTX FILE FORMAT"
You can find some information about the
.Tn TTX
file format in
.Pa documentation.html .
In particular, you will find in that file the list of tables understood by
.Nm
and the relations between TrueType GlyphIDs and the glyph names used in
.Tn TTX
files.
.Sh EXAMPLES
In the following examples, all files are read from and written to the
current directory. Additionally, the name given for the output file
assumes in every case that it did not exist before
.Nm
was invoked.
.Pp
Dump the TrueType font contained in
.Pa FreeSans.ttf
to
.Pa FreeSans.ttx :
.Pp
.Dl ttx FreeSans.ttf
.Pp
Compile
.Pa MyFont.ttx
into a TrueType or OpenType font file:
.Pp
.Dl ttx MyFont.ttx
.Pp
List the tables in
.Pa FreeSans.ttf
along with some information:
.Pp
.Dl ttx -l FreeSans.ttf
.Pp
Dump the
.Sq cmap
table from
.Pa FreeSans.ttf
to
.Pa FreeSans.ttx :
.Pp
.Dl ttx -t cmap FreeSans.ttf
.Sh NOTES
On MS\-Windows and MacOS,
.Nm
is available as a graphical application to which files can be dropped.
.Sh SEE ALSO
.Pa documentation.html
.Pp
.Xr fontforge 1 ,
.Xr ftinfo 1 ,
.Xr gfontview 1 ,
.Xr xmbdfed 1 ,
.Xr Font::TTF 3pm
.Sh AUTHORS
.Nm
was written by
.An -nosplit
.An "Just van Rossum" Aq just@letterror.com .
.Pp
This manual page was written by
.An "Florent Rougon" Aq f.rougon@free.fr
for the Debian GNU/Linux system based on the existing FontTools
documentation. It may be freely used, modified and distributed without
restrictions.
.\" For Emacs:
.\" Local Variables:
.\" fill-column: 72
.\" sentence-end: "[.?!][]\"')}]*\\($\\| $\\| \\| \\)[ \n]*"
.\" sentence-end-double-space: t
.\" End:

BIN
simfang.ttf Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More