├── requirements.txt ├── config.py ├── README.md ├── LICENSE └── craigslist.py /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.3.2 2 | wsgiref==0.1.2 3 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | email = dict( 2 | username = '', 3 | password = '' 4 | ) 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Craiglist Checker 2 | ================= 3 | Send a text when there's a new "For Sale" post for a given keyword or phrase. 4 | 5 | The script sends an SMS message to a given phone number using GMail's SMTP protocol, so you'll need to add your GMail username and password to the config file. 6 | 7 | An SMS message will only be sent if a new post appears (based on the full URL). 8 | 9 | Setup 10 | ----- 11 | Install the required libraries via pip: 12 | 13 | pip install -r requirements.txt 14 | 15 | Usage 16 | ----- 17 | python craigslist-checker.py 18 | 19 | It's useful to setup a cronjob that will run the script every N minutes. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Greg Reda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /craigslist.py: -------------------------------------------------------------------------------- 1 | from bs4 import BeautifulSoup 2 | from urllib2 import urlopen 3 | from datetime import datetime 4 | import csv 5 | import sys 6 | import os 7 | import smtplib 8 | import config 9 | 10 | # Craigslist search URL 11 | BASE_URL = ('http://chicago.craigslist.org/search/' 12 | '?sort=rel&areaID=11&subAreaID=&query={0}&catAbb=sss') 13 | 14 | def parse_results(search_term): 15 | results = [] 16 | search_term = search_term.strip().replace(' ', '+') 17 | search_url = BASE_URL.format(search_term) 18 | soup = BeautifulSoup(urlopen(search_url).read()) 19 | rows = soup.find('div', 'content').find_all('p', 'row') 20 | for row in rows: 21 | url = 'http://chicago.craigslist.org' + row.a['href'] 22 | # price = row.find('span', class_='price').get_text() 23 | create_date = row.find('time').get('datetime') 24 | title = row.find_all('a')[1].get_text() 25 | results.append({'url': url, 'create_date': create_date, 'title': title}) 26 | return results 27 | 28 | def write_results(results): 29 | """Writes list of dictionaries to file.""" 30 | fields = results[0].keys() 31 | with open('results.csv', 'w') as f: 32 | dw = csv.DictWriter(f, fieldnames=fields, delimiter='|') 33 | dw.writer.writerow(dw.fieldnames) 34 | dw.writerows(results) 35 | 36 | def has_new_records(results): 37 | current_posts = [x['url'] for x in results] 38 | fields = results[0].keys() 39 | if not os.path.exists('results.csv'): 40 | return True 41 | 42 | with open('results.csv', 'r') as f: 43 | reader = csv.DictReader(f, fieldnames=fields, delimiter='|') 44 | seen_posts = [row['url'] for row in reader] 45 | 46 | is_new = False 47 | for post in current_posts: 48 | if post in seen_posts: 49 | pass 50 | else: 51 | is_new = True 52 | return is_new 53 | 54 | def send_text(phone_number, msg): 55 | fromaddr = "Craigslist Checker" 56 | toaddrs = phone_number + "@txt.att.net" 57 | msg = ("From: {0}\r\nTo: {1}\r\n\r\n{2}").format(fromaddr, toaddrs, msg) 58 | server = smtplib.SMTP('smtp.gmail.com:587') 59 | server.starttls() 60 | server.login(config.email['username'], config.email['password']) 61 | server.sendmail(fromaddr, toaddrs, msg) 62 | server.quit() 63 | 64 | def get_current_time(): 65 | return datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S') 66 | 67 | if __name__ == '__main__': 68 | try: 69 | TERM = sys.argv[1] 70 | PHONE_NUMBER = sys.argv[2].strip().replace('-', '') 71 | except: 72 | print "You need to include a search term and a 10-digit phone number!\n" 73 | sys.exit(1) 74 | 75 | if len(PHONE_NUMBER) != 10: 76 | print "Phone numbers must be 10 digits!\n" 77 | sys.exit(1) 78 | 79 | results = parse_results(TERM) 80 | 81 | # Send the SMS message if there are new results 82 | if has_new_records(results): 83 | message = "Hey - there are new Craigslist posts for: {0}".format(TERM.strip()) 84 | print "[{0}] There are new results - sending text message to {0}".format(get_current_time(), PHONE_NUMBER) 85 | send_text(PHONE_NUMBER, message) 86 | write_results(results) 87 | else: 88 | print "[{0}] No new results - will try again later".format(get_current_time()) 89 | --------------------------------------------------------------------------------