저번 포스팅 - 알라딘 중고책 프로젝트 (2) - 데이터 추출하기
중복될 수 있는 데이터를 적재하는 방법
데이터를 추출했으니 이제 추출한 데이터를 DB에 적재해야 한다. 그런데 여기서 주의해야 할 점이 한 가지 있었다!
이 프로젝트에서는 새로 들어온 중고책 데이터를 사용한다. 하지만 얼마의 주기를 가지고 매번 어느정도의 데이터가 생성되는지는 알 수 있는 방법이 없기 때문에 데이터가 중복될 가능성이 있다.
조금 더 쉽게 설명해보도록 하자.
내가 지금 1000개의 데이터를 추출한다고 가정하자. 그러면 DB에는 1000개의 데이터가 적재될 것이다. 시간이 지나고 6시간 뒤 다시 1000개의 데이터를 추출하고 적재해보자. 그러면 DB에는 2000개의 데이터가 존재할까?
정답은 아닐 수 있다이다. 만약 6시간 동안 새로 들어온 중고책이 200권 밖에 안된다면? 그러면 1000개의 데이터를 추출하였지만 새로운 데이터는 200권 밖에 안되고, 이전의 적재했던 데이터와 겹치는 데이터가 800개가 될 것이다.
이 문제를 어떻게 해결하면 좋을 지 구글링을 해본 결과 특정 SQL문을 이용하면 된다는 사실을 알았다! 우선 DB에 적재하는 코드를 직접 보면서 알아보자.
DB 적재 코드
col = ', '.join(data)
place_holders = ', '.join(['%s']*len(data))
key_holders = ', '.join([k+'=%s' for k in data.keys()])
que = 'INSERT INTO used_book ({}) VALUES ({}) ON CONFLICT (itemid) DO UPDATE SET {}'.format(col, place_holders, key_holders)
cursor.execute(que, list(data.values())*2)
이 프로젝트에서는 데이터베이스로 postgresql을 사용하는데, 해당 DB에 적재하기 위해서는 psycopg2 라이브러리를 사용해야 한다. 사용방법은 별로 어렵지 않다. (간단한 사용법을 알려면 이 링크로 한 번 가보는 것도...ㅎㅎ 파이썬 DB 커넥터(psycopg2) 간단한 사용법/executor과 commit 차이)
여기서 핵심은 4번째 줄의 SQL문이다.INSERT INTO TABLE {} VALUES {} ON CONFLICT {} DO UPDATE SET {}이 SQL문은 데이터를 추가할 때 만약 해당 데이터가 DB에 존재한다면 설정한 데이터로 교체한다.
만약 아무런 동작을 하고 싶지 않다면INSERT INTO TABLE {} VALUES {} ON CONFLICT {} DO NOTHING을 이용할 수도 있다.
여기서는 해당 SQL문에 포맷팅을 하여 execute 명령어에 인자로 해당 포맷팅에 들어갈 데이터를 설정하여 만약 데이터가 중복된다면 새로운 데이터로 교체하도록 하였다.
DO NOTHING이 아니라 DO UPDATE SET을 사용한 이유는 혹시나 중고책 개수가 바뀌었거나 데이터의 수정이 있을 때를 대비하였다.
(근데 지금 생각해보니 수정이 그렇게 많이 일어나는 것도 아니고, 만약 수정이 일어나도 최종 결과에는 굉장히 미미한 영향이 있을 것 같아서 DO NOTHING으로 바꾸는 게 더 좋을 거 같다.)
+ DO UPDATE SET보다 DO NOTHING이 데이터베이스에 부하가 덜 가는지 찾아봐야겠다.
코드 완성
import os
import requests
import json
import psycopg2
from dotenv import load_dotenv
def used_book_info(TTBKey):
book_lists = []
for i in range(1, 11):
url = f"http://www.aladin.co.kr/ttb/api/ItemList.aspx?ttbkey={TTBKey}&QueryType=ItemNewAll&SearchTarget=Used&SubSearchTarget=Book&MaxResults=50&start={i}&output=js&Version=20131101&OptResult=usedList"
res = requests.get(url)
items = json.loads(res.text)['item']
for item in items:
book_dict = {}
book_dict['itemid'] = item['itemId']
book_dict['title'] = item['title'][5:]
book_dict['author'] = item['author'].split(',')[0].split('(')[0]
book_dict['categoryname_large'] = item['categoryName'].split('>')[2] if len(item['categoryName'].split('>')) > 2 else "UNKNOWN"
book_dict['categoryname_small'] = item['categoryName'].split('>')[3] if len(item['categoryName'].split('>')) > 3 else "UNKNOWN"
book_dict['customer_review_rank'] = item['customerReviewRank']
book_dict['pricesales'] = item['priceSales']
book_dict['pricestandard'] = item['priceStandard']
book_dict['pubdate'] = item['pubDate']
book_dict['publisher'] = item['publisher']
book_dict['aladinused_itemcount'] = int(item['subInfo']['usedList']['aladinUsed']['itemCount'])
book_dict['userused_itemcount'] = int(item['subInfo']['usedList']['userUsed']['itemCount'])
book_dict['userused_minprice'] = item['subInfo']['usedList']['userUsed']['minPrice']
book_lists.append(book_dict)
print(f"{i}번째 배치 완료!")
length = len(book_lists)
print(f"총 {length}개의 데이터가 추출되었습니다.")
return book_lists
def insert_row(cursor, data):
col = ', '.join(data)
place_holders = ', '.join(['%s']*len(data))
key_holders = ', '.join([k+'=%s' for k in data.keys()])
que = 'INSERT INTO used_book ({}) VALUES ({}) ON CONFLICT (itemid) DO UPDATE SET {}'.format(col, place_holders, key_holders)
cursor.execute(que, list(data.values())*2)
def main():
load_dotenv()
TTBKey = os.environ.get("TTBKey")
book_lists = used_book_info(TTBKey)
host = os.environ.get('host')
port = os.environ.get('port')
user = os.environ.get('user')
database = os.environ.get('database')
password = os.environ.get('password')
conn = psycopg2.connect(host=host, user=user, password=password,
dbname=database, port=port)
cursor = conn.cursor()
print("DB에 저장중...")
for book_info in book_lists:
insert_row(cursor, book_info)
conn.commit()
cursor.close()
conn.close()
print("저장 완료!")
if __name__ == "__main__":
main()
이렇게 코드가 완성이 되었다. 정말 별 거 없는 보잘 것 없는 코드이긴 하지만... 그래도 나름 열심히 만들었던 것 같다.
중요한 인증정보는 dotenv 패키지를 통해 따로 관리하도록 하였고, 중간중간 print문을 통해 혹시나 에러가 나면 살펴볼 수 있도록 하여 디버깅을 조금 더 쉽게 하였다. try except문으로 더 깔끔하게 할 수도 있었지만, 오류가 날 확률이 그리 크지 않아서 간단하게 print문으로 구현했다.
구현할 것💻
이제 프로젝트가 거의 막바지에 도달했다(별로 한 것도 없는데...) 앞으로 구현해야 할 것은 우선 코드가 크론탭으로 돌아갈 수 있도록 하기위한 EC2 서버와 데이터를 적재할 RDS를 구축하는 것과 그 데이터를 시각화할 대시보드를 만드는 일이다.
남은 작업도 화이팅해서 작업해보자!!!🔥🔥🔥
'프로젝트 > 알라딘 중고책 프로젝트' 카테고리의 다른 글
알라딘 중고책 프로젝트 (5) - superset (0) | 2022.04.23 |
---|---|
알라딘 중고책 프로젝트 (4) - RDS, EC2 구축하기 (0) | 2022.04.19 |
알라딘 중고책 프로젝트 (2) - 데이터 추출하기 (0) | 2022.04.08 |
알라딘 중고책 프로젝트 (1) - 알라딘 API 살펴보기 (3) | 2022.04.07 |
알라딘 중고책 프로젝트 (0) - 프로젝트 개요 (0) | 2022.03.24 |