Automatically exported from code.google.com/p/planningalerts
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.
 
 
 
 
 
 

392 lines
14 KiB

  1. #!/usr/local/bin/python
  2. import urllib, urllib2
  3. import HTMLParser
  4. #from BeautifulSoup import BeautifulSoup
  5. import urlparse
  6. import re
  7. end_head_regex = re.compile("</head", re.IGNORECASE)
  8. import MultipartPostHandler
  9. # this is not mine, or part of standard python (though it should be!)
  10. # it comes from http://pipe.scs.fsu.edu/PostHandler/MultipartPostHandler.py
  11. from PlanningUtils import getPostcodeFromText, PlanningAuthorityResults, PlanningApplication
  12. from datetime import date
  13. from time import strptime
  14. date_format = "%d/%m/%Y"
  15. our_date = date(2007,4,25)
  16. class AcolnetParser(HTMLParser.HTMLParser):
  17. case_number_tr = None # this one can be got by the td class attribute
  18. reg_date_tr = None
  19. location_tr = None
  20. proposal_tr = None
  21. # There is no online comment facility in these, so we provide an
  22. # appropriate email address instead
  23. comments_email_address = None
  24. def __init__(self,
  25. authority_name,
  26. authority_short_name,
  27. base_url,
  28. debug=False):
  29. HTMLParser.HTMLParser.__init__(self)
  30. self.authority_name = authority_name
  31. self.authority_short_name = authority_short_name
  32. self.base_url = base_url
  33. self.debug = debug
  34. self._tr_number = 0
  35. # This will be used to track the subtable depth
  36. # when we are in a results-table, in order to
  37. # avoid adding an application before we have got to
  38. # the end of the results-table
  39. self._subtable_depth = None
  40. self._in_td = False
  41. # This in where we store the results
  42. self._results = PlanningAuthorityResults(self.authority_name, self.authority_short_name)
  43. # This will store the planning application we are currently working on.
  44. self._current_application = None
  45. def _cleanupHTML(self, html):
  46. """This method should be overridden in subclasses to perform site specific
  47. HTML cleanup."""
  48. return html
  49. def handle_starttag(self, tag, attrs):
  50. #print tag, attrs
  51. if tag == "table":
  52. if self._current_application is None:
  53. # Each application is in a separate table with class "results-table"
  54. for key, value in attrs:
  55. if key == "class" and value == "results-table":
  56. #print "found results-table"
  57. self._current_application = PlanningApplication()
  58. self._tr_number = 0
  59. self._subtable_depth = 0
  60. self._current_application.comment_url = self.comments_email_address
  61. break
  62. else:
  63. # We are already in a results-table, and this is the start of a subtable,
  64. # so increment the subtable depth.
  65. self._subtable_depth += 1
  66. elif self._current_application is not None:
  67. if tag == "tr" and self._subtable_depth == 0:
  68. self._tr_number += 1
  69. if tag == "td":
  70. self._in_td = True
  71. if self._tr_number == self.case_number_tr:
  72. #get the reference and the info link here
  73. pass
  74. elif self._tr_number == self.reg_date_tr:
  75. #get the registration date here
  76. pass
  77. elif self._tr_number == self.location_tr:
  78. #get the address and postcode here
  79. pass
  80. elif self._tr_number == self.proposal_tr:
  81. #get the description here
  82. pass
  83. if tag == "a" and self._tr_number == self.case_number_tr:
  84. # this is where we get the info link and the case number
  85. for key, value in attrs:
  86. if key == "href":
  87. self._current_application.info_url = value
  88. def handle_data(self, data):
  89. # If we are in the tr which contains the case number,
  90. # then data is the council reference, so
  91. # add it to self._current_application.
  92. if self._in_td:
  93. if self._tr_number == self.case_number_tr:
  94. self._current_application.council_reference = data.strip()
  95. elif self._tr_number == self.reg_date_tr:
  96. # we need to make a date object out of data
  97. date_as_str = ''.join(data.strip().split())
  98. received_date = date(*strptime(date_as_str, date_format)[0:3])
  99. #print received_date
  100. self._current_application.date_received = received_date
  101. elif self._tr_number == self.location_tr:
  102. location = data.strip()
  103. self._current_application.address = location
  104. self._current_application.postcode = getPostcodeFromText(location)
  105. elif self._tr_number == self.proposal_tr:
  106. self._current_application.description = data.strip()
  107. def handle_endtag(self, tag):
  108. #print "ending: ", tag
  109. if tag == "table" and self._current_application is not None:
  110. if self._subtable_depth > 0:
  111. self._subtable_depth -= 1
  112. else:
  113. # We need to add the last application in the table
  114. if self._current_application is not None:
  115. #print "adding application"
  116. self._results.addApplication(self._current_application)
  117. #print self._current_application
  118. self._current_application = None
  119. self._tr_number = None
  120. self._subtable_depth = None
  121. elif tag == "td":
  122. self._in_td = False
  123. def getResultsByDayMonthYear(self, day, month, year):
  124. # first we fetch the search page to get ourselves some session info...
  125. search_form_response = urllib2.urlopen(self.base_url)
  126. search_form_contents = search_form_response.read()
  127. # This sometimes causes a problem in HTMLParser, so let's just get the link
  128. # out with a regex...
  129. groups = self.action_regex.search(search_form_contents).groups()
  130. action = groups[0]
  131. #print action
  132. action_url = urlparse.urljoin(self.base_url, action)
  133. #print action_url
  134. our_date = date(year, month, day)
  135. search_data = {"regdate1": our_date.strftime(date_format),
  136. "regdate2": our_date.strftime(date_format),
  137. }
  138. opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
  139. response = opener.open(action_url, search_data)
  140. results_html = response.read()
  141. # This is for doing site specific html cleanup
  142. results_html = self._cleanupHTML(results_html)
  143. #some javascript garbage in the header upsets HTMLParser,
  144. #so we'll just have the body
  145. just_body = "<html>" + end_head_regex.split(results_html)[-1]
  146. #outfile = open(self.authority_short_name + ".debug", "w")
  147. #outfile.write(just_body)
  148. self.feed(just_body)
  149. return self._results
  150. def getResults(self, day, month, year):
  151. return self.getResultsByDayMonthYear(int(day), int(month), int(year)).displayXML()
  152. class BaberghParser(AcolnetParser):
  153. #search_url = "http://planning.babergh.gov.uk/dataOnlinePlanning/acolnetcgi.gov?ACTION=UNWRAP&RIPNAME=Root.pgesearch"
  154. case_number_tr = 1 # this one can be got by the td class attribute
  155. reg_date_tr = 2
  156. location_tr = 4
  157. proposal_tr = 5
  158. #authority_name = "Babergh District Council"
  159. #authority_short_name = "Babergh"
  160. # It would be nice to scrape this...
  161. comments_email_address = "planning.reception@babergh.gov.uk"
  162. action_regex = re.compile("<FORM name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" onSubmit=\"return ValidateSearch\(\)\" enctype=\"multipart/form-data\">")
  163. class BasingstokeParser(AcolnetParser):
  164. #search_url = "http://planning.basingstoke.gov.uk/DCOnline2/acolnetcgi.exe?ACTION=UNWRAP&RIPNAME=Root.pgesearch"
  165. case_number_tr = 1 # this one can be got by the td class attribute
  166. reg_date_tr = 3
  167. location_tr = 6
  168. proposal_tr = 8
  169. #authority_name = "Basingstoke and Deane Borough Council"
  170. #authority_short_name = "Basingstoke and Deane"
  171. # It would be nice to scrape this...
  172. comments_email_address = "development.control@basingstoke.gov.uk"
  173. action_regex = re.compile("<form id=\"frmSearch\" onSubmit=\"\"return ValidateSearch\(\)\"\" name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" enctype=\"multipart/form-data\">")
  174. class BassetlawParser(AcolnetParser):
  175. #search_url = "http://www.bassetlaw.gov.uk/planning/acolnetcgi.gov?ACTION=UNWRAP&RIPNAME=Root.pgesearch"
  176. case_number_tr = 1 # this one can be got by the td class attribute
  177. reg_date_tr = 2
  178. location_tr = 5
  179. proposal_tr = 6
  180. #authority_name = "Bassetlaw District Council"
  181. #authority_short_name = "Bassetlaw"
  182. comments_email_address = "planning@bassetlaw.gov.uk"
  183. action_regex = re.compile("<FORM name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" onSubmit=\"return ValidateSearch\(\)\" enctype=\"multipart/form-data\">", re.IGNORECASE)
  184. def _cleanupHTML(self, html):
  185. """There is a broken div in this page. We don't need any divs, so
  186. let's get rid of them all."""
  187. div_regex = re.compile("</?div[^>]*>", re.IGNORECASE)
  188. return div_regex.sub('', html)
  189. class BridgenorthParser(AcolnetParser):
  190. #search_url = "http://www2.bridgnorth-dc.gov.uk/planning/acolnetcgi.gov?ACTION=UNWRAP&RIPNAME=Root.PgeSearch"
  191. case_number_tr = 1 # this one can be got by the td class attribute
  192. reg_date_tr = 2
  193. location_tr = 4
  194. proposal_tr = 5
  195. #authority_name = "Bridgenorth District Council"
  196. #authority_short_name = "Bridgenorth"
  197. comments_email_address = "contactus@bridgnorth-dc.gov.uk"
  198. action_regex = re.compile("<FORM name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" onSubmit=\"return ValidateSearch\(\)\" enctype=\"multipart/form-data\">")
  199. class BuryParser(AcolnetParser):
  200. #search_url = "http://e-planning.bury.gov.uk/ePlanning/acolnetcgi.gov?ACTION=UNWRAP&RIPNAME=Root.PgeSearch"
  201. case_number_tr = 1 # this one can be got by the td class attribute
  202. reg_date_tr = 2
  203. location_tr = 4
  204. proposal_tr = 5
  205. #authority_name = "Bury Metropolitan Borough Council"
  206. #authority_short_name = "Bury"
  207. comments_email_address = "development.control@bury.gov.uk"
  208. action_regex = re.compile("<FORM name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" onSubmit=\"return ValidateSearch\(\)\" enctype=\"multipart/form-data\">")
  209. ## class CanterburyParser(AcolnetParser):
  210. ## search_url = "http://planning.canterbury.gov.uk/scripts/acolnetcgi.exe?ACTION=UNWRAP&RIPNAME=Root.pgesearch"
  211. ## case_number_tr = 1 # this one can be got by the td class attribute
  212. ## reg_date_tr = 2
  213. ## location_tr = 4
  214. ## proposal_tr = 5
  215. ## authority_name = "Canterbury City Council"
  216. ## authority_short_name = "Canterbury"
  217. ## comments_email_address = ""
  218. ## action_regex = re.compile("<form id=\"frmSearch\" onSubmit=\"\"return ValidateSearch\(\)\"\" name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" enctype=\"multipart/form-data\">")
  219. class CarlisleParser(AcolnetParser):
  220. #search_url = "http://planning.carlisle.gov.uk/acolnet/acolnetcgi.gov?ACTION=UNWRAP&RIPNAME=Root.PgeSearch"
  221. case_number_tr = 1 # this one can be got by the td class attribute
  222. reg_date_tr = 2
  223. location_tr = 5
  224. proposal_tr = 6
  225. #authority_name = "Carlisle City Council"
  226. #authority_short_name = "Carlisle"
  227. comments_email_address = "dc@carlisle.gov.uk"
  228. action_regex = re.compile("<form id=\"frmSearch\" onSubmit=\"\"return ValidateSearch\(\)\"\" name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" enctype=\"multipart/form-data\">")
  229. class DerbyParser(AcolnetParser):
  230. #search_url = "http://195.224.106.204/scripts/planningpages02%5CXSLPagesDC_DERBY%5CDCWebPages/acolnetcgi.exe?ACTION=UNWRAP&RIPNAME=Root.pgesearch"
  231. case_number_tr = 1 # this one can be got by the td class attribute
  232. reg_date_tr = 3
  233. location_tr = 4
  234. proposal_tr = 5
  235. #authority_name = "Derby City Council"
  236. #authority_short_name = "Derby"
  237. comments_email_address = "developmentcontrol@derby.gov.uk"
  238. action_regex = re.compile("<FORM name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" onSubmit=\"return ValidateSearch\(\)\" enctype=\"multipart/form-data\">")
  239. class CroydonParser(AcolnetParser):
  240. case_number_tr = 1 # this one can be got by the td class attribute
  241. reg_date_tr = 3
  242. location_tr = 5
  243. proposal_tr = 6
  244. comments_email_address = "planning.control@croydon.gov.uk"
  245. action_regex = re.compile("<form id=\"frmSearch\" onSubmit=\"\"return ValidateSearch\(\)\"\" name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" enctype=\"multipart/form-data\">")
  246. class EastLindseyParser(AcolnetParser):
  247. case_number_tr = 1 # this one can be got by the td class attribute
  248. reg_date_tr = 3
  249. location_tr = 5
  250. proposal_tr = 6
  251. comments_email_address = "development.control@e-lindsey.gov.uk"
  252. action_regex = re.compile("<form id=\"frmSearch\" onSubmit=\"return ValidateSearch\(\)\" name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" enctype=\"multipart/form-data\">")
  253. class FyldeParser(AcolnetParser):
  254. case_number_tr = 1 # this one can be got by the td class attribute
  255. reg_date_tr = 2
  256. location_tr = 4
  257. proposal_tr = 5
  258. comments_email_address = "planning@fylde.gov.uk"
  259. action_regex = re.compile("<FORM name=\"frmSearch\" method=\"post\" action=\"([^\"]*)\" onSubmit=\"return ValidateSearch\(\)\" enctype=\"multipart/form-data\">")
  260. if __name__ == '__main__':
  261. day = 15
  262. month = 3
  263. year = 2007
  264. # working
  265. # parser = BasingstokeParser()
  266. parser = BaberghParser("Babergh District Council", "Babergh", "http://planning.babergh.gov.uk/dataOnlinePlanning/acolnetcgi.gov?ACTION=UNWRAP&RIPNAME=Root.pgesearch")
  267. # works with the divs stripped out
  268. #parser = BassetlawParser()
  269. # returns error 400 - bad request
  270. #parser = BridgenorthParser()
  271. # working
  272. #parser = BuryParser()
  273. # cambridgeshire is a bit different...
  274. # no advanced search page
  275. # canterbury
  276. # results as columns of one table
  277. # returns error 400 - bad request
  278. #parser = CarlisleParser()
  279. # working
  280. #parser = DerbyParser()
  281. print parser.getResults(day, month, year)