본문 바로가기

데이터 청년 캠퍼스(경남대학교)/스터디

2021-07-14

인스타그램 해시태그 정보 수집

 

from bs4 import BeautifulSoup
from selenium import webdriver
import time
import math
import os
import random
import unicodedata   # 인스타그램의 해시태그 수집 중 자음/모음 분리현상 방지용 모듈
#Step 2. 사용자에게 필요한 정보들을를 입력 받습니다.
print("=" *70)
print("   이 크롤러는 인스타그램의 해시태그 정보를 수집합니다")
print("   본 제품은 서진수가 교육용으로 특별 제작했으며 ")
print("   용도외의 사용으로 저작권을 침해하는 행위는 불법입니다")
print("   본 제품에 대한 문의는 seojinsu@gmail.com 으로 보내주세요~^^")
print("=" *70)

v_id = input("1.인스타그램의 ID를 입력하세요: ")
v_passwd = input("2.인스타그램의 비밀번호를 입력하세요: ")
query_txt = input("3.검색할 해쉬태그를 입력하세요(예: 강남맛집): ")
cnt = int(input('4.크롤링 할 건수는 몇건입니까?: '))
real_cnt = math.ceil(cnt / 10)

f_dir=input('5.파일이 저장될 경로만 쓰세요(기본경로 : c:\\temp\\ ) : ')
if f_dir =='' :
    f_dir = "c:\\temp\\"
#Step 3. 결과를 저장할 폴더명과 파일명을 설정하고 폴더를 생성합니다.
s_time = time.time( )
query_txt2 = '인스타그램'
now = time.localtime()
s = '%04d-%02d-%02d-%02d-%02d-%02d' % (now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec)

f_dir = input('사진을 저장할 폴더를 지정하세요(예: c:\data\) : ')
#query_txt ='사진저장'

#Step 2. 파일을 저장할 폴더를 생성합니다
now = time.localtime()
s = '%04d-%02d-%02d-%02d-%02d-%02d' % (now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec)

os.chdir(f_dir)
os.makedirs(f_dir+s+'-'+query_txt)
os.chdir(f_dir+s+'-'+query_txt)
f_result_dir = f_dir+s+'-'+query_txt
# Step 4. 인스타그램 자동 로그인 하기
chrome_path = "c:/temp/chromedriver.exe"
driver = webdriver.Chrome(chrome_path)

driver.get("https://www.instagram.com/")
time.sleep(random.randrange(1,5))

print("\n")
print("요청하신 데이터를 추출중이오니 잠시만 기다려 주세요~~~~^^")
print("\n")

#ID와 비번 입력후 로그인하기
eid = driver.find_element_by_name('username') #아이디를 입력하는 창
for a in v_id :
        eid.send_keys(a)
        time.sleep(0.3) #너무 빨리 검색하면 입력이 안되는 경우가 있어서 천천히 입력하도록 넣어줌
epwd = driver.find_element_by_name('password')
for b in v_passwd :
        epwd.send_keys(b)
        time.sleep(0.5)

driver.find_element_by_xpath('//*[@id="loginForm"]/div/div[3]/button/div').click()
time.sleep(5)

# Step 5. 검색할 해쉬태그 입력하기
#로그인 정보 나중에 저장하기
driver.find_element_by_xpath('//*[@id="react-root"]/div/div/section/main/div/div/div/div/button').click()
time.sleep(2)

driver.find_element_by_xpath('/html/body/div[4]/div/div/div/div[3]/button[2]').click() #알림정보 나중에하기
time.sleep(2)

# 검색할 키워드 입력하기
element = driver.find_element_by_xpath('''//*[@id="react-root"]/div/div/section/nav/div[2]/div/div/div[2]/div[1]/div''').click()
element1 = driver.find_element_by_xpath('''//*[@id="react-root"]/div/div/section/nav/div[2]/div/div/div[2]/input''')

time.sleep(1)
element1.send_keys(query_txt)

#for c in query_txt :
#    element.send_keys(c)
#    time.sleep(0.2)

time.sleep(5)
driver.find_element_by_xpath('//*[@id="react-root"]/div/div/section/nav/div[2]/div/div/div[2]/div[3]/div/div[2]/div/div[1]/a/div').click()

import pandas as pd

# 자동 스크롤다운 함수
def scroll_down(driver):
  driver.execute_script("window.scrollTo(0,100);")
  time.sleep(5)

