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)
네가 손으로 할 수 있는 모든 것은 셀레니움으로 자동화할 수 있다. 빠르지는 않지만 꽤나 유용하다.
'2019년 혁신성장 청년인재 집중양성(빅데이터) > 집중양성과정 프로젝트 01' 카테고리의 다른 글
데이터 공유 웹사이트 및 비트코인 센티먼트 코퍼스(TWITTER BITCOIN SENTIMENT CORPUS) (0) | 2019.08.22 |
---|---|
[데이터 전처리 준비] 감성 분석을 해부해 보자! (0) | 2019.08.20 |
6. 크롤링한 JSON 파일을 SQLITE DATABSE로 옮기기 (0) | 2019.07.23 |
5. 파이썬 신문사 홈페이지에서 원하는 텍스트가 포함된 타이틀 가져오기 (0) | 2019.07.22 |
04. mysql과 python 연결하기 (0) | 2019.07.22 |