Notice
Recent Posts
Recent Comments
Link
관리 메뉴

왕초보 코딩 개발 일지 블로그

[23.05.03] 웹크롤링 라이브러리 (한솥 도시락, pokemon, kakaomap) 본문

Python 공부/웹크롤러 대마왕편 (이론)

[23.05.03] 웹크롤링 라이브러리 (한솥 도시락, pokemon, kakaomap)

아캔두우잇 2023. 5. 4. 16:03
반응형

한솥 도시락 (메뉴이름 | 가격 수집)

1. Selenium Method 활용

2. 메뉴이름, 가격 수집하기

3. 더보기 계속 자동적으로 클릭하기 (try / except)

4. DataFrame

# 라이브러리 import 
from selenium import webdriver as wb

# 구분자
from selenium.webdriver.common.by import By

# 컴퓨터용 키보드 할당 - Keys의 'K' 대문자!!!
from selenium.webdriver.common.keys import Keys

#요청할 때 지연시간을 주기 위한 library
import time
    # 지연시간 없이 크롤링을 하면, 봇으로 인식되어 막혀버린다. 
    # 아무리 인터넷이 빠르고 좋아도 여전히 페이지는 데이터를 로딩하는 시간이 필요하다. 
    # 이 부분이 매치가 안 될 경우 데이터가 뽑히지 않을 수 있다. 
    
# 진행상황을 progress bar로 시각해주는 library
from tqdm import tqdm

import pandas as pd

브라우저 오픈

# 사용자가 크롬창을 열듯, 컴퓨터에게 명령하자 
driver = wb.Chrome()
driver.get('https://www.hsd.co.kr/')

# 웹페이지가 축소될 경우, html 구조가 변경될 수 있다. 
# 최대한 최대창으로 진행을 권장한다.
driver.maximize_window()
# menu -> 전체메뉴

menu = driver.find_element(By.XPATH,'//*[@id="gnb"]/div[2]/ul/li[3]/p/a')
all_menu = driver.find_element(By.XPATH,'//*[@id="gnb"]/div[2]/ul/li[3]/div/ul/li[1]/a')

Action Chain 으로 메뉴에 접근해보자

# Action Chain이란?
    # selenium 에서 제공하는 기능 중 하나, 마우스, 키보드의 액션을 생성할 수 있다.

from selenium.webdriver.common.action_chains import ActionChains
# action 만들기 / 변수 생성
action = ActionChains(driver)

# 첫번째 관문, MENU로 이동
    # 사용자가 마우스를 MENU 버튼으로 이동하듯
    # 컴퓨터에게 Menu 버튼 위로 마우스 커서를 이동하라고 명령
    # 우리는 이 행위를 코드로 작성한 것
action.move_to_element(menu)

# 두번째 관문, '전체 메뉴 클릭'
action.click(all_menu)
time.sleep(1)

# 그리고, 해당 작업을 수행해줘
action.perform()
# 더보기 버튼 수집 

btn = driver.find_element(By.XPATH, '//*[@id="btn_more"]/span/a')
btn.click

요소들 출력

# 상품 제목을 뽑아보자

title = driver.find_elements(By.CSS_SELECTOR, 'h4.h.fz_03')
# 요소'들'이니까 title을 한번 추출해보자
for i in title:
    print(i.text)
# 가격을 출력해보자
price = driver.find_elements(By.CSS_SELECTOR,'div.item-price')
for i in price:
    print(i.text)
# 엇 쌤, 처음에는 10개 항목만 추출이 됐는데, 다시 실행해보니 요소들이 다 뽑혀요

# 코드 문제는 아니다

# 그 이유는
    # 클라이언트(브라우저)가 서버에서 이미 모든 데이터를 다 불러온 상태
    # 그렇기 때문에 브라우저를 종료한 후에도 다시 접속해서 
    # 더보기 버튼을 누르지 않아도 전체 목록을 열람할 수 있었다. 
    
 # 더보기 버튼을 누르는데 지장이 있지 않을까?
    # 만약에 예외처리를 하지 않았다면, 문제발생 가능 
    # 하지만 우리는 곧 예외 처리를 해줄 거기 때문에 
        # 더보기 버튼이 없어도 크롤링 끊기지 않고 진행되게 해줄 거다.

