셀레니움은 이제 그만, Playwright로 네이버 뉴스 크롤링하기

Playwright는 마이크로소프트에서 개발한 오픈 소스 라이브러리로, 웹 브라우저를 자동화하기 위한 강력한 도구입니다. Node.js와 Python 등 여러 언어에서 사용 가능하며 Chrome, Firefox, WebKit, Electron 등 다양한 브라우저를 지원합니다. 웹 스크래핑부터 다양한 웹 애플리케이션 테스트까지 다양한 용도로 활용할 수 있습니다.

0. Playwright란?

Playwright는 마이크로소프트에서 개발한 오픈 소스 라이브러리로, 웹 브라우저를 자동화하기 위한 것입니다. 본래 웹 스크래핑을 위해 설계된 것은 아니지만, 브라우저와 설정을 적절히 조합하면 이 영역에서 탁월한 성능을 발휘합니다. Node.js와 Python 등 여러 언어에서 사용할 수 있으며, 현재 Chrome, Firefox, WebKit(모바일 Safari), Electron 등 여러 브라우저를 지원합니다.
☝️ 해당 포스팅에서는 Python을 사용합니다.

 
 

1. Playwright 설치

playwright 패키지 설치:
pip install playwright
필수 브라우저 설치:
playwright install

 
 

2. 브라우저 켜기

테스트로 네이버 홈페이지에 접속해보도록 하겠습니다.
# 패키지 import import asyncio # 비동기 라이브러리 from playwright.async_api import Playwright, async_playwright async def run(playwright: Playwright) -> None: # headless=False => 브라우저 GUI 모드로 실행 browser = await playwright.chromium.launch(headless=False) # 크롬 사용 context = await browser.new_context() # 새 콘텍스트 생성 page = await context.new_page() # 새 페이지 열기 await page.goto("<https://naver.com/>") # 네이버 홈페이지로 이동 await context.close() # 콘텍스트 종료 await browser.close() # 브라우저 종료 async def main() -> None: async with async_playwright() as playwright: await run(playwright) asyncio.run(main()) # 실행
notion image
위 코드 실행 결과, 브라우저는 '시크릿 모드'로 띄워졌고 네이버 홈페이지에 정상적으로 접속한 것을 볼 수 있습니다.

 
 

3. 네이버 뉴스 크롤링

네이버 뉴스의 검색 결과 리스트를 크롤링 해보겠습니다.

3.1. URL 구하기

URL 규칙을 찾아 본 결과는 다음과 같습니다:
f"<https://search.naver.com/search.naver?where=news&query=>{인코딩된 검색어}&sort={정렬 기준}&field=0&pd=3&start={시작 페이지}"
  • 인코딩된 검색어: quote_plus 메서드를 사용하여 검색어를 넣습니다.
  • 정렬 옵션: {"관련도순": 0, "최신순": 1, "오래된순": 2}
  • 시작 페이지: {1페이지: 1, 2페이지: 11}과 같이 페이지 당 10씩 증가합니다.
따라서, 검색 URL은 다음과 같습니다:
# 정렬 옵션("관련도순")과 시작 페이지("1")은 고정 search_url = f"<https://search.naver.com/search.naver?where=news&query={quote_plus(keyword)}&sort=0&field=0&pd=3&start=1>"
notion image
해당 URL로 접속하게 되면 입력한 키워드에 대한 네이버 뉴스 검색 결과를 볼 수 있어요.

3.2. 수집할 데이터 + xpath

  • 기사 리스트: //*[@class='list_news']/li
  • 기사 URL: .//a[@class='news_tit']href 속성
  • 언론사: .//a[@class='info press']
  • 작성일자: .//div[@class='info_group']/span[@class='info']
  • 제목: .//a[@class='news_tit']title 속성
  • 본문 요약: .//div[@class='news_dsc']

3.3. 크롤링 방법

3.3.1. xpath로 요소 선택하기

playwright에서는 다음과 같이 xpath로 요소를 가져옵니다:
title_element = await page.query_selector("xpath=//a[@class='news_tit']")
await로 query_selector 메서드가 완료될 때까지 코드 실행을 일시 중지하게 됩니다. 인자로 xpath를 넣어주되, "xpath=" 뒤에 적어주도록 합니다.

