본문으로 바로가기

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

category Bigdata 2020. 6. 11. 17:32

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

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

그중에서도 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
이렇게 데이터가 저장되어 축적되면 빅데이터 관련해서  많은 부분에 사용될 수 있다고 생각합니다.

 

감사합니다.

'Bigdata' 카테고리의 다른 글

Selenium을 이용한 인스타그램 크롤링  (3) 2020.06.11
Spark을 이용한 Deeplearning  (0) 2020.06.11
Spark SQL(Pyspark)  (0) 2020.05.26
Spark DataFrame (PySpark)  (0) 2020.04.20
R을 이용한 Bioinformatics (Bioconductor)  (0) 2020.04.20
Apache Spark 기능  (0) 2020.02.13

댓글을 달아 주세요

  1. Favicon of https://jhun1004.tistory.com Lightfidelity 2020.08.29 15:24 신고





    에러가 발생되어 ..! 이렇게 무례하게 여쭤봅니다. 혹시나 자문을 ..!
    C:\Anaconda3\envs\python_instagram\python.exe C:/Users/HP/pythonProject/crawl_inst.py
    검색할 태그를 입력하세요 ## : car
    Traceback (most recent call last):
    File "C:/Users/HP/pythonProject/crawl_inst.py", line 39, in <module>
    bs = BeautifulSoup(pageString, "lxml")
    File "C:\Anaconda3\envs\python_instagram\lib\site-packages\bs4\__init__.py", line 242, in __init__
    raise FeatureNotFound(
    bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: lxml. Do you need to install a parser library?

    Process finished with exit code 1

    • Favicon of https://jhun1004.tistory.com Lightfidelity 2020.08.29 15:34 신고

      IndexError: list index out of range

      During handling of the above exception, another exception occurred:

      "lxml" install 하고 나니..! 성공 한듯 한데
      Traceback (most recent call last):
      File "C:/Users/HP/pythonProject/crawl_inst.py", line 115, in <module>
      pandas_csv.to_csv(insert_data)
      NameError: name 'insert_data' is not defined

      Process finished with exit code 1

    • Favicon of https://cntechsystems.tistory.com 사용자 씨앤텍 2020.09.14 10:02 신고

      답변이 늦어져서 죄송합니다 ㅠㅠ
      소스 내에서 insert_data를 못찾는 것 같습니다. 혹시나 선언한 이름과 호출하는 이름을 다르게 적으신건 아니신지요??
      전체 소스 코드 복사하셔서 붙여놓고 실행 한번 해보시는게 좋을 것 같습니다.

      아니면 저는 실행할 때 python3로 실행하는데 이게 관련이 있을지도 모르겠습니다