You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

87 lines
3.3 KiB

7 years ago
  1. from bs4 import BeautifulSoup as BS
  2. import datetime
  3. import re
  4. from operator import sub
  5. def dateparse(datetime_str):
  6. date = '%b %d, %Y'
  7. time = '%I:%M %p'
  8. try:
  9. return datetime.datetime.strptime(datetime_str,date)
  10. except ValueError:
  11. return datetime.datetime.strptime(datetime_str,time)
  12. days = [None,'M','T','W','R','F',None]
  13. simp_exceptions = ['Grade Mode']
  14. def datetime2date_time(dtime,mode):
  15. if mode == 'date':
  16. return datetime.date(dtime.year,dtime.month,dtime.day)
  17. elif mode == 'time':
  18. return datetime.time(dtime.hour,dtime.minute,dtime.second)
  19. def seconds_from_midnight(t):
  20. return t.hour*60**2+ t.minute*60+t.second
  21. class Class:
  22. def __init__(self,data):
  23. info,times = data
  24. #info
  25. self.title,self.abrv,self.session = info.find('caption').text.split(' - ')
  26. self.session = int(self.session)
  27. rows = info.find_all('tr')
  28. for row in rows:
  29. name = row.find('th').text.rstrip(':')
  30. data = re.sub(r'^ +|[\n\r\t]','',row.find('td').text)
  31. if name == 'Status':
  32. type,date = data.split(' on ')
  33. type = type.replace('*','')
  34. self.type = type
  35. self.registration_date = dateparse(date)
  36. else:
  37. if name in simp_exceptions:
  38. name = name.lower().replace(' ','_')
  39. else:
  40. name = name.lower().split(' ')[-1]
  41. if name != 'instructor':
  42. data = data.lower()
  43. try:
  44. data = int(re.sub(r'\.\d+','',data))
  45. except:
  46. pass
  47. self.__dict__[name] = data
  48. #time
  49. headers,data = times.find_all('tr')
  50. data = (col.text for col in data.find_all('td'))
  51. headers = (header.text.lower() for header in headers.find_all('th'))
  52. time_data = dict(zip(headers,data))
  53. if time_data['time'] == 'TBA':
  54. self.time_range = None
  55. else:
  56. s,e = map(dateparse,time_data['time'].split(' - '))
  57. self.time_range = (
  58. datetime2date_time(s,'time'),
  59. datetime2date_time(e,'time'),
  60. )
  61. s,e = map(dateparse,time_data['date range'].split(' - '))
  62. self.date_range = (
  63. datetime2date_time(s,'date'),
  64. datetime2date_time(e,'date'),
  65. )
  66. time_data['days'] = re.sub('[^{}]'.format(''.join(filter(bool,days))),'',time_data['days'])
  67. self.days = list(days.index(time_data['days'][i]) for i in range(len(time_data['days'])))
  68. self.location = time_data['where']
  69. @property
  70. def length(self):
  71. return datetime.timedelta(seconds = sub(
  72. seconds_from_midnight(self.time_range[1]),
  73. seconds_from_midnight(self.time_range[0]),
  74. ))
  75. def get_classes(page):
  76. if not isinstance(page,BS):
  77. page = BS(page,'lxml')
  78. tables = page.find_all('table',attrs= {'class':'datadisplaytable'})
  79. groups = ((tables[i],tables[i+1]) for i in range(0,len(tables),2))
  80. return list(map(Class,groups))
  81. if __name__ == "__main__":
  82. with open('schedule.html') as file:
  83. page = BS(file.read(),'lxml')
  84. class1,*classes = get_classes(page)