3.3.2. 텍스트 가져오기

HTML 요소의 텍스트는 inner_text() 메서드를 사용하여 가져옵니다. 그러나 playwright에서는 비동기로 웹 요소에 접근하므로 query_selector().inner_text()와 같이 요소를 선택하면서 텍스트를 가져올 수 없습니다. await로 앞의 작업이 완료될 때까지 기다리도록 합니다.
# HTML 요소 선택 title_element = await page.query_selector("xpath=//a[@class='news_tit']") # 텍스트 가져오기 title_text = await title_element.inner_text()

3.3.3. 속성 가져오기

notion image
위와 같은 HTML 요소에서 속성을 가져오는 것은 get_attribute() 함수를 사용합니다. 마찬가지로 await을 사용합니다.
# HTML 요소 선택 title_element = await page.query_selector("xpath=//a[@class='news_tit']") # href 속성 가져오기 news_link = await title_element.get_attribute("href") # title 속성 가져오기 title = await title_element.get_attribute("title")

3.4. 크롤링 코드

3.4.1. 패키지 Import

import re import asyncio from pprint import pprint from datetime import datetime, timedelta from urllib.parse import quote_plus from playwright.async_api import Playwright, async_playwright

3.4.2. 작성일자 변환 함수

일반적인 케이스:
notion image
문제의 케이스:
notion image
작성일자 xpath를 .//div[@class='info_group']/span[@class='info']로 설정했을 때, 위 사진의 "16면 TOP"과 "4시간 전"의 class가 'info'로 같아 "16면 TOP"을 가져오게 되어 에러가 발생하는 케이스가 존재했습니다. 때문에 시간에 "면"이 포함된다면 에러 메시지를 return하고, 그 외의 경우에는 날짜 형식을 %Y-%m-%d 형식으로 변환하는 함수를 추가했습니다.
async def convert_write_date(raw_date): now = datetime.now() if "면" in raw_date: return f"[error] raw_date is not datetime form: {raw_date}" if "분 전" in raw_date: minutes = re.findall('\\d+', raw_date)[0] article_date = now - timedelta(minutes=float(minutes)) elif "시간 전" in raw_date: hours = re.findall('\\d+', raw_date)[0] article_date = now - timedelta(hours=float(hours)) elif "일 전" in raw_date: days = re.findall('\\d+', raw_date)[0] article_date = now - timedelta(days=float(days)) else: article_date = datetime.strptime(raw_date, "%Y-%m-%d") article_date = datetime.strftime(article_date, "%Y-%m-%d") return article_date

3.4.3. run 함수

async def run(playwright: Playwright) -> None: # 파라미터 설정 keyword = input("Keyword: ") # 컨텍스트 및 브라우저 설정 browser = await playwright.chromium.launch(headless=False) context = await browser.new_context() page = await context.new_page() # 브라우저 이동 search_url = f"<https://search.naver.com/search.naver?where=news&query={quote_plus(keyword)}&sort=0&field=0&pd=3&start=1>" await page.goto(search_url) # 검색 결과 리스트 articles = await page.query_selector_all("xpath=//*[@class='list_news']/li") # 각 리스트에 대해 반복 for article in articles: article_info = {} # 기사 URL, 제목 title_element = await article.query_selector("xpath=.//a[@class='news_tit']") article_info["URL"] = await title_element.get_attribute("href") article_info["제목"] = await title_element.get_attribute("title") # 언론사 press_element = await article.query_selector("xpath=.//a[@class='info press']") raw_press = await press_element.inner_text() article_info["언론사"] = raw_press.replace("언론사 선정", "") # 작성일자 date_element = await article.query_selector("xpath=.//div[@class='info_group']/span[@class='info']") raw_date = await date_element.inner_text() # raw_date에 에러 메시지가 있을 경우 HTML 재선택 if "[error]" in await convert_write_date(raw_date): date_element_list = await article.query_selector_all("xpath=.//div[@class='info_group']/span[@class='info']") date_element = date_element_list[-1] # 마지막 요소 raw_date = await date_element.inner_text() if "[error]" in await convert_write_date(raw_date): raise # 작성일자 찾지 못해 에러 발생 article_info["작성일자"] = await convert_write_date(raw_date) else: article_info["작성일자"] = await convert_write_date(raw_date) # 본문 요약 summary_element = await article.query_selector("xpath=.//div[@class='news_dsc']") summary = await summary_element.inner_text() article_info["본문 요약"] = summary print("=" * 70) pprint(article_info) # 컨텍스트 및 브라우저 종료 await context.close() await browser.close()