print('요청하신 데이터를 수집중이니 잠시만 기다려 주세요~^^')
item=[]     # 인스타그램 URL 주소 저장할 리스트
item2=[]
file_no = 0                                
count = 1
img_src2=[]

검색할 키워드를 입력하는 과정에서 로그인때처럼 경로에 send_keys()를 적용하니 오류가 뜸

그래서 키워드 창의 xpath를 추출하여 한 번 클릭을 한 후에 입력창에 검색키워드를 넣어서 해결함

 

a = 1
while (a <= real_cnt):
    scroll_down(driver) 

    # Step 6. 전체 게시물의 원본 URL 추출하기
    html = driver.page_source
    soup = BeautifulSoup(html, 'html.parser')

    all = soup.find('article','KC1QD').find_all('div','Nnq7C weEfm')
    #img_src = soup.find('div','box_txtPhoto').find_all('img') #이미지

    for i in all:    
        url = i.find('a')['href']
        item.append(url)          
        item2 = pd.Series(item).drop_duplicates() #중복되는거 삭제하기
        item2.append(pd.Series(item).drop_duplicates())
        
        if len(item2) >= cnt :
            break
    

    a += 1
    print('요청하신 데이터를 수집중이니 잠시만 더 기다려 주세요~^^')
    
    #입력받은 개수보다 많아지면 멈춤
    if len(item2) >= cnt :
        break
   
# 추출된 URL 사용하여 전체 URL 완성하기
full_url=[]
url_cnt = 0
for x in range(0,len(item2)) :
    url = 'https://www.instagram.com' +item[x]
    full_url.append(url)
    url_cnt += 1
    
    if url_cnt > cnt:
        break

# 아래 for 반복문은 수집된 URL 주소를 확인하는 코드임        
#for c in range(1,len(full_url)+1) :
#    print(c,':',full_url[c-1])

#Step 7. 각 페이지별로 그림과 해쉬태그를 수집하기
count = 1        # 추출 데이터 건수 세기
hash_txt = []    # 해쉬 태그 저장 

# 비트맵 이미지 아이콘을 위한 대체 딕셔너리를 만든다
import sys
bmp_map = dict.fromkeys(range(0x10000, sys.maxunicode + 1), 0xfffd) #이모티콘 변환해서 저장할 수 있게 함

count = 0

os.makedirs(f_dir+s+'-'+query_txt2+'-'+query_txt)
f_name=f_dir+s+'-'+query_txt2+'-'+query_txt+'\\'+s+'-'+query_txt+'.txt'

for c in range(0,len(full_url)) :
    driver.get(full_url[c])
    time.sleep(2)

    html = driver.page_source
    soup = BeautifulSoup(html, 'html.parser')

    f = open(f_name, 'a',encoding='UTF-8')

    # 해당 페이지의 해시태그 수집
    tags = soup.find('div','EtaWk')

    try :
        tags_1 = tags.find_all('a')
    except :
        pass
    else :
        for d in range(0, len(tags_1)) :
            tags = tags_1[d].get_text()
            #이모티콘 처리
            tags_11 = tags.translate(bmp_map)                #특별한 이모티콘을 일반적인 글자로 변환
            #자음,모음 분리 처리
            tags_2 = unicodedata.normalize('NFC', tags_11)   #중요! 텍스트 마이닝을 위해 해시태그를 모음
            #한 번 수정된 게시물은 자음과 모음이 깨지는 경우가 발생함
            #자음과 모음이 분리된 것을 다시 합쳐줌

            for i in tags_2 :
                if i[0:1]=='#' :
                    hash_txt.append(tags_2)
                    print(tags_2)
                    f.write("\n" + str(tags_2))
    f.close()
    count += 1
    
    img_src = soup.find('div','KL4Bh').find_all('img') #이미지
    for i in img_src :
        img_src1=i['src'] #img 태그의 속성값
        img_src2.append(img_src1)
        count += 1

        if len(img_src1) >= cnt :
            break

    for i in range(0,len(img_src2)) :
        try :
                urllib.request.urlretrieve(img_src2[i],str(file_no)+'.jpg')
        except :
                continue        
        file_no += 1                
        time.sleep(0.5)      
        print("%s 번째 이미지 저장중입니다=======" %file_no)

        
    
