엑셀출력조정

This commit is contained in:
Envy_PC 2024-11-01 13:06:12 +09:00
parent e9cf688f78
commit 6709877677
10 changed files with 249 additions and 85 deletions

4
.gitignore vendored
View File

@ -5,4 +5,6 @@ pyvenv.cfg
*.log
__pycache__/
src/__pycache__/
src/browsers/user_data/
src/browsers/user_data/
*.xlsx
!baseXLS_Percenty.xlsx

BIN
src/baseXLS_Percenty.xlsx Normal file

Binary file not shown.

View File

@ -1,50 +1,54 @@
import sqlite3
import logging
import pandas as pd
logger = logging.getLogger(__name__)
# SQLite DB 초기화 및 데이터 관리
class DatabaseManager:
def __init__(self, db_name="taobao_items.db"):
self.conn = sqlite3.connect(db_name)
self.cur = self.conn.cursor()
self.db_path = db_name
logger.info("DBManager 초기화 완료")
self.create_table()
def create_table(self):
try:
self.cur.execute("""
CREATE TABLE IF NOT EXISTS items (
item_id TEXT PRIMARY KEY,
pc_url TEXT,
name TEXT,
price REAL,
image_url TEXT,
sales TEXT
)
""")
self.conn.commit()
logger.info("데이터베이스 테이블 초기화 완료")
with sqlite3.connect(self.db_path) as conn:
conn.execute('''CREATE TABLE IF NOT EXISTS items (
id TEXT PRIMARY KEY,
pc_url TEXT,
name TEXT,
price REAL,
image_url TEXT,
sales TEXT,
translated_name TEXT, -- 번역된 상품명
margin_price REAL, -- 계산된 더하기 마진
international_shipping_fee REAL, -- 해외 배송비
category_code TEXT -- 카테고리 코드
)''')
logger.info("데이터베이스 테이블 생성 완료")
except sqlite3.Error as e:
logger.error(f"DB 테이블 생성 오류: {e}")
logger.error(f"테이블 생성 오류 발생: {e}")
def insert_items(self, items_data):
def insert_items(self, items):
try:
self.cur.executemany("""
INSERT OR REPLACE INTO items (item_id, pc_url, name, price, image_url, sales)
VALUES (?, ?, ?, ?, ?, ?)
""", items_data)
self.conn.commit()
logger.info("데이터 삽입 완료")
with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
conn.executemany('''INSERT OR REPLACE INTO items (id, pc_url, name, price, image_url, sales)
VALUES (?, ?, ?, ?, ?, ?)''', items)
logger.info(f"{len(items)}개의 항목이 데이터베이스에 저장되었습니다.")
except sqlite3.Error as e:
logger.error(f"DB 삽입 오류: {e}")
logger.error(f"데이터베이스 저장 중 오류 발생: {e}")
def fetch_all_items(self):
def fetch_all(self):
try:
self.cur.execute("SELECT * FROM items")
return self.cur.fetchall()
with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
df = pd.read_sql_query("SELECT * FROM items", conn)
logger.info("데이터베이스에서 데이터 로드 완료")
return df
except sqlite3.Error as e:
logger.error(f"DB 조회 오류: {e}")
return []
logger.error(f"데이터베이스 읽기 중 오류 발생: {e}")
return pd.DataFrame()
def close(self):
self.conn.close()
# 별도의 연결을 사용하는 구조에서는 close 메서드가 필요하지 않을 수 있습니다.
logger.info("DBManager 종료")

View File