3.4.4. run 함수 실행

async def main() -> None: async with async_playwright() as playwright: await run(playwright) asyncio.run(main())

3.5. 수집 결과

다음은 "AI"로 검색한 네이버 뉴스 검색 결과입니다:
Keyword: AI ====================================================================== {'URL': '<http://www.newsis.com/view/?id=NISX20230908_0002443154&cID=10406&pID=13100>', '본문 요약': '정부가 군 장병과 대학생 등을 대상으로 국방 인공지능(AI) 기술 활용 대회를 진행한다. 청년들의 AI 기술 활용 능력을 ' "통해 국방 분야 난제 해결에 나선다는 목표다. 과학기술정보통신부와 국방부는 '2023 제2회 국방...", '언론사': '뉴시스', '작성일자': '2023-09-08', '제목': "과기정통부, 국방부와 '국방 AI' 경진대회 개최…총 상금 8200만원"} ====================================================================== {'URL': '<https://www.mk.co.kr/article/10825161>', '본문 요약': '마이크로소프트가 인공지능(AI) 기술을 이용해 암 식별 프로그램을 개발한다. MS는 7일(현지시각) 디지털 병리학 ' '제공업체인 페이지(Paige)와 협력해 세계 최대의 이미지 기반 AI 모델을 구축하고 있다고 밝혔다. 이 AI...', '언론사': '매일경제', '작성일자': '2023-09-08', '제목': '“희소암까지 싹 잡아낸다”…MS, AI 기반 암 식별 프로그램 개발'} ====================================================================== {'URL': '<https://zdnet.co.kr/view/?no=20230907220708>', '본문 요약': '초거대 인공지능(AI)을 전산업으로 확산하기 위한 산‧학‧연 전문가 협의체가 만들어졌다. 과기정통부는 이런 목적을 가진 ' "'AI 데이터 융합 네트워크' 발족식을 8일 서울 롯데백화점 인근 이비스 앰배서더 명동 호텔 19층...", '언론사': '지디넷코리아', '작성일자': '2023-09-08', '제목': '"초거대AI 전산업 확산"···AI데이터 융합 네트워크 발족'} ====================================================================== {'URL': '<https://www.news1.kr/articles/5164994>', '본문 요약': '박정환 문화전문기자 = 인공지능(Ai)이 국내 거주 중인 외국인과 다문화 가정 자녀들의 한국어 실력을... 열리는 ' "'제1회 AI 한국어 말하기 대회'에서다. 제1회 AI 한국어 말하기 대회는 한글과컴퓨터는 자회사...", '언론사': '뉴스1', '작성일자': '2023-09-08', '제목': '예선 심사위원은 AI…외국인·다문화가정 자녀의 한국어 실력 경연대회'} ====================================================================== {'URL': '<https://www.yna.co.kr/view/AKR20230908046351017?input=1195m>', '본문 요약': 'AI) 기술로 진화한 플랫폼을 만들겠다는 비전을 제시했다. 카카오모빌은 8일 서울 강남구 그랜드인터컨티넨탈 ' "서울파르나스에서 올해로 2회째를 맞는 '넥스트 모빌리티'(NEMO) 행사를 열어 '우리의 세상을 이해하는 AI...", '언론사': '연합뉴스', '작성일자': '2023-09-08', '제목': '카카오모빌 "내년 상반기까지 모빌리티 특화 생성AI 엔진 구축"(종합)'} ====================================================================== {'URL': '<https://www.munhwa.com/news/view.html?no=2023090801071607275001>', '본문 요약': '■ 카카오모빌리티‘네모2023’ 내년 상반기 생성AI 엔진 구축 이동·물류·배송분야 적용 계획 모임장소 제안서 ' '택시호출까지 맞춤여행경로 생성서비스 제시 ‘AI랩’서 자율주행로봇 등 전시 카카오모빌리티가 내년...', '언론사': '문화일보', '작성일자': '2023-09-08', '제목': '“모빌리티에 특화된 생성형 AI엔진 구축”'} ====================================================================== {'URL': '<http://www.edaily.co.kr/news/newspath.asp?newsid=01420246635737824>', '본문 요약': '프라이빗 뱅커(PB) 1000여 명이 2차전지 다음으로 인공지능(AI)·반도체 테마 상장지수펀드(ETF)를... ' '‘인공지능(AI)&반도체’가 335명(32%)의 선택을 받아 1위를 했다고 7일 밝혔다. 이어 249명(23%)이 ' '△‘2차전지&전기차’...', '언론사': '이데일리', '작성일자': '2023-09-07', '제목': '"2차전지 다음은 AI·반도체"…PB 1000명이 주목한 이유'} ====================================================================== {'URL': '<https://www.etoday.co.kr/news/view/2282422>', '본문 요약': '인공지능(AI)보이스봇이 안정적으로 정착되면서, 상담사가 보다 섬세한 고객 케어에 집중할 수 있었던 결과라고 설명했다. ' 'KT는 2017년부터 준비를 시작해, 2021년 4월, 365일 24시간 상담이 가능한 AI 보이스봇 ‘지니’...', '언론사': '이투데이', '작성일자': '2023-09-08', '제목': 'KT, 10년 연속 우수 콜센터…AI가 응대하고 사람이 집중케어'} ====================================================================== {'URL': '<http://news.mt.co.kr/mtview.php?no=2023090810400076118>', '본문 요약': '월간큐브/나무는 고객의 월별 투자활동을 시각화해 보여주고 해당 월의 시황을 반영한 인공지능(AI) 음악과 함께 제공하는 ' '서비스다. 투자자들의 즐거운 순간을 포착해 투자에 대한 긍정적인 경험을 강화하기 위해...', '언론사': '머니투데이', '작성일자': '2023-09-08', '제목': '내 투자가 AI 음악으로…NH투자증권, 월간큐브·월간나무 출시'} ====================================================================== {'URL': '<https://www.hankyung.com/article/202309083127i>', '본문 요약': '중국에서 인공지능(AI) 백가쟁명(百家爭鳴) 시대가 열렸다. 기존 중국 정보기술(IT) 업계 강호인 바이두... 다만 ' "공산당의 사상 검열로 인해 중국산 AI가 글로벌 경쟁에서 뒤처질 경우 '그들만의 리그'가 될 수 있다는...", '언론사': '한국경제', '작성일자': '2023-09-08', '제목': '중국 \\'AI 백가쟁명\\' 시대 열렸다…"100개 넘는 모델이 전쟁"'}
 
 

4. 결론

사실 playwright는 웹 크롤링을 위한 것이 아니라, 웹 테스트 및 자동화를 위한 프레임워크입니다.
그러나 playwright는 웹 크롤링에서도 아주 강력한 도구입니다. 본문에서는 다루지 못했지만 다양하고 편리한 기능들을 가지고 있습니다. Chromium, Firefox, Webkit 등 여러 브라우저를 지원하여 브라우저나 엔진에 제한되지 않은 웹 크롤링을 가능하게 하며, 특히 모던 웹 사이트에 대해 높은 호환성을 가집니다. 또한 필요 없는 이미지나 stylesheets의 로딩을 건너뛰어 크롤링 성능을 향상시킬 수도 있습니다.
우리는 웹 크롤링을 할 때 거의 bs4 또는 selenium을 사용하게 됩니다. 이는 이미 훌륭한 라이브러리이지만, playwright도 사용해보면서 위와 같은 장점을 느껴보면 더욱 좋을 것 같습니다.