본문 바로가기

2019년 혁신성장 청년인재 집중양성(빅데이터)/집중양성과정 프로젝트 01

셀레니움으로 트위터 크롤링[번역]

 

JANUARY 16, 2018 BY AS9736

 

Mining Twitter with Selenium

This is great for freaking people out. It looks like a ghost is typing in your web browser. Web crawling using html parsers to grab links and navigate to new pages with the requests library is all …

allofyourbases.com

 

Mining Twitter with Selenium

셀레니움으로 트위터 마이닝하기

 

 

 

사람들을 괴롭히는데 탁월하다. 셀레니움은 웹 브라우져에서 타이핑을 하고 있는 유령과 비슷하다. 

requests library 를 이용하여 새로운 페이지를 찾거나 링크의 위치를 찾기 위해 html parser를 사용하는 웹 크롤링도 정말 좋지만, 실제로 로그인 정보다, 어떤 단어나 클릭을 하길 월할 떄는 셀레니움이 많은 재미를 준다.

이것은 문자 그대로, 웹 브라우저를 실시간으로 움직이며, 당신이 이것이 무엇을 하는지도 볼 수 있다.

 

1

pip install selenium

 

 

1. pip install selenium으로 설치를 한다.

 

셀레니움은 많은 종류의 웹브라우저를 자동화 할 수 있지만, 그에 따라서 드라이버 또한 필요하다.

여기서는 크롬을 웹브라우져로 써 사용할 것이고 chromedriver binary,를 이용한다. 링크에서 다운 받을 수 있고 

my /usr/local/bin directory.이 곳에 저장해 놓아야 한다.

 

주의: 당신이 쓰는 웹 브라우저가 있을 것이다. 내가 크롬 쓰는 것처럼. 하지만, 처음부터 하기 힘드니 그냥 이거 따라 해라.

Getting started

시작하기

 

 

 

많은 것들을 임포트 하는 것으로부터 시작한다. 이유는 나중에 다 사용되기 때문이다.

import selenium

from selenium import webdriver

from selenium.webdriver.common.keys import Keys

from selenium.webdriver.common.by import By

from selenium.webdriver.support.ui import WebDriverWait

from selenium.webdriver.support import expected_conditions as EC

from selenium.common.exceptions import TimeoutException



from bs4 import BeautifulSoup as bs

import time

 

Starting a web browser

웹 브라우져 시작하기

 

일단 웹브라우져를 위한 드라이버를 가동시키는 것이 첫번째 스텝이다. 멋져 보이고 싶었으면 함수를 포함하는 클래스를 만들었겠지만, 명료함을 위해 각각의 함수들을 쓸 것이다.

 

이것이 웹브라우져 드라이버를 가동시키기 위한 함수다.

def init_driver():



     # initiate the driver:

     driver = webdriver.Chrome()



     # set a default wait time for the browser [5 seconds here]:

     driver.wait = WebDriverWait(driver, 5)



     return driver


만약 어떤한 이유로 크롬을 위한 드라이버를 경로에 넣고 싶지 않다면, 다른 경로를 특정할 수 있다. 

대신에 여기에 드라이버를 닫는 함수가 있다. 프로그램의 마지막에 이 함수를 쓸 것이다. 

 

def close_driver(driver):



    driver.close()



    return

 

 

Log in to Twitter

트위터에 로그인하기

 

드라이버가 가동 되면, 할 일이 있다. 나는 이것을 트위터의 기록을 캐내는데 사용할 것이기 떄문에 로그인을 할 필요가 있다.

 

 

로그인 페이지에는 패스워드, 아이디를 넣는 두개의 박스가 있다.

우리는 셀리니움 드라이버를 사용해서 두개의 값을 넣어 줄 것이다ㅣ

그러기 위해서는 셀레님움에게 말해줄 필요가 있다.

이 정보는 html에서 찾을 수 있다.

이 정보를 찾기 위한 가장 쉬운 방법은 아래와 같다.

 

1. 로그인 페이지를 열어라

2. 박스 위에 다가 커서를 가져다 대라.

3. 우클릭

4. 메뉴바에서 나오는 검사를 눌러라

5. 이 작업이 html에서의 관련있는 위치를 찾아주고 색깔을 바꿔 강조해준다. 아래와 같이 보일 것이다. 

 

 

 

그러면 input클래스에 js-username-field와 js-password-field.를 볼 수 있을 것이다.

 

def login_twitter(driver, username, password):



    # open the web page in the browser:

    driver.get("https://twitter.com/login")



    # find the boxes for username and password

    username_field = driver.find_element_by_class_name("js-username-field")

    password_field = driver.find_element_by_class_name("js-password-field")



    # enter your username:

    username_field.send_keys(username)

    driver.implicitly_wait(1)



    # enter your password:

    password_field.send_keys(password)

    driver.implicitly_wait(1)



    # click the "Log In" button:

    driver.find_element_by_class_name("EdgeButtom--medium").click()



    return
 

Search Twitter 

트위터 검색

 

 

로그인을 하면, 단어를 검색을 할 수 있다. 나는 쿼리라고 불리는 문자열을 정의했다. 이 작업을 하기 위해서 우리는 다시 html 안에 있는 박스를 찾아야 한다. 로그인을 할 때 사용한 바로 그 방법으로 찾을 수 있다.

그런데 만약, 인터넷이 느려서 html이 로딩되기도 전에 명령을 실행한다면 명령은 실패할 것이다. 그래서 약간의 조치가 필요하다.

 

