class CRAW_ETC:
def __init__(self):
self.site_specific_classes = {
'naver.com': [
'se-main-container', # 네이버 블로그
'se_component_wrap',
'se-component-content',
'article_viewer', # 네이버 카페
'article_view',
'entryDetailView' # 네이버 지식백과
],
'namu.wiki': [
'wiki-content', # 나무위키
'content'
],
'tistory.com': [
'tt_article_useless_p_margin',
'contents_style',
'entry-content',
'post-content'
],
'brunch.co.kr': [
'wrap_article', # 브런치
'wrap_body'
],
'wikipedia.org': [
'mw-content-text',
'mw-parser-output'
],
'yonhapnews.co.kr': [
'article', # 연합뉴스
'article-txt'
],
'chosun.com': [
'article-body', # 조선비즈
'news-article-body'
],
'default': [
'article',
'post-content',
'entry-content',
'content',
'main-content',
'post-body',
'article-body'
]
}
def check_url(self, session, url) :
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 = session.get(url, headers=headers)
if response.status_code != 200 :
return False, None
soup = BeautifulSoup(response.text, 'html.parser')
if soup.title :
title = soup.title.text
else :
title = url
return True, title
def get_iframe_url(self, soup, base_url, iframe_id='mainFrame'):
"""iframe URL 추출 (네이버 블로그/카페 등)"""
iframe = soup.find('iframe', id=iframe_id)
if iframe and 'src' in iframe.attrs:
iframe_src = iframe['src']
if iframe_src.startswith('/'):
from urllib.parse import urljoin
iframe_src = urljoin(base_url, iframe_src)
return iframe_src
return None
def extract_content(self, soup, url):
# 2. URL에서 도메인 추출
domain_match = re.search(r'^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n?]+)', url)
if not domain_match:
return "유효하지 않은 URL입니다."
domain = domain_match.group(1)
# 3. 도메인에 맞는 클래스 목록 선택
classes = None
for key in self.site_specific_classes:
if key in domain:
classes = self.site_specific_classes[key]
break
if not classes:
classes = self.site_specific_classes['default']
# 4. 본문 클래스 탐색
content = None
found_class = None
for class_name in classes:
content = soup.find('div', class_=class_name)
if content:
found_class = class_name
break
# 5. 클래스 탐색 실패 시 article, main, section 태그 시도
if not content:
content = soup.find('article')
found_class = 'article' if content else None
if not content:
for tag in ['main', 'section']:
content = soup.find(tag, class_=re.compile(r'content|article|post|main', re.I))
if content:
found_class = f"{tag} with content-related class"
break
# 6. 본문 텍스트 추출
if content:
# <p> 태그 우선, 없으면 전체 텍스트 추출
# paragraphs = content.find_all('p')
paragraphs = soup.find_all(['p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
if paragraphs:
full_text = '\n'.join(p.get_text(strip=True) for p in paragraphs if p.get_text(strip=True))
else:
full_text = content.get_text(strip=True, separator='\n')
if full_text:
LOGGER.info(f"본문 클래스 발견: {found_class}\n본문 내용:\n{full_text}")
return True, full_text
else:
LOGGER.info(f"본문 클래스 발견: {found_class}\n본문 텍스트를 추출할 수 없습니다.\nHTML 일부:\n{content.prettify()[:500]}")
return True, content.prettify()
else:
# 디버깅: 발견된 div 클래스 출력
divs = soup.find_all('div', class_=True)
classes_found = [div.get('class') for div in divs[:10]]
LOGGER.info(f"본문을 찾을 수 없습니다.\n발견된 div 클래스들: {classes_found}")
return True, soup
def crawl_website(self, url):
"""웹사이트 본문 크롤링"""
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'
}
# 1. 웹페이지 요청
try:
response = requests.get(url, headers=headers, timeout=10)
except requests.RequestException as e:
LOGGER.info(f"페이지 요청 실패: {e}")
LOGGER.info(f"페이지 요청 실패: {response.status_code}")
if response.status_code != 200:
None, False
# 2. BeautifulSoup 객체 생성
soup = BeautifulSoup(response.text, 'html.parser')
if soup.title :
filename = soup.title.text
else :
filename = url
LOGGER.info(f'[crawl_website] filename : {filename}')
# 3. 네이버 블로그/카페 iframe 처리
if 'blog.naver.com' in url:
iframe_url = self.get_iframe_url(soup, url)
if iframe_url:
try:
response = requests.get(iframe_url, headers=headers, timeout=10)
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser')
else:
LOGGER.info(f"iframe 요청 실패: {response.status_code}")
return None, False
except requests.RequestException as e:
LOGGER.info(f"iframe 요청 실패: {e}")
return None, False
else:
LOGGER.info("iframe URL을 찾을 수 없습니다.")
return None, False
# 4. 본문 추출
res, return_data = self.extract_content(soup, url)
html_bytes = return_data.encode('utf-8')
filename = f'{filename}.html'
return UploadFile(file=io.BytesIO(html_bytes), filename=filename), True
카테고리 없음