반복문을 통해서 데이터 크롤링 진행

# 리스트 생성

title_list = []
price_list = []

try:#시도해봐(아래 블럭의 코드를 실행해봐)
    while True : # 무한루프로 돌리다가 더보기 버튼이 없으면 오류가 나서 except를 실행하게 됨.
        # 더보기 버튼 선언
        btn = driver.find_element(By.XPATH,'//*[@id="btn_more"]/span/a')
        # 더보기 버튼 클릭
        btn.click()
        # 지연시간을 2초 정도
        time.sleep(2)
        
        # 여기까지, 더보기 버튼이 계속 있다면, 클릭을해주면서 계속 돌아갑니다.
except:
    #예외의 경우
    
    # 이 실습에서 예외의 경우란, 더보기 버튼이 없는 경우
    # 예외처리가 안되어 있을 경우, 더보기 버튼이 없을 시,
        # 동작은 되지만 데이터가 잘 안 뽑힐 수 있고,
        # 에러를 배출하면서 아예 크롤러가 종료 될 수 있다.
        
    # 1. 우선 '클릭완료' '계속 진행합니다'라는 문구를 출력
    print('더보기 클릭 완료, 계속 진행합니다. ')
    
title = driver.find_elements(By.CSS_SELECTOR,'h4.h.fz_03')
price = driver.find_elements(By.CSS_SELECTOR,'div.item-price')

for i in tqdm(range(len(title))):
    title_list.append(title[i].text)
    price_list.append(price[i].text)
    
driver.quit()

print('크롤링 완료 수고하셨습니다.')
len(title_list), len(price_list)
(95, 95)

DataFrame에 담기

# 딕셔너리로 우선 선언을 해준다.
    # 데이터 구조가 보존되므로,
        # Pandas DataFrame으로 변환할 때 일관성이 유지된다고 말씀드렸다.
#DataFrame으로 변환을 해줘야 함

menu_price_dic = {'menu':title_list, 'price':price_list}
menu_price_df = pd.DataFrame(menu_price_dic)
menu_price_df

# 혹시 중복되는 값이 있을까?
menu_price_df.drop_duplicates()
menu_price_df

EXCELL로 저장

# 엑셀로 내보내기
menu_price_df.to_excel('C:/Users/user14/WebCrawling/menu_price_df.xlsx', encoding = 'euc-kr')

포켓몬 이미지 수집

# 라이브러리 import

# 브라우저 할당
from selenium import webdriver as wb

# 키보드 할당
from selenium.webdriver.common.keys import Keys

# 구분자
from selenium.webdriver.common.by import By

import time 

from tqdm import tqdm

import pandas as pd

############### 여기는 친숙 ##################


import os
    # os -? 운영체제 
    # os library는 운영체제와 상호작용을 하게 해주는 라이브러리
    # 파일, 폴더를 생성/삭제 등 파일 관리에 좀 더 중점
    
    
from urllib.request import urlretrieve
    # 해당 라이브러리는 인터넷에서 파일을 다운로드 하는 함수
    # 다운로드 하려는 파일의 url과 파일 명을 지정하면,
        # 해당 파일을 인터넷에서 다운 로컬 파일 시스템에 저장해주는 라이브러리
# 크롬창을 열자!

driver = wb.Chrome()
driver.get('https://pokemonkorea.co.kr/pokedex')

# 웹페이지가 축소될 경우, html 구조가 변경될 수 있다. 
# 최대한 최대창으로 진행을 권장한다.
driver.maximize_window()
# body를 선언
    # 크롤링할 이미지는 body에 있다. 
    # 그렇기 때문에, body란 변순를 우선 선언해준다. 
body = driver.find_element(By.TAG_NAME, 'body')
# for loop? while loop?

# body를 선언해줬다.
    # 하지만 크롤링이 끝나고, body는 사라지지않고 계속 존재하게 된다. 
    # 끝내주게 브레이크를 걸어주지 않는 이상
    # 계속 존재하는 body 때문에 무한루프에 빠질 수 있다.

for loop으로 추출

# range -> 스크롤 횟수 

