#10기문과생도AI
저는 직업상 연구를 하는데, 특정 검색어를 입력해서 관련 사료를 다 긁어모아 파일을 만드는 작업을 자주 수행합니다. 매우 노가다 스러운 일인데요.. 대략 작업수행 방식은 아래와 같습니다.
이런 작업을 x 999……
사료를 모아서 읽기도 전에 작업을 하다 지치기 때문에… 이 노가다를 대신 해줄 개인 연구비서가 매우 필요했습니다.
다행히 제가 주로 사용하는 사이트에서 open API 를 제공하길래, 아래와 같이 Grimoire에게 부탁해보았습니다.
한국고전종합DB(https://db.itkc.or.kr/)라는 웹사이트에서 내가 필요한 정보를 검색하고, 검색한 결과값을 csv 파일로 다운로드 받으려고 해. 해당 웹사트는 open API를 제공해. 아래는 호출 규칙이야. 내가 이 작업을 수행할 수 있게 파이썬 코드를 짜 줘.
그랬더니 그리모어가 친절하게 잘 알려주었어요.
정확한 코딩 내용은 잘 이해하지 못했지만, 고전종합DB 사이트의 API 호출규칙에 의거해서 코드를 주고 있지 않은 것 같았어요. 그래서 다음과 같이 보다 분명하게 요구사항을 말했습니다.
더불어서 제가 스트림릿을 활용해서 검색어를 입력하고 파일을 받을 수 있게 인터페이스 구축 코드도 달라고 했어요.
고전종합DB에서 데이터를 호출할 때, 반드시 다음과 같은 형식으로 호출해야 해: http://db.itkc.or.kr/openapi/search?secId=JT_BD&keyword=%EC%A1%B0%EC%84%A0&start=0&rows=10
그리고 secld라는 string()은 반드시 다음 중 하나여야 해.
---
[검색대상유형]
- 반드시 아래값중에 하나여야 합니다.
*고전번역서
BT_AA(전체), BT_SJ(서지), BT_AU(저/편/필자), BT_KW(권차/문체명), BT_GS(기사명), BT_BD(본문)
*고전원문
GO_AA(전체), GO_SJ(서지), GO_AU(저/편/필자), GO_KW(권차/문체명), GO_GS(기사명), GO_BD(본문)
*한국문집총간
MO_AA(전체), MO_SJ(서지), MO_AU(저/편/필자), MO_KW(권차/문체명), MO_GS(기사명), MO_BD(본문)
*한국고전총간
KP_AA(전체), KP_SJ(서지), KP_AU(저/편/필자), KP_KW(권차/문체명), KP_GS(기사명), KP_BD(본문)
*조선왕조실록
JT_AA(전체), JT_SJ(서지), JT_GS(기사명), JT_BD(본문)
*신역 조선왕조실록
JR_AA(전체), JR_SJ(서지), JR_GS(기사명), JR_BD(본문)
*승정원일기
ST_AA(전체), ST_SJ(서지), ST_GS(기사명), ST_BD(본문)
*일성록
IT_AA(전체), IT_SJ(서지), IT_GS(기사명), IT_BD(본문)
*해제
BT_HJ(고전번역서), JR_HJ(신역 조선왕조실록), GO_HJ(고전원문), MI_HJ(문집총간), KI_HJ(고전총간)
*경서성독 : KU_JA(전체)
*도설자료 : PC_IL(전체)
*서지정보 : SJ_AA(전체)
*각주정보 : JS_AA(전체)
*시소러스 : TR_AA(전체)
*동양고전종합DB : CS_AA(전체)
*세종한글고전 : SE_AA(전체)
*해제 상세검색
MI_H1(형태서지), MI_H2(문집저자), MI_H3(행력), MI_H4(가계), MI_H5(편찬및간행), MI_H6(구성과내용)
---
나는 스트림릿을 설치해둔 상태야. 스트림릿을 활용해서 내가 검색대상유형을 편하게 선택할수 있게 해줘.
스트림릿에서 내가 '검색어'를 입력하고 '검색대상유형'을 선택하면,
검색대상유형에 맞는 정확한 값을 자동으로 입력해서 파이썬 코드를 돌려줘.
그래서 내가 스트림릿을 통해 fetch해온 데이터를 csv 파일로 받을 수 있게 해줘.
그랬더니 아래와 같은 코드를 짜 주었어요.
import streamlit as st
import requests
import pandas as pd
# Define the API endpoint
API_URL = "http://db.itkc.or.kr/openapi/search"
# Dictionary mapping for search sections
sections = {
"전체": "AA", "서지": "SJ", "저/편/필자": "AU", "권차/문체명": "KW",
"기사명": "GS", "본문": "BD", "해제": "HJ", "형태서지": "H1", "문집저자": "H2",
"행력": "H3", "가계": "H4", "편찬및간행": "H5", "구성과내용": "H6",
"경서성독": "JA", "도설자료": "IL", "서지정보": "SJ_AA", "각주정보": "JS_AA",
"시소러스": "TR_AA", "동양고전종합DB": "CS_AA", "세종한글고전": "SE_AA"
}
# Streamlit app layout
st.title('한국고전종합DB 검색기')
keyword = st.text_input('검색어를 입력하세요:', '')
search_section = st.selectbox('검색대상유형을 선택하세요:', list(sections.keys()))
# Perform search and display results
if st.button('검색'):
secId = f"{search_section}_{sections[search_section]}"
params = {'secId': secId, 'keyword': keyword, 'start': 0, 'rows': 10}
response = requests.get(API_URL, params=params)
data = response.json()['documents']
# Convert data to DataFrame for display and CSV download
df = pd.DataFrame(data)
st.write(df)
# Download link for CSV file
csv = df.to_csv(index=False).encode('utf-8-sig')
st.download_button(label="CSV로 다운로드", data=csv, file_name=f"{keyword}_search_results.csv", mime='text/csv')
# Instructions to run the app
st.sidebar.info('이 앱은 한국고전종합DB에서 데이터를 검색하고 결과를 CSV 파일로 다운로드합니다. 검색어와 검색대상유형을 선택한 후 검색 버튼을 클릭하세요.')
이 중 Sections 정보가 고전종합DB 사이트가 명시한 [검색대상유형](위에 붉은 상자로 표시해둠)을 제대로 반영하고 있지 않은 것 같아서 다음과 같이 추가 요청을 넣었어요.
- 나는 검색한 모든 결과값을 가져오고 싶어. 그래서 start와 rows를 지정할 필요는 없을 것 같아.
- 검색유형 선택을 다음과 같이 두 단계로 나누어서 세분화 해줘:
"*"에 해당하는 걸 먼저 선택하게 해줘. 예를 들어, 고전번역서인지, 한국문집총간인지 등. 복수 선택도 가능하게 해줘.
그 다음에 (전체), (서지) 등의 사항을 선택하게 해줘. 복수 선택도 가능하게 해줘.
예를 들어, 내가 "고전번역서"를 선택하고 "전체"를 선택하면, 섹션 값은 "BT_AA"가 될거야.
예시를 들어주면 좋다고 해서, 예시를 분명하게 넣어보았습니다.
그리모어가 수정한 코드를 주었고, 저는 추가 요청사항을 또 넣었습니다.
- 세부유형을 선택할 때, AA, SJ, AU 등은 내가 알아보기 힘들어. 이건 값으로 두고, 이 값들에 해당하는 지시어들 "전체", "서지", "저/편/필자" 등을 선택버튼으로 만들어줘. \
- rows가 몇개까지 나올지 검색 시점에서 나는 알 수가 없어. 몇개가 검색이 되든 검색되는 모든 데이터를 fetch해올 수 있게 코드를 짜줘.
그래서 다음과 같은 코드를 얻었습니다.
import streamlit as st
import requests
import pandas as pd
API_URL = "http://db.itkc.or.kr/openapi/search"
# Dictionary mapping for sections with user-friendly labels
section_labels = {
"BT_AA": "전체", "BT_SJ": "서지", "BT_AU": "저/편/필자", "BT_KW": "권차/문체명", "BT_GS": "기사명", "BT_BD": "본문",
"GO_AA": "전체", "GO_SJ": "서지", "GO_AU": "저/편/필자", "GO_KW": "권차/문체명", "GO_GS": "기사명", "GO_BD": "본문",
"MO_AA": "전체", "MO_SJ": "서지", "MO_AU": "저/편/필자", "MO_KW": "권차/문체명", "MO_GS": "기사명", "MO_BD": "본문",
# Add additional categories and sections as required
}
# Main categories and their possible sections
categories = {
"고전번역서": ["BT_AA", "BT_SJ", "BT_AU", "BT_KW", "BT_GS", "BT_BD"],
"고전원문": ["GO_AA", "GO_SJ", "GO_AU", "GO_KW", "GO_GS", "GO_BD"],
"한국문집총간": ["MO_AA", "MO_SJ", "MO_AU", "MO_KW", "MO_GS", "MO_BD"],
# Add more categories and sections as needed
}
def fetch_all_results(secId, keyword):
all_documents = []
start = 0
rows = 100 # Initial batch size; adjust based on API performance
while True:
params = {'secId': secId, 'keyword': keyword, 'start': start, 'rows': rows}
response = requests.get(API_URL, params=params)
data = response.json().get('documents', [])
if not data: # Stop if no more data is returned
break
all_documents.extend(data)
start += len(data) # Update the starting point based on the number of data fetched
return all_documents
# Streamlit application interface
st.title('한국고전종합DB 검색 인터페이스')
# User inputs for keyword and selections
keyword = st.text_input('검색어를 입력하세요:', '')
selected_categories = st.multiselect('대분류를 선택하세요:', list(categories.keys()))
# Section selection based on selected categories
selected_section_labels = []
if selected_categories:
options = [section_labels[sec] for cat in selected_categories for sec in categories[cat]]
selected_section_labels = st.multiselect('세부 유형을 선택하세요:', options)
# Perform the search and provide results
if st.button('검색'):
if not selected_section_labels:
st.error("선택된 세부 유형이 없습니다.")
else:
selected_sections = [key for key, value in section_labels.items() if value in selected_section_labels]
combined_results = []
for section in selected_sections:
results = fetch_all_results(section, keyword)
combined_results.extend(results)
df = pd.DataFrame(combined_results)
st.write(df)
# Provide download option for CSV
if not df.empty:
csv = df.to_csv(index=False).encode('utf-8-sig')
st.download_button(label="CSV로 다운로드", data=csv, file_name=f"{keyword}_search_results.csv", mime='text/csv')
st.sidebar.info('이 앱은 한국고전종합DB에서 데이터를 검색하고 결과를 CSV 파일로 다운로드합니다. 검색어와 검색대상유형을 선택한 후 검색 버튼을 클릭하세요.')
저는 여기서 도대체 어떻게 100개 이상 검색값을 가져오는 것인지 독해가 안되어서.. 그리모어에게 이 코드가 어떻게 여러 개의 결과값을 가져올 수 있게 하는 거냐고 물어봤습니다. 아래와 같이 자세히 설명해주어서 파이썬 공부도 했습니다:)
그 후 스트림릿으로 실행을 해보았는데, 에러가 나왔어요. 에러 코드를 입력했더니 그리모어가 에러를 파악할 수 있게끔 코드를 다시 주었어요. 그 코드대로 실행했더니 다음과 같이 에러가 떴습니다.
그리모어에게 결과값 복붙해주었더니 json이 아니라 xml 형식으로 데이터를 가져와야 하는 거라고 하더라구요? 무슨 말인지 잘 모르겠지만… 어쨌든 그리모어가 데이터 형식을 수정해서 받아오는 코드를 새로 짜주었습니다.
최종 코드는 다음과 같이 얻었습니다.
import requests
import streamlit as st
import pandas as pd
import xml.etree.ElementTree as ET
API_URL = "http://db.itkc.or.kr/openapi/search"
section_labels = {
"BT_AA": "전체", "BT_SJ": "서지", "BT_AU": "저/편/필자", "BT_KW": "권차/문체명", "BT_GS": "기사명", "BT_BD": "본문",
"GO_AA": "전체", "GO_SJ": "서지", "GO_AU": "저/편/필자", "GO_KW": "권차/문체명", "GO_GS": "기사명", "GO_BD": "본문",
"MO_AA": "전체", "MO_SJ": "서지", "MO_AU": "저/편/필자", "MO_KW": "권차/문체명", "MO_GS": "기사명", "MO_BD": "본문",
"KP_AA": "전체", "KP_SJ": "서지", "KP_AU": "저/편/필자", "KP_KW": "권차/문체명", "KP_GS": "기사명", "KP_BD": "본문",
"JT_AA": "전체", "JT_SJ": "서지", "JT_GS": "기사명", "JT_BD": "본문",
"JR_AA": "전체", "JR_SJ": "서지", "JR_GS": "기사명", "JR_BD": "본문",
"ST_AA": "전체", "ST_SJ": "서지", "ST_GS": "기사명", "ST_BD": "본문",
"IT_AA": "전체", "IT_SJ": "서지", "IT_GS": "기사명", "IT_BD": "본문"
}
# Main categories and their possible sections
categories = {
"고전번역서": ["BT_AA", "BT_SJ", "BT_AU", "BT_KW", "BT_GS", "BT_BD"],
"고전원문": ["GO_AA", "GO_SJ", "GO_AU", "GO_KW", "GO_GS", "GO_BD"],
"한국문집총간": ["MO_AA", "MO_SJ", "MO_AU", "MO_KW", "MO_GS", "MO_BD"],
"한국고전총간": ["KP_AA", "KP_SJ", "KP_AU", "KP_KW", "KP_GS", "KP_BD"],
"조선왕조실록": ["JT_AA", "JT_SJ", "JT_GS", "JT_BD"],
"신역조선왕조실록": ["JR_AA", "JR_SJ", "JR_GS", "JR_BD"],
"승정원일기": ["ST_AA", "ST_SJ", "ST_GS", "ST_BD"],
"일성록": ["IT_AA", "IT_SJ", "IT_GS", "IT_BD"]
}
def fetch_all_results(secId, keyword):
all_documents = []
start = 0
rows = 100 # Adjust based on maximum allowed by the API or desired fetch size
while True:
params = {'secId': secId, 'keyword': keyword, 'start': start, 'rows': rows}
response = requests.get(API_URL, params=params)
if response.status_code != 200:
st.error(f"Failed to fetch data: {response.status_code} - {response.text}")
break
try:
# Parse the XML response
root = ET.fromstring(response.content)
for doc in root.findall('.//doc'):
document = {field.get('name'): field.text for field in doc.findall('field')}
all_documents.append(document)
if len(all_documents) < rows:
break # Break if there are no more documents to fetch
start += rows
except ET.ParseError as e:
st.error(f"XML parse error: {e}")
break
return all_documents
# Streamlit application setup and other functionalities remain the same
st.title('한국고전종합DB 검색 인터페이스')
keyword = st.text_input('검색어를 입력하세요:', '')
selected_categories = st.multiselect('대분류를 선택하세요:', list(categories.keys()))
selected_section_labels = []
if selected_categories:
options = [section_labels[sec] for cat in selected_categories for sec in categories[cat]]
selected_section_labels = st.multiselect('세부 유형을 선택하세요:', options)
if st.button('검색'):
if not selected_section_labels:
st.error("선택된 세부 유형이 없습니다.")
else:
selected_sections = [key for key, value in section_labels.items() if value in selected_section_labels]
combined_results = []
for section in selected_sections:
results = fetch_all_results(section, keyword)
combined_results.extend(results)
df = pd.DataFrame(combined_results)
st.write(df)
if not df.empty:
csv = df.to_csv(index=False).encode('utf-8-sig')
st.download_button(label="CSV로 다운로드", data=csv, file_name=f"{keyword}_search_results.csv", mime='text/csv')
st.sidebar.info('이 앱은 한국고전종합DB에서 데이터를 검색하고 결과를 CSV 파일로 다운로드합니다. 검색어와 검색대상유형을 선택한 후 검색 버튼을 클릭하세요.')
그랬더니 다음과 같이 일단은 실행되었어요.
스트림릿에서 잘 실행되었고, csv 파일도 위와 같이 잘 받았습니다.
추가과제
검색결과가 500건 정도 되는 건 러닝타임이 너무너무 깁니다. 4분 넘게 기다렸는데 실행이 완료되지 않아서 그냥 stop을 눌렀어요. 그리모어에게 물어보니 500건 정도 검색결과가 나올 때 예상 러닝타임은 15초라고 하는데, 뭔가 문제가 있는 것일지도 모르겠습니다. 러닝타임 문제 해결하는 게 제 다음 과제..
<검색필드>에 잡히는 내용은 아마 검색 시점에 디스플레이되는 항목 내의 내용만 잡아오는 듯합니다. 저는 해당 항목을 클릭해서 들어가서 (원문)과 (번역문) 전체를 긁어오는 작업이 필요한데… 이건 아마 고전종합DB에서 제공하는 호출키로 는 불가능한 작업인 것 같습니다. 셀레니움 등을 활용해서 직접 각 검색 항목을 클릭해서 들어가서 각각의 (원문), (번역문)을 복사해서 가져오는 작업을 코딩하는 게 제 두 번째 과제입니다.
사례글을 처음 게시해보는 거라 주저리주저리 길어진 게 아닌가 싶네요.
추가과제 관련해서 혹 도움 주실 수 있다면 너무너무 감사하겠습니다!!