- 작성 중
크롤링
-
크롤링을 하기 위해서 python, html, css를 이해하는 것이 선제되어야 합니다.
-
(주의) 밑의 코드는 네이버 블로그를 크롤링 한 파일입니다. 네이버에서 내부 클래스 명이 자주 바뀌므로 동작하지 않을 수 있습니다.
-
필요성 네이버 지도를 통한 데이터를 가져올때 모든 데이터를 하나씩 입력하는 것이 오랜 시간이 걸립니다.
-
또한 데이터를 다양하게 수집해 필터링 한다면 데이터를 유용하게 관리할 수 있다는 판단하에 시작하게 되었습니다.
-
우선 검색어를 필터링 하기 위해 시도 정보를 가져옵니다.
-
시도 정보 가져오기
import requests
import json
import xml.etree.ElementTree as ET
import os
url = 'http://api.vworld.kr/req/data'
params = {
'service': 'data',
'request': 'GetFeature',
'data': 'LT_C_ADSIDO_INFO',
'key': "개인 키",
'geometry': 'false',
'size': '100',
'geomFilter': 'BOX(124,33,132,43)'
}
try:
# API 요청 보내기
response = requests.get(url, params=params)
response.raise_for_status() # HTTP 에러 발생 시 예외 발생
# JSON 데이터 파싱
data = response.json()
# ctp_kor_nm 값 추출하여 리스트로 저장
ctp_kor_nm_list = [feature['properties']['ctp_kor_nm'] for feature in data['response']['result']['featureCollection']['features']]
# 결과 출력
print(ctp_kor_nm_list)
except requests.exceptions.RequestException as e:
print('API 요청 중 오류 발생:', e)
except KeyError as e:
print('JSON 데이터 파싱 중 오류 발생 - 필드가 존재하지 않음:', e)
except Exception as e:
print('오류 발생:', e)
root = ET.Element("data")
try:
for ctp_kor_nm in ctp_kor_nm_list:
# API 요청 파라미터 설정
params = base_params.copy()
params['attrFilter'] = f"full_nm:like:{ctp_kor_nm}"
# API 요청 보내기
response = requests.get(url, params=params)
response.raise_for_status() # HTTP 에러 발생 시 예외 발생
# JSON 데이터 파싱
data = response.json()
# XML에 데이터 추가
for feature in data['response']['result']['featureCollection']['features']:
item = ET.SubElement(root, "item")
ctp_kor_nm_elem = ET.SubElement(item, "ctp_kor_nm")
ctp_kor_nm_elem.text = ctp_kor_nm
sig_kor_nm = ET.SubElement(item, "sig_kor_nm")
sig_kor_nm.text = feature['properties']['sig_kor_nm']
except requests.exceptions.RequestException as e:
print('API 요청 중 오류 발생:', e)
except KeyError as e:
print('JSON 데이터 파싱 중 오류 발생 - 필드가 존재하지 않음:', e)
except Exception as e:
print('오류 발생:', e)
else:
# XML 파일 저장
desktop_path = os.path.expanduser("~/Desktop")
xml_filename = os.path.join(desktop_path, "sigg_data.xml")
tree = ET.ElementTree(root)
tree.write(xml_filename, encoding='utf-8', xml_declaration=True)
print(f"XML 파일이 {xml_filename}에 저장되었습니다.")
# ctp_kor_nm_list를 저장할 sido.xml 파일 생성 및 저장
sido_root = ET.Element("sido")
for ctp_kor_nm in ctp_kor_nm_list:
ctp_kor_nm_elem = ET.SubElement(sido_root, "ctp_kor_nm")
ctp_kor_nm_elem.text = ctp_kor_nm
sido_xml_filename = os.path.join(desktop_path, "sido.xml")
sido_tree = ET.ElementTree(sido_root)
sido_tree.write(sido_xml_filename, encoding='utf-8', xml_declaration=True)
print(f"XML 파일이 {sido_xml_filename}에 저장되었습니다.")
크롤링 시작하기
-
가져온 시도 정보를 이용해 키워드를 조합하고 크롤링을 시작합니다.
-
목표는 필터링을 통해 리뷰순으로 만들고, 장소들 정보를 excel로 만드는 것입니다.
-
다만 이 글에는 장소들 정보를 가져오는 것만 기재되어 있습니다.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from time import sleep
import random
import re
from selenium import webdriver
import sys
options = webdriver.ChromeOptions()
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3')
options.add_argument('window-size=1380,900') ## 윈도우 사이즈 설정
driver = webdriver.Chrome(options=options)
# 대기 시간
driver.implicitly_wait(time_to_wait=3)
# 반복 종료 조건
loop = True
# 네이버 지도 페이지 접속
driver.get("https://map.naver.com")
time.sleep(3) # 페이지 로딩 대기
# 검색창 찾기
search_box = driver.find_element(By.CSS_SELECTOR, "input.input_search")
searchList = "강서구 카페" # 이것은 리스트로 넣어서 따로 for문을 돌려도 됩니다.
search_box.send_keys(searchList)
search_box.send_keys(Keys.RETURN)
# 검색 결과 로딩 대기
time.sleep(1)
def switch_left():
############## iframe으로 왼쪽 포커스 맞추기 ##############
driver.switch_to.parent_frame()
iframe = driver.find_element(By.XPATH,'//*[@id="searchIframe"]')
driver.switch_to.frame(iframe)
def switch_right():
############## iframe으로 오른쪽 포커스 맞추기 ##############
a = 3
try:
if a > 0:
driver.switch_to.parent_frame()
iframe = driver.find_element(By.XPATH,'//*[@id="entryIframe"]')
driver.switch_to.frame(iframe)
else:
time.sleep(1)
except:
time.sleep(1)
switch_right()
while(True):
switch_left()
############## 맨 밑까지 스크롤 ##############
scrollable_element = driver.find_element(By.CLASS_NAME, "Ryr1F")
last_height = driver.execute_script("return arguments[0].scrollHeight", scrollable_element)
while True:
# 요소 내에서 아래로 600px 스크롤
driver.execute_script("arguments[0].scrollTop += 600;", scrollable_element)
# 페이지 로드를 기다림
sleep(1) # 동적 콘텐츠 로드 시간에 따라 조절
# 새 높이 계산
new_height = driver.execute_script("return arguments[0].scrollHeight", scrollable_element)
# 스크롤이 더 이상 늘어나지 않으면 루프 종료
if new_height == last_height:
break
last_height = new_height
############## 현재 page number 가져오기 - 1 페이지 ##############
page_no = driver.find_element(By.XPATH,'//a[contains(@class, "mBN2s qxokY")]').text
# 현재 페이지에 등록된 모든 가게 조회
# 첫페이지 광고 2개 때문에 첫페이지는 앞 2개를 빼야함
if(page_no == '1'):
elemets = driver.find_elements(By.XPATH,'//*[@id="_pcmap_list_scroll_container"]//li')[2:]
else:
elemets = driver.find_elements(By.XPATH,'//*[@id="_pcmap_list_scroll_container"]//li')
print('현재 ' + '\033[95m' + str(page_no) + '\033[0m' + ' 페이지 / '+ '총 ' + '\033[95m' + str(len(elemets)) + '\033[0m' + '개의 가게를 찾았습니다.\n')
for index, e in enumerate(elemets, start=1):
final_element = e.find_element(By.CLASS_NAME,'CHC5F').find_element(By.XPATH, ".//a/div/div/span")
print(str(index) + ". " + final_element.text)
print("-"*50)
switch_left()
sleep(2)
for index, e in enumerate(elemets, start=1):
store_name = '' # 가게 이름
category = '' # 카테고리
new_open = '' # 새로 오픈
rating = 0.0 # 평점
visited_review = 0 # 방문자 리뷰
blog_review = 0 # 블로그 리뷰
store_id = '' # 가게 고유 번호
address = '' # 가게 주소
business_hours = [] # 영업 시간
phone_num = '' # 전화번호
switch_left()
# 순서대로 값을 하나씩 클릭
e.find_element(By.CLASS_NAME,'CHC5F').find_element(By.XPATH, ".//a/div/div/span").click()
sleep(2)
switch_right()
################### 여기부터 크롤링 시작 ##################
title = driver.find_element(By.XPATH,'//div[@class="zD5Nm undefined"]')
store_info = title.find_elements(By.XPATH,'//div[@class="YouOG DZucB"]/div/span')
# 가게 이름
store_name = title.find_element(By.XPATH,'.//div[1]/div[1]/span[1]').text
# 카테고리
category = title.find_element(By.XPATH,'.//div[1]/div[1]/span[2]').text
if(len(store_info) > 2):
# 새로 오픈
new_open = title.find_element(By.XPATH,'.//div[1]/div[1]/span[3]').text
###############################
review = title.find_elements(By.XPATH,'.//div[2]/span')
# 인덱스 변수 값
_index = 1
# 리뷰 ROW의 갯수가 3개 이상일 경우 [별점, 방문자 리뷰, 블로그 리뷰]
if len(review) > 2:
rating_xpath = f'.//div[2]/span[{_index}]'
rating_element = title.find_element(By.XPATH, rating_xpath)
rating = rating_element.text.replace("\n", " ")
_index += 1
try:
# 방문자 리뷰
visited_review = title.find_element(By.XPATH,f'.//div[2]/span[{_index}]/a').text
# 인덱스를 다시 +1 증가 시킴
_index += 1
# 블로그 리뷰
blog_review = title.find_element(By.XPATH,f'.//div[2]/span[{_index}]/a').text
except:
print('------------ 리뷰 부분 오류 ------------')
# 가게 id
store_id = driver.find_element(By.XPATH,'//div[@class="flicking-camera"]/a').get_attribute('href').split('/')[4]
# 가게 주소
address = driver.find_element(By.XPATH,'//span[@class="LDgIH"]').text
try:
driver.find_element(By.XPATH,'//div[@class="y6tNq"]//span').click()
# 영업 시간 더보기 버튼을 누르고 2초 반영시간 기다림
sleep(2)
parent_element = driver.find_element(By.XPATH,'//a[@class="gKP9i RMgN0"]')
child_elements = parent_element.find_elements(By.XPATH, './*[@class="w9QyJ" or @class="w9QyJ undefined"]')
for child in child_elements:
# 각 자식 요소 내에서 클래스가 'A_cdD'인 span 요소 찾기
span_elements = child.find_elements(By.XPATH, './/span[@class="A_cdD"]')
# 찾은 span 요소들의 텍스트 출력
for span in span_elements:
business_hours.append(span)
# 가게 전화번호
phone_num = driver.find_element(By.XPATH,'//span[@class="xlx7Q"]').text
except:
print(print('------------ 영업시간 / 전화번호 부분 오류 ------------'))
print(f'{index}. ' + str(store_name)' · ' + str(category) + str(new_open))
print('평점 ' + str(rating) + ' / ' + visited_review + ' · ' + blog_review)
print(f'가게 고유 번호 -> {store_id}')
print('가게 주소 ' + str(address))
print('가게 영업 시간')
for i in business_hours:
print(i.text)
print('')
print('가게 번호 ' + phone_num)
print("-"*50)
switch_left()
loop = False