RAG: Apify로 웹사이트 크롤링하고 Milvus에 저장해 질문 답변하기 (Retrieval-Augmented Generation: Crawling Websites with Apify and Saving Data to Milvus for Question Answering)
이 튜토리얼에서는 Apify의 Website Content Crawler로 웹사이트를 크롤링하고, 데이터를 Milvus/Zilliz 벡터 데이터베이스에 저장한 뒤 질문 답변에 사용하는 방법을 설명해요.
Apify는 웹 스크래핑 및 데이터 추출 플랫폼으로, Actor라고 알려진 2천 개 이상의 준비된 클라우드 도구를 제공하는 앱 마켓플레이스를 갖추고 있어요. 이 도구들은 전자상거래 웹사이트, 소셜 미디어, 검색 엔진, 온라인 지도 등에서 구조화 데이터를 추출하는 사용 사례에 이상적이에요.
예를 들어 Website Content Crawler Actor는 웹사이트를 깊이 크롤링하고, 쿠키 모달, 푸터, 내비게이션을 제거해 HTML을 정리한 뒤 HTML을 Markdown으로 변환할 수 있어요.
Milvus/Zilliz용 Apify 통합은 웹에서 벡터 데이터베이스로 데이터를 업로드하는 것을 쉽게 만들어 줘요.
출처: Milvus 문서
본문
시작하기 전에 (Before you begin)
이 노트북을 실행하기 전에 다음을 준비했는지 확인하세요.
- Apify 계정과 APIFY_API_TOKEN
- OpenAI 계정과 OPENAI_API_KEY
- Zilliz Cloud 계정 (Milvus의 완전 관리형 클라우드 서비스)
- Zilliz 데이터베이스 URI와 Token
의존성 설치 (Install dependencies)
$ pip install --upgrade --quiet apify==1.7.2 langchain-core==0.3.5 langchain-milvus==0.1.5 langchain-openai==0.2.0
Apify와 OpenAI 키 설정 (Set up Apify and Open API keys)
import os
from getpass import getpass
os.environ["APIFY_API_TOKEN"] = getpass("Enter YOUR APIFY_API_TOKEN")
os.environ["OPENAI_API_KEY"] = getpass("Enter YOUR OPENAI_API_KEY")
Enter YOUR APIFY_API_TOKEN··········
Enter YOUR OPENAI_API_KEY··········
Milvus/Zilliz URI, token, 컬렉션 이름 설정 (Set up Milvus/Zilliz URI, token and collection name)
클라이언트를 설정하려면 Milvus/Zilliz의 URI와 Token이 필요해요.
- Docker 또는 Kubernetes에 Milvus 서버를 직접 배포했다면 서버 주소와 포트를 uri로 사용해요. 예:
http://localhost:19530. Milvus에서 인증 기능을 활성화했다면 “:” 형식의 토큰을 사용하고, 그렇지 않으면 token을 빈 문자열로 남겨 둬요. - Milvus의 완전 관리형 클라우드 서비스인 Zilliz Cloud를 사용한다면, Zilliz Cloud의 Public Endpoint and API key에 해당하는
uri와token을 조정해요.
컬렉션은 미리 존재할 필요가 없어요. 데이터를 데이터베이스에 업로드할 때 자동으로 생성돼요.
os.environ["MILVUS_URI"] = getpass("Enter YOUR MILVUS_URI")
os.environ["MILVUS_TOKEN"] = getpass("Enter YOUR MILVUS_TOKEN")
MILVUS_COLLECTION_NAME = "apify"
Enter YOUR MILVUS_URI··········
Enter YOUR MILVUS_TOKEN··········
Website Content Crawler로 Milvus.io에서 텍스트 콘텐츠 스크래핑하기
다음으로 Apify Python SDK와 함께 Website Content Crawler를 사용해요. 먼저 actor_id와 run_input을 정의하고, 벡터 데이터베이스에 저장할 정보를 지정해요.
actor_id="apify/website-content-crawler"는 Website Content Crawler의 식별자예요. 크롤러의 동작은 run_input 파라미터로 완전히 제어할 수 있어요(자세한 내용은 input page 참고). 이 예시에서는 Milvus 문서를 크롤링하는데, JavaScript 렌더링이 필요하지 않아요. 따라서 crawlerType=cheerio를 설정하고 startUrls를 정의하며 maxCrawlPages=10으로 크롤링할 페이지 수를 제한해요.
from apify_client import ApifyClient
client = ApifyClient(os.getenv("APIFY_API_TOKEN"))
actor_id = "apify/website-content-crawler"
run_input = {
"crawlerType": "cheerio",
"maxCrawlPages": 10,
"startUrls": [{"url": "https://milvus.io/"}, {"url": "https://zilliz.com/"}],
}
actor_call = client.actor(actor_id).call(run_input=run_input)
Website Content Crawler는 maxCrawlPages가 정한 사전 정의 한도에 도달할 때까지 사이트를 철저히 크롤링해요. 스크래핑된 데이터는 Apify 플랫폼의 Dataset에 저장돼요. 이 데이터에 접근하고 분석하려면 defaultDatasetId를 사용할 수 있어요.
dataset_id = actor_call["defaultDatasetId"]
dataset_id
'P9dLFfeJAljlePWnz'
다음 코드는 Apify Dataset에서 스크래핑된 데이터를 가져와 첫 번째로 스크래핑된 웹사이트를 보여 줘요.
item = client.dataset(dataset_id).list_items(limit=1).items
item[0].get("text")
'The High-Performance Vector Database Built for Scale\nStart running Milvus in seconds\nfrom pymilvus import MilvusClient client = MilvusClient("milvus_demo.db") client.create_collection( collection_name="demo_collection", dimension=5 )\nDeployment Options to Match Your Unique Journey\nMilvus Lite\nLightweight, easy to start\nVectorDB-as-a-library runs in notebooks/ laptops with a pip install\nBest for learning and prototyping\nMilvus Standalone\nRobust, single-machine deployment\nComplete vector database for production or testing\nIdeal for datasets with up to millions of vectors\nMilvus Distributed\nScalable, enterprise-grade solution\nHighly reliable and distributed vector database with comprehensive toolkit\nScale horizontally to handle billions of vectors\nZilliz Cloud\nFully managed with minimal operations\nAvailable in both serverless and dedicated cluster\nSaaS and BYOC options for different security and compliance requirements\nTry Free\nLearn more about different Milvus deployment models\nLoved by GenAI developers\nBased on our research, Milvus was selected as the vector database of choice (over Chroma and Pinecone). ...'
Milvus 데이터베이스에 데이터를 업로드하려면 Apify Milvus integration을 사용해요. 먼저 Milvus 데이터베이스용 파라미터를 설정해요. 다음으로 데이터베이스에 저장할 필드(datasetFields)를 선택해요. 아래 예시에서는 text 필드와 metadata.title을 저장해요.
milvus_integration_inputs = {
"milvusUri": os.getenv("MILVUS_URI"),
"milvusToken": os.getenv("MILVUS_TOKEN"),
"milvusCollectionName": MILVUS_COLLECTION_NAME,
"datasetFields": ["text", "metadata.title"],
"datasetId": actor_call["defaultDatasetId"],
"performChunking": True,
"embeddingsApiKey": os.getenv("OPENAI_API_KEY"),
"embeddingsProvider": "OpenAI",
}
이제 apify/milvus-integration을 호출해 데이터를 저장해요.
actor_call = client.actor("apify/milvus-integration").call(
run_input=milvus_integration_inputs
)
이제 스크래핑된 모든 데이터가 Milvus 데이터베이스에 저장되어 검색과 질문 답변을 준비하게 됐어요.
검색과 LLM 생성 파이프라인
다음으로 Langchain을 사용해 retrieval-augmented 파이프라인을 정의해요. 이 파이프라인은 두 단계로 동작해요.
- Vectorstore (Milvus): Langchain은 쿼리 임베딩을 저장된 문서 임베딩과 매칭해 Milvus에서 관련 문서를 검색해요.
- LLM 응답: 검색된 문서가 LLM(예: GPT-4)이 정보에 기반한 답변을 생성하도록 컨텍스트를 제공해요.
RAG 체인에 대한 자세한 내용은 Langchain 문서를 참고하세요.
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_milvus.vectorstores import Milvus
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Milvus(
connection_args={
"uri": os.getenv("MILVUS_URI"),
"token": os.getenv("MILVUS_TOKEN"),
},
embedding_function=embeddings,
collection_name=MILVUS_COLLECTION_NAME,
)
prompt = PromptTemplate(
input_variables=["context", "question"],
template="Use the following pieces of retrieved context to answer the question. If you don't know the answer, "
"just say that you don't know. \nQuestion: {question} \nContext: {context} \nAnswer:",
)
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{
"context": vectorstore.as_retriever() | format_docs,
"question": RunnablePassthrough(),
}
| prompt
| ChatOpenAI(model="gpt-4o-mini")
| StrOutputParser()
)
데이터베이스에 데이터가 있으면 질문을 시작할 수 있어요.
question = "What is Milvus database?"
rag_chain.invoke(question)
'Milvus is an open-source vector database specifically designed for billion-scale vector similarity search. It facilitates efficient management and querying of vector data, which is essential for applications involving unstructured data, such as AI and machine learning. Milvus allows users to perform operations like CRUD (Create, Read, Update, Delete) and vector searches, making it a powerful tool for handling large datasets.'
결론 (Conclusion)
이 튜토리얼에서는 Apify로 웹사이트 콘텐츠를 크롤링하고, Milvus 벡터 데이터베이스에 저장하며, retrieval-augmented 파이프라인으로 질문 답변 작업을 수행하는 방법을 보여 줬어요. Apify의 웹 스크래핑 기능과 Milvus/Zilliz의 벡터 저장소, 그리고 LLM용 Langchain을 결합하면 매우 효과적인 정보 검색 시스템을 구축할 수 있어요.
데이터 수집과 데이터베이스 업데이트를 개선하기 위해 Apify 통합은 증분 업데이트를 제공하는데, 이는 체크섬을 기반으로 새 데이터나 수정된 데이터만 업데이트해요. 또한 지정된 시간 내에 크롤링되지 않은 오래된 데이터를 자동으로 제거할 수도 있어요. 이 기능들은 벡터 데이터베이스를 최적화된 상태로 유지하고, 최소한의 수동 노력으로 retrieval-augmented 파이프라인이 효율적이고 최신 상태를 유지하도록 도와줘요.
Apify-Milvus 통합에 대한 자세한 내용은 Apify Milvus 문서와 통합 README 파일을 참고하세요.
더 알아보기 (Learn more)
- Apify Milvus 문서 — Apify-Milvus 통합
- Langchain 문서 — RAG 체인 튜토리얼
- Milvus quickstart — Milvus 서버 시작