데이터를 다루는 이야기를 하면 빼놓을 수 없는 이야기가 크롤링이다. 크롤링에 대해서는 많이 들어보았을텐데 스크래핑은 또 무엇일까?
정확히 우리가 아는 크롤링은 스크래핑이라고 볼 수 있다.
웹 크롤링 - 웹 크롤러(자동화 봇)가 일정 규칙으로 웹페이지를 브라우징 하는 것
웹 스크래핑 - 웹 사이트 상에서 원하는 정보를 추출하는 기술
엄밀히 말하면 둘은 다르고 구분되어야 한다. 쉽게 말해서 웹 크롤링은 그냥 돌아만 다니는 거고 스크래핑은 긁어 오는 것이다. 그래서 우리가 보통 크롤링을 한다고 하는것은 사실은 스크래핑이라고 할 수 있다. 그러나 묘한 용어적인 차이이므로 크게 신경쓰지는 않아도 된다.
이번에 특정 웹사이트들에서 정보를 모아서 저장해야했고, 파이썬의 대표적인 라이브러리인 beautifulsoup과 selenium을 활용한 바를 정리해보려 한다.
크롤링/스크래핑 하기
beautifulsoup & selenium
두 개의 라이브러리는 모두 크롤링을 위해 필요한데, 같이 사용하기도 하고, 둘 중 하나만 사용하기도 한다.
나의 경우는 beautifulsoup만으로 크롤링 할 수 있는 건 해당 라이브러리만 사용했고(좀 더 간단하고 쉽다) 필요에 따라 selenium도 사용했다.
정확히 beautifulsoup은 html을 파싱하기 위해 필요한 도구이다. 내가 크롤링하려는 브라우저 페이지가, 별도의 Javascript의 동적인 작동없는 정적인 페이지의 경우 request로 해당 url을 불러온 다음 바로 파싱하여 원하는 데이터를 가지고 올 수 있다.
그러나 동적인 페이지여서 어떠한 작업이 필요한 경우에, selenium을 통해 해당 페이지에 접속한 다음에 원하는 데이터를 가지고 와야 한다.
정적 페이지 크롤링하기
크롤링하기에 앞서, 우리가 데이터를 가지고 오려는 사이트의 url을 정확히 파악해야한다. 가령 나의 경우, A-Z까지의 정보를 가지고 와야 했고, 어떤 사이트를 살펴보다가 url이 다 동일하고 특정 부분이 인덱스처리되어서 계속 바뀌는 것을 발견했다.
이런 경우 for 반복문을 통해서 한 번에 데이터를 크롤링 해올 수 있다.
코드
from bs4 import BeautifulSoup
import csv
import os
import requests
import time
import re
from random import *
def crawling_bs4():
try:
searchlist = []
for idx in range(1, 2000): #인덱스 범위는 자유롭게
target_url = f'https://www.url.com?id={idx}'
try:
temp = []
session = requests.Session()
html = session.get(target_url).content
#html 파싱
soup = BeautifulSoup(html, 'html.parser')
div_id = soup.find('div', {'id': 'contents'})
name_tag = div_id.select('div > p')[24]
name = name_tag.text
temp.append(idx)
temp.append(name)
searchlist.append(temp)
except:
print('없어용')
return searchlist
except Exception as e:
print(e)
-> url을 가지고 와서 크롤링하는 방법은 다양하다. 많이들 알고 있는 urllib 모듈을 활용할 수도 있다. 그러나 해당 모듈을 사용했을 때
CERTIFICATE_VERIFY_FAILED 에러가 발생했고, (물론 해결책은 있지만) 나는 requests 모듈을 사용하여 해당 에러를 방지했다.
* 위의 에러가 발생했을 때 urllib 모듈을 그대로 사용하면서 에러를 해결하고 싶다면 해당 블로그를 참고하면 된다.
->beautifulsoup 모듈을 활용하여 가져온 html 페이지를 파싱할때는 크게 find/find_all/select 이 세가지 메소드만 기억하면 된다.
- find와 find_all은 비슷한 기능을 하는데, 차이점은 이름 그대로 find_all 은 해당하는 태그를 모두 가져온다.
- select의 경우는 위의 코드를 참고하면 알 수 있듯이, html 태그의 상하위 구조를 토대로 원하는 데이터를 가지고 오며, list의 형태로 반환한다. 따라서 원하는 데이터를 정확히 가져오려면 인덱스를 활용해야 한다.
위의 코드로 일반적인 크롤링은 가능하다. 하지만, 각 페이지들의 서버에서도 비정상적으로 많고 빠른 접속을 탐지하고 있을 것이고, 자칫하면 내 IP에서 해당 사이트로의 접속이 차단당할 수 있다.
이를 위해 나는 header 부분에 임의적으로, 유저가 접속하는 듯한 코드를 추가해 주었고, 크롤링 또한 중간중간 sleep메소드를 통해 쉬어주면서 사람이 실제로 접속하는 듯하게 해주었다.
header 부분 코드
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit 537.36 (KHTML, like Gecko) Chrome",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
}
html = session.get(target_url, headers=headers).content
sleep 메소드
rand_value = randint(1, 30)
time.sleep(rand_value)
-> randint 메소드를 활용하여, 1초부터 30초까지 임의의 숫자를 랜덤하게 뽑아내서 그 초만큼 크롤링을 강제로 쉬게 해주었다.
크롤링한 데이터 csv 파일에 저장하기
가져온 데이터를 그냥 두지 않고 저장해야한다. 일반적으로 csv 확장자 파일을 만들어서 해당 데이터를 저장하게 된다.
csv 파일이란?
몇 가지 필드를 쉽표로 구분한 텍스트 데이터 및 텍스트 파일이다.
f = open('craw.csv', 'w', encoding='utf-8', newline='') # 파일오픈
csv_writer = csv.writer(f) # 열어둔 파일
for i in searchlist:
csv_writer.writerow(i)
f.close()
-> 만약에 파일을 open 한 후에, 기존에 있는 csv 파일에 추가 하고 싶다면 open 메소드 내에, 'w'대신에, 'a'를 사용하면 된다.
최종코드
def crawling_bs4():
try:
searchlist = []
for idx in range(1, 2000):
target_url = f'https://www.url.com?idx={idx}'
try:
temp = []
session = requests.Session()
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit 537.36 (KHTML, like Gecko) Chrome",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
}
html = session.get(target_url, headers=headers).content
soup = BeautifulSoup(html, 'html.parser')
div_id = soup.find('div', {'id': 'contents'})
name_tag = div_id.select('div > p')[24]
name = name_tag.text
temp.append(idx)
temp.append(name)
searchlist.append(temp)
rand_value = randint(1, 30)
time.sleep(rand_value)
except:
print('없어용')
f = open('craw.csv', 'w', encoding='utf-8', newline='') # 파일오픈
csv_writer = csv.writer(f) # 열어둔 파일
for i in searchlist:
csv_writer.writerow(i)
f.close()
print("완료 !")
except Exception as e:
print(e)
selenium으로 동적 페이지 크롤링하기
selenium으로 크롤링할 때도 전체적인 흐름은 크게 다르지 않다. url을 불러오고, header 값을 지정해준다음 데이터를 불러와 html을 파싱해주면 된다. 그리고 똑같이 csv 파일에 저장해주면된다.
그러나 selenium으로 크롤링 할 때 가장 중요한 것은 chrome webdriver의 경로를 잘 설정해주어서 제대로 사용하는 것이다.
또한 실제로 해당 라이브러리를 써서 크롤링을 해야 하는 경우는 페이지가 동적인 경우고, 그말인 즉슨 실제 사용자가 사용하는 것 처럼 버튼을 클릭하거나 창을 열어야 하는 액션이 필요하다는 것이다. 그만큼 시간이 오래 걸리고, 중간에 네트워크가 끊길 가능성도 있다.
나의 경우는, selenium을 사용하지 않은 정적페이지 크롤링 때는 데이터를 한꺼번에 csv파일에 저장했다면, selenium을 쓸때는 조금 번거롭지만 하나하나를 일일이 저장하는 방식으로 썼다,(네트워크가 끊겨서, 기존에 불러온 데이터가 하나도 저장되지 않았고, 다시 처음부터 진행해야 하는 수고를 덜기 위해서)
webdriver 설치하고 경로 설정하기(설치 링크)
나의 경우는 우선 print(__file__)을 해주어서, 현재 경로를 파악한 다음, 현재 경로에 webdriver를 이동해주었다.
여러 방법이 있긴한데, 경로 설정이 내가 확인할 수 있는 바랑 실제 인식하는 거랑 차이가 있는지 자꾸 읽지를 못해서 가장 안전하고 기본적인 방법인 같은 폴더에 위치해 놓는 것으로 지정했다.
chrome_driver_path = '/Users/songhyeonju/Desktop/SW_code_study/django-docker-practice
/proj/first/chromedriver'
html 파싱하기
1) bs4 soup으로 파싱하기
페이지를 동적으로 열되, 파싱은 bs4로 가능하기도 하다. 이런 경우는 보통 버튼 클릭과 같은 특정 액션이 필요한 페이지는 아니고,
페이지 렌더링이 jquery같은 동적인 동작에 묶여있는 경우 가능하다.
2) selenium execute_script로 파싱하기
처음에는 해당 라이브러리를 어떻게 사용해야할 지 몰라서 헤맸다. 그러나 버튼 클릭을 해서 display: none 인 부분을 보이게 만들어야 했다.
그러나 사용해놓고 보니 이 방법으로 파싱하는 것도 매우 쉽구나를 느끼게 되었다. 바로 크롬 개발자 도구에서 제공하는 xpath 복사하기를 활용할 수 있기 때문이다.
-> html구조를 알기 위해서도 자주 사용하는 개발자 도구를 켠 후, 내가 데이터를 가져 오려는 부분에 마우스를 대고 오른쪽 우클릭하면
왼쪽과 같은 창이 뜨게 된다. 내가 가져오려는 부분의 full xpath를 copy 하자.
btn = driver.find_element_by_xpath(
'/html/body/div[2]/div/div[2]/div[2]/div[3]/div/table/tbody/tr[1]/td[1]')
#xpath로 경로를 불러온 다음, 해당 부분의 클릭 액션을 execute_script로 실행해줄 수 있다.
driver.execute_script(
"arguments[0].click()", btn)
tbody_name = driver.find_element_by_xpath(
'/html/body/div[2]/div/div[2]/div[2]/div[3]/div/table/tbody/tr[1]/td[5]')
tbody_address = driver.find_element_by_xpath(
'/html/body/div[2]/div/div[2]/div[2]/div[3]/div/table/tbody/tr[2]/td[1]/p[4]')
name = tbody_name.text
최종코드
from bs4 import BeautifulSoup
import csv
import os
import requests
import time
import re
from random import *
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
def crawling_selenium():
try:
# selenium 헤더추가
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument(
"user-agent = Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit 537.36 (KHTML, like Gecko) Chrome")
chrome_options.add_argument(
"acept = text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
chrome_driver_path = '/Users/songhyeonju/Desktop/SW_code_study/django-docker-practice/proj/first/chromedriver'
for idx in range(1, 2000):
try:
temp = []
target_url = f'http://www.url.com?idx={idx}'
driver = webdriver.Chrome(
chrome_options=chrome_options, executable_path=chrome_driver_path)
driver.implicitly_wait(8) # seconds
driver.get(target_url)
try:
btn = driver.find_element_by_xpath(
'/html/body/div[2]/div/div[2]/div[2]/div[3]/div/table/tbody/tr[1]/td[1]')
driver.execute_script(
"arguments[0].click()", btn)
tbody_name = driver.find_element_by_xpath(
'/html/body/div[2]/div/div[2]/div[2]/div[3]/div/table/tbody/tr[1]/td[5]')
tbody_address = driver.find_element_by_xpath(
'/html/body/div[2]/div/div[2]/div[2]/div[3]/div/table/tbody/tr[2]/td[1]/p[4]')
name = tbody_name.text
temp.append(idx)
temp.append(name)
if idx == 1: # 첫번째 정보일 경우 파일 생성
f = open('golfang_craw.csv', 'w',
encoding='utf-8', newline='') # 파일오픈
else: # 두번째부터는 데이터 추가
f = open('golfang_craw.csv', 'a',
encoding='utf-8', newline='') # 파일오픈
csv_writer = csv.writer(f)
csv_writer.writerow(temp)
f.close()
except:
print(idx, '없어용')
except:
print('에러')
return
print('완료')
except Exception as e:
print('e')
return
Reference
'Data' 카테고리의 다른 글
매장 정렬 알고리즘(Ranking Algorithm) (1) | 2021.05.12 |
---|---|
[Practice] 이미지 처리_gray scale & 해상도 향상 (0) | 2020.12.25 |
[Practice] 이미지 해상도 향상시키기 (3) | 2020.12.25 |
[Practice]흑백 사진의 일부만 컬러처리하기 (0) | 2020.12.25 |
[Practice]흑백사진 컬러처리 하기 (0) | 2020.12.25 |