4달만에 올리는 크롤링 포스팅인데 그 사이에는 개인 프로젝트, 웹 공부(spring, bootstrap)을 하느라 올릴 시간이 부족했던 것 같다.
오랜만에 소개할 예제는 이미지 크롤링이다.
이미지 크롤링 자체는 지금까지 해왔던 예제들만 잘 이해했다면 전혀 어렵지 않을 것인데, 이번 포스팅에서는 이미지 파일 저장을 위한 디렉토리 폴더 생성, 다운로드, 헤더 추가 개념이 새로 도입될 것이고 중요한 개념이니 꼭 이해하고 넘어가는 것이 좋다.
설명은 최대한 자세하게 할 것이고 이해가 안되는 부분이 있다면 댓글로 질문도 받을 것이니 편한 마음으로 시작해보자.
0. 크롤링 계획 세우기
저작권 없는 무료 이미지 사이트에서, 원하는 키워드와 관련된 이미지를 원하는 만큼 크롤링하여 컴퓨터에 저장하도록 하는 프로그램을 만든다.
1. 크롤링할 페이지 접속
pixabay.com의 /images/search 로 접속했다.
2. 크롤링 가능 여부 확인(필수)
Disallow(비허용)에 /images/search가 없다.
그리고 그 외의 페이지들은 Allow(/) 이기 때문에 크롤링을 해도 괜찮다.
3. 크롤링할 대상 결정
검색창에 키워드를 입력하면 관련 이미지가 뜨는데, 여기서 free images를 크롤링해야 한다.
필자가 위에 빨갛게 x로 그어놓은 영역에 해당하는 사진들은 저작권이 있는 이미지들이며, 이런 이미지를 사용하려면 몇 달러를 내야 한다. 그 아래가 free images영역이다.
robots.txt의 Disallow기준을 피했다고 하더라도 이런 부분에서도 주의를 기울여야 한다.
apple의 이미지 검색 결과가 12895건이었고 129페이지까지 있었던걸로 보아, 한 페이지당 100개의 사진이 표시된다는 사실을 알 수 있었다.
아래의 Next page를 누르면 다음 페이지로 넘어가게 되는데,
주소창을 보니까 pixabay.com/images/search/키워드/?page=페이지번호와 같은 규칙으로 uri가 이루어져있음을 알 수 있었다.
이렇게 특정 페이지를 크롤링 할 때, 필수 규칙들을 빠르게 파악하는 것도 크롤링의 핵심이자 실력이다.
4. 크롤링할 데이터가 있는 태그 파악
이제 웹 페이지를 우클릭하고 검사로 들어가서 소스를 검사해보자.
검사 아이콘(빨간원으로 표시함)을 누르고 free images영역에 있는 아무 이미지를 클릭하면, 소스 코드상에서 해당 이미지 태그가 있는 위치로 이동한다.
class가 "flex_grid.credits.search_results"인 div태그가 free images들을 감싸고 있다. (공백은 크롤링 작업 시 '.'으로 연결)
그리고 img태그의 src, srcset 속성에서 이미지의 경로를 확인 할 수 있다.
5. 태그 규칙 파악
파이썬에서 print()를 이용하여, 데이터를 잘 가져왔는지 수시로 테스트했는데 3번째 방법만에 성공했다.
그 과정을 공개할 것이고 1,2차 시도는 왜 작동이 안되었는지까지 다뤄볼 것이다.
이러한 과정도 웹 페이지의 특성을 파악하는데 도움이 되었다.
※ 태그의 속성값을 추출할 때에는 get메소드를 이용한다. [element.get('속성값')]
1차 시도
이미지 경로 : class가 'flex_grid.credits.search_results' 인 <div> 태그 안에 있는 <img> 태그의 src 속성값
print(img.get('src')) # img : 추출한 img 태그
결과
잘 추출되다가 어느 순간부터 추출이 안되기 시작했다.
2차 시도
이미지 경로 : class가 'flex_grid.credits.search_results' 인 <div> 태그 안에 있는 <img> 태그의 srcset 속성값
print(img.get('srcset'))
결과
이 역시 어느 순간부터 추출이 안되기 시작했다. 파이썬에서는 널(Null)을 None으로 표시한다.
1,2차 시도 실패 원인
초반 이미지 데이터만 정상적으로 추출이 된 것을 보아, 중간 이미지부터는 웹 사이트에 아직 로드가 안되어서 추출이 안된 것이었음을 알 수 있었다.
해결책
동적 크롤링 기법으로 웹 페이지를 스크롤하여 이미지 데이터를 로드 시키는 방법을 써도 괜찮다.
그런데 친절히도 pixabay에서 이런 문제를 고려했는지, 중간 이미지부터는 img태그에 data-lazy-srcset이라는 속성이 존재했다. 동적 크롤링 없이도 이 속성에 접근하여 이미지 경로를 추출할 수 있는 것이다.
다시 한번 언급하지만, 웹 사이트의 규칙을 알아야 크롤링이 가능하다.
3차 시도(성공)
이미지 경로 : class가 'flex_grid.credits.search_results' 인 <div> 태그 안에 있는 <img> 태그의 srcset 속성값, srcset 속성값이 없다면 data-lazy-srcset 속성값.
srcset = ""
if img.get('srcset')==None:
srcset = img.get('data-lazy-srcset')
else:
srcset = img.get('srcset')
print(srcset)
결과
모두 추출 완료되었다.
6. 코딩
설명은 주석에 자세히 달아놓았다.
import os
import sys
import urllib.request
from selenium import webdriver
from time import sleep
from bs4 import BeautifulSoup
keyword = input('검색어 : ')
maxImages = int(input('다운로드 시도할 최대 이미지 수 : '))
# 프로젝트에 미리 생성해놓은 crawled_img폴더 안에 하위 폴더 생성
# 폴더명에는 입력한 키워드, 이미지 수 정보를 표시함
path = 'crawled_img/'+keyword+'_'+str(maxImages)
try:
# 중복되는 폴더 명이 없다면 생성
if not os.path.exists(path):
os.makedirs(path)
# 중복된다면 문구 출력 후 프로그램 종료
else:
print('이전에 같은 [검색어, 이미지 수]로 다운로드한 폴더가 존재합니다.')
sys.exit(0)
except OSError:
print ('os error')
sys.exit(0)
pages = int((maxImages-1)/100)+1 #한 페이지당 표시되는 이미지 수(100)을 참고하여 확인할 페이지 수 계산
imgCount = 0 # 추출 시도 이미지 수
success = 0 # 추출 성공 이미지 수
finish = False # 이미지에 모두 접근했는지 여부
# 크롬 드라이버 설정
# (크롤링할 때 웹 페이지 띄우지 않음, gpu 사용 안함, 한글 지원, user-agent 헤더 추가)
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('headless')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('lang=ko_KR')
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36")
driver = webdriver.Chrome('C:\chromedriver\chromedriver.exe',chrome_options=chrome_options)
for i in range(1,int(pages)+1):
#웹 페이지 접근 후 1초동안 로드를 기다림
driver.get('https://pixabay.com/images/search/'+keyword+'/?pagi='+str(i))
sleep(1)
#크롤링이 가능하도록 html코드 가공
html = driver.page_source
soup = BeautifulSoup(html,'html.parser')
imgs = soup.select('div.flex_grid.credits.search_results img') #요소 선택
#마지막 페이지 여부 결정
lastPage=False
if len(imgs)!=100:
lastPage=True
#5번 제목에서 설명함
for img in imgs:
srcset = ""
if img.get('srcset')==None:
srcset = img.get('data-lazy-srcset')
else:
srcset = img.get('srcset')
src = ""
if len(srcset):
src = str(srcset).split()[0] #가장 작은 사이즈의 이미지 경로 추출
print(src)
filename = src.split('/')[-1] #이미지 경로에서 날짜 부분뒤의 순 파일명만 추출
print(filename)
saveUrl = path+'/'+filename #저장 경로 결정
print(saveUrl)
#파일 저장
#user-agent 헤더를 가지고 있어야 접근 허용하는 사이트도 있을 수 있음(pixabay가 이에 해당)
req = urllib.request.Request(src, headers={'User-Agent': 'Mozilla/5.0'})
try:
imgUrl = urllib.request.urlopen(req).read() #웹 페이지 상의 이미지를 불러옴
with open(saveUrl,"wb") as f: #디렉토리 오픈
f.write(imgUrl) #파일 저장
success+=1
except urllib.error.HTTPError:
print('에러')
sys.exit(0)
imgCount+=1
if imgCount==maxImages:
finish = True #입력한 이미지 수 만큼 모두 접근했음을 알림
break
#finish가 참이거나 더 이상 접근할 이미지가 없을 경우 크롤링 종료
if finish or lastPage:
break
print('성공 : '+str(success)+', 실패 : '+str(maxImages-success))
(추가) User-agent 헤더
위 소스코드에 있는 user-agent가 무엇인지 궁금할 것이다.
user-agent 헤더가 확인되어야만 접속을 허용하는 웹 사이트가 있을 수 있기 때문에 헤더를 추가해준 것이다.
user-agent 헤더에는 현재 사용자가 어떤 운영체제나 브라우저로 접속하는지에 대한 정보가 나와있으며,
user-agent는 아래 사이트에서 확인할 수 있다.
7. 실행
입력1
flower을 검색하여 나온 이미지 15개를 얻어올 것이다.
결과1
crawled_img 폴더 내에 flower_15라는 폴더가 생성되었고
15개의 이미지가 모두 다운로드 완료되었다.
입력2
programmer을 검색하여 나온 이미지 105개를 얻어올 것이다.
결과2
이번에도 programmer_105라는 폴더가 생성되었고
1페이지에 있는 사진 100개 + 2페이지에 있는 사진 5개 모두 정상적으로 다운로드 완료되었다.
실행마다 crawled_img 폴더 안에 결과물이 차곡차곡 쌓인 모습이다.
'Crawling' 카테고리의 다른 글
[크롤링, 보충] 웹 요소 선택 정리 (0) | 2020.11.03 |
---|---|
[크롤링, 예제 4] Youtube - 필터를 적용하여 영상 목록 보기 (0) | 2020.11.02 |
[크롤링, 예제 3] 게임 포럼 사이트에서 정보 긁어오기 (+그래프) (0) | 2020.10.31 |
[크롤링, 예제 2] 레벨 분포 구하기 (0) | 2020.10.26 |
[크롤링, 예제 1-3] Selenium - 새 창 안띄우고 크롤링 하기 (0) | 2020.10.23 |
댓글