# 스크롤을 20회 정도 해보자

for i in tqdm(range(20)):
    # body 태그를 body 변수에 지정 후 Scroll buttonn 끝까지 내리기
    body = driver.find_element(By.TAG_NAME,'body')
    body.send_keys(Keys.END)
    time.sleep(2)
# 스크롤을 통해서 포켓몬 이미지를 서버로부터 브라우저에 로드

# 이미지
imgs_url = driver.find_elements(By.CSS_SELECTOR,'img.img-fluid')

# imgs_url의 번째 요소에서 src란 요소를 가져와줘
    # 기존 텍스트 추출에 .text를 사용했다면
        # get_attribute() -> 속성을 가져와줘
imgs_url[0].get_attribute('src')
# src url 받아왔다
    # srcList 란 빈 그릇, 빈 리스트에 담아줌
    
src_List =[]

for i in imgs_url :
    src_List.append(i.get_attribute('src'))
len(src_List)
1440

폴더 생성

# 사용자가 바탕화면에서 이미지나 파일을 담을 때, 폴더를 생성
    # 하지만 우리는 이 작업까지 코드로 
        # 어떻게 ? os 라이브러리를 사용해서~

# 1. 만약 경로 상에 Pokemon 폴더가 없다면:
if not os.path.isdir('C:/Users/user14/Desktop/Pokemon'):
    
# 2. 경로 내 폴더를 만들어줘, Poketmon이란 폴더 
os.mkdir('C:/Users/user14/Desktop/Pokemon')

 

파이썬(Python)의 isdir() 함수는 디렉토리의 존재 여부를 확인하여 True / False로 반환하여 주는 함수입니다. 

mkdir의 뜻은 make directory의 약자로 디렉토리(폴더)를 생성할 때 사용하는 명령어입니다. 

# 우리가 가져온 것은 이미지 데이터가 아닌, 
    # src url (소스 유알엘) 이다.
# 그럼 온전한 이미지 파일로 변환하고 폴더에 저장시켜줘야겠네
    # 함수 urlretrieve()를 사용!!! 

cnt = 1
# cnt는 파일이 순차적으로 변환되면서, 파일명을 1씩 증가시켜줌

# 반복문으로 집어넣어보자, 그리고 이름도 바꿔주자 

for i in src_List:
    
    # 1번째 파일에서 해당 경로의 Pokemon 폴더 내,
    # .jpg확장자 이름으로 변환해줘
    # 문자열인 cnt로 명명된 파일들을 
    
    urlretrieve(i,"C:/Users/user14/Desktop/Pokemon/"+str(cnt)+".jpg")
    
    # 첫번째 항목이 완료되었을 경우, cnt + 1 증가시키기
    
    cnt += 1
    
    # + 부호는 말그대로 더하기 혹은 '결합'을 의미한다
        # cnt는 숫자인데
        # 파일명을 저장할 때는 "문자"로 저장해야한다. *** str으로 변환
    
    # 컴퓨터는 확장자 구분에 엄청 깐깐한데
        # 정작 사용자의 명령없이는 센스없이 확장자를 생성해주지 않는다.
        # 그래서 뒤에 + .jpg라고 붙여줘야 붙여준다.

Kakao map을 이용한 이수 맛집 싸그리 검색

# BeautifulSoup + Selenium을 같이 조화롭게 코드를 작성
# 왜?
    # Selenium은 브라우저 제어 등 자동화에 아주 특화
    # but Selenium 자체로 html 파싱, 데이터 추출에 어느 정도 한계가 있다. 
    # 그렇기 때문에 각자의 장점을 살려서, 짬뽕
    
    
    
# 파싱 (Parsing)이란?
# - 웹페이지에서 원하는 데이터를 추출, 가공하기 쉬운 상태로 변환하는 것
# - 예를 들면, 생선을 낚았다 최종적으로 통조림의 형태로 만들어야 하는데, 손질 하려는 절차를 파싱이라고 한다.

# 생선과는 달리, 웹페이지에 존재하는 데이터는 list, dictionary와 같은 자료구조와 다르다. 

# 그래서 parser라는 함수나 프로그램으로 다루기 쉬운 형태로 바꾸어 주는데

