first commit

This commit is contained in:
Envy_PC 2024-04-30 09:01:24 +09:00
commit 9ab761eff7
19 changed files with 1575 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
__pycache__/
dist/
build/
Lib/
.vscode/
Include/
Scripts/
pyvenv.cfg

1
README.md Normal file
View File

@ -0,0 +1 @@
##키프리스에서 API 또는 웹방식으로 상표권을 검색하는 프로그램

24
api.py Normal file
View File

@ -0,0 +1,24 @@
# Python3 샘플 코드 #
import requests
apikey ='X9Tz3JqC%2FJcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6%2Bt3dyLZV3gh2TPXdNhxsRQwaKP673Q%3D%3D'
apikey2 = 'X9Tz3JqC/JcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6+t3dyLZV3gh2TPXdNhxsRQwaKP673Q=='
apikey3 = "X9Tz3JqC/JcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6+t3dyLZV3gh2TPXdNhxsRQwaKP673Q=="
# 디코딩된 API 키 사용
# apikey2 ='X9Tz3JqC/JcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6+t3dyLZV3gh2TPXdNhxsRQwaKP673Q=='
# url = 'http://kipo-api.kipi.or.kr/openapi/service/trademarkInfoSearchService'
url = 'http://kipo-api.kipi.or.kr/openapi/service/trademarkInfoSearchService/getWordSearch'
params = {
'serviceKey' : apikey3, # 디코딩된 키 사용
'searchString' : 'love*롯데',
'searchRecentYear' : '0',
'title' : '사랑 : LOVE',
'fullText' : '',
'drawing' : '',
'bigDrawing' : ''
}
response = requests.get(url, params=params)
print(response.content)

47
categories.json Normal file
View File

@ -0,0 +1,47 @@
{
"01": "공업/과학 및 사진용 및 농업/원예 및 임업용 화학제; 미가공 인조수지, 미가공 플라스틱; 소화 및 화재예방용 조성물; 조질제 및 땜납용 조제; 수피용 무두질제; 공업용 접착제; 퍼티 및 기타 페이스트 충전제; 퇴비, 거름, 비료; 산업용 및 과학용 생물학적 제제",
"02": "페인트, 니스, 래커; 방청제 및 목재 보존제; 착색제, 염료; 인쇄, 표시 및 판화용 잉크; 미가공 천연수지; 도장용/장식용/인쇄용/미술용 금속박(箔) 및 금속분(紛)",
"03": "비의료용 화장품 및 세면용품; 비의료용 치약; 향료, 에센셜 오일; 표백제 및 기타 세탁용 제제; 세정/광택 및 연마재",
"04": "공업용 오일 및 그리스, 왁스; 윤활제; 먼지흡수제, 먼지습윤제 및 먼지흡착제; 연료 및 발광체; 조명용 양초 및 심지",
"05": "약제, 의료용 및 수의과용 제제; 의료용 위생제; 의료용 또는 수의과용 식이요법 식품 및 제제, 유아용 식품; 인체용 및 동물용 식이보충제; 플라스터, 외상치료용 재료; 치과용 충전재료, 치과용 왁스; 소독제; 해충구제제; 살균제, 제초제",
"06": "일반금속 및 그 합금, 광석; 금속제 건축 및 구축용 재료; 금속제 이동식 건축물; 비전기용 일반금속제 케이블 및 와이어; 소형금속제품; 저장 또는 운반용 금속제 용기; 금고",
"07": "기계, 공작기계, 전동공구; 모터 및 엔진(육상차량용은 제외); 기계 커플링 및 전동장치 부품(육상차량용은 제외); 농기구(수동식 수공구는 제외); 부란기(孵卵器); 자동판매기",
"08": "수동식 수공구 및 수동기구; 커틀러리; 휴대 무기(화기는 제외); 면도기",
"09": "과학, 연구, 항법, 측량, 사진, 영화, 시청각, 광학, 계량, 측정, 신호, 탐지, 시험, 검사, 구명 및 교육용 기기; 전기 분배 또는 전기 사용의 전도, 전환, 변형, 축적, 조절 또는 통제를 위한 기기; 음향/영상 또는 데이터의 기록/전송/재생 또는 처리용 장치 및 기구; 기록 및 내려받기 가능한 미디어, 컴퓨터 소프트웨어, 빈 디지털 또는 아날로그 기록 및 저장매체; 동전작동식 기계장치; 금전등록기, 계산기; 컴퓨터 및 컴퓨터주변기기; 잠수복, 잠수마스크, 잠수용 귀마개, 다이버 및 수영용 노즈클립, 잠수용 장갑, 잠수용 호흡장치; 소화기기",
"10": "외과용, 내과용, 치과용 및 수의과용 기계기구; 의지(義肢), 의안(義眼) 및 의치(義齒); 정형외과용품; 봉합용 재료; 장애인용 치료 및 재활보조장치; 안마기; 유아수유용 기기 및 용품; 성활동용 기기 및 용품",
"11": "조명용, 가열용, 냉각용, 증기발생용, 조리용, 건조용, 환기용, 급수용, 위생용 장치 및 설비",
"12": "수송기계기구; 육상, 항공 또는 해상을 통해 이동하는 수송수단",
"13": "화기(火器); 탄약 및 발사체; 폭약; 폭죽",
"14": "귀금속 및 그 합금; 보석, 귀석 및 반귀석; 시계용구",
"15": "악기; 악보대 및 악기용 받침대; 지휘봉",
"16": "종이 및 판지; 인쇄물; 제본재료; 사진; 문방구 및 사무용품(가구는 제외); 문방구용 또는 가정용 접착제; 제도용구 및 미술용 재료; 회화용 솔; 교재; 포장용 플라스틱제 시트, 필름 및 가방; 인쇄활자, 프린팅블록",
"17": "미가공 및 반가공 고무, 구타페르카, 고무액(gum), 석면, 운모(雲母) 및 이들의 제품; 제조용 압출성형형태의 플라스틱 및 수지; 충전용, 마개용 및 절연용 재료; 비금속제 신축관, 튜브 및 호스",
"18": "가죽 및 모조가죽; 수피; 수하물가방 및 운반용 가방; 우산 및 파라솔; 걷기용 지팡이; 채찍 및 마구(馬具); 동물용 목걸이, 가죽끈 및 의류",
"19": "건축용 및 구축용 비금속제 건축재료; 건축용 비금속제 경질관(硬質管); 아스팔트, 피치, 타르 및 역청; 비금속제 이동식 건축물; 비금속제 기념물",
"20": "가구, 거울, 액자; 보관 또는 운송용 비금속제 컨테이너; 미가공 또는 반가공 뼈, 뿔, 고래수염 또는 나전(螺鈿); 패각; 해포석(海泡石); 호박(琥珀)(원석)",
"21": "가정용 또는 주방용 기구 및 용기; 조리기구 및 식기(포크, 나이프 및 스푼은 제외); 빗 및 스펀지; 솔(페인트 솔은 제외); 솔 제조용 재료; 청소용구; 비건축용 미가공 또는 반가공 유리; 유리제품, 도자기제품 및 토기제품",
"22": "로프 및 노끈; 망(網); 텐트 및 타폴린; 직물제 또는 합성재료제 차양; 돛; 하역물운반용 및 보관용 포대; 충전재료(고무/플라스틱/종이 및 판지제는 제외); 직물용 미가공 섬유 및 그 대용품",
"23": "직물용 실(絲)",
"24": "직물 및 직물대용품; 가정용 린넨; 직물 또는 플라스틱제 커튼",
"25": "의류, 신발, 모자",
"26": "레이스, 장식용 끈 및 자수포, 의류장식용 리본 및 나비매듭리본; 단추, 훅 및 아이(hooks and eyes), 핀 및 바늘; 조화(造花); 머리장식품; 가발",
"27": "카펫, 융단, 매트, 리놀륨 및 기타 바닥깔개용 재료; 비직물제 벽걸이",
"28": "오락용구, 장난감; 비디오게임장치; 체조 및 스포츠용품; 크리스마스트리용 장식품",
"29": "식육, 생선, 가금 및 엽조수; 고기진액; 보존처리/냉동/건조 및 조리된 과일 및 채소; 젤리, 잼, 콤폿; 달걀; 우유, 치즈, 버터, 요구르트 및 기타 유제품; 식용 유지(油脂)",
"30": "커피, 차(茶), 코코아 및 그 대용물; 쌀, 파스타 및 국수; 타피오카 및 사고(sago); 곡분 및 곡물 조제품; 빵, 페이스트리 및 과자; 초콜릿; 아이스크림, 셔벗 및 기타 식용 얼음; 설탕, 꿀, 당밀(糖蜜); 식품용 이스트, 베이킹 파우더; 소금, 조미료, 향신료, 보존처리된 허브; 식초, 소스 및 기타 조미료; 얼음",
"31": "미가공 농업, 수산양식, 원예 및 임업 생산물; 미가공 곡물 및 종자; 신선한 과실 및 채소, 신선한 허브; 살아 있는 식물 및 꽃; 구근(球根), 모종 및 재배용 곡물종자; 살아있는 동물; 동물용 사료 및 음료; 맥아",
"32": "맥주; 비알코올성 음료; 광천수 및 탄산수; 과실음료 및 과실주스; 시럽 및 비알코올성 음료용 제제",
"33": "알코올성 음료(맥주는 제외); 음료제조용 알코올성 제제",
"34": "담배 및 대용담배; 권연 및 여송연; 흡연자용 전자담배 및 기화기; 흡연용구; 성냥",
"35": "광고업; 사업관리/조직 및 경영업; 사무처리업",
"36": "금융, 통화 및 은행업; 보험서비스업; 부동산업",
"37": "건축서비스업; 설치 및 수리서비스업; 채광업/석유 및 가스 시추업",
"38": "통신서비스업",
"39": "운송업; 상품의 포장 및 보관업; 여행알선업",
"40": "재료처리업; 폐기물 재생업; 공기 정화 및 물 처리업; 인쇄 서비스업; 음식 및 음료수 보존업",
"41": "교육업; 훈련제공업; 연예오락업; 스포츠 및 문화활동업",
"42": "과학적, 기술적 서비스업 및 관련 연구, 디자인업; 산업분석, 산업연구 및 산업디자인 서비스업; 품질 관리 및 인증 서비스업; 컴퓨터 하드웨어 및 소프트웨어의 디자인 및 개발업",
"43": "식음료제공서비스업; 임시숙박시설업",
"44": "의료업; 수의업; 인간 또는 동물을 위한 위생 및 미용업; 농업, 수산양식, 원예 및 임업 서비스업",
"45": "법무서비스업; 유형의 재산 및 개인을 물리적으로 보호하기 위한 보안서비스업; 이성(異性) 소개업, 온라인 소셜 네트워킹 서비스업; 장례업; 베이비시팅업"
}

