IT/개발

Openai API를 이용하여 web 검색 하기 + 결과에서 얼굴이 있는 이미지만 저장하기(Web search, Responses API, opencv)

빗자루검 2025. 7. 16. 10:20
반응형

openai API를 사용해서 web search를 수행할 수 있습니다. 

API가 워낙에 잘 되어 있어서 몇줄 되지도 않네요. 그냥 그것만 하면 재미 없으니까 후처리를 좀 추가해 보겠습니다.

 

1. Web Search Tools 이란?

 

openai api에는 몇가지 tools을 사용할 수 있는 방법을 제공하고 있습니다. 

그중 web search tool을 사용하겠습니다. 

 

 

상세는 아래에서.

https://platform.openai.com/docs/guides/tools?api-mode=responses 

 

 

2. response 정보 확인하기

(API key 등록은 OPENAI_API_KEY라는 이름으로 먼저 환경변수에 등록해놓아야 합니다. 키 발급방법은 패스)

 
from openai import OpenAI           # OpenAI API를 사용하기 위한 패키지

# OpenAI 클라이언트 객체 생성
client = OpenAI()

# GPT-4.1 모델을 사용하여 웹 검색 프리뷰 도구로 질의 실행
response = client.responses.create(
    model="gpt-4.1",  # 사용할 모델 지정
    tools=[{"type": "web_search_preview"}],  # 웹 검색 프리뷰 도구 사용
    input="금발의 연예인 사진을 찾아줘"    # 검색할 내용 입력
)

print(response.output_text)
 

 

 

응답은 chatgpt의 설명과 함께 찾아낸  url이 한덩어리의 text로 되어 있습니다. 

제대로 사용하기 위해서는 문자열을 적당히 파싱해야 합니다. 

 

응답을 한번 보겠습니다.

 

"

금발 헤어스타일로 주목받은 여러 연예인들의 사진을 소개해 드리겠습니다.