#Step 7. 요약 정보 출력하기    
e_time = time.time( )
t_time = e_time - s_time

print("=" *100)
print("총 소요시간: %s 초" %round(t_time,1))
print("총 저장 건수: %s 건 " %count)
print("파일 저장 경로: %s" %f_name)
print("=" *100)

driver.close( )

이미지의 url은 잘 뽑혔는데 사진저장이 안됐음

이 부분에 대한 문제는 해결하지 못하였음

 


유튜브

 

# 유투브에서 검색 후 동영상의 리뷰 정보 수집하기
#Step 1. 필요한 모듈과 라이브러리를 로딩합니다.

from bs4 import BeautifulSoup
from selenium import webdriver

import time
import sys
import re
import math
import numpy 
import pandas as pd   
import xlwt 
import random
import os
#Step 2. 사용자에게 검색어 키워드를 입력 받습니다.
print("=" *80)
print("유투브 영상의 댓글을 수집하는 웹크롤러입니다")
print("=" *80)

query_txt=input("1.유튜브에서 검색할 주제 키워드를 입력하세요(예:올리브영): ")

movie_cnt = int(input('2.위 주제로 댓글을 크롤링할 유튜브 영상은 몇건입니까?:'))
reple_cnt = int(input('3.각 동영상에서 추출할 댓글은 몇건입니까?: '))

f_dir = input("4.크롤링 결과를 저장할 폴더명만 쓰세요(기본값:c:\\temp\\):")
if f_dir == '' :
    f_dir = 'c:\\temp\\'

print("\n")
print("요청하신 정보로 데이터를 수집중이니 잠시만 기다려 주세요~~^^")
 # Step 3. 저장될 파일위치와 이름을 지정합니다
now = time.localtime()
s = '%04d-%02d-%02d-%02d-%02d-%02d' % (now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec)

os.makedirs(f_dir+'youtube'+'-'+s+'-'+query_txt)
os.chdir(f_dir+'youtube'+'-'+s+'-'+query_txt)

ff_dir=f_dir+'youtube'+'-'+s+'-'+query_txt
ff_name=f_dir+'youtube'+'-'+s+'-'+query_txt+'\\'+s+'-'+query_txt+'.txt'
fc_name=f_dir+'youtube'+'-'+s+'-'+query_txt+'\\'+s+'-'+query_txt+'.csv'
fx_name=f_dir+'youtube'+'-'+s+'-'+query_txt+'\\'+s+'-'+query_txt+'.xls'

s_time = time.time( )
# Step 4. 크롬 드라이버 설정하고 유투브 사이트에 접속하여 검색을 진행합니다 
import chromedriver_autoinstaller
chromedriver_autoinstaller.install()
driver= webdriver.Chrome()

driver.get('https://www.youtube.com')

time.sleep(5)
element = driver.find_element_by_name("search_query")
element.send_keys(query_txt)
element.submit()
time.sleep(2)
def scroll_down(driver):
    #driver.execute_script("window.scrollTo(500,document.body.scrollHeight);")
    driver.execute_script("window.scrollBy(0,3000);") # 한페이지 20개씩 출력값
    time.sleep(5)
    
def scroll_updown(driver):
    driver.execute_script("window.scrollTo(500,document.body.scrollHeight);")
    time.sleep(10)
    driver.execute_script("window.scrollBy(0,200);") # 한페이지 20개씩 출력값
    time.sleep(1)
    driver.execute_script("window.scrollBy(0,300);")

스크롤하는 함수/

유튜브는 바로 아래로 스크롤하면 댓글창이 안떠서 위로 올렸다가 천천히 내리면서 댓글창을 확인할 수 있도록 해야한다.

 

if movie_cnt < 20 :
    page_cnt = 1 
else :
    page_cnt = math.ceil(cnt/20)

if movie_cnt > 20 :
    i = 1
    while ( i <=  page_cnt) :
        print("화면을 %s 회 아래로 스크롤 합니다" %i)
        scroll_down(driver)
        time.sleep(1)
        i += 1

# 검색된 유튜브 영상의 URL 을 추출합니다

time.sleep(2)

html = driver.page_source
soup1 = BeautifulSoup(html, 'html.parser')

count = 0
item=[]

print("\n")

for i in soup1.find_all('a','yt-simple-endpoint style-scope ytd-video-renderer'):
    item.append(i['href'])
    count += 1

    if count == movie_cnt :
        break

