Optimo Sweet
Red Bull
Twitter "Like" Bot
I was on Twitter and became angry after seeing a website charge for a course that teaches how to "like" using Python and Selenium. Yes, the exact same thing this tutorial teaches for free. First off, do not pay for this type of course! There's tons of documentation available to learn on your own. This post is a breakdown of my personal and professional use of Selenium.
Full script available here.
Step 1: Requirements
- Install Selenium:
pip install selenium
- Install CHROMEDRIVER and place in your $PATH
Step 2: Gather Elements
I prefer to find elements using iPython and then wrap them into classes. Here's an example:
In [1]: from selenium import webdriver
In [2]: driver = webdriver.Chrome()
In [3]: driver.get('http://twitter.com')
drive
In [4]: username = driver.find_element_by_name('session[username_or_email]')
In [5]: username.send_keys('test-user')
As you can see, I've found the username
input field and successfully sent test-user
to the element, so I add username
as an attribute on the Locator
class. I've done the same for all elements that I'll interact with throughout this example
class TwitterLocator:
username = (By.NAME, "session[username_or_email]")
password = (By.NAME, "session[password]")
submit_btn = (By.CLASS_NAME, "js-submit")
search_input = (By.ID, "search-query")
search_btn = (By.ID, "nav-search")
tweets = (By.CLASS_NAME, "js-stream-item")
like_btn = (By.CLASS_NAME, "HeartAnimation")
latest_tweets = (By.PARTIAL_LINK_TEXT, 'Latest')
Step 3: Define Constants
I've identified the below three attributes as constant variables (not required - I prefer the organization):
class Constants:
USERNAME = os.environ.get("TWITTER_USERNAME")
PASSWORD = os.environ.get("TWITTER_PASSWORD")
GLOBAL_ENTRY_Q = '#globalentry'
Step 4: Bot
Now that we have the above out of the way, let's create the Bot
class to piece this all together.
class LikeBot(object):
def __init__(self):
self.locator_dictionary = TwitterLocator.__dict__
self.browser = webdriver.Chrome()
self.browser.get(URL.TWITTER)
self.timeout = 10
Take a look at locator_dictionary
. It's a dictionary
of TwitterLocator
attributes, attached to the LikeBot
class. We've basically given LikeBot
the same attributes as TwitterLocator
(as a variable). More on this later...
It's important to instantiate with a webdriver
, to visit the url
intended (http://twitter.com
) in this case, and to set the timeout
(in seconds).
Getattr
From here we can start creating methods, such as login
, that will login to an account as long as the browser is on the login page. A convenient. syntax-smart way of writing methods is to include a find_element
along with a __getattr__
method that looks at the locator dictionary.
def _find_element(self, *loc):
return self.browser.find_element(*loc)
def __getattr__(self, what):
try:
if what in self.locator_dictionary.keys():
try:
element = WebDriverWait(self.browser, self.timeout).until(
EC.presence_of_element_located(self.locator_dictionary[what])
)
except(TimeoutException, StaleElementReferenceException):
traceback.print_exc()
try:
element = WebDriverWait(self.browser, self.timeout).until(
EC.visibility_of_element_located(self.locator_dictionary[what])
)
except(TimeoutException, StaleElementReferenceException):
traceback.print_exc()
# I could have returned element, however because of lazy loading, I am seeking the element before return
return self._find_element(*self.locator_dictionary[what])
except AttributeError:
super(LikeBot, self).__getattribute__("method_missing")(what)
def method_missing(self, what):
print "No %s here!" % what
Now we can write methods using the below syntax in the methods:
def login(self, username=Constants.USERNAME, password=Constants.PASSWORD):
self.username.send_keys(username)
self.password.send_keys(password)
self.submit_btn.click()
I created a run
method to handle all actions as one:
def run(self):
self.login()
self.search()
self.view_latest_tweets()
time.sleep(2)
self.like_tweet()
self.browser.quit()
Notice the time.sleep()
line – this is only to wait a few seconds before executing the next lines (because changing between tabs isn't always instantaneous)