@ -1,19 +1,66 @@
import logging
import os
import shutil
import sqlite3
import pandas as pd
import xlwings as xw
import logging
from PySide6.QtWidgets import QMessageBox
logger = logging.getLogger(__name__)
class ExcelExporter:
def __init__(self, db_manager):
self.db_manager = db_manager
def __init__(self, db_manager, base_excel_path="src\\baseXLS_Percenty.xlsx"):
self.db_manager = db_manager # DBManager 인스턴스
self.base_excel_path = base_excel_path
self.saved_files = []
def export_to_excel(self, file_name="taobao_items.xlsx"):
def fetch_data_from_db(self):
try:
data = self.db_manager.fetch_all_items()
df = pd.DataFrame(data, columns=["item_id", "pc_url", "name", "price", "image_url", "sales"])
df.to_excel(file_name, index=False)
logger.info("엑셀 파일로 저장 완료")
return True
df = self.db_manager.fetch_all() # DBManager 인스턴스를 사용하여 데이터 가져오기
logger.debug("DB에서 데이터 로드 완료")
return df
except Exception as e:
logger.error(f"엑셀 저장 오류: {e}")
return False
logger.error(f"DB에서 데이터 로드 중 오류 발생: {e}")
return pd.DataFrame()
def save_to_excel(self, output_path="output.xlsx"):
df = self.fetch_data_from_db()
if df.empty:
logger.warning("DB에서 불러온 데이터가 없습니다.")
return False # 성공 여부 반환
app = xw.App(visible=False)
logger.debug("xlwings 시작")
try:
for i in range(0, len(df), 50):
df_subset = df.iloc[i:i+50]
logger.debug(f"{i}번째 출력할 데이터:\n{df_subset}") # 데이터 검증 로그 추가
part_file_name = output_path.replace('.xlsx', f'_part{i//50 + 1}.xlsx')
shutil.copy(self.base_excel_path, part_file_name)
logger.debug(f"기본 엑셀 파일 '{self.base_excel_path}' 복사 완료")
wb = xw.Book(part_file_name)
ws = wb.sheets['multi_ss']
for index, row in df_subset.iterrows():
row_num = 4 + (index % 50)
logger.debug(f"{index + 1}번째 행 기록 시작: B{row_num}, C{row_num}, D{row_num}") # 셀 위치 로그 추가
ws.range(f'B{row_num}').value = row['pc_url']
ws.range(f'C{row_num}').value = row['name']
ws.range(f'D{row_num}').value = row['price']
logger.debug(f"{index + 1}번째 행 기록 완료")
wb.save(part_file_name) # SaveCopyAs 대신 save 사용
wb.close()
self.saved_files.append(part_file_name)
logger.info(f"파일 '{part_file_name}'에 데이터가 저장되었습니다.")
return True # 성공 여부 반환
except Exception as e:
logger.error(f"엑셀 저장 중 예외 발생: {e}", exc_info=True)
return False # 실패 시 False 반환
finally:
app.quit()
logger.debug("xlwings 종료")

View File

@ -18,7 +18,7 @@ class TaobaoScraperApp(QWidget):
self.collect_button = QPushButton("수집")
self.collect_button.clicked.connect(self.collect_data)
self.excel_button = QPushButton("엑셀출력")
self.excel_button.clicked.connect(self.export_to_excel)
self.excel_button.clicked.connect(self.save_to_excel)
self.close_button = QPushButton("닫기")
self.close_button.clicked.connect(self.close)
@ -45,8 +45,8 @@ class TaobaoScraperApp(QWidget):
self.playwright_thread.run()
@Slot()
def export_to_excel(self):
success = self.excel_exporter.export_to_excel()
def save_to_excel(self):
success = self.excel_exporter.save_to_excel()
if success:
QMessageBox.information(self, "엑셀 출력", "엑셀 파일로 저장 완료")
else:

View File