print("검색된 영상 중 %s 건 동영상의 댓글을 추출하겠습니다" %movie_cnt)
print("\n")

# Step 6. 동영상의 상세 정보를 수집합니다.
# 수집된 전체 URL 중에서 사용자가 입력한 수만큼의 동영상의 댓글을 수집합니다.
# 먼저 전체 URL 을 완성 -> 동영상 페이지 열기 -> 댓글 수집 -> 다음 동영상 순으로 진행합니다
full_url=[]
url_cnt = 0
for x in range(0,len(item)) :
    url_cnt += 1
    url = 'https://www.youtube.com/' +item[x]
    full_url.append(url)
    if url_cnt == movie_cnt :
        break

play_cnt = 1
url2=[]
reple2=[]
reple3=[]
reple4=[]
writer2=[]
wdate2=[]
good2=[]
bad2=[]

real_collect_count = 0   #실제 총 수집한 리플 갯수
# 비트맵 이미지 아이콘을 위한 대체 딕셔너리를 만든다
bmp_map = dict.fromkeys(range(0x10000, sys.maxunicode + 1), 0xfffd)

for y in range(0,len(full_url)) :
    driver.get(full_url[y])
    time.sleep(10)
    print("\n")
    print("\n")
    print("%s 번째 동영상의 정보를 수집합니다." %play_cnt)

    i = 1
    while ( i <=  page_cnt) :
        scroll_down(driver)
        time.sleep(3)
        i += 1
        
    j = 1
    while ( j <=  page_cnt) :
        scroll_updown(driver)
        time.sleep(5)
        j += 1

    html = driver.page_source
    soup2 = BeautifulSoup(html, 'html.parser')
    
    # 조회수
    t_count_0 = soup2.select('#count')
    t_count_1 = t_count_0[1].get_text()
    t_count_2 = t_count_1.replace(",","")
    t_count_3 = re.search("\d+",t_count_2)
    t_count_4 = int(t_count_3.group())
    
    # 제목
    t_title_1 = soup2.select('#info-contents')
    t_title_2 = t_title_1[0].find('h1').get_text()
    t_title_3 = t_title_2.translate(bmp_map).replace("\n","")
    
    # 댓글수
    t_view_1 = soup2.find('yt-formatted-string','count-text style-scope ytd-comments-header-renderer').get_text()#.get_text( ).replace("\n","")
    t_view_2 = t_view_1.replace("," ,"")
    t_view_3 = re.search("\d+",t_view_2)
    t_view_4 = int(t_view_3.group())
    
    #좋아요 / 싫어요 수
    t_good_bad = soup2.select('#top-level-buttons-computed')
    t_good = t_good_bad[0].select('#text')[0].get_text()  # 좋아요 수
    t_bad = t_good_bad[0].select('#text')[1].get_text()   # 싫어요 수

    print("=" *120)
    print("%s 번째 동영상의 제목은 %s 입니다" %(play_cnt,t_title_3))
    print("%s 번째 동영상의 조회수는 %s 회, 댓글수는 총 %s개, 좋아요는 %s개, 싫어요는 %s개입니다" %(play_cnt,t_count_4,t_view_4,t_good,t_bad))  
    print("=" *120)
    print("\n")
        
    #댓글 정보를 수집하기 위해 화면을 스크롤해서 아래로 이동합니다
    if t_view_4 > 0 :
        print("%s 번째 동영상에서 댓글 수집을 시작합니다" %play_cnt)
        print("댓글의 개수가 많아서 시간이 걸릴 수 있으니 잠시 기다려 주세요~~^^")
        print("\n")
        if reple_cnt < 20 :
            reple_page_cnt = 1
        else :
            reple_page_cnt = math.ceil(reple_cnt / 20)

        i = 1
        while ( i <  reple_page_cnt) :
            print("%s 번째 페이지의 댓글을 수집하는 중입니다~" %i)
            scroll_down(driver)
            time.sleep(0.5)
            i += 1
            

        #내용을 수집합니다

        time.sleep(2)

        html = driver.page_source
        soup3 = BeautifulSoup(html, 'html.parser')

        count = 0

        reple_result = soup3.select('#comments > #sections > #contents')

        for a in range(0,reple_cnt+1) :
            real_collect_count += 1
            count += 1

            f = open(ff_name, 'a',encoding='UTF-8')
            f.write("%s 번째 영상의 %s 번째  댓글 " %(play_cnt,count))
            f.write("------------------------------------------------------------------"+"\n")

            # 댓글 작성자
            try : 
                writer = reple_result[0].select(" #author-text")[a].get_text().replace("\n","").strip()
            except  :
                break
            else :
                print("\n")
                print("%s 번째 영상의 %s 번째  댓글 " %(play_cnt,count))
                print("-" *70)

                # 유튜브 URL 주소
                print("1.URL 주소:",full_url[y])
                f.write("1.유투브 URL주소: " + full_url[y] + "\n")
                url2.append(full_url[y])

                print("2.댓글 작성자명:", writer)
                f.write("2.댓글 작성자명: " + writer + "\n")
                writer2.append(writer)

                # 댓글 작성일자
                wdate = reple_result[0].select("yt-formatted-string.published-time-text > a.yt-simple-endpoint")[a].get_text().replace("\n","")

                print("3.댓글 작성일자:",wdate)
                f.write("3.댓글 작성일자: " + wdate + "\n")
                wdate2.append(wdate)

                # 댓글 내용
                reple1 = reple_result[0].select('#expander > #content > #content-text')[a].get_text().replace("\n","")

                reple2=reple1.translate(bmp_map).replace("\n","")
                print("4.댓글 내용:",reple2)
                f.write("4.댓글 내용: " + reple2 + "\n")
                reple3.append(reple2)

                f.write("\n")

                #좋아요 / 싫어요
                good2.append(t_good)
                bad2.append(t_bad)
                
            f.close( )

            if count == reple_cnt :
                break

        time.sleep(2)       
        play_cnt += 1  
    else :
        print("%s 번째 동영상의 댓글수가 %s개이므로 다음영상으로 넘어갑니다" %(play_cnt,t_view_4) )
        f = open(ff_name, 'a',encoding='UTF-8')
        f.write("%s 번째 영상의 %s 번째  댓글 " %(play_cnt,count))
        f.write("------------------------------------------------------------------"+"\n")
        f.write("%s 번째 동영상의 댓글수가 %s개이므로 다음영상으로 넘어갑니다" %(play_cnt,t_view_4) )
        f.write("\n")
        f.close( )
        
        play_cnt += 1
