Bigdata

Selenium을 이용한 인스타그램 크롤링

안녕하세요 씨앤텍 시스템즈의 김준형 입니다.

이번 포스트는 크롤링에 대해서 정리해 보았습니다. 

그중에서도 BeautifulSoup 와 Selenium을 이용해서 인스타그램의 데이터를 받아와 보겠습니다.

셀레니움이란

Selenium은 웹사이트 테스트를 위한 도구로 브라우저 동작을 자동화할 수 있습니다.  프로그래밍으로 브라우저 동작을 제어해서 마치 사람이 이용하는 것 같이 웹페이지를 요청하고 응답을 받아올 수 있습니다. 예를들어 2페이지버튼이 단순 url이아니라 Javascript로 이루어져 있다면 시스템이 동작해서 화면전환이 이루어지기 때문에 크롤링할때 꼭 필요한 기술이라고 할 수 있습니다.

 

실행환경은 윈도우에서 진행하였습니다.

Python은 설치되어 있고 환경변수를 설정했다고 가정하고 진행하겠습니다.

 

인스타그램 크롤링은 Selenium과 Chrome을 연결해서 진행하기 때문에 Chrome 드라이버가 필요합니다.
( Chrome버전은 크롬 설정에서 -> Chrome 정보탭으로 가면 크롬 정보에서 확인할 수 있습니다. )

 

아래 Chrome 홈페이지에서 자신의 크롬버전에 해당하는 드라이버를 다운받아 주세요.

https://chromedriver.chromium.org/downloads

 

Downloads - ChromeDriver - WebDriver for Chrome

WebDriver for Chrome

chromedriver.chromium.org

 

이제 크롬 드라이버 옆에 적당한 이름의 .py 파일을 만들어주세요. (ex. insta.py )

그 후에 다음과 같이 필요한 프레임워크를 추가합니다.

from bs4 import BeautifulSoup
import selenium.webdriver as webdriver
import urllib.parse
from urllib.request import Request, urlopen
from time import sleep
import pandas as pd

import pandas_csv #검색 결과를 .csv파일로 만들기 위한 파이썬 파일

 

 

검색어를 사용자에게 받아오고 해당하는 인스타그램 게시물의 정보를 받아오기 위한 소스를 추가합니다.

search = input("검색어를 입력하세요 : " )
searching = str(search)
search = urllib.parse.quote(search)
url = 'https://www.instagram.com/explore/tags/'+str(search)+'/'
driver = webdriver.Chrome('chromedriver.exe')

driver.get(url) #검색어입력한 인스타그램 url 저장
sleep(3) #로딩 시간을 위한 속도조절 

SCROLL_PAUSE_TIME = 1.2  #인스타게시물 스크롤 속도 조절 ( 1.0 ~ 2.0까지 사양에 맞게 조절 )

 

 

반복문을 통해 가져올 게시물의 개수와 URL 정보를 구합니다.

크롬에서 f12를 눌러 개발자 모드로 진입하여  인스타 검색화면의 소스를 보면 class가 "Nnq7C weEfm"로 해당하는 부분을 찾을 수 있습니다. 이렇게 페이지 하나씩 해당하는 부분을 찾아 데이터를 변수에 저장하고 불러내는 방식입니다.

해당 페이지의 정보를 찾은 후 스크롤을 통해 다음페이지로 넘어갑니다.

reallink = []

while True: # 반복문 시작
    pageString = driver.page_source
    bs = BeautifulSoup(pageString, "lxml")

# 게시물 정보 
    for link1 in bs.find_all(name="div",attrs={"class":"Nnq7C weEfm"}):
        title = link1.select('a')[0] 
        real = title.attrs['href']
        reallink.append(real) 
        title = link1.select('a')[1] 
        real = title.attrs['href']
        reallink.append(real) 

# 페이지 스크롤
    last_height = driver.execute_script("return document.body.scrollHeight")
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    #sleep(SCROLL_PAUSE_TIME)
    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        sleep(SCROLL_PAUSE_TIME)
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            break
            
        else:
            last_height = new_height
            continue

 

 

