时间:2021-07-01 10:21:17 帮助过:33人阅读
这是前段时间参加UTD Hackathon的总结。
这是在TAMU的同学邀请一起去往往的,我主要负责了爬虫这个部分。所以这里主要讲讲我的工作。其他我不了解的部分也就不多说了,不要布鼓雷门了。
这个项目的出发点就是我们希望开发一个类似12306抢票助手的工具来预约驾照考试。主要是我同学提出的需求,原来美帝和我们买火车票一样,需要先预约驾照考试。但是这儿存在一个什么冒顿呢,TAMU所在的college town人特别多,就和春运火车票一样需要不断刷预约。
据同学说有中国人就开发了这个软件,对外销售。当然这个行为我们等会再讨论,首先指望我们这种喜欢自己动手的人花钱买这个是不切实际的。所以这次同学联合他的两个学弟我们一起开发这个项目。
我们的项目在这儿,全部开源。当然因为时间有限,所以我们有的代码写得非常粗糙,欢迎斧正。
HackUTD : Find Available Driving Test Appointment
首先如果你希望自动话一个功能,那么你一定要能够手动实现这个功能。如果你没有很高的AI设计,计算机是很难自己自动实现某些功能的。
首先计算机的预约过程是打开 Texas Department of Public Safety ,如下图所示,然后选择服务,一般都是小车预约。当然这里我们已经选择好了所在城市2571 North Earl Rudder Freeway Bryan, TX 77803,也就是TAMU所在的college town。
然后就会出现一个时间表,这个表中灰色的是已经预约满了的,白色的是可以预约的点击即可。值得注意的不能预约当天,同时不能预约超过90天的。
这里需要注意的,这个网页是在我们点击之后才生成的,同时表格月份日期的那栏有向左和向右的箭头。这两个箭头如果打开分别对应了两个Javascript脚本从而实现动态更新部分网页的功能。
对于静态的HTML页面我们可以使用Beautiful Soup进行页面解析从而得到我们需要的页面内容,但是如何处理Ajax,Javascript?
通常来说有两个思路:
我选择第二个方法,使用selenium 的 webdriver 功能来实现解析。这样,通过率模拟浏览器的点击等行为我们可以方便的进行交互。
然后得到了需要的静态HTML之后只要进行相应的解析处理即可。主要来说所有需要的信息是日期和是不是可以注册,所以只要针对这个日期标的数据做解析即可,而DPS的这个网页的写得非常规范,所有日期分成四个种类,calendar-closed 这是指当天,calendar-notavailable这是已经过去的日期和超过90天的日期,calendar-fullday这是虽然在90天内但是注册满了的日期,calendar-available顾名思义就是可以注册的日期。所以爬取信息也很简单直接通过class得到这几个属性即可。最后得到的结果放到数据库中,给其他程序使用。
上图就是我们的整体设计框架,主要的部分是首先前端基于 ruby on rails 。UI效果如下:
后端实现一个每过10min扫描一遍预约信息网站的更新脚本。然后如果检测到不同且该日期属于注册用户的指定日期那么调用发用邮件程序给该用户发送可以预约信息的邮件,提醒用户赶快上网预约。
这个任务其实比较简单,但是能够连贯起来还是非常不错的。当然对于一般用户而言,我们不需要暴露这么多细节。事实上做成一个网站就可以了,让用户使用的主要是前段部分。后端应该都放在网上。
当然要是方便的话,实现iOS或者Andriod客户端就更好了。
这个留待以后解决。
#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on Sun Mar 27 06:42:50 2016@author: iphyeroutput :return value1 means sth changed0 nothing changed"""#import needed packagefrom selenium import webdriver # web actionfrom pyvirtualdisplay import Displayfrom datetime import dateimport BeautifulSoup as bsimport sqlite3import pickle# function setupdef htmltableGetinfomation(websource,data_page): """ get information from html source input : html source code output : dict of data and availibility """ month2num = { 'January' : 1, 'February': 2,'March' : 3,'April': 4,'May': 5,'June': 6,'July' : 7,'August': 8, 'September': 9, 'October': 10, 'November' : 11, 'December': 12} soup = bs.BeautifulSoup(websource) table = soup.find(lambda tag: tag.name=='table' and tag.has_key('id') and tag['id']=="calendar") temp = table.findAll('span') Mon, Year = temp[0].string.split() # insert starting day calendar-closed Mon = month2num.get(Mon) Mon = int(Mon) Year = int(Year) days_closed = table.findAll(lambda tag: tag.name=='td' and tag.has_key('class') and tag['class'] == 'calendar-closed') for i in days_closed: Day = int(i.text) date_temp = date(Year,Mon,Day).strftime("%m/%d/%Y") data_page[date_temp] = 0 # insert following day calendar-fullday days_full = table.findAll(lambda tag: tag.name=='td' and tag.has_key('class') and tag['class'] == 'calendar-fullday') for i in days_full: Day = int(i.text) date_temp = date(Year,Mon,Day).strftime("%m/%d/%Y") data_page[date_temp] = 0 # insert following day calendar-available days_available = table.findAll(lambda tag: tag.name=='td' and tag.has_key('class') and tag['class'] == 'calendar-available') for i in days_available: Day = int(i.text) date_temp = date(Year,Mon,Day).strftime("%m/%d/%Y") data_page[date_temp] = 1 #print "===========================================" #print data_page return data_pagedef creat_sqlite(Allowed_Days_Dict): """ creat sqlite of 90 allowed time with their availability input : Allowed_Days_Dict = {date : availability} """ #connecting database and creating table availability conn = sqlite3.connect("development.sqlite3") cursor = conn.cursor() cursor.execute("""DROP TABLE IF EXISTS availability""") cursor.execute("""CREATE TABLE IF NOT EXISTS availability (date text,av int) """) dictlist = [] for key, value in Allowed_Days_Dict.iteritems(): temp = [key,value] dictlist.append(temp) cursor.executemany("INSERT INTO availability VALUES (?,?)" ,dictlist) conn.commit() cursor.close() conn.close()def sqlreading(): pkl_file = open('myfile.pkl', 'rb') Allowed_Days_Dict_old = pickle.load(pkl_file) pkl_file.close() return Allowed_Days_Dict_olddef dictDiff(dictA,dictB): for key,val in dictA.iteritems(): if ( val != dictB[key] ): return 1def renewSQL(changed,Allowed_Days_Dict): if ( changed ): creat_sqlite(Allowed_Days_Dict) output = open('myfile.pkl', 'wb') pickle.dump(Allowed_Days_Dict, output) output.close()if __name__ == "__main__": """ method description : 1.get four month available and unavailable days and then return as a dict 2.dict to sqlite """ #Assign URL for search #Appointment Locator # Location Address # 2571 North Earl Rudder Freeway # Bryan, TX 77803 URL = 'https://booknow.securedata-trans.com/1qed83ds/' #four_months for result record Allowed_Days_Dict = dict() # get webpage information display = Display(visible=0, size=(800, 600)) display.start()# now Firefox will run in a virtual display. # you will not see the browser. browser = webdriver.Firefox() browser.implicitly_wait(5) browser.get('https://booknow.securedata-trans.com/1qed83ds/') browser.find_element_by_xpath("//select[@name='service_id']/option[text()='Driving Test - Regular Car / Pick-up Truck']").click() source = browser.page_source Allowed_Days_Dict = htmltableGetinfomation(source,Allowed_Days_Dict) #get another 3 pages information for i in range(1,4): browser.execute_script("javascript:dosubmit1('no', 'yes', 'log_in')") source = browser.page_source Allowed_Days_Dict = htmltableGetinfomation(source,Allowed_Days_Dict) #print "===========================================" #print Allowed_Days_Dict browser.close() #reading in the old date dict Allowed_Days_Dict_old = sqlreading() changed = 0 changed = dictDiff( Allowed_Days_Dict_old, Allowed_Days_Dict) renewSQL(changed,Allowed_Days_Dict) print "================update_10min Done===========================" exit(changed)