**블랙핑크 로제**
로제는 금발로 변신한 후 더욱 화려한 미모를 뽐내며 팬들의 찬사를 받았습니다. ([insight.co.kr](https://www.insight.co.kr/news/233198?utm_source=openai))

**에스파 윈터**
윈터는 흑발과 금발 모두 잘 어울리는 '톤파괴자'로 알려져 있으며, 금발로 신비로운 매력을 발산합니다. ([allurekorea.com](https://www.allurekorea.com/2022/08/08/%EC%85%80%EB%9F%BD%EB%93%A4%EC%9D%98-%ED%9D%91%EB%B0%9Cvs%EA%B8%88%EB%B0%9C-%EB%85%BC%EC%9F%81/?utm_source=openai))

**전소미**
전소미는 금발로 염색한 후 바비인형 같은 비주얼로 화제를 모았습니다. ([news-ade.com](https://news-ade.com/star-ade/article/142403/?utm_source=openai))

**트와이스 사나**
사나는 금발로 변신하여 더욱 상큼한 매력을 선보였습니다. ([insight.co.kr](https://www.insight.co.kr/news/233198?utm_source=openai))

**소녀시대 태연**
태연은 다양한 금발 스타일을 시도하며 우아한 미모를 뽐냈습니다. ([news-ade.com](https://news-ade.com/star-ade/article/142403/?utm_source=openai))

이러한 연예인들의 금발 변신은 각자의 매력을 한층 더 돋보이게 하며 팬들의 큰 사랑을 받고 있습니다.

"

 

3. url 파싱 

 

응답 문자열에서 url만 잘래내서 배열로 저장합니다. 

 
# URL 추출
# 정규식을 사용하여 'https://' 부터 '?utm_source=openai' 전까지의 URL을 찾습니다.
url_pattern = re.compile(r'https://[^\s\)]+')
found_urls = url_pattern.findall(response.output_text)

# UTM 소스를 제거하여 순수 URL만 추출합니다.
cleaned_urls = [url.split('?utm_source=openai')[0] for url in found_urls]

print("추출된 URL 목록:")
for url in cleaned_urls:
    print(url)
 
 

 

추출된 URL 목록:
https://www.insight.co.kr/news/233198
https://www.allurekorea.com/2022/08/08/%EC%85%80%EB%9F%BD%EB%93%A4%EC%9D%98-%ED%9D%91%EB%B0%9Cvs%EA%B8%88%EB%B0%9C-%EB%85%BC%EC%9F%81/
https://news-ade.com/star-ade/article/142403/
https://www.insight.co.kr/news/233198
https://news-ade.com/star-ade/article/142403/

 

3. 사이트에서 이미지 다운로드 및 얼굴 찾기 

 

다운로드는 requests를 이용하고  얼굴찾기는 opencv에서 제공되는 사전 학습된 얼굴 탐지 모델을 사용합니다. 

모델파일은 먼저 내려받아서 파이썬 파일이 있는 폴더에 같이 저장해놓으면 됩니다. 

 

모델파일 위치 : 

https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml

 

이미지 중 특정 크기 이상, 얼굴을 포함한 이미지만 저장하고 얼굴이 없는 이미지는 따로 저장하도록 구현하였습니다. 

 

다음은 최종 코드 입니다 .

 

 
from openai import OpenAI           # OpenAI API를 사용하기 위한 패키지
import os                          # 파일 및 디렉토리 관리(경로, 폴더 생성 등)
import re                          # 정규표현식(문자열 검색 및 추출)
import io                          # 파일 및 스트림 입출력 처리
import requests                    # HTTP 요청(웹 페이지, 이미지 등 다운로드)
from urllib.parse import urlparse, urljoin  # URL 파싱 및 결합
from bs4 import BeautifulSoup      # HTML 파싱 및 웹 크롤링
from PIL import Image              # 이미지 파일 처리 및 변환
import cv2                         # OpenCV 라이브러리(이미지 처리, 얼굴 인식 등)
import numpy as np                 # 수치 연산 및 배열 처리

# OpenAI 클라이언트 객체 생성
client = OpenAI()

# GPT-4.1 모델을 사용하여 웹 검색 프리뷰 도구로 질의 실행
response = client.responses.create(
    model="gpt-4.1",  # 사용할 모델 지정
    tools=[{"type": "web_search_preview"}],  # 웹 검색 프리뷰 도구 사용
    input="금발의 연예인 사진을 찾아줘"    # 검색할 내용 입력
)

print(response.output_text)

# 사전 학습된 얼굴 탐지 모델 로드
# 얼굴 탐지 모델 파일 다운로드
# (링크를 마우스 오른쪽 버튼으로 클릭하여 '다른 이름으로 링크 저장')
# 다운로드한 haarcascade_frontalface_default.xml 파일을 파이썬 스크립트(.py)가 있는 동일한 폴더에 저장하기

face_cascade_path = 'haarcascade_frontalface_default.xml'
if not os.path.exists(face_cascade_path):
    print(f"오류: '{face_cascade_path}' 파일을 찾을 수 없습니다.")
    print("스크립트와 같은 폴더에 모델 파일을 다운로드해주세요.")
    exit()
face_cascade = cv2.CascadeClassifier(face_cascade_path)

# URL 추출
# 정규식을 사용하여 'https://' 부터 '?utm_source=openai' 전까지의 URL을 찾습니다.
url_pattern = re.compile(r'https://[^\s\)]+')
found_urls = url_pattern.findall(response.output_text)

# UTM 소스를 제거하여 순수 URL만 추출합니다.
cleaned_urls = [url.split('?utm_source=openai')[0] for url in found_urls]

print("추출된 URL 목록:")
for url in cleaned_urls:
    print(url)

# 이미지 다운로드 폴더 생성
# 저장할 폴더 2개 생성
face_folder = 'images'  # 얼굴 있는 이미지 폴더
noface_folder = 'noface' # 얼굴 없는 이미지 폴더
os.makedirs(face_folder, exist_ok=True)
os.makedirs(noface_folder, exist_ok=True)


# 각 URL을 순회하며 이미지 다운로드
for page_url in cleaned_urls:
    try:
        # 파일명에 사용할 사이트 이름 추출
        site_name = urlparse(page_url).netloc.replace('.', '_')
        image_counter = 1  # 각 사이트마다 카운터 초기화
       
        # 페이지에서 이미지 다운로드 시작 알림 출력
        print(f"\n[{page_url}] 페이지에서 이미지 다운로드를 시작합니다...")
       
        # User-Agent 헤더 설정 (크롤링 차단 방지용)
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
        # 해당 페이지 요청 및 응답 받기
        response = requests.get(page_url, headers=headers, timeout=10)
        response.raise_for_status()  # 요청 실패 시 예외 발생

        # BeautifulSoup을 사용하여 HTML 파싱
        soup = BeautifulSoup(response.text, 'html.parser')
        # 모든 <img> 태그 추출
        img_tags = soup.find_all('img')

        # 이미지 태그가 없으면 안내 후 다음 URL로
        if not img_tags:
            print(" -> 이 페이지에서 이미지를 찾을 수 없습니다.")
            continue

        # 각 이미지 태그 순회
        for img_tag in img_tags:
            img_url = img_tag.get('src')  # 이미지 URL 추출
            if not img_url:
                continue  # src 속성이 없으면 건너뜀

            # 상대경로일 경우 절대경로로 변환
            img_url = urljoin(page_url, img_url)
           
            # base64 등 data URI 형식은 건너뜀
            if img_url.startswith('data:image'):
                continue
               
           
            if img_url.startswith('//'):
                img_url = 'https:' + img_url

            try:
                # 이미지 다운로드 요청
                img_response = requests.get(img_url, headers=headers, stream=True, timeout=15)
                img_response.raise_for_status()
                img_data_bytes = img_response.content  # 이미지 바이너리 데이터

                # PIL을 사용하여 이미지 열기
                with Image.open(io.BytesIO(img_data_bytes)) as img:
                    width, height = img.size  # 이미지 크기 추출
                   
                    if width >= 300 and height >= 300:
                        # 2. 이미지를 OpenCV가 처리할 수 있는 형태로 변환
                        image_stream = np.frombuffer(img_data_bytes, np.uint8)
                        image_for_cv = cv2.imdecode(image_stream, cv2.IMREAD_COLOR)

                        # 흑백으로 변환하여 탐지 성능 향상
                        gray = cv2.cvtColor(image_for_cv, cv2.COLOR_BGR2GRAY)

                        # 3. 얼굴 탐지 수행
                        # scaleFactor: 이미지 크기를 얼마나 줄일지 (1.1 = 10%)
                        # minNeighbors: 얼마나 많은 이웃 사각형이 겹쳐야 얼굴로 인정할지
                        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
                       
                         # 파일 확장자 결정
                        original_filename = os.path.basename(urlparse(img_url).path)
                        _, extension = os.path.splitext(original_filename)
                        if not extension: extension = '.jpg'
                       
                        # 새 파일 이름 생성
                        new_filename = f"{site_name}_img{image_counter:03d}{extension}"

                        # ⭐ 얼굴 유무에 따라 저장 경로 분기
                        if len(faces) > 0:
                            # 얼굴이 있으면 'images' 폴더에 저장
                            filepath = os.path.join(face_folder, new_filename)
                            print(f" -> 얼굴 {len(faces)}개 검출! '{face_folder}' 폴더에 저장합니다.")
                        else:
                            # 얼굴이 없으면 'noface' 폴더에 저장
                            filepath = os.path.join(noface_folder, new_filename)
                            print(f" -> 얼굴 없음. '{noface_folder}' 폴더에 저장합니다.")
                       
                        # 파일 저장
                        with open(filepath, 'wb') as f:
                            f.write(img_data_bytes)
                       
                        print(f" -> {new_filename} 저장 완료 (크기: {width}x{height})")
                        image_counter += 1 # 파일이 저장되었으므로 카운터 증가

                    else:
                        print(f" -> 크기 작음({width}x{height}). 건너뛰기")

            except Exception:
                # 이미지 다운로드, 처리 중 오류 발생 시 건너뛰기
                pass

    except Exception as e:
        print(f"페이지 접속 또는 처리 실패: {page_url} ({e})")

print("\n모든 작업이 완료되었습니다.")
 
 

 

 

4. 결과 확인 

 

이제 결과를 확인해 보겠습니다. 

금발의 연예인 사진 중 얼굴이 있는 것만 잘 저장이 되었을까요?

 

 

금발 연예인 키워드로 사이트를 먼저 찾고 얼굴이 있는 것들을 저장하니 사이트 안에 있는 다른 이미지들도 같이 저장되어 몇개 불필요한 사진도 있네요. 

 

대충 잘 돌아가는 듯 합니다. ^^

 

반응형