The website labsandmore.org/dogs uploads dogs in California that are up for adoption. The problem is the waitlist fills up extremely fast and there's no notification system or email list.
Thus I've been called to duty to scrape the website every hour and send a text when new dogs have been found for my friend looking to adopt! I use the Selenium package to scrape the website, Pandas for some of the minor data wrangling, and finally Yagmail to send the text (from email).
import pandas as pd
import yagmail
import time
import re
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
# Setting path and opening browser
path = r'C:\Users\lukef\AppData\Local\BrowserDriver\geckodriver.exe'
# Setting headless
options = webdriver.FirefoxOptions()
options.headless = True
driver = webdriver.Firefox(executable_path = path, options = options)
# Navigating to website
driver.get('https://labsandmore.org/dogs/')
i = 0
breed = []
details = []
name = []
# First looping through breed and details elements
for li in driver.find_elements_by_xpath("//div[@class='property-stats']//ul//li"):
# Alternates between breed and details in the li (list) elements
if i % 2 == 0:
breed.append(li.text)
else:
details.append(li.text)
i += 1
# Now looping through the name elements
for li in driver.find_elements_by_xpath("//div[@class='tile-footer']/div[@class='price']/a"):
name.append(li.text)
driver.close()
df = pd.DataFrame(dict(name=name,breed=breed,details=details))
df
| name | breed | details | |
|---|---|---|---|
| 0 | America | Shepherd (mix) | Female, 12 years, 46 lbs. ID 7874 |
| 1 | Artichoke | Husky (mix) | Female, 2 years, 60 lbs. ID 11658 |
| 2 | Artichoke Pup - Broccoli | Husky - Shepherd (mix) | Female, 7 weeks, 5 lbs. ID 11659 |
| 3 | Artichoke Pup - Celery | Husky - Shepherd (mix) | Male, 7 weeks, 5 lbs. ID 11660 |
| 4 | Artichoke Pup - Cucumber | Husky - Shepherd (mix) | Male, 7 weeks, 5 lbs. ID 11661 |
| ... | ... | ... | ... |
| 155 | Yoda | Labrador Retriever - American Staffordshire Te... | Male, 3 years, 54 lbs. ID 11751 |
| 156 | Yoko - Adopted! | Boxer (purebred) | Female, 2 years, 63 lbs. ID 11365 |
| 157 | Zinc | Terrier - Pointer (mix) | Female, 2 years, 32 lbs. ID 11531 |
| 158 | Zipper - Adopted! | Labrador Retriever - Cattle Dog (mix) | Female, 2 years, 40 lbs. ID 11489 |
| 159 | Zippy - Adopted! | Shepherd - Labrador Retriever (mix) | Male, 5 months, 22 lbs. ID 11794 |
df[['gender','age','weight','id']] = df['details'].str.extract(pat = r'(.*?),\s(.*?),\s(.*?)\.\sID\s(\d*)')
df.drop(columns = 'details', inplace = True)
df['id'] = df['id'].astype('int32')
df
| name | breed | gender | age | weight | id | |
|---|---|---|---|---|---|---|
| 0 | America | Shepherd (mix) | Female | 12 years | 46 lbs | 7874 |
| 1 | Artichoke | Husky (mix) | Female | 2 years | 60 lbs | 11658 |
| 2 | Artichoke Pup - Broccoli | Husky - Shepherd (mix) | Female | 7 weeks | 5 lbs | 11659 |
| 3 | Artichoke Pup - Celery | Husky - Shepherd (mix) | Male | 7 weeks | 5 lbs | 11660 |
| 4 | Artichoke Pup - Cucumber | Husky - Shepherd (mix) | Male | 7 weeks | 5 lbs | 11661 |
| ... | ... | ... | ... | ... | ... | ... |
| 155 | Yoda | Labrador Retriever - American Staffordshire Te... | Male | 3 years | 54 lbs | 11751 |
| 156 | Yoko - Adopted! | Boxer (purebred) | Female | 2 years | 63 lbs | 11365 |
| 157 | Zinc | Terrier - Pointer (mix) | Female | 2 years | 32 lbs | 11531 |
| 158 | Zipper - Adopted! | Labrador Retriever - Cattle Dog (mix) | Female | 2 years | 40 lbs | 11489 |
| 159 | Zippy - Adopted! | Shepherd - Labrador Retriever (mix) | Male | 5 months | 22 lbs | 11794 |
i.e. already adopted or the waiting list is full.
df['unavailable'] = df['name'].str.lower().str.contains('adopted') \
| df['name'].str.lower().str.contains('list full')
df
| name | breed | gender | age | weight | id | unavailable | |
|---|---|---|---|---|---|---|---|
| 0 | America | Shepherd (mix) | Female | 12 years | 46 lbs | 7874 | False |
| 1 | Artichoke | Husky (mix) | Female | 2 years | 60 lbs | 11658 | False |
| 2 | Artichoke Pup - Broccoli | Husky - Shepherd (mix) | Female | 7 weeks | 5 lbs | 11659 | False |
| 3 | Artichoke Pup - Celery | Husky - Shepherd (mix) | Male | 7 weeks | 5 lbs | 11660 | False |
| 4 | Artichoke Pup - Cucumber | Husky - Shepherd (mix) | Male | 7 weeks | 5 lbs | 11661 | False |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 155 | Yoda | Labrador Retriever - American Staffordshire Te... | Male | 3 years | 54 lbs | 11751 | False |
| 156 | Yoko - Adopted! | Boxer (purebred) | Female | 2 years | 63 lbs | 11365 | True |
| 157 | Zinc | Terrier - Pointer (mix) | Female | 2 years | 32 lbs | 11531 | False |
| 158 | Zipper - Adopted! | Labrador Retriever - Cattle Dog (mix) | Female | 2 years | 40 lbs | 11489 | True |
| 159 | Zippy - Adopted! | Shepherd - Labrador Retriever (mix) | Male | 5 months | 22 lbs | 11794 | True |
try:
old = pd.read_csv('dogs.csv')
except:
old = pd.DataFrame(columns = df.columns)
old
| name | breed | gender | age | weight | id | unavailable | |
|---|---|---|---|---|---|---|---|
| 0 | America | Shepherd (mix) | Female | 12 years | 46 lbs | 7874 | False |
| 1 | Asteroid - (Medical) | Labrador Retriever - Cattle Dog (mix) | Female | 3 years | 28 lbs | 11632 | False |
| 2 | Barcelona - Adopted! | Labrador Retriever - Shepherd (mix) | Female | 4 months | 15 lbs | 11636 | True |
| 3 | Barnes - Waiting List Full | Labrador Retriever - Shepherd (mix) | Female | 5 months | 10 lbs | 11474 | True |
| 4 | Bayleigh | Labrador Retriever (mix) | Female | 4 years | 47 lbs | 11547 | False |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 209 | NaN | Shepherd (mix) | Female | 1 year | 42 lbs | 11410 | NaN |
| 210 | NaN | Labrador Retriever - Terrier (mix) | Female | 5 years | 30 lbs | 10888 | NaN |
| 211 | NaN | Labrador Retriever (mix) | Female | 2 years | 40 lbs | 11409 | NaN |
| 212 | NaN | Labrador Retriever (mix) | Male | 1 year | 60 lbs | 11282 | NaN |
| 213 | NaN | Labrador Retriever - Terrier (mix) | Female | 1 year | 35 lbs | 11439 | NaN |
def send_email(df):
sender_email = 'lukefeilbergp@gmail.com'
receiver_email = 'enter_phone_number_here@tmomail.net'
subject = "New dogs alert!"
password = 'Enter_Password_Here'
contents = []
yag = yagmail.SMTP(user=sender_email, password=password)
# Displaying at most 5
for i in range(min(len(df), 5)):
contents.append('')
contents.append(df.iloc[i]['name']
+ ', '
+ df.iloc[i]['breed']
+ ', '
+ df.iloc[i]['age'])
contents.extend(['', 'https://labsandmore.org/dogs/'])
yag.send(receiver_email, subject, contents);
if sum(~df['id'].isin(old['id'])) > 0:
print('New dogs found! :-)')
new_dogs = df[~df['id'].isin(old['id'])]
send_email(new_dogs)
New dogs found! :-)
old_and_new = pd.concat([df, old])
old_and_new.drop_duplicates(subset='id',keep='first',inplace=True)
old_and_new
| name | breed | gender | age | weight | id | unavailable | |
|---|---|---|---|---|---|---|---|
| 0 | America | Shepherd (mix) | Female | 12 years | 46 lbs | 7874 | False |
| 1 | Artichoke | Husky (mix) | Female | 2 years | 60 lbs | 11658 | False |
| 2 | Artichoke Pup - Broccoli | Husky - Shepherd (mix) | Female | 7 weeks | 5 lbs | 11659 | False |
| 3 | Artichoke Pup - Celery | Husky - Shepherd (mix) | Male | 7 weeks | 5 lbs | 11660 | False |
| 4 | Artichoke Pup - Cucumber | Husky - Shepherd (mix) | Male | 7 weeks | 5 lbs | 11661 | False |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 209 | NaN | Shepherd (mix) | Female | 1 year | 42 lbs | 11410 | NaN |
| 210 | NaN | Labrador Retriever - Terrier (mix) | Female | 5 years | 30 lbs | 10888 | NaN |
| 211 | NaN | Labrador Retriever (mix) | Female | 2 years | 40 lbs | 11409 | NaN |
| 212 | NaN | Labrador Retriever (mix) | Male | 1 year | 60 lbs | 11282 | NaN |
| 213 | NaN | Labrador Retriever - Terrier (mix) | Female | 1 year | 35 lbs | 11439 | NaN |
old_and_new.to_csv('dogs.csv', index = False)
The Jupyter Notebook (embedded all above) was used to build up the script from which I then essentially just copy-pasted over the cells to an actual .py file (labs_scrape_and_text.py). I then created a batch script which runs the python file and finally created a Task on Windows Task Scheduler that runs the batch script (which again runs the Python script) every hour every day from 10am to 8pm (only sending texts when new dogs are found). You can check out the Github repo to see it all in one place!
An example text is shown below:
Note: The Husky "Artichoke" had many pups all uploaded at the same time in case that data looked off to you!