40
kipris.py Normal file
View File

@ -0,0 +1,40 @@
import requests
class KiprisApiClient:
def __init__(self, service_key):
self.service_key = service_key
self.base_url = "http://plus.kipris.or.kr/"
def search_patents(self, word, year, patent, utility, numOfRows=30, pageNo=1):
params = {
'accessKey': self.service_key,
'word': word,
'year': year,
'patent': str(patent).lower(),
'utility': str(utility).lower(),
'numOfRows': numOfRows,
'pageNo': pageNo
}
response = requests.get(self.base_url, params=params)
return response.json()
if __name__ == "__main__":
# API 키와 검색하고자 하는 단어 설정
service_key = '/loM8C4yXZsTLAmp7PLoq2UCl5zg/OFhZCibzO/D968='
word = '스키에이트'
client = KiprisApiClient(service_key)
# API 호출 예제
# word = '전자회로'
year = '0' # 검색 년도 범위
patent = True
utility = False
numOfRows = 30
pageNo = 1
result = client.search_patents(word, year, patent, utility, numOfRows, pageNo)
if 'items' in result:
for item in result['items']:
print(f"발명의 명칭: {item['inventionTitle']}, 등록번호: {item['registerNumber']}")
else:
print("검색 결과가 없습니다.")

View File

@ -0,0 +1,65 @@
import xml.etree.ElementTree as ET
import requests
class Kipris:
def __init__(self, url, params=None):
self.url = url
self.params = params
self.results = []
def fetch_and_decode(self):
# API 요청 및 응답 받기
response = requests.get(self.url, params=self.params)
decoded_data = response.content.decode('utf-8')
return decoded_data
def parse_xml(self, xml_data):
# XML 데이터 파싱
root = ET.fromstring(xml_data)
total_items = 0
status_registered = 0
status_published = 0
# 'body/items/item' 경로에 맞춰 'item' 태그를 순회하면서 필요한 데이터 추출
for item in root.findall('.//body/items/item'):
total_items += 1
application_status = item.find('applicationStatus').text if item.find('applicationStatus') is not None else None
# 각 상태의 개수를 카운트
if application_status == "등록":
status_registered += 1
if application_status == "공개":
status_published += 1
if application_status in ["등록", "공개"]:
result = {
"index_no": item.find('indexNo').text if item.find('indexNo') is not None else None,
"application_number": item.find('applicationNumber').text if item.find('applicationNumber') is not None else None,
"application_date": item.find('applicationDate').text if item.find('applicationDate') is not None else None,
"publication_number": item.find('publicationNumber').text if item.find('publicationNumber') is not None else None,
"publication_date": item.find('publicationDate').text if item.find('publicationDate') is not None else None,
"registration_date": item.find('registrationDate').text if item.find('registrationDate') is not None else None,
"registration_number": item.find('registrationNumber').text if item.find('registrationNumber') is not None else None,
"applicant_name": item.find('applicantName').text if item.find('applicantName') is not None else None,
"agent_name": item.find('agentName').text if item.find('agentName') is not None else None,
"title": item.find('title').text if item.find('title') is not None else None,
"drawing_url": item.find('drawing').text if item.find('drawing') is not None else None,
"big_drawing_url": item.find('bigDrawing').text if item.find('bigDrawing') is not None else None,
"full_text": item.find('fullText').text if item.find('fullText') is not None else None,
"application_status": application_status,
"classification_code": item.find('classificationCode').text if item.find('classificationCode') is not None else None
}
self.results.append(result)
# 상태 개수와 총 아이템 개수 출력
print(f"검색된 item 총 개수: {total_items}")
print(f"등록 상태인 item 개수: {status_registered}")
print(f"공개 상태인 item 개수: {status_published}")
def get_results(self):
return self.results
def run(self):
xml_data = self.fetch_and_decode()
self.parse_xml(xml_data)
return self.get_results()

47
kkapi.py Normal file
View File

@ -0,0 +1,47 @@
import requests
import xml.etree.ElementTree as ET
# 디코딩된 API '
apikey2 = 'X9Tz3JqC/JcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6+t3dyLZV3gh2TPXdNhxsRQwaKP673Q=='
# 인코딩 api
# apikey1 = 'X9Tz3JqC%2FJcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6%2Bt3dyLZV3gh2TPXdNhxsRQwaKP673Q%3D%3D'
url = 'http://kipo-api.kipi.or.kr/openapi/service/trademarkInfoSearchService/getWordSearch'
params = {
'serviceKey': apikey2, # 디코딩된 키 사용
'searchString': '스키에이트',
'searchRecentYear': '0',
'title': '',
'fullText': '',
'drawing': '',
'bigDrawing': ''
}
response1 = requests.get(url, params=params)
print(f"response1 : {response1.content}")
root = ET.fromstring(response1.content)
# response2 = requests.get(
# 'http://kipo-api.kipi.or.kr/openapi/service/patUtiModInfoSearchSevice/getWordSearch?serviceKey=X9Tz3JqC%2FJcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6%2Bt3dyLZV3gh2TPXdNhxsRQwaKP673Q%3D%3D&word=%EC%8A%A4%ED%82%A4%EC%97%90%EC%9D%B4%ED%8A%B8&year=0&patent=true&utility=true'
# )
# print(f"response2 : {response2.content}")
# Extracting items
items = []
for item in root.findall(".//item"):
data = {
"applicantName": item.find("applicantName").text,
"applicationNumber": item.find("applicationNumber").text,
"applicationDate": item.find("applicationDate").text,
"applicationStatus": item.find("applicationStatus").text,
"publicationNumber": item.find("publicationNumber").text,
"publicationDate": item.find("publicationDate").text,
"registrationNumber": item.find("registrationNumber").text,
"registrationDate": item.find("registrationDate").text,
"agentName": item.find("agentName").text if item.find("agentName") is not None else "",
"drawing": item.find("drawing").text,
"bigDrawing": item.find("bigDrawing").text
}

182
main.py Normal file
View File