위 반복문에서 구한 Reallink의 개수로 총 데이터의 개수를 파악하고,
Reallink[ ]에 있는 URL을 하나씩 열어서 해당 게시물에 원하는 데이터를 변수에 저장합니다.
이 방식 또한 해당하는 class 명을 찾아내어 소스위치를 파악하여 원하는 데이터를 확인합니다.

hashtags2 = []

reallinknum = len(reallink)
print("총"+str(reallinknum)+"개의 데이터.")
try:  # 반복문 시작 ( print 명령어로 원하는 문자열인지 하나씩 확인해보시길 바랍니다.
    for i in range(0,reallinknum):
        hashtags2.append([])
        req = 'https://www.instagram.com/p'+reallink[i]
        driver.get(req)
        webpage = driver.page_source
        soup = BeautifulSoup(webpage, "html.parser")
        #print(soup)
        soup1 = str(soup.find_all(attrs={'class': 'e1e1d'}))
        #print(soup1)
        user_id = soup1.split('href="/')[1].split('/">')[0]
        #print(user_id)
        soup1 = str(soup.find_all(attrs={'class': 'Nm9Fw'}))
        #print(soup1)
        subValue = 'span'
        if(soup1=="[]"): #좋아요가 0개, 1개, n개 일경우 모두 소스가 다르다. 
            likes = '0'
        elif( soup1.find(subValue)==-1):
            likes = soup1.split('좋아요 ')[1].split('개')[0]
        elif( soup1.find(subValue)!=-1):
            likes = soup1.split('<span>')[1].split('</span>')[0]
        
        soup1 = str(soup.find_all(attrs={'class': 'xil3i'}))
        if(soup1=="[]"): #해쉬태그가 없을 경우 소스가 다르다.
            hashtags = '해쉬태그없음'
            insert_data = { "search" : searching,
                            "user_id" : user_id,
                            "좋아요" : likes,
                            "hashtags" : hashtags}
            pandas_csv.to_csv(insert_data)
        else:
            soup2 = soup1.split(',')
            soup2num = len(soup2)
            for j in range(0,soup2num):
                hashtags = soup2[j].split('#')[1].split('</a>')[0]
                print(hashtags) 
                insert_data = { "search" : searching,
                                "user_id" : user_id,
                                "좋아요" : likes,
                                "hashtags" : hashtags}
                #insert_data에 저장한 변수들 저장
                pandas_csv.to_csv(insert_data)

except:
    print("오류발생"+str(i+1)+"개의 데이터를 저장합니다.")   
    
    pandas_csv.to_csv(insert_data)  #insert_data에 저장한 데이터를 pandas_csv.py로 보냅니다.
print("저장성공")   

 

 

전체 소스입니다.

from bs4 import BeautifulSoup
import selenium.webdriver as webdriver
import urllib.parse
from urllib.request import Request, urlopen
from time import sleep
import pandas as pd

import pandas_csv #검색 결과를 .csv파일로 만들기 위한 파이썬 파일

search = input("검색어를 입력하세요 : " )
searching = str(search)
search = urllib.parse.quote(search)
url = 'https://www.instagram.com/explore/tags/'+str(search)+'/'
driver = webdriver.Chrome('chromedriver.exe')

driver.get(url) #검색어입력한 인스타그램 url 저장
sleep(3) #로딩 시간을 위한 속도조절 

SCROLL_PAUSE_TIME = 1.2  #인스타게시물 스크롤 속도 조절 ( 1.0 ~ 2.0까지 사양에 맞게 조절 )
reallink = []

while True: # 반복문 시작
    pageString = driver.page_source
    bs = BeautifulSoup(pageString, "lxml")

# 게시물 정보 
    for link1 in bs.find_all(name="div",attrs={"class":"Nnq7C weEfm"}):
        title = link1.select('a')[0] 
        real = title.attrs['href']
        reallink.append(real) 
        title = link1.select('a')[1] 
        real = title.attrs['href']
        reallink.append(real) 

# 페이지 스크롤
    last_height = driver.execute_script("return document.body.scrollHeight")
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    #sleep(SCROLL_PAUSE_TIME)
    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        sleep(SCROLL_PAUSE_TIME)
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            break
            
        else:
            last_height = new_height
            continue


hashtags2 = []