# 이 과정을 '파싱'이라고 한다.
# 라이브러리 import 

from selenium import webdriver as wb
from bs4 import BeautifulSoup as bs

from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

import time 
from tqdm import tqdm
import pandas as pd
# 브라우저 할당 
driver = wb.Chrome()
driver.get('https://map.kakao.com/')
driver.maximize_window()
# 파란색 팝업 창 기억나는가?
    # 이 부분을 사용자가 클릭해서 없애듯, 
    # 컴퓨터에게도 똑같이 시켜줘야 한다.
    
click_blue_popup = driver.find_element(By.CSS_SELECTOR,'body > div.coach_layer.coach_layer_type1 > div > div > div > span')
click_blue_popup.click()
# 검색창 지정 (마우스로 올려놓기)

search = driver.find_element(By.CSS_SELECTOR, '#search\.keyword\.query')

# 검색어 입력('이수역 맛집')

search.send_keys('이수역 맛집')

# ENTER키 누르고 데이터가 로드할 수 있는 시간까지 할당해주기

search.send_keys(Keys.ENTER)
time.sleep(3)

BeautifulSoup으로 객체화

# 보다 더 빠른 데이터 수집을 위해 -> 추출하는 시간도 이제는 고려

soup = bs(driver.page_source,'lxml')
soup
# 식당이름 (titles)

titles = soup.select('a.link_name')

for i in titles:
    print(i.text)
# 식당 주소 -> addresses

addresses = soup.select('div.addr > p:nth-child(1)')

for i in addresses:
    print(i.text)
# 더보기 버튼 지정 및 클릭 
    # 기능적인 부분이니까 XPATH -> 동적인 요소 selenium 사용

click_more_location = driver.find_element(By.XPATH,'//*[@id="info.search.place.more"]')
click_more_location.click()
time.sleep(2)
# 더보기 버튼을 누른 후에는 , 번호 버튼들이 존재
    # 해당 항목들을 수집
page = driver.find_elements(By.CSS_SELECTOR,'#info\.search\.page > div > a')
page

크롤러 조각하기

# 빈 리스트 2개를 생성 


titles_list = []
addresses_list = []

try : # 시도해봐! 뭘? 아래 실행코드들을
    while True: # 조건이 참일 경우를 
        for i in tqdm(range(6)):
            # 만약 순차가 5 미만일 경우 
            if i < 5:
                # page 클릭
                # page = driver.find_elements(By.CSS_SELECTOR,'#info\.search\.page > div > a')
                    # find_elements 는 요소를 지정해주는 기능이지 값을 저장하는 함수가 아니다! 그래서 for문 반복문에 넣을 필요없음
                page[i].click()
                time.sleep(2)
                
                # beautifulsoup 객체화
                soup = bs(driver.page_source,'lxml')
                
                # 식당이름 
                    # 변수 = soup.select 선택한 데이터 값을 저장하는 함수! 그래서 페이지가 바뀔 때마다 새로 선언해줘야함
                titles = soup.select('a.link_name')
                
                # for문을 돌면서 titles에서 순차적으로 
                for i in titles :
                    # titles_list에 순차적으로 뽑은 요소의 텍스트만 넣어줘
                    titles_list.append(i.text)
                
                # 식당주소
                    # 변수 = soup.select 선택한 데이터 값을 저장하는 함수! 그래서 페이지가 바뀔 때마다 새로 선언해줘야함
                addresses = soup.select('div.addr > p:nth-child(1)')
                
                # for문을 돌면서 addresses에서 순차적으로 
                for i in addresses :
                    # addresses_list에 순차적으로 뽑은 요소의 텍스트만 넣어줘
                    addresses_list.append(i.text)
            else:
                # 그렇지 않은 경우는,
                # i가 5보다 큰 경우
                # 5번 버튼까지 다 돌고, 다음 버튼을 눌러야하는 경우
                click_next = driver.find_element(By.XPATH,'//*[@id="info.search.page.next"]')
                click_next.click()
                time.sleep(3)
except:
    print("수집이 완료되었습니다.")

driver.quit()
len(titles_list1), len(addresses_list1)
반응형