์ง๋ ๊ธ์ ์ด์ด ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ํฌ๋กค๋งํ๊ณ , pinecone์ ์ ์ฅํ๋ ์์ ์ ์งํํ๋ค. ์ฌ์ค ์ด๋ค DB๋ฅผ ์ฌ์ฉํ ๊น ๊ณ ๋ฏผํ๋ ์ด์ ๋, vectorDB์ vector๋ง ์ ์ฅํ ์ ์๋ ์ค ์์๊ธฐ ๋๋ฌธ์ด์๋ค. ํ์ง๋ง ์ฐพ์๋ณด๋, ์ ๋ง ๋คํ์ค๋ฝ๊ฒ๋ metadata๋ก string, integer, bool ์ ๋์ ๊ธฐ๋ณธ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ์ ์๋ค๋ ๊ฒ์ ์๊ฒ ๋์๋ค. ๊ทธ๋ ๊ฒ pinecone์ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ค. ์๋๋ ๊ตฌํ ๊ณผ์ ์ด๋ค.
1. ํฌ๋กค๋ง ํ sqlite3์ ์ ์ฅ
์ง๋ ๊ธ์๋ ์ก์ฝ๋ฆฌ์์์ '์ง์ ํ์ฌ, ์ง์ ์๊ธฐ, ์คํ, ์ง๋ฌธ, ๋ต๋ณ' ์ด๋ ๊ฒ 5๊ฐ์ง์ ํญ๋ชฉ์ ์ ์ฅํ๋ ค๊ณ ํ์๋ค. ๊ทธ๋ฌ๋, ์ด์ฐจํผ ๋ฒกํฐ ์ ์ฌ๋๋ฅผ ๊ตฌํ ๊ฑฐ์์ผ๋ฉด ์ง๋ฌธ๊ณผ ๋ต๋ณ ์๋ง ์ ์ง์ฌ์ ธ ์์ผ๋ฉด ๋ ๊ฒ ๊ฐ๋ค๊ณ ์๊ฐํ์ฌ, ๋๋จธ์ง๋ฅผ ๋ชจ๋ ์ญ์ ํ๊ณ ์ง๋ฌธ, ๋ต๋ณ ๋ฆฌ์คํธ๋ง ํฌ๋กค๋งํ๋ ์ฝ๋๋ก ๋ฐ๊พธ์๋ค.
#jobkorea.py
def self_introduction_crawl(driver: webdriver.Chrome, file_url):
question_list = []
answer_list = []
driver.get(file_url)
paper = driver.find_element(By.CLASS_NAME, "qnaLists")
questions = paper.find_elements(By.TAG_NAME, "dt")
# print("ํ์ฌ ์ง๋ฌธ")
for index in questions:
question = index.find_element(By.CLASS_NAME, "tx")
if question.text == "":
index.find_element(By.TAG_NAME, "button").click()
question = index.find_element(By.CLASS_NAME, "tx")
question_list.append(question.text)
else:
question_list.append(question.text)
# print(question_list)
driver.implicitly_wait(3)
answers = paper.find_elements(By.TAG_NAME, "dd")
driver.implicitly_wait(3)
# print("๋ต๋ณ")
for index in range(len(answers)):
answer = answers[index].find_element(By.CLASS_NAME, "tx")
if answer.text == "":
questions[index].find_element(By.TAG_NAME, "button").click()
answer = answers[index].find_element(By.CLASS_NAME, "tx")
answer_list.append(answer.text)
# print(answer_list)
return {
"question_list": question_list,
"answer_list": answer_list
}
์ด ์ฝ๋๋ฅผ ์ด์ฉํด์ quesiton_list, answer_list๋ฅผ ๋ฐ์์์, ์๋ฒ ๋ฉ ๋ฐ ์ ์ฒ๋ฆฌ๋ฅผ ํ ํ, vectorDB์ ๋ฃ์ ์๊ฐ์ด์์ง๋ง,,
์ด ๊ณผ์ ์ ํ๊บผ๋ฒ์ ํ๋ ๊ฒ์ ๋๋ฌด ์ํํ ๊ฒ ๊ฐ๋ค๊ณ ์๊ฐํ๋ค.
์ค๋น๋ ์์์ ๋งํฌ๋ ์ฝ 950๊ฐ์๊ณ , ๊ฐ ๋งํฌ๋ง๋ค ํ๊ท 3~4๊ฐ์ ์ง๋ฌธ์ด ์์ผ๋ฏ๋ก, ์ต์ 3000๊ฐ์ ์ง๋ฌธ-๋ต๋ณ ์์ด ๋์ฌ ๊ฒ์ธ๋ฐ, ํฌ๋กค๋ง ๋๋ ๋ฐ์ดํฐ ์ ์ฌ ๋์ค ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ๋๊ธด ์ง์ ๋ถํฐ ๋ค์ ํด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ด์, ์์ ์ฑ์ ์ํด sqlite3๋ผ๋ ๊ฒฝ๋ DB์ ๋จผ์ ํฌ๋กค๋ง ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ , ์ดํ์ vectorDB์ ์ ์ฌํ๋ ๋ฐฉํฅ์ผ๋ก ๊ฒฐ์ ํ๋ค.
๊ทธ๋์ ์๋์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ค.
# ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ
conn = sqlite3.connect('crawling_data.db')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS data
(Question TEXT, Answer TEXT)''')
# ๋ฐ์ดํฐ ์ ์ฅ ํจ์
def save_to_db(question, answer):
# ์ค๋ณต ๊ฒ์ฌ
cursor.execute("SELECT * FROM data WHERE Question = ? AND Answer = ?", (question, answer))
if cursor.fetchone() is None:
cursor.execute("INSERT INTO data (Question, Answer) VALUES (?, ?)", (question, answer))
conn.commit()
# ์๋ฌ๋ ํ ๋ค์ ์๋ํ์ฌ ์ฒ์ n์ค์ ๊ฑด๋๋ฐ๊ธฐ ์ํจ.
crawled = 499
for _ in range(crawled):
next(file)
read_count = crawled
qa_data = []
while True:
file_url = file.readline()
print(read_count, "๋ฒ์งธ ์ค")
if file_url == "":
break
try:
qa_result = jobkorea.self_introduction_crawl(driver=driver, file_url=file_url)
question_list = qa_result["question_list"]
answer_list = qa_result["answer_list"]
for index in range(len(question_list)):
question = question_list[index]
answer = answer_list[index]
# DB์ ์ ์ฅ
save_to_db(question, answer)
qa_data.append({
"Question": question,
"Answer": answer
})
except Exception as e:
print(f"{read_count}๋ฒ์งธ์์ ๋ค์ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค: {e}")
read_count += 1
์ด๋ ๊ฒ ์ ๋ฐ๋ถ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ, table ์์ฑํ์ฌ, ์ค๋ณต๋ ๋ฐ์ดํฐ๊ฐ ์๋์ง์ ๋ํด ๊ฒ์ฌํ๋ ์ฝ๋๋ ์์ฑํ๋ค.
์ดํ, Question, Answer ์์ save_to_db() ํจ์๋ฅผ ์ด์ฉํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๋ค.
๋ค๋ง, ์ด ๊ณผ์ ์์ SSL ์ธ์ฆ ๋๋ฌธ์ ์๋ฌ๊ฐ ๋ฐ์ํ์ฌ ๋๊ธฐ๋ ๊ฒฝ์ฐ๊ฐ ์์๋๋ฐ, ์ด๋ print() ๋ฌธ์ผ๋ก ๊ณ์ ๋ก๊น ํ๋ค๊ฐ ๋๊ธด ๋งํผ ๋ฐ์ด๋๊ณ ์ ์ฅํ๋ ๋ฐฉ์์ผ๋ก ์งํํ๋ค.
์ด๋ ๊ฒ ์ฑ๊ณต์ ์ผ๋ก sqlite3์ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ์ ์ฅํ ์ ์์๋ค.
2. Pinecone ์ ์ฅ: ๊ณต์๋ฌธ์๋ถํฐ ํจ์จ์ ์ธ ์ ์ฅ๊น์ง
์ด์ ์์์ ์ ์ฅํ ๋ฐ์ดํฐ๋ค์ ๋ถ๋ฌ์ pinecone์ ์ ์ฅํ๋ ์์ ์ ๊ฑฐ์ณ์ผ ํ๋ค. ์ผ๋จ pinecone์ ๋ํด์ ๋๋ฌด ๋ชฐ๋ผ์, ๊ณต์ ๋ฌธ์๋ฅผ ๊ณต๋ถํด ๋ณด์๋ค.
์ฐ์ pinecone api key๋ฅผ ๋ฐ๊ธ๋ฐ์ init ์ํค๊ณ , ์ฐ๊ฒฐํ์ฌ upsert() ํจ์๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๋ฃ๋ ๋ฐฉ์์ธ ๊ฒ ๊ฐ์๋ค. (๋ฌผ๋ก upsert ์ ์ ๋ฒกํฐ๋ก ๋ฐ๊พธ์ด์ผ ํ๋ค.)
๊ทธ๋ฆฌ๊ณ ์ค์ํ ๋ฉํ๋ฐ์ดํฐ. ๋ค์ ํญ๋ชฉ๋ค๋ง ๊ฐ๋ฅํ๋ค๊ณ ํ๋๋ผ.
์ฌ์ค ์ธ๊ฑด number, string ๋ฐ์ ์์๊ธฐ์, ์ด ์ ๋๋ฉด ์์ ์ถฉ๋ถํ๋ค.
๋ง์ง๋ง์ผ๋ก query๋ก ์กฐํํ๋ฉด ๋ค์์ฒ๋ผ ๋์จ๋ค๊ณ ํ๋ค.
return ๋๋ ํญ๋ชฉ์ ๋ณด๋, ๋ฑ ํ์ํ ํญ๋ชฉ๋ค๋ง ์์ด์ ๋ง์์ ๋ค์๋ค.
์ด์ ์ด๊ฑธ ์ฝ๋๋ก ๊ตฌํํด ๋ณด์.
๋จผ์ , ์ฝ์ ๊ฒ์ฒ๋ผ init ํ๊ณ , ์ ์ฅํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ datas๋ผ๊ณ ์ด๋ฆ ๋ถ์๋ค.
with open("secrets.json", "r") as file:
secrets = json.load(file)
pinecone.init(api_key=secrets["PINECONE_API_KEY"], environment="gcp-starter")
index = pinecone.Index('resumai-self-introduction-index')
#๋ฐ์ดํฐ ๋ก๋ฉ
conn = sqlite3.connect('crawling_data.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM data")
datas = cursor.fetchall()
conn.close()
๊ทธ๋ฆฌ๊ณ print(len(data)๋ฅผ ํด๋ดค๋๋ฐ 3800๊ฐ๊ฐ ๋์๋ค.
์ด๋ฅผ ๋ณด๊ณ ์๊ฐ์ด ๋ ๊ฑด, '๊ณผ์ฐ ์ด 3800๊ฐ์ ๋ฐ์ดํฐ๋ฅผ for๋ฌธ์ ํตํด ํ๋ํ๋ upsert ํด์ผ ํ๋?'์๋ค.
์ผ๋จ ๋๋ฌด ๋นํจ์จ์ ์ด๋ผ๊ณ ์๊ฐํ๊ธฐ๋ ํ๊ณ , ์์ฒญ ํ์์ ์ ํ์ ์์ ๊ฒ ๊ฐ๊ธฐ๋ ํ๋ค.
์๋๋ ๋ค๋ฅผ๊น, batch๋ก ๋ง๋ค์ด ์ ์ฅํ๋ ๋ฐฉ์์ด ๊ณต์๋ฌธ์์ ์๋ ค์ ธ ์์๋ค.
๊ทธ๊ฒ์ ์ฐธ๊ณ ํ์ฌ ์ฝ๋๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
with pinecone.Index('resumai-self-introduction-index', pool_threads=30) as pinecone_index:
async_results = [
pinecone_index.upsert(vectors=ids_vectors_chunk, async_req=True)
for ids_vectors_chunk in chunks(generate_data(datas), batch_size=100)
]
[async_result.get() for async_result in async_results]
์ฌ๊ธฐ์ chunks์ generate_data๋ lib ํ์ผ ๋ด์ ์ ์ฅํ๋ค. ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค:
def chunks(iterable, batch_size=100):
it = iter(iterable)
chunk = tuple(itertools.islice(it, batch_size))
while chunk:
yield chunk
chunk = tuple(itertools.islice(it, batch_size))
def generate_data(datas):
for index, data in enumerate(datas):
question = data[0]
answer = data[1]
qa = question + "\n\n" + answer
qa_embedding = get_embedding(qa)
qa_index = index + 1
print(qa_index)
yield (
str(qa_index),
qa_embedding,
{
"question": question,
"answer": answer,
"qa_index": qa_index
}
)
์ด๋ ๊ฒ ์์ฑํจ์ผ๋ก์จ, 100๊ฐ์ ๋ฌถ์์ผ๋ก ๋ ๋ฐฐ์น๋ฅผ 38๋ฒ (3800 / 100 = 38)๋ง ๋ณด๋ด๋ฉด ๋์๊ณ ,
์ด ๋ํ ๋น๋๊ธฐ ์์ฒญ์ ์ฌ์ฉํจ์ผ๋ก์จ, ์ฌ๋ฌ ์ ๋ก๋ ์์ฒญ์ ๋์์ ๋ณด๋ผ ์ ์์ผ๋ฉฐ, ๊ฐ ์์ฒญ์ด ๋ ๋ฆฝ์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ฏ๋ก ์ ์ฒด ์ ๋ก๋ ์๊ฐ์ ๋จ์ถํ ์ ์์๋ค.
์ต์ข ์ ์ผ๋ก, pinecone์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ ๋ค์๊ณผ ๊ฐ์๋ค.
pinecone์ ๋ํด ์์ ๋ชฐ๋ผ์ ์ด๊ฒ์ ๊ฒ ๋ง์ ธ๋ดค๋๋ฐ, ๋๋ฌด ์ ๊ธฐํ๋ค.
์ด๋ ๊ฒ, ์ฟผ๋ฆฌ๋ฅผ ์ง์ ๋ ๋ ค๋ณด๊ณ , topK๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ๋ ํ ์ ์์๋ค. (์ฝ๊ฐ postman ๊ฐ์ ๋๋ ???)
์๋ฌดํผ ๋ฑ ์ํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ฑ ์๋ง๊ฒ ์ฐพ์์ ๋คํ์ด์๊ณ , ์๊ฐ๋ณด๋ค ์ฝ๊ฒ ์ ์ฅํด์ ๋คํ์ด์๋ค.
3. ์ ๋ฆฌ ๋ฐ ์์ฌ์ด ์
์ด๋ ๊ฒ pinecone์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ๊ฒ๊น์ง ๋๋ด๋ดค๋ค.
ํ๋ก์ ํธ๊ฐ ์ด๋ ์ ๋ ์์ฑ๋๊ณ ๋๋ฉด, ์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ํฌ๋กค๋ฌ๋ถํฐ, pinecone์ ์ ์ฅํ๋ ๊ฒ๊น์ง ์๋ํํด๋ณด๊ณ ์ถ๋ค๊ณ ์๊ฐํ๋ค. ๊ทธ๋ผ ์ต์ ๋ฐ์ดํฐ๊ฐ ํจ์จ์ ์ผ๋ก ์ ์ ์ฌ๋ ์ ์์ง ์์๊น..ใ ใ
์์ฌ์ ๋ ์ ์, pinecone์ 3800๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๋ ์ฌ์ด ํ ๊ฒ ์์ด์ sqlite์ ์ ์ฅํ๋ ๋ฐ์ดํฐ๋ค์ ์ข ๋๋ฌ๋ดค๋๋ฐ,
๋ฌด์จ ์ด์ํ ๋ฌธ์๋ค์ด ์์๊ณ , ๋ง์ง๋ง์๋ ๊ธ์์๊ฐ ๋ช ๊ฐ์ธ์ง ์ธ์ด ์ฃผ๋ ๊ฒ๋ ํจ๊ป ํฌ๋กค๋ง๋ผ ์์๋ค.
์... ๋ฐ์ดํฐ๋ฅผ ์ ํ์ธํ์ด์ผ ํ๋๋ฐ,,, ๋๋ฌด ์์ฝ์ง๋ง ์ง๊ธ ๋น์ฅ์ ๋ฃ์ด๋์ ๋ฐ์ดํฐ๋ก ์งํํ๊ณ , ์ข ์ถํ์ ์ ์ฒ๋ฆฌํ๋ ๋ก์ง๋ ์ง๋ณด๊ธฐ๋ก ํ๋ค.
๋ค์๋ถํฐ๋ ๋ฌด์กฐ๊ฑด ํฌ๋กค๋งํ๊ณ ๋ฐ์ดํฐ๋ฅผ ๋จผ์ ๋ณด๋ ์ต๊ด์ ๋ค์ฌ์ผ๊ฒ ๋ค !!!!!
'๐ป ํ๋ก์ ํธ > ๐ RESUMAI' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[RESUMAI] 6. ์๋น์ค ๊ฐ๋ฐ ์์ - DRF ์นด์นด์ค ์์ ๋ก๊ทธ์ธ, ์ด๊ฒ๋ง ๋ณด๋ฉด ํ๋ฒ์ ๋!! (0) | 2024.05.25 |
---|---|
[RESUMAI] 5. ์๊ธฐ์๊ฐ์ ์์ฑ ๋ฐ๋ชจ ์๋น์ค (1) | 2024.01.03 |
[RESUMAI] 3. RAG์ ๋์ ๊ณผ ์๊ธฐ์๊ฐ์ ํฌ๋กค๋ง (1) | 2023.12.21 |
[RESUMAI] 2. ํ๋กฌํํธ ์คํ (2) | 2023.11.12 |
[RESUMAI] 1. ์ฐ๋ก ๋ง๋๋ ์์์์ ์์ / ๊ธฐํ (0) | 2023.11.03 |