트위터 검색결과는 무한 스크롤링 형태로 표현된다. 그렇기 때문에 나는 모든 결과를 추출하기 위해 스크롤을 내리는 루프를 폼하시켰다. 이것을 하기 위해서 스택 오버플로우에 있는 함수를 이용하였다.

 

class wait_for_more_than_n_elements_to_be_present(object):

    def __init__(self, locator, count):

        self.locator = locator

        self.count = count



    def __call__(self, driver):

        try:

            elements = EC._find_elements(driver, self.locator)

            return len(elements) > self.count

        except StaleElementReferenceException:

            return False

 

 

which I call inside this function:

이 기능을 불러올 수 있다.

 

def search_twitter(driver, query):



    # wait until the search box has loaded:

    box = driver.wait.until(EC.presence_of_element_located((By.NAME, "q")))



    # find the search box in the html:

    driver.find_element_by_name("q").clear()



    # enter your search string in the search box:

    box.send_keys(query)



    # submit the query (like hitting return):

    box.submit()



    # initial wait for the search results to load

    wait = WebDriverWait(driver, 10)



    try:

        # wait until the first search result is found. Search results will be tweets, which are html list items and have the class='data-item-id':

        wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "li[data-item-id]")))



        # scroll down to the last tweet until there are no more tweets:

        while True:



            # extract all the tweets:

            tweets = driver.find_elements_by_css_selector("li[data-item-id]")



            # find number of visible tweets:

            number_of_tweets = len(tweets)



            # keep scrolling:

            driver.execute_script("arguments[0].scrollIntoView();", tweets[-1])



            try:

                # wait for more tweets to be visible:

                wait.until(wait_for_more_than_n_elements_to_be_present(

                    (By.CSS_SELECTOR, "li[data-item-id]"), number_of_tweets))



            except TimeoutException:

                # if no more are visible the "wait.until" call will timeout. Catch the exception and exit the while loop:

                break



        # extract the html for the whole lot:

        page_source = driver.page_source



    except TimeoutException:



        # if there are no search results then the "wait.until" call in the first "try" statement will never happen and it will time out. So we catch that exception and return no html.

        page_source=None



    return page_source

 

.html클래스를 통해서 트윗을 끌어온 것을 볼 수 있다. 트윗은 html list items의 형태로 반환되며 tag는 li이며 클래스는'data-item-id'다.

 

Read info from tweets

트위터에서 정보 읽기

 

html을 추출하면, 어떤 정보든지 추출할 수 있다. 개인적으로 나는 뷰티풀 숩을 이용한다.

 

 

def extract_tweets(page_source):



    soup = bs(page_source,'lxml')



    tweets = []

    for li in soup.find_all("li", class_='js-stream-item'):



        # If our li doesn't have a tweet-id, we skip it as it's not going to be a tweet.

        if 'data-item-id' not in li.attrs:

            continue



        else:

            tweet = {

                'tweet_id': li['data-item-id'],

                'text': None,

                'user_id': None,

                'user_screen_name': None,

                'user_name': None,

                'created_at': None,

                'retweets': 0,

                'likes': 0,

                'replies': 0

            }



            # Tweet Text

            text_p = li.find("p", class_="tweet-text")

            if text_p is not None:

                tweet['text'] = text_p.get_text()



            # Tweet User ID, User Screen Name, User Name

            user_details_div = li.find("div", class_="tweet")

            if user_details_div is not None:

                tweet['user_id'] = user_details_div['data-user-id']

                tweet['user_screen_name'] = user_details_div['data-screen-name']

                tweet['user_name'] = user_details_div['data-name']



            # Tweet date

            date_span = li.find("span", class_="_timestamp")

            if date_span is not None:

                tweet['created_at'] = float(date_span['data-time-ms'])



            # Tweet Retweets

            retweet_span = li.select("span.ProfileTweet-action--retweet > span.ProfileTweet-actionCount")

            if retweet_span is not None and len(retweet_span) > 0:

                tweet['retweets'] = int(retweet_span[0]['data-tweet-stat-count'])



            # Tweet Likes

            like_span = li.select("span.ProfileTweet-action--favorite > span.ProfileTweet-actionCount")

            if like_span is not None and len(like_span) > 0:

                tweet['likes'] = int(like_span[0]['data-tweet-stat-count'])



            # Tweet Replies

            reply_span = li.select("span.ProfileTweet-action--reply > span.ProfileTweet-actionCount")

            if reply_span is not None and len(reply_span) > 0:

                tweet['replies'] = int(reply_span[0]['data-tweet-stat-count'])



            tweets.append(tweet)



    return tweets

 

 

:Putting it all together

한꺼번에 수행하기

 

한꺼번에 다 넣으면 이렇다.

if __name__ == "__main__":



    # start a driver for a web browser:

    driver = init_driver()



    # log in to twitter (replace username/password with your own):

    username = <<USERNAME>>

    password = <<PASSWORD>>

    login_twitter(driver, username, password)



    # search twitter:

    query = "what ever you want to search for"

    page_source = search_twitter(driver, query)



    # extract info from the search results:

    tweets = extract_tweets(page_source)

     

    # ==============================================

    # add in any other functions here

    # maybe some analysis functions

    # maybe a function to write the info to file

    # ==============================================



    # close the driver:

    close_driver(driver)

 

네가 손으로 할 수 있는 모든 것은 셀레니움으로 자동화할 수 있다. 빠르지는 않지만 꽤나 유용하다.