@ -0,0 +1,182 @@
import sys
from PyQt5.QtWidgets import QApplication, QMessageBox, QSizePolicy, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit, QGridLayout
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt, QByteArray, QBuffer, QIODevice
from web_scraper import WebScraper
class MainApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.scraper = WebScraper()
self.scraper.setup_browser()
def initUI(self):
# 검색 레이아웃
searchLayout = QHBoxLayout()
self.searchLabel = QLabel('검색어 입력:', self)
self.searchLineEdit = QLineEdit(self)
self.searchLineEdit.returnPressed.connect(self.start_search)
self.searchButton = QPushButton('검색 실행', self)
self.searchButton.clicked.connect(self.start_search)
searchLayout.addWidget(self.searchLabel)
searchLayout.addWidget(self.searchLineEdit)
searchLayout.addWidget(self.searchButton)
# 로그 레이아웃
logLayout = QVBoxLayout()
self.logTextEdit = QTextEdit(self)
self.logTextEdit.setReadOnly(True)
logLayout.addWidget(self.logTextEdit)
# 메인 레이아웃
mainLayout = QVBoxLayout()
mainLayout.addLayout(searchLayout)
mainLayout.addLayout(logLayout)
self.setLayout(mainLayout)
self.setWindowTitle('Web Scraper')
self.setGeometry(300, 300, 300, 400)
def wrap_text(self, text, width=40):
"""주어진 너비에 맞게 텍스트를 줄바꿈합니다."""
words = text.split()
wrapped_text = ''
line_length = 0
for word in words:
if line_length + len(word) + 1 > width:
wrapped_text += '\n'
line_length = 0
wrapped_text += word + ' '
line_length += len(word) + 1
return wrapped_text.strip()
def start_search(self):
term = self.searchLineEdit.text()
if not term:
self.logTextEdit.append("검색어를 입력해주세요.")
return
self.logTextEdit.append("검색을 시작합니다...")
results = self.scraper.search_for_term(term)
if results:
self.logTextEdit.append("검색 완료. 결과를 처리합니다...")
self.show_results(results)
else:
self.logTextEdit.append("검색 결과가 없거나 오류가 발생했습니다.")
QMessageBox.information(self, "검색 결과 없음", "검색 결과가 없으므로 지재권에 안심하시면 됩니다.", QMessageBox.Ok)
def show_results(self, results):
try:
# 결과 위젯 생성
self.results_widget = QWidget()
layout = QVBoxLayout()
self.results_widget.setLayout(layout)
# 결과 갯수 확인 및 레이아웃 동적 생성
total_count = int(results['total_count'])
set_count = min(total_count, 10)
grid_layout = QGridLayout()
layout.addLayout(grid_layout)
grid_index = 0
grid_columns = 5
for i in range(1, set_count + 1):
result_key = f"result_{i}"
if result_key in results:
result = results[result_key]
# 테두리 설정
border_style = ''
if result['admin_status'] == '등록':
border_style = 'border: 4px solid red;'
elif result['admin_status'] == '공고':
border_style = 'border: 3px solid black;'
# 각 결과에 대한 레이아웃 생성
item_layout = QVBoxLayout()
item_widget = QWidget() # 위젯 생성
# item_layout의 크기 정책 설정
item_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)
# 이미지 처리
image_label = QLabel()
image_label.setFixedSize(150, 150)
image_data = self.scraper.fetch_image_data(result['IDimageURL'])
pixmap = QPixmap()
pixmap.loadFromData(image_data)
# QLabel의 크기에 맞게 이미지 크기 조정
scaled_pixmap = pixmap.scaled(image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
image_label.setPixmap(scaled_pixmap)
# QLabel의 가로 세로 중앙에 이미지 표시
image_label.setAlignment(Qt.AlignCenter)
# 이미지 표시 위젯의 크기 조정 정책 설정
image_label.setScaledContents(True)
#이미지 중앙배치를 위해
horizontal_layout = QHBoxLayout()
horizontal_layout.addWidget(image_label)
horizontal_layout.setAlignment(Qt.AlignCenter)
item_layout.addLayout(horizontal_layout)
# item_layout.addWidget(image_label)
# 정보 텍스트
# info_text = f"상표권명: {result['title']}\n등록상태: {result['admin_status']}\nCategory: {result['product_category']}\nApplicant: {result['applicant']}\nPublication Date: {result['publication_date']}\nRegistration Date: {result['registration_date']}"
info_text = f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>상표권명: {result['title']}</span><br>\n" \
f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>등록상태: {result['admin_status']}</span><br>\n" \
f"<span style='font-size: 9pt;'>카테고리: {result['product_category']}</span><br>\n" \
f"<span style='font-size: 9pt;'>권리자: {result['applicant']}</span><br>\n" \
f"<span style='font-size: 9pt;'> {result['publication_date']}</span><br>\n" \
f"<span style='font-size: 9pt;'> {result['registration_date']}</span>"
info_label = QLabel(info_text)
info_label.setToolTip(self.wrap_text(result['category_description'], 50))
image_label.setToolTip(self.wrap_text(result['category_description'], 50))
item_layout.addWidget(info_label)
image_label.setStyleSheet(border_style)
info_label.setStyleSheet(border_style)
# 레이아웃에 위젯 추가
row = grid_index // grid_columns
col = grid_index % grid_columns
grid_layout.addLayout(item_layout, row, col)
grid_index += 1
# 결과 위젯에 닫기 버튼 추가
close_button = QPushButton("Close")
close_button.clicked.connect(self.close_results_widget)
layout.addWidget(close_button)
# 결과 위젯을 메인 윈도우에 추가
self.results_widget.setGeometry(300, 300, 600, 300) # 위치와 크기 설정
self.results_widget.setWindowTitle('Search Results') # 타이틀 설정
self.results_widget.show()
except Exception as e:
print(f"Error displaying results: {e}")
def close_results_widget(self):
# 결과 위젯닫기 함수를 호출할 때 사용하는 메서드
self.results_widget.close()
def closeEvent(self, event):
self.scraper.close_browser()
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainApp()
ex.show()
sys.exit(app.exec_())

248
main_async.py Normal file
View File

@ -0,0 +1,248 @@
import sys
import asyncio
from tkinter import EXCEPTION
import qasync
from PyQt5.QtWidgets import QApplication, QGridLayout, QSizePolicy, QProgressDialog, QMessageBox, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from web_scraper_async import WebScraper
class MainApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.scraper = WebScraper()
self.results_widget = None # 초기에는 결과 위젯이 없음
def initUI(self):
searchLayout = QHBoxLayout()
self.searchLabel = QLabel('검색어 입력:', self)
self.searchButton = QPushButton('검색 실행', self)
# 검색 버튼 클릭 시 start_search를 비동기로 실행
self.searchButton.clicked.connect(self.on_search_button_clicked)
self.searchButton.setEnabled(False) # 초기에 버튼을 비활성화합니다.
self.searchLineEdit = QLineEdit(self)
# 엔터 키를 누를 때 start_search를 비동기로 실행
self.searchLineEdit.returnPressed.connect(self.on_search_button_clicked)
# self.searchLineEdit.setEnabled(False) # 초기에 입력 필드도 비활성화
searchLayout.addWidget(self.searchLabel)
searchLayout.addWidget(self.searchLineEdit)
searchLayout.addWidget(self.searchButton)
logLayout = QVBoxLayout()
self.logTextEdit = QTextEdit(self)
self.logTextEdit.setReadOnly(True)
logLayout.addWidget(self.logTextEdit)
mainLayout = QVBoxLayout()
mainLayout.addLayout(searchLayout)
mainLayout.addLayout(logLayout)
self.setLayout(mainLayout)
self.setWindowTitle('Web Scraper')
self.setGeometry(300, 300, 300, 400)
def start_search_process(self):
# 검색 프로세스 시작 시 QProgressDialog 설정
self.progress_dialog = QProgressDialog("검색 진행 중... 잠시만 기다려 주세요.", "취소", 0, 100, self)
self.progress_dialog.setWindowTitle("검색 진행 상태")
self.progress_dialog.setWindowModality(Qt.WindowModal)
self.progress_dialog.setAutoClose(True)
self.progress_dialog.setAutoReset(True)
self.progress_dialog.canceled.connect(self.cancel_search)
self.progress_dialog.show()
# 검색 시작
asyncio.ensure_future(self.perform_search(self.searchLineEdit.text()))
async def perform_search(self, term):
# 검색어를 사용하여 실제 검색 수행을 시뮬레이션합니다.
for i in range(101):
if self.progress_dialog.wasCanceled():
break
self.progress_dialog.setValue(i) # 진행 상황 업데이트
await asyncio.sleep(0.05) # 검색 시간을 시뮬레이션
if not self.progress_dialog.wasCanceled():
self.progress_dialog.setValue(100) # 작업 완료
def cancel_search(self):
# 검색 취소 로직
print("검색이 사용자에 의해 취소되었습니다.")
def on_search_button_clicked(self):
asyncio.ensure_future(self.start_search())
async def prepare_search(self):
# setup_browser에서 페이지 로딩을 관리하므로 여기서는 로딩 완료를 기다립니다.
print(f"prepare_search Start : is_page_loaded : {self.scraper.is_page_loaded}")
while not self.scraper.is_page_loaded:
print(f"is_page_loaded : {self.scraper.is_page_loaded}")
await asyncio.sleep(1) # 로딩 상태를 주기적으로 체크합니다.
self.searchButton.setEnabled(True)
# self.searchLineEdit.setEnabled(True)
async def start_search(self):
term = self.searchLineEdit.text()
if not term:
self.logTextEdit.append("검색어를 입력해주세요.")
QMessageBox.warning(self, "경고", "검색어를 입력해주세요.")
return
self.logTextEdit.append("검색을 시작합니다...")
try:
results = await self.scraper.search_for_term(term)
if results:
self.logTextEdit.append("검색 완료. 결과를 처리합니다...")
await self.show_results(results)
else:
self.logTextEdit.append("검색 결과가 없거나 오류가 발생했습니다.")
QMessageBox.information(self, "검색 결과 없음", "검색 결과가 없으므로 지재권에 안심하시면 됩니다.", QMessageBox.Ok)
except Exception as e:
self.logTextEdit.append(f"검색 중 오류 발생: {str(e)}")
async def show_results(self, results):
try:
# 이전 결과 위젯이 있으면 닫기
if self.results_widget is not None:
self.results_widget.close() # 기존 위젯 닫기
self.results_widget = None # 참조 제거
# 새 결과 위젯 생성
self.results_widget = QWidget()
layout = QVBoxLayout()
self.results_widget.setLayout(layout)
total_count = results['total_count']
if total_count is not int:
total_count = int(results['total_count'])
set_count = min(total_count, 10)
grid_layout = QGridLayout()
layout.addLayout(grid_layout)
grid_index = 0
grid_columns = 5
for i in range(1, set_count + 1):
result_key = f"result_{i}"
if result_key in results:
result = results[result_key]
print(f"result num {i}: {result}")
# 테두리 설정
border_style = ''
if result['admin_status'] == '등록':
border_style = 'border: 4px solid red;'
elif result['admin_status'] == '공고':
border_style = 'border: 3px solid black;'
item_layout = QVBoxLayout()
item_widget = QWidget()
item_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)
print("item_widget set")
try:
image_label = QLabel()
image_label.setFixedSize(150, 150)
image_data = await self.scraper.fetch_image_data(result['IDimageURL'])
print(f"image_data : {image_data}")
pixmap = QPixmap()
if image_data:
pixmap.loadFromData(image_data)
scaled_pixmap = pixmap.scaled(image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
image_label.setPixmap(scaled_pixmap)
image_label.setAlignment(Qt.AlignCenter)
image_label.setScaledContents(True)
print(f"image URL : [{result['IDimageURL']}]")
print("image set")
horizontal_layout = QHBoxLayout()
horizontal_layout.addWidget(image_label)
horizontal_layout.setAlignment(Qt.AlignCenter)
item_layout.addLayout(horizontal_layout)
print("horizontal_layout set")
except Exception as e:
print(f" 이미지 데이터 처리 중 에러 : {e}")
info_text = f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>상표권명: {result['title']}</span><br>\n" \
f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>등록상태: {result['admin_status']}</span><br>\n" \
f"<span style='font-size: 9pt;'>카테고리: {result['product_category']}</span><br>\n" \
f"<span style='font-size: 9pt;'>권리자: {result['applicant']}</span><br>\n" \
f"<span style='font-size: 9pt;'> {result['publication_date']}</span><br>\n" \
f"<span style='font-size: 9pt;'> {result['registration_date']}</span>"
print(f"info_text : {info_text}")
info_label = QLabel(info_text)
print(f"result['category_description'] : {result['category_description']}")
tooltip_text = self.wrap_text(result['category_description'], 50)
info_label.setToolTip(tooltip_text)
image_label.setToolTip(tooltip_text)
item_layout.addWidget(info_label)
image_label.setStyleSheet(border_style)
info_label.setStyleSheet(border_style)
print("item_layout set")
# 레이아웃에 위젯 추가
row = grid_index // grid_columns
col = grid_index % grid_columns
grid_layout.addLayout(item_layout, row, col)
# grid_layout.addLayout(item_layout, grid_index // grid_columns, grid_index % grid_columns)
grid_index += 1
close_button = QPushButton("Close")
close_button.clicked.connect(self.close_results_widget)
layout.addWidget(close_button)
print("close_button set")
self.results_widget.setGeometry(300, 300, 800, 600)
self.results_widget.setWindowTitle('Search Results')
print("Search Results results_widget set")
self.results_widget.show()
except Exception as e:
print(f"Error displaying results: {e}")
def wrap_text(self, text, width=40):
"""주어진 너비에 맞게 텍스트를 줄바꿈합니다."""
words = text.split()
wrapped_text = ''
line_length = 0
for word in words:
if line_length + len(word) + 1 > width:
wrapped_text += '\n'
line_length = 0
wrapped_text += word + ' '
line_length += len(word) + 1
return wrapped_text.strip()
def close_results_widget(self):
self.results_widget.close()
def closeEvent(self, event):
asyncio.create_task(self.scraper.close_browser())
super().closeEvent(event) # QWidget의 기본 closeEvent 처리
if __name__ == '__main__':
app = qasync.QApplication(sys.argv)
ex = MainApp()
loop = qasync.QEventLoop(app)
asyncio.set_event_loop(loop)
ex.show()
# `setup_browser`가 완료될 때까지 기다립니다.
loop.create_task(ex.scraper.setup_browser())
loop.create_task(ex.prepare_search())
# `loop.run_forever()`를 사용하여 이벤트 루프를 시작합니다.
loop.run_forever()

111
main_with_re.py Normal file
View File

@ -0,0 +1,111 @@
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit, QMessageBox, QGridLayout, QSizePolicy
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from web_scraper_with_re import WebScraper
class MainApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.scraper = WebScraper()
def initUI(self):
self.searchLayout = QHBoxLayout()
self.searchLabel = QLabel('검색어 입력:', self)
self.searchLineEdit = QLineEdit(self)
self.searchButton = QPushButton('검색 실행', self)
self.searchButton.clicked.connect(self.start_search)
self.searchLineEdit.returnPressed.connect(self.start_search)
self.searchButton.setEnabled(False) # 버튼을 초기에 비활성화합니다.
self.searchLayout.addWidget(self.searchLabel)
self.searchLayout.addWidget(self.searchLineEdit)
self.searchLayout.addWidget(self.searchButton)
self.logLayout = QVBoxLayout()
self.logTextEdit = QTextEdit(self)
self.logTextEdit.setReadOnly(True)
self.logLayout.addWidget(self.logTextEdit)
self.mainLayout = QVBoxLayout()
self.mainLayout.addLayout(self.searchLayout)
self.mainLayout.addLayout(self.logLayout)
self.setLayout(self.mainLayout)
self.setWindowTitle('Web Scraper')
self.setGeometry(300, 300, 300, 400)
self.prepare_search() # 페이지 로딩과 관련된 초기 설정
def prepare_search(self):
# self.scraper.setup_browser() # 브라우저 설정
self.searchButton.setEnabled(True) # 페이지 로드 후 버튼 활성화
def start_search(self):
term = self.searchLineEdit.text()
if not term:
QMessageBox.warning(self, "경고", "검색어를 입력해주세요.")
return
self.logTextEdit.append("검색을 시작합니다...")
results = self.scraper.search_for_term(term)
if results:
self.logTextEdit.append("검색 완료. 결과를 처리합니다...")
self.show_results(results)
else:
self.logTextEdit.append("검색 결과가 없거나 오류가 발생했습니다.")
QMessageBox.information(self, "검색 결과 없음", "검색 결과가 없으므로 지재권에 안심하시면 됩니다.", QMessageBox.Ok)
def show_results(self, results):
# 이전 결과 위젯이 있으면 닫기
if self.results_widget is not None:
self.results_widget.close()
self.results_widget = QWidget()
layout = QVBoxLayout()
self.results_widget.setLayout(layout)
total_count = results['total_count']
set_count = min(total_count, 10)
grid_layout = QGridLayout()
layout.addLayout(grid_layout)
grid_index = 0
grid_columns = 5
for i in range(1, set_count + 1):
result_key = f"result_{i}"
if result_key in results:
result = results[result_key]
item_layout = QVBoxLayout()
item_widget = QWidget()
item_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)
image_label = QLabel()
image_label.setFixedSize(150, 150)
image_data = self.scraper.fetch_image_data(result['IDimageURL'])
if image_data:
pixmap = QPixmap()
pixmap.loadFromData(image_data)
scaled_pixmap = pixmap.scaled(image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
image_label.setPixmap(scaled_pixmap)
image_label.setAlignment(Qt.AlignCenter)
image_label.setScaledContents(True)
item_layout.addWidget(image_label)
info_text = f"{result['title']} - {result['status']}"
info_label = QLabel(info_text)
item_layout.addWidget(info_label)
grid_layout.addLayout(item_layout, grid_index // grid_columns, grid_index % grid_columns)
grid_index += 1
self.results_widget.setGeometry(300, 300, 800, 600)
self.results_widget.setWindowTitle('Search Results')
self.results_widget.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainApp()
ex.show()
sys.exit(app.exec_())

86
src/kipo_api_client.py Normal file
View File

@ -0,0 +1,86 @@
import requests
import xml.etree.ElementTree as ET
class KipoApiClientError(Exception):
"""Custom exception class for Kipo API client errors, includes the error code."""
def __init__(self, message, code=None):
super().__init__(message)
self.code = code
print(f"Error : {code} - {message}")
class KipoApiClient:
def __init__(self, service_key):
self.service_key = service_key
self.base_url = 'http://kipo-api.kipi.or.kr/openapi/service/patUtiModInfoSearchSevice/getWordSearch'
def search_patents(self, word, year, patent=True, utility=True):
params = {
'serviceKey': self.service_key,
'word': word,
'year': year,
'patent': str(patent).lower(),
'utility': str(utility).lower()
}
response = requests.get(self.base_url, params=params)
if response.status_code != 200:
raise KipoApiClientError("HTTP 오류: 상태 코드 {}".format(response.status_code))
return self.parse_response(response.content)
def parse_response(self, xml_response):
root = ET.fromstring(xml_response)
if root.find('.//cmmMsgHeader') is not None:
return self.handle_error(root)
else:
return self.extract_data(root)
def handle_error(self, root):
err_msg = root.find('.//errMsg').text
return_auth_msg = root.find('.//returnAuthMsg').text
return_reason_code = int(root.find('.//returnReasonCode').text)
error_message = f"API Error {return_reason_code}: {return_auth_msg} ({err_msg})"
if return_reason_code == 30:
raise KipoApiClientError("Unregistered Service Key Error\n등록되지 않은 서비스키", code=return_reason_code)
elif return_reason_code == 20:
raise KipoApiClientError("Access Denied Error\n서비스 접근거부", code=return_reason_code)
elif return_reason_code == 22:
raise KipoApiClientError("Service Request Limit Exceeded Error\n서비스 요청제한횟수 초과에러", code=return_reason_code)
elif return_reason_code == 32:
raise KipoApiClientError("Unregistered IP Error\n등록되지 않은 IP", code=return_reason_code)
elif return_reason_code == 1:
raise KipoApiClientError("Application Error\n어플리케이션 에러", code=return_reason_code)
elif return_reason_code == 4:
raise KipoApiClientError("HTTP Error\nHTTP 에러", code=return_reason_code)
elif return_reason_code == 12:
raise KipoApiClientError("No OpenAPI Service Error\n해당 오픈 API 서비스가 없거나 폐기됨", code=return_reason_code)
elif return_reason_code == 31:
raise KipoApiClientError("Deadline Expired Error\n활용기간 만료", code=return_reason_code)
elif return_reason_code == 99:
raise KipoApiClientError("Unknown Error\n기타에러", code=return_reason_code)
else:
raise KipoApiClientError("Unexpected Error: " + error_message, code=return_reason_code)
def extract_data(self, root):
items = []
for item in root.findall('.//item'):
data = {
'registerStatus': item.find('registerStatus').text if item.find('registerStatus') is not None else None,
'inventionTitle': item.find('inventionTitle').text if item.find('inventionTitle') is not None else None,
'ipcNumber': item.find('ipcNumber').text if item.find('ipcNumber') is not None else None,
'registerNumber': item.find('registerNumber').text if item.find('registerNumber') is not None else None,
'registerDate': item.find('registerDate').text if item.find('registerDate') is not None else None,
'applicationNumber': item.find('applicationNumber').text if item.find('applicationNumber') is not None else None,
'applicationDate': item.find('applicationDate').text if item.find('applicationDate') is not None else None,
'openNumber': item.find('openNumber').text if item.find('openNumber') is not None else None,
'openDate': item.find('openDate').text if item.find('openDate') is not None else None,
'publicationNumber': item.find('publicationNumber').text if item.find('publicationNumber') is not None else None,
'publicationDate': item.find('publicationDate').text if item.find('publicationDate') is not None else None,
'abstractContent': item.find('astrtCont').text if item.find('astrtCont') is not None else None,
'bigDrawing': item.find('bigDrawing').text if item.find('bigDrawing') is not None else None,
'drawing': item.find('drawing').text if item.find('drawing') is not None else None,
'applicantName': item.find('applicantName').text if item.find('applicantName') is not None else None
}
items.append(data)
print(f"성공적인 응답결과 : {items}")
return items

14
test.py Normal file
View File

@ -0,0 +1,14 @@
import requests
from src.kipo_api_client import KipoApiClient
if __name__ == "__main__":
service_key = 'X9Tz3JqC%2FJcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6%2Bt3dyLZV3gh2TPXdNhxsRQwaKP673Q%3D%3D'
client = KipoApiClient(service_key)
try:
# 테스트 실행: 'view'라는 단어로 특허 검색을 시도합니다.
result = client.search_patents('view', '2022', True, True)
# 결과를 콘솔에 출력합니다.
print("Test Success:", result)
except Exception as e:
print("Test Failed:", e)

42
test_api.py Normal file
View File

@ -0,0 +1,42 @@
from kipris_api_from_publicdata import Kipris
# 테스트를 위한 URL 및 파라미터 설정
apikey = 'X9Tz3JqC/JcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6+t3dyLZV3gh2TPXdNhxsRQwaKP673Q=='
search_keyword = "써지오"
url = 'http://kipo-api.kipi.or.kr/openapi/service/trademarkInfoSearchService/getWordSearch'
params = {
'serviceKey': apikey,
'searchString': search_keyword,
'searchRecentYear': '0',
'title': '',
'fullText': '',
'drawing': '',
'bigDrawing': ''
}
# XMLParser 인스턴스 생성 및 실행
parser = Kipris(url, params)
results = parser.run()
# 모든 아이템의 결과 출력
if results:
print("결과가 있는 아이템 목록:")
for index, item in enumerate(results, start=1):
print(f"\n아이템 {index} 정보:")
for key, value in item.items():
print(f"{key}: {value}")
else:
print("결과가 없습니다.")
# ※ 행정 상태 도움말
# 거절 : 출원 후 특허 심사과정에서 실체적인 특허 등록요건을 만족하지 못할 경우에 심사관이 취하는 행정처분
# 등록 : 심사관이 심사한 결과 등록요건에 적합하여 설정등록을 받을 수 있다는 내용의 행정처분
# 소멸 : 특허등록 후 존속기간이 만료되어 권리가 소멸된 상태
# 무효 : 출원 또는 등록된 상태에 대하여 특정 사유로 인해 그 권리나 행위가 무효화 된 상태
# 취하 : 출원한 특허가 등록되기전 여러 사유로 인하여 출원이 취소된 상태
# 포기 : 출원인의 포기서 제출, 등록료 불납 등으로 등록결정이나 권리를 포기한 상태
# 공개 : 출원이나 등록사실이 일반 공중에게 공표된 상태로 출원 후 18개월이 지난 건
# *조기공개신청시 18개월 미만도 공개가능
#

48
test_main.py Normal file
View File

@ -0,0 +1,48 @@
import unittest
from unittest.mock import patch
from src.kipo_api_client import KipoApiClient, KipoApiClientError
class TestKipoApiClient(unittest.TestCase):
def setUp(self):
apikey = '''X9Tz3JqC%2FJcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6%2Bt3dyLZV3gh2TPXdNhxsRQwaKP673Q%3D%3D'''
self.client = KipoApiClient(apikey)
def test_search_patents_successful(self):
# 예상되는 성공적인 응답을 시뮬레이션합니다.
xml_response = '''
<OpenAPI_ServiceResponse>
<item>
<registerStatus>1000319440000</registerStatus>
<inventionTitle>회전체의센서와센서의엔코더</inventionTitle>
<ipcNumber>G01D 5/00</ipcNumber>
...
</item>
</OpenAPI_ServiceResponse>'''.encode('utf-8') # 문자열을 UTF-8로 인코딩
with patch('requests.get') as mocked_get:
mocked_get.return_value.status_code = 200
mocked_get.return_value.content = xml_response
response = self.client.search_patents('view', '0', True, True)
self.assertEqual(len(response), 1)
self.assertEqual(response[0]['inventionTitle'], '회전체의센서와센서의엔코더')
def test_handle_error(self):
# 에러 상황을 시뮬레이션하여 에러 처리를 테스트합니다.
xml_response = '''
<OpenAPI_ServiceResponse>
<cmmMsgHeader>
<errMsg>서비스 오류</errMsg>
<returnAuthMsg>등록되지 않은 서비스키 오류</returnAuthMsg>
<returnReasonCode>30</returnReasonCode>
</cmmMsgHeader>
</OpenAPI_ServiceResponse>'''.encode('utf-8') # 문자열을 UTF-8로 인코딩
with patch('requests.get') as mocked_get:
mocked_get.return_value.status_code = 200
mocked_get.return_value.content = xml_response
with self.assertRaises(KipoApiClientError) as context:
self.client.search_patents('view', '0', True, True)
self.assertIn('등록되지 않은 서비스키 오류', str(context.exception))
if __name__ == '__main__':
unittest.main(verbosity=2)

20
test_web.py Normal file
View File

@ -0,0 +1,20 @@
from web_scraper import WebScraper
def test_scraper():
scraper = WebScraper()
scraper.setup_browser()
try:
scraper.navigate_to_page("http://kdtj.kipris.or.kr/kdtj/searchLogina.do?method=loginTM#page1")
keyword = "스키에이트"
results = scraper.search_for_term(keyword)
if results:
print("Search Results:")
for key, value in results.items():
print(f"{key}: {value}")
else:
print("No results found or an error occurred.")
finally:
scraper.close_browser()
if __name__ == "__main__":
test_scraper()

199
web_scraper.py Normal file
View File

@ -0,0 +1,199 @@
# import asyncio
from playwright.sync_api import sync_playwright
import random, requests, json
from PIL import Image
from io import BytesIO
class WebScraper:
def __init__(self):
self.results = {}
self.playwright = None
self.browser = None
self.context = None
self.page = None
filename = 'categories.json'
self.category_description = self.load_category_descriptions(filename)
self.url = "http://kdtj.kipris.or.kr/kdtj/searchLogina.do?method=loginTM#page1"
def setup_browser(self):
"""브라우저 설정 및 인스턴스 생성"""
self.playwright = sync_playwright().start()
self.browser = self.playwright.chromium.launch(headless=True) # For testing, set headless to True
self.context = self.browser.new_context(user_agent=random.choice([
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 OPR/85.0.0.0"
]))
self.page = self.context.new_page()
self.navigate_to_page(self.url)
def navigate_to_page(self, url):
"""주어진 URL로 이동"""
self.page.goto(url)
# def search_for_term_success(self, term):
# """검색어로 검색하고 결과 수집"""
# try:
# self.page.fill("#queryText", term)
# self.page.click(".input_btn")
# # JavaScript에 의해 결과가 동적으로 로드되기를 기다립니다.
# loaded = self.page.wait_for_function("document.querySelector('form#listForm section.search_section article') != null", timeout=10000)
# if not loaded:
# print("검색 결과가 시간 내에 로드되지 않았습니다.")
# return None
# # 결과 로딩이 확인된 후, 실제 요소를 수집
# self.page.wait_for_selector("form#listForm section.search_section", state="visible", timeout=10000)
# total_count = self.page.text_content("form#listForm section.search_section div p span.total")
# if total_count:
# self.results['total_count'] = total_count.strip()
# articles = self.page.query_selector_all("form#listForm section.search_section article")
# for i, article in enumerate(articles):
# img_element = article.query_selector("div.thumb a img")
# if img_element:
# trademark_image = img_element.get_attribute("src")
# name_element = article.query_selector("div.search_section_title h1.stitle a b")
# if name_element:
# trademark_name = name_element.text_content()
# self.results[f"result_{i+1}"] = {"image": trademark_image, "name": trademark_name}
# else:
# print(f"이미지 요소가 없는 기사: {i+1}")
# return self.results
# except Exception as e:
# print(f"오류 발생 : {e}")
# return None
def search_for_term(self, term):
"""검색어로 검색하고 결과 수집"""
try:
self.page.fill("#queryText", term)
self.page.click(".input_btn")
# JavaScript에 의해 결과가 동적으로 로드되기를 기다립니다.
loaded = self.page.wait_for_function("document.querySelector('form#listForm section.search_section article') != null")
if not loaded:
print("검색 결과가 시간 내에 로드되지 않았습니다.")
return None
# 검색 결과가 없는지 확인
nodata_info = self.page.query_selector(".nodata_info")
if nodata_info:
print("검색 결과가 없습니다.")
# 특정 메서드 호출 및 함수 종료
# self.handle_no_search_results()
return None
# 결과 로딩이 확인된 후, 실제 요소를 수집
self.page.wait_for_selector("form#listForm section.search_section", state="visible", timeout=10000)
total_count = self.page.text_content("form#listForm section.search_section div p span.total")
if total_count:
self.results['total_count'] = total_count.strip()
articles = self.page.query_selector_all("form#listForm section.search_section article")
for i, article in enumerate(articles):
id_and_name_element = article.query_selector("div:nth-child(1) span input[type='checkbox']")
trademark_name = id_and_name_element.get_attribute("title") if id_and_name_element else "No name found"
applno = id_and_name_element.get_attribute("value") if id_and_name_element else "No ID found"
if applno:
trademark_image_url_by_id = f"http://kdtj.kipris.or.kr/kdtj/remoteFile.do?method=bigImageTM&applno={applno}&no={applno}_tm000001.jpg"
img_element = article.query_selector("div:nth-child(2) div a img")
trademark_image = img_element.get_attribute("src") if img_element else "No image found"
admin_status_element = article.query_selector("div:nth-child(1) h1 a:nth-child(1) span")
admin_status = admin_status_element.text_content() if admin_status_element else "No status found"
product_category_element = article.query_selector("div:nth-child(2) ul li:nth-child(1) a span")
product_category = product_category_element.text_content() if product_category_element else "No category found"
if product_category:
category_desc = self.add_category_description(product_category)
applicant_element = article.query_selector("div:nth-child(2) ul li:nth-child(2) span[title]")
applicant = applicant_element.get_attribute("title") if applicant_element else "No applicant found"
publication_date_element = article.query_selector("div:nth-child(2) ul li:nth-child(8)")
publication_date = publication_date_element.text_content().strip() if publication_date_element else "No publication date found"
registration_date_element = article.query_selector("div:nth-child(2) ul li:nth-child(6)")
registration_date = registration_date_element.text_content().strip() if registration_date_element else "No registration date found"
# name_element = article.query_selector("div:nth-child(1) h1 a:nth-child(2) font b")
# trademark_name = name_element.text_content() if name_element else "No name found"
# Use the title attribute of the checkbox input for the trademark name
name_element = article.query_selector("div:nth-child(1) span input[type='checkbox']")
trademark_name = name_element.get_attribute("title") if name_element else "No name found"
if not admin_status == "소멸":
self.results[f"result_{i+1}"] = {
"ID": applno,
"title": trademark_name,
"admin_status": admin_status,
"imageURL": trademark_image,
"IDimageURL": trademark_image_url_by_id,
"product_category": product_category,
"category_description": category_desc,
"applicant": applicant,
"publication_date": publication_date,
"registration_date": registration_date,
}
return self.results
else:
print("No total count element found, possibly incorrect selector or page structure has changed.")
except Exception as e:
print(f"오류 발생 : {e}")
return None
def download_image(url, applno):
"""이미지를 다운로드하고 applno를 파일 이름으로 사용하여 저장합니다."""
response = requests.get(url)
if response.status_code == 200:
filename = f"{applno}.jpeg" # 파일 이름을 ID로 설정
with open(filename, 'wb') as file:
file.write(response.content)
print(f"이미지가 성공적으로 저장되었습니다: {filename}")
else:
print(f"이미지 다운로드 실패: HTTP {response.status_code}")
def load_category_descriptions(self, filename):
"""JSON 파일에서 카테고리 설명을 로드합니다."""
with open(filename, 'r', encoding='utf-8') as file:
return json.load(file)
def add_category_description(self, category_code):
"""주어진 카테고리 코드에 따라 설명을 반환합니다."""
return self.category_description.get(category_code, "카테고리 설명을 찾을 수 없습니다.")
def fetch_image_data(self, url):
"""주어진 URL로부터 이미지 데이터를 가져와 반환합니다."""
response = requests.get(url)
if response.status_code == 200:
# 서버 응답 헤더에서 Content-Type 확인
content_type = response.headers.get('Content-Type', '')
if 'image' in content_type:
return response.content
else:
# Content-Type이 이미지가 아니면, 데이터를 이미지로 변환
try:
image = Image.open(BytesIO(response.content))
with BytesIO() as buffer:
image.save(buffer, 'JPEG') # 예시로 JPEG 포맷을 사용
return buffer.getvalue()
except Exception as e:
print(f"이미지 변환 실패: {e}")
return None
else:
print(f"이미지 다운로드 실패: HTTP {response.status_code}")
return None
def close_browser(self):
"""브라우저 리소스 정리"""
if self.context:
self.context.close()
self.browser.close()
self.playwright.stop()

228
web_scraper_async.py Normal file
View File

@ -0,0 +1,228 @@
import asyncio, aiofiles, aiohttp
from playwright.async_api import async_playwright
import random, requests, json
from PIL import Image
from io import BytesIO
class WebScraper:
def __init__(self):
self.results = {}
self.playwright = None
self.browser = None
self.context = None
self.page = None
filename = 'categories.json'
self.category_description = self.load_category_descriptions(filename)
self.url = "http://kdtj.kipris.or.kr/kdtj/searchLogina.do?method=loginTM#page1"
self.is_page_loaded = False
async def setup_browser(self):
"""브라우저 설정 및 인스턴스 생성"""
self.playwright = await async_playwright().start()
self.browser = await self.playwright.chromium.launch(headless=False)
self.context = await self.browser.new_context(
user_agent=random.choice([
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 OPR/85.0.0.0",
]))
# ]),
# cache_enabled=True # 캐시 활성화
# )
self.page = await self.context.new_page()
await self.page.route('**/*.{png,jpg,jpeg,svg,gif,css}', lambda route: route.abort())
try:
await self.page.goto(self.url, wait_until='networkidle')
self.is_page_loaded = True
print("Page loaded successfully.")
except Exception as e:
print(f"Failed to load the page: {e}")
self.is_page_loaded = False # 로드 실패 처리
async def navigate_to_page(self, url):
try:
await self.page.goto(url, wait_until='networkidle')
self.is_page_loaded = True
print("Page loaded successfully.")
except Exception as e:
print(f"Failed to load the page: {e}")
self.is_page_loaded = False # 로드 실패 처리
async def search_for_term(self, term):
"""검색어로 비동기적으로 검색하고 결과를 수집합니다."""
try:
self.results = {}
# await self.page.fill("#queryText", term)
await self.page.fill("#keywordTextarea", term)
print(f"검색어 입력 : {term}")
await self.page.click(".input_btn")
print(f"검색버튼 클릭")
# JavaScript에 의해 결과가 동적으로 로드되기를 기다립니다.
loaded = await self.page.wait_for_function(
"document.querySelector('form#listForm section.search_section article') != null"
)
if not loaded:
print("검색 결과가 시간 내에 로드되지 않았습니다.")
return None
print(f"결과가 동적으로 로드되기를 기다림 : {loaded}")
# 검색 결과가 없는지 확인
nodata_info = await self.page.query_selector(".nodata_info")
if nodata_info:
print("검색 결과가 없습니다.")
return None
# 결과 로딩이 확인된 후, 실제 요소를 수집
await self.page.wait_for_selector("form#listForm section.search_section", state="visible", timeout=10000)
total_count = await self.page.text_content("form#listForm section.search_section div p span.total")
total_count = total_count.strip()
total_count = int(total_count.replace(',', ''))
print(f"total_count : {total_count}")
if total_count:
self.results['total_count'] = total_count
articles = await self.page.query_selector_all("form#listForm section.search_section article")
print(f"articles : {len(articles)}")
for i, article in enumerate(articles):
id_and_name_element = await article.query_selector("div:nth-child(1) span input[type='checkbox']")
trademark_name = await id_and_name_element.get_attribute("title") if id_and_name_element else "No name found"
print(f"trademark_name : {trademark_name}")
applno = await id_and_name_element.get_attribute("value") if id_and_name_element else "No ID found"
trademark_image_url_by_id = (
f"http://kdtj.kipris.or.kr/kdtj/remoteFile.do?method=bigImageTM&applno={applno}&no={applno}_tm000001.jpg"
if applno else None
)
print(f"trademark_image_url_by_id : {trademark_image_url_by_id}")
img_element = await article.query_selector("div:nth-child(2) div a img")
trademark_image = await img_element.get_attribute("src") if img_element else "No image found"
print(f"trademark_image : {trademark_image}")
admin_status_element = await article.query_selector("div:nth-child(1) h1 a:nth-child(1) span")
admin_status = await admin_status_element.text_content() if admin_status_element else "No status found"
print(f"admin_status : {admin_status}")
product_category_element = await article.query_selector("div:nth-child(2) ul li:nth-child(1) a span")
product_category = await product_category_element.text_content() if product_category_element else "No category found"
category_desc = self.add_category_description(product_category) if product_category else "No category description"
print(f"product_category : {product_category}")
print(f"category_desc : {category_desc}")
applicant_element = await article.query_selector("div:nth-child(2) ul li:nth-child(2) span[title]")
applicant = await applicant_element.get_attribute("title") if applicant_element else "No applicant found"
print(f"applicant : {applicant}")
publication_date_element = await article.query_selector("div:nth-child(2) ul li:nth-child(8)")
if publication_date_element:
publication_date_content = await publication_date_element.text_content()
publication_date = publication_date_content.strip() if publication_date_content else "No publication date found"
else:
publication_date = "No publication date found"
print(f"publication_date : {publication_date}")
registration_date_element = await article.query_selector("div:nth-child(2) ul li:nth-child(6)")
if registration_date_element:
registration_date_content = await registration_date_element.text_content()
registration_date = registration_date_content.strip() if registration_date_content else "No registration date found"
else:
registration_date = "No registration date found"
print(f"registration_date : {registration_date}")
if not (admin_status == "소멸" or admin_status == "거절"):
self.results[f"result_{i+1}"] = {
"ID": applno,
"title": trademark_name,
"admin_status": admin_status,
"imageURL": trademark_image,
"IDimageURL": trademark_image_url_by_id,
"product_category": product_category,
"category_description": category_desc,
"applicant": applicant,
"publication_date": publication_date,
"registration_date": registration_date,
}
# print(f"results : {self.results}")
await self.navigate_to_page(self.url)
return self.results
else:
print("No total count element found, possibly incorrect selector or page structure has changed.")
except Exception as e:
print(f"오류 발생 : {e}")
return None
async def download_image(self, url, applno):
"""이미지를 비동기적으로 다운로드하고 applno를 파일 이름으로 사용하여 저장합니다."""
async with aiohttp.ClientSession() as session:
print(f"download_image session Start!!")
async with session.get(url) as response:
print(f"download_image url : {url}")
if response.status == 200:
filename = f"{applno}.jpeg"
async with aiofiles.open(filename, 'wb') as file:
content = await response.read()
await file.write(content)
print(f"이미지가 성공적으로 저장되었습니다: {filename}")
else:
print(f"이미지 다운로드 실패: HTTP {response.status}")
async def fetch_image_data(self, url):
"""주어진 URL로부터 이미지 데이터를 비동기적으로 가져와 반환합니다."""
async with aiohttp.ClientSession() as session:
# print(f"download_image session Start!!")
async with session.get(url) as response:
print(f"download_image url : {url}")
if response.status == 200:
# print(f"response : {response}")
content_type = response.headers.get('Content-Type', '') # await 제거
print(f"content_type : {content_type}")
if 'image' in content_type or 'octet-stream' in content_type:
# print(f"image content type or octet-stream : {content_type}")
return await response.read()
else:
try:
# Content-Type이 이미지가 아니면, 데이터를 이미지로 변환
data = await response.read()
# print(f"Content-Type이 이미지가 아님 : {data}")
image = Image.open(BytesIO(data))
with BytesIO() as buffer:
image.save(buffer, 'JPEG')
print(f"image 를 JPEG로 저장")
return buffer.getvalue()
except Exception as e:
print(f"이미지 변환 실패: {e}")
return None
else:
print(f"이미지 다운로드 실패: HTTP {response.status}")
return None
# async def load_category_descriptions(self, filename):
# """JSON 파일에서 카테고리 설명을 비동기적으로 로드합니다."""
# async with aiofiles.open(filename, 'r', encoding='utf-8') as file:
# content = await file.read()
# print(f"JSON 파일에서 카테고리 설명을 비동기적으로 로드합니다: {content}")
# return json.loads(content)
def load_category_descriptions(self, filename):
"""JSON 파일에서 카테고리 설명을 로드합니다."""
with open(filename, 'r', encoding='utf-8') as file:
return json.load(file)
def add_category_description(self, category_code):
"""주어진 카테고리 코드에 따라 설명을 반환합니다."""
print(f"add_category_description => category_code: {category_code}")
return self.category_description.get(category_code, "카테고리 설명을 찾을 수 없습니다.")
async def close_browser(self):
if self.context:
await self.context.close() # context를 닫습니다.
await self.browser.close() # browser를 닫습니다.
await self.playwright.stop() # playwright 세션을 종료합니다.

110
web_scraper_with_re.py Normal file
View File

@ -0,0 +1,110 @@
from requests_html import HTMLSession
import json
import requests
class WebScraper:
def __init__(self):
self.session = HTMLSession()
self.url = "http://kdtj.kipris.or.kr/kdtj/searchLogina.do?method=loginTM#page1"
self.results = {}
self.category_description = self.load_category_descriptions('categories.json')
def add_category_description(self, category_code):
"""주어진 카테고리 코드에 따라 설명을 반환합니다."""
description = self.category_description.get(category_code, "카테고리 설명을 찾을 수 없습니다.")
return description
def load_category_descriptions(self, filename):
"""JSON 파일에서 카테고리 설명을 로드합니다."""
with open(filename, 'r', encoding='utf-8') as file:
return json.load(file)
def navigate_to_page(self, url):
"""지정된 URL로 이동하고 페이지를 로드합니다."""
response = self.session.get(url)
response.html.render() # 필요 시 JavaScript 실행
print("Page loaded successfully.")
return response
def search_for_term(self, term):
"""검색어로 검색하고 결과를 수집합니다."""
response = self.navigate_to_page(self.url) # 로그인 페이지 로드
# await self.page.fill("#queryText", term)
# 검색어 입력과 폼 제출을 시뮬레이션
try:
form = response.html.find('form', containing='검색', first=True)
if form:
search_url = form.attrs.get('action')
if search_url:
data = {
'keywordTextarea': term, # form에서 요구하는 필드 이름에 맞추어야 함
}
search_response = self.session.post(search_url, data=data)
search_response.html.render()
return search_response
else:
print("폼의 action URL을 찾을 수 없습니다.")
else:
print("검색 폼을 찾을 수 없습니다.")
except Exception as e:
print(f"검색 실행 중 오류 발생: {e}")
# response.html.render(script=script, reload=False)
# # response.html.find('#keywordTextarea', first=True).fill(term)
# self.page.evaluate(f"document.querySelector('#keywordTextarea').value = '{term}';")
# print(f"검색어 입력: {term}")
# input_field = response.html.find('#keywordTextarea', first=True)
# script = f"document.querySelector('#keywordTextarea').value = '{term}';"
# response.html.page.evaluate(script) # JavaScript 실행
# search_button = response.html.find('.input_btn', first=True)
# search_button.click()
response.html.render(wait=10, timeout=20) # 결과 로드를 위해 대기
articles = response.html.find('form#listForm section.search_section article')
if not articles:
print("검색 결과가 없습니다.")
return None
# Store results in a structured format
for i, article in enumerate(articles, 1):
title = article.find('.title', first=True).text
status = article.find('.status', first=True).text
image_url = article.find('img', first=True).attrs.get('src', '')
self.results[f'result_{i}'] = {
'title': title,
'status': status,
'image_url': image_url
}
return self.results
def download_image(self, url, applno):
"""이미지를 다운로드하고 applno를 파일 이름으로 사용하여 저장합니다."""
response = requests.get(url) # 이미지 URL에 대한 요청
if response.status_code == 200:
filename = f"{applno}.jpeg"
with open(filename, 'wb') as file:
file.write(response.content)
print(f"이미지가 성공적으로 저장되었습니다: {filename}")
else:
print(f"이미지 다운로드 실패: HTTP {response.status_code}")
def fetch_image_data(self, url):
"""주어진 URL로부터 이미지 데이터를 직접 가져와 반환합니다."""
response = requests.get(url)
if response.status_code == 200:
return response.content
else:
print(f"이미지 다운로드 실패: HTTP {response.status_code}")
return None
def close_browser(self):
"""세션을 종료합니다."""
self.session.close()
# # 사용 예시
# scraper = WebScraper()
# results = scraper.search_for_term("특허")
# print(results)

55
xmltest.py Normal file
View File

@ -0,0 +1,55 @@
import xml.etree.ElementTree as ET
import requests
# XML 응답 데이터 (예시)
# xml_response = b'\xec\xa3\xbc\xec\x8b\x9d\xed\x9a\x8c\xec\x82\xac</applicantName><applicationDate>20230102</applicationDate><applicationNumber>4020230000430</applicationNumber><applicationStatus>\xea\xb3\xb5\xea\xb3\xa0</applicationStatus><bigDrawing>http://plus.kipris.or.kr/kiprisplusws/fileToss.jsp?arg=ad7a17eeeef6e4ea4b5e22ef00dd3e2991ba5d9d09407d4bf69fd0e282d93ba4822ea711d1c0014105513d5758c737d00c9e62115cad0aef</bigDrawing><classificationCode>03</classificationCode><drawing>http://plus.kipris.or.kr/kiprisplusws/fileToss.jsp?arg=ed43a0609e94d6e251697a9d72a913440be47db3b03c4b733d99b169217b523f0853e0d6dd613d455ccd291c28fd8817ed949b84dd8bb0c3</drawing><fullText>Y</fullText><indexNo>4</indexNo><internationalRegisterDate></internationalRegisterDate><internationalRegisterNumber></internationalRegisterNumber><priorityDate></priorityDate><priorityNumber></priorityNumber><publicationDate>20240403</publicationDate><publicationNumber>4020240052090</publicationNumber><regPrivilegeName></regPrivilegeName><regReferenceNumber></regReferenceNumber><registrationDate></registrationDate><registrationNumber></registrationNumber><registrationPublicDate></registrationPublicDate><registrationPublicNumber></registrationPublicNumber><title>\xec\x8a\xa4\xed\x82\xa4\xec\x97\x90\xec\x9d\xb4\xed\x8a\xb8</title><viennaCode></viennaCode></item></items></body><count><numOfRows>10</numOfRows><pageNo>1</pageNo><totalCount>4</totalCount></count></response>'
apikey = 'X9Tz3JqC/JcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6+t3dyLZV3gh2TPXdNhxsRQwaKP673Q=='
url = 'http://kipo-api.kipi.or.kr/openapi/service/trademarkInfoSearchService/getWordSearch'
params = {
'serviceKey': apikey, # 디코딩된 키 사용
'searchString': '스키에이트',
'searchRecentYear': '0',
'title': '',
'fullText': '',
'drawing': '',
'bigDrawing': ''
}
response = requests.get(url, params=params)
decoded_data = response.content.decode('utf-8')
print(f"decoded_data : {decoded_data}")
root = ET.fromstring(decoded_data)
# 결과 저장을 위한 딕셔너리 생성
results = []
# 'item' 태그를 순회하면서 필요한 데이터 추출
for item in root.findall('.//body/items/item'):
result = {
"index_no": item.find('indexNo').text if item.find('indexNo') is not None else None,
"application_number": item.find('applicationNumber').text if item.find('applicationNumber') is not None else None,
"application_date": item.find('applicationDate').text if item.find('applicationDate') is not None else None,
"publication_number": item.find('publicationNumber').text if item.find('publicationNumber') is not None else None,
"publication_date": item.find('publicationDate').text if item.find('publicationDate') is not None else None,
"registration_date": item.find('registrationDate').text if item.find('registrationDate') is not None else None,
"registration_number": item.find('registrationNumber').text if item.find('registrationNumber') is not None else None,
"applicant_name": item.find('applicantName').text if item.find('applicantName') is not None else None,
"agent_name": item.find('agentName').text if item.find('agentName') is not None else None,
"title": item.find('title').text if item.find('title') is not None else None,
"drawing_url": item.find('drawing').text if item.find('drawing') is not None else None,
"big_drawing_url": item.find('bigDrawing').text if item.find('bigDrawing') is not None else None,
"full_text": item.find('fullText').text if item.find('fullText') is not None else None,
"application_status": item.find('applicationStatus').text if item.find('applicationStatus') is not None else None,
"classification_code": item.find('classificationCode').text if item.find('classificationCode') is not None else None
}
results.append(result)
# 결과 출력 (첫 번째 아이템 예시)
if results:
first_item = results[0]
print("첫 번째 아이템 정보:")
for key, value in first_item.items():
print(f"{key}: {value}")
else:
print("결과가 없습니다.")