@ -3,6 +3,7 @@ import sys
import random
import asyncio
import logging
import re
from PySide6.QtCore import QThread, Signal
from playwright.async_api import async_playwright, Page
@ -18,19 +19,14 @@ class PlaywrightThread(QThread):
async def collect_data(self):
try:
async with async_playwright() as p:
# 경로 설정
# 브라우저 경로 설정
browser_path = None
if getattr(sys, 'frozen', False):
browser_path = os.path.join(os.path.dirname(sys.executable), 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe')
extension_path = os.path.join(os.path.dirname(sys.executable), 'browsers', 'extensions', '1.1.100_0')
user_data_dir = os.path.join(os.path.dirname(sys.executable), 'browsers', 'user_data')
else:
browser_path = os.path.join(os.path.dirname(__file__), 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe')
extension_path = os.path.join(os.path.dirname(__file__), 'browsers', 'extensions', '1.1.100_0')
user_data_dir = os.path.join(os.path.dirname(__file__), 'browsers', 'user_data')
logger.debug(f"브라우저 경로: {browser_path}")
logger.debug(f"확장 프로그램 경로: {extension_path}")
logger.debug(f"사용자 폴더 경로: {user_data_dir}")
# 사용자 에이전트 설정
user_agent = random.choice([
@ -42,71 +38,112 @@ class PlaywrightThread(QThread):
])
logger.debug(f"user_agent: {user_agent}")
# 브라우저 시작 및 설정
context = await p.chromium.launch_persistent_context(
user_data_dir=user_data_dir,
headless=False,
# 브라우저 시작 (headless 모드)
browser = await p.chromium.launch(
headless=True, # headless 모드로 설정
executable_path=browser_path,
locale="ko-KR",
permissions=["geolocation", "notifications"],
geolocation={"latitude": 37.5665, "longitude": 126.9780},
user_agent=user_agent,
args=[
'--disable-popup-blocking',
f'--disable-extensions-except={extension_path}',
f'--load-extension={extension_path}',
'--start-maximized',
'--window-size=1920,1080'
]
)
# 현재 활성 탭 가져오기
if context.pages:
page = context.pages[0] # 첫 번째 탭을 가져옴
if page.url == "about:blank":
logger.debug("현재 탭의 URL이 about:blank입니다. 새 페이지로 이동합니다.")
await page.goto("https://world.taobao.com/")
else:
page = await context.new_page()
await page.goto("https://world.taobao.com/")
logger.debug("새로운 페이지가 생성되었습니다.")
# 시크릿 브라우저 컨텍스트 생성
context = await browser.new_context(
user_agent=user_agent,
geolocation={"latitude": 37.5665, "longitude": 126.9780},
locale="ko-KR",
permissions=["geolocation", "notifications"]
)
# 페이지 열기
page = await context.new_page()
await page.goto("https://world.taobao.com")
logger.info("타오바오 사이트에 접속했습니다.")
await self.wait_for_user()
# 페이지 로딩 확인 및 pagedown
await page.wait_for_selector(".tb-pick-content-item") # 상품 카드 로딩 대기
logger.info("페이지 로딩 완료 - Pagedown을 두 번 누릅니다.")
await page.keyboard.press("PageDown")
await page.wait_for_timeout(1000) # 1초 대기
await page.keyboard.press("PageDown")
await page.wait_for_timeout(2000) # 추가 2초 대기 (상품 로딩)
# 상품 수집
items_data = await self.scrape_items(page)
if items_data:
self.db_manager.insert_items(items_data)
self.data_collected.emit(True, "데이터 수집 완료")
else:
self.data_collected.emit(False, "데이터 수집 실패")
await context.close()
await browser.close()
except Exception as e:
logger.error(f"브라우저 작업 중 오류 발생: {e}")
self.data_collected.emit(False, f"오류 발생: {e}")
logger.error(f"브라우저 작업 중 오류 발생: {e}", exec=True)
self.data_collected.emit(False, f"오류 발생: {e}", exec=True)
async def scrape_items(self, page: Page):
try:
items = await page.query_selector_all(".tb-pick-content-item")
items = await page.query_selector_all("div#ice-container div.tb-pick-feeds-container > div")
logger.debug(f"{len(items)}개의 상품 카드가 발견되었습니다.")
items_data = []
for item in items:
item_id = await item.get_attribute("href").split("itemIds=")[-1]
pc_url = f"https://item.taobao.com/item.htm?id={item_id}"
name = await item.query_selector(".info-wrapper-title-text").inner_text()
price = await item.query_selector(".price-value").inner_text()
image_url = await item.query_selector(".img-wrapper").get_attribute("style").split("url(")[-1].strip('")')
sales = await item.query_selector(".month-sale").inner_text()
for idx, item in enumerate(items):
logger.debug(f"{idx + 1}번째 상품 카드 처리 중 - XPath 확인")
# a 태그를 명시적으로 선택하여 href 가져오기
link_element = await item.query_selector("a.item-link")
if link_element:
item_href = await link_element.get_attribute("href")
logger.debug(f"{idx + 1}번째 상품 href: {item_href}")
if item_href:
# 숫자만 추출하고 9~12자리 필터링
item_id_match = re.search(r'(\d{9,12})', item_href)
if item_id_match:
item_id = item_id_match.group(1)
pc_url = f"https://item.taobao.com/item.htm?id={item_id}"
logger.debug(f"{idx + 1}번째 상품 ID: {item_id}, 상품 URL: {pc_url}")
else:
logger.warning(f"{idx + 1}번째 상품 ID를 올바르게 추출할 수 없습니다.")
continue
else:
logger.warning(f"{idx + 1}번째 상품 ID를 가져올 수 없습니다.")
continue
else:
logger.warning(f"{idx + 1}번째 상품의 a 태그를 찾을 수 없습니다.")
continue
name_element = await item.query_selector(".info-wrapper-title-text")
name = await name_element.inner_text() if name_element else "N/A"
logger.debug(f"{idx + 1}번째 상품명: {name}")
price_element = await item.query_selector(".price-value")
price = await price_element.inner_text() if price_element else "0"
logger.debug(f"{idx + 1}번째 상품 가격: {price}")
image_element = await item.query_selector(".img-wrapper")
image_style = await image_element.get_attribute("style") if image_element else ""
image_url = image_style.split("url(")[-1].strip('")') if image_style else "N/A"
logger.debug(f"{idx + 1}번째 상품 이미지 URL: {image_url}")
sales_element = await item.query_selector(".month-sale")
sales = await sales_element.inner_text() if sales_element else "0"
logger.debug(f"{idx + 1}번째 상품 판매량: {sales}")
items_data.append((item_id, pc_url, name, float(price), image_url, sales))
logger.debug(f"수집된 상품 수 : {len(items_data)}")
return items_data
except Exception as e:
logger.error(f"데이터 수집 중 오류 발생: {e}")
return None
async def wait_for_user(self):
await asyncio.sleep(2000)
await asyncio.sleep(2)
def run(self):
asyncio.run(self.collect_data())

74
src/post_processor.py Normal file
View File

@ -0,0 +1,74 @@
import sqlite3
import logging
import requests
from googletrans import Translator # 구글 번역 API 라이브러리
logger = logging.getLogger(__name__)
class PostProcessor:
def __init__(self, db_manager):
self.db_manager = db_manager
self.translator = Translator() # 구글 번역 초기화
def translate_name(self, translation_method='google'):
try:
# 데이터 가져오기
items = self.db_manager.fetch_all() # 데이터프레임으로 가져오기
if items.empty:
logger.warning("DB에 데이터가 없습니다.")
return
for index, row in items.iterrows():
if pd.isna(row['translated_name']): # 번역된 상품명이 비어 있는 경우
item_id = row['id']
original_name = row['name']
translated_name = self._translate_text(original_name, method=translation_method)
# 데이터베이스에 번역된 상품명 업데이트
self.db_manager.update_translated_name(item_id, translated_name)
logger.info(f"상품 ID {item_id}의 상품명이 번역되었습니다: {translated_name}")
logger.info("모든 번역 작업 완료")
except Exception as e:
logger.error(f"번역 처리 중 예외 발생: {e}")
def _translate_text(self, text, method='google'):
if method == 'deepl':
return self._translate_deepl(text)
elif method == 'gpt-4o-mini':
return self._translate_gpt(text)
else:
return self._translate_google(text)
def _translate_google(self, text):
try:
translated = self.translator.translate(text, src='zh-cn', dest='ko')
return translated.text
except Exception as e:
logger.error(f"구글 번역 중 오류 발생: {e}")
return text
def _translate_deepl(self, text):
try:
# DeepL API 키 설정
api_key = 'YOUR_DEEPL_API_KEY'
url = f"https://api-free.deepl.com/v2/translate?auth_key={api_key}&text={text}&source_lang=ZH&target_lang=KO"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
return data['translations'][0]['text']
else:
logger.error(f"DeepL 번역 중 오류 발생: {response.text}")
return text
except Exception as e:
logger.error(f"DeepL 번역 요청 중 오류 발생: {e}")
return text
def _translate_gpt(self, text):
try:
# GPT-4o-mini API 호출 예시 (구현 필요)
logger.info("GPT-4o-mini 번역 기능은 구현되지 않았습니다.")
return text # 현재는 원문 반환
except Exception as e:
logger.error(f"GPT-4o-mini 번역 중 오류 발생: {e}")
return text

Binary file not shown.

BIN
xlwings32-0.33.3.dll Normal file

Binary file not shown.

BIN
xlwings64-0.33.3.dll Normal file

Binary file not shown.