reallinknum = len(reallink)
print("총"+str(reallinknum)+"개의 데이터.")
try:  # 반복문 시작 ( print 명령어로 원하는 문자열인지 하나씩 확인해보시길 바랍니다.
    for i in range(0,reallinknum):
        hashtags2.append([])
        req = 'https://www.instagram.com/p'+reallink[i]
        driver.get(req)
        webpage = driver.page_source
        soup = BeautifulSoup(webpage, "html.parser")
        #print(soup)
        soup1 = str(soup.find_all(attrs={'class': 'e1e1d'}))
        #print(soup1)
        user_id = soup1.split('href="/')[1].split('/">')[0]
        #print(user_id)
        soup1 = str(soup.find_all(attrs={'class': 'Nm9Fw'}))
        #print(soup1)
        subValue = 'span'
        if(soup1=="[]"): #좋아요가 0개, 1개, n개 일경우 모두 소스가 다르다. 
            likes = '0'
        elif( soup1.find(subValue)==-1):
            likes = soup1.split('좋아요 ')[1].split('개')[0]
        elif( soup1.find(subValue)!=-1):
            likes = soup1.split('<span>')[1].split('</span>')[0]
        
        soup1 = str(soup.find_all(attrs={'class': 'xil3i'}))
        if(soup1=="[]"): #해쉬태그가 없을 경우 소스가 다르다.
            hashtags = '해쉬태그없음'
            insert_data = { "search" : searching,
                            "user_id" : user_id,
                            "좋아요" : likes,
                            "hashtags" : hashtags}
            pandas_csv.to_csv(insert_data)
        else:
            soup2 = soup1.split(',')
            soup2num = len(soup2)
            for j in range(0,soup2num):
                hashtags = soup2[j].split('#')[1].split('</a>')[0]
                print(hashtags) 
                insert_data = { "search" : searching,
                                "user_id" : user_id,
                                "좋아요" : likes,
                                "hashtags" : hashtags}
                #insert_data에 저장한 변수들 저장
                pandas_csv.to_csv(insert_data)

except:
    print("오류발생"+str(i+1)+"개의 데이터를 저장합니다.")   
    
    pandas_csv.to_csv(insert_data)  #insert_data에 저장한 데이터를 pandas_csv.py로 보냅니다.
print("저장성공")   


 

수집 데이터를 CSV파일로 저장하기위한 pandas_csv.py 파일입니다.

import os
import pandas as pd
from datetime import datetime, timedelta
import configparser
import glob

config = configparser.ConfigParser()
#설정파일이 있는경우 설정합니다.
#config.read('/root/crawling/crawler.conf') 

def to_csv(data):

	pathlink ="./"
		    
	# db create
	if not os.path.isdir(pathlink):
		os.mkdir(pathlink)

	present_date = str(datetime.utcnow() + timedelta(hours=9))[:10] #파일명에 날짜구분하기 위한 시간

	# col = ["search", "user_id", "좋아요", "hashtags"]
    
    # CSV파일 생성
	if len(glob.glob(pathlink + "/" + present_date + ".csv")) == 1:
		cnt = len(pd.read_csv(pathlink + "/" + present_date + ".csv", index_col=0).index)
		time_pd = pd.DataFrame(data, index=[cnt])
		time_pd.to_csv(pathlink + "/" + present_date + ".csv", mode='a', header=False, encoding='utf-8-sig')
	else:
		cnt = 0
		time_pd = pd.DataFrame(data, index=[cnt])
		time_pd.to_csv(pathlink + "/" + present_date + ".csv", mode='a', encoding='utf-8-sig')

 

--실행화면

   -- 수집 명령어 실행 및 로그확인

   

    -- Chrome 페이지가 시작되고 수집을 시작합니다.

 

    -- .py폴더 안에 .csv파일을 확인할 수 있습니다.

지금은 CSV파일로 저장하고 끝나지만 다음 시간에는 ElasticSearch나 RDB로 바로 저장되는 자동화 부분을 배워보도록 하겠습니다.   
웹데이터  ->  .CSV  -> ElasticSearch,RDB
이렇게 데이터가 저장되어 축적되면 빅데이터 관련해서  많은 부분에 사용될 수 있다고 생각합니다.

 

감사합니다.

728x90