#step 9. 엑셀 형태로 저장하기
                
youtube_reple = pd.DataFrame()
youtube_reple['URL 주소']=url2
youtube_reple['댓글작성자명']=pd.Series(writer2)
youtube_reple['댓글작성일자']=pd.Series(wdate2)
youtube_reple['댓글 내용']=pd.Series(reple3)
youtube_reple['좋아요수']=pd.Series(good2)
youtube_reple['싫어요수']=pd.Series(bad2)

# csv 형태로 저장하기
youtube_reple.to_csv(fc_name,encoding="utf-8-sig",index=True)

# 엑셀 형태로 저장하기
youtube_reple.to_excel(fx_name ,index=True)

e_time = time.time( )
t_time = e_time - s_time

real_collect_count -= 1

# txt 파일에 크롤링 요약 정보 저장하기
orig_stdout = sys.stdout
f = open(ff_name, 'a',encoding='UTF-8')
sys.stdout = f

print("\n")
print("=" *50)
print("총 소요시간은 %s 초 이며," %t_time)
print("총 저장 건수는 %s 건 입니다 " %real_collect_count)
print("=" *50)

sys.stdout = orig_stdout
f.close( )

print("\n") 
print("=" *100)
print("1.요청된 총 %s 건 동영상에서 각 영상별 실제 크롤링 된 리뷰수는 총 %s 건입니다" %(movie_cnt,real_collect_count))
print("2.총 소요시간은 %s 초 입니다 " %round(t_time,1))
print("3.파일 저장 완료: txt 파일명 : %s " %ff_name)
print("4.파일 저장 완료: csv 파일명 : %s " %fc_name)
print("5.파일 저장 완료: xls 파일명 : %s " %fx_name)
print("=" *100)

driver.close( )

'데이터 청년 캠퍼스(경남대학교) > 스터디' 카테고리의 다른 글

2021-07-19  (0) 2021.07.20
2021-07-15  (0) 2021.07.16
2021-07-13  (0) 2021.07.14
2021-07-12  (0) 2021.07.12
2021 - 07 - 07  (0) 2021.07.07