Automatically exported from code.google.com/p/planningalerts
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

PublicAccess.py 13 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. #!/usr/local/bin/python
  2. import urllib, urllib2
  3. import HTMLParser
  4. import urlparse
  5. import datetime, time
  6. import cookielib
  7. cookie_jar = cookielib.CookieJar()
  8. from PlanningUtils import fixNewlines, getPostcodeFromText, PlanningAuthorityResults, PlanningApplication
  9. search_form_url_end = "DcApplication/application_searchform.aspx"
  10. search_results_url_end = "DcApplication/application_searchresults.aspx"
  11. comments_url_end = "DcApplication/application_comments_entryform.aspx"
  12. class PublicAccessParser(HTMLParser.HTMLParser):
  13. """This is the class which parses the PublicAccess search results page.
  14. """
  15. def __init__(self,
  16. authority_name,
  17. authority_short_name,
  18. base_url,
  19. debug=False):
  20. HTMLParser.HTMLParser.__init__(self)
  21. self.authority_name = authority_name
  22. self.authority_short_name = authority_short_name
  23. self.base_url = base_url
  24. self.debug = debug
  25. # this will change to True when we enter the table of results
  26. self._in_results_table = False
  27. # this will be set to True when we have passed the header row
  28. # in the results table
  29. self._past_header_row = False
  30. # this will be true when we are in a <td> in the results table
  31. self._in_td = False
  32. # For each row, this will say how many tds we have seen so far
  33. self._td_count = 0
  34. # The object which stores our set of planning application results
  35. self._results = PlanningAuthorityResults(self.authority_name, self.authority_short_name)
  36. # This will store the planning application we are currently working on.
  37. self._current_application = None
  38. def handle_starttag(self, tag, attrs):
  39. if tag == "table":
  40. self.handle_start_table(attrs)
  41. # we are only interested in tr tags if we are in the results table
  42. elif self._in_results_table and tag == "tr":
  43. self.handle_start_tr(attrs)
  44. # we are only interested in td tags if we are in the results table
  45. elif self._in_results_table and tag == "td":
  46. self.handle_start_td(attrs)
  47. # we are only interested in <a> tags if we are in the 6th td in
  48. # the results table.
  49. elif self._in_td and self._td_count == 6 and tag == "a":
  50. self.handle_start_a(attrs)
  51. # If the tag is not one of these then we aren't interested
  52. def handle_endtag(self, tag):
  53. # we only need to consider end tags if we are in the results table
  54. if self._in_results_table:
  55. if tag == "table":
  56. self.handle_end_table()
  57. if tag == "tr":
  58. self.handle_end_tr()
  59. if tag == "td":
  60. self.handle_end_td()
  61. def handle_start_table(self, attrs):
  62. for attr,value in attrs:
  63. if attr == "class":
  64. if value == "cResultsForm":
  65. self._in_results_table = True
  66. break
  67. def handle_end_table(self):
  68. # If we see an end table tag, then note that we have left the
  69. # results table. This method is only called if we are in that table.
  70. self._in_results_table = False
  71. def handle_start_tr(self, attrs):
  72. # The first tr we meet in the results table is just headers
  73. # We will set a flag at the end of that tr to avoid creating
  74. # a blank PlanningApplication
  75. if self._past_header_row:
  76. # Create a candidate result object
  77. self._current_application = PlanningApplication()
  78. self._td_count = 0
  79. def handle_end_tr(self):
  80. # If we are in the results table, and not finishing the header row
  81. # append the current result to the results list.
  82. if self._past_header_row:
  83. self._results.addApplication(self._current_application)
  84. else:
  85. # The first row of the results table is headers
  86. # We want to do nothing until after it
  87. self._past_header_row = True
  88. def handle_start_td(self, attrs):
  89. # increase the td count by one
  90. self._td_count += 1
  91. # note that we are now in a td
  92. self._in_td = True
  93. def handle_end_td(self):
  94. # note that we are now not in a td
  95. self._in_td = False
  96. def handle_start_a(self, attrs):
  97. # this method is only getting called if we are in the
  98. # 6th td of a non-header row of the results table.
  99. # go through the attributes of the <a> looking for one
  100. # named 'href'
  101. for attr,value in attrs:
  102. if attr == "href":
  103. # the value of this tag is a relative url.
  104. # parse it so we can get the query string from it
  105. parsed_info_url = urlparse.urlparse(value)
  106. # the 4th part of the tuple is the query string
  107. query_string = parsed_info_url[4]
  108. # join this query string to the search URL, and store this as
  109. # the info URL of the current planning application
  110. self._current_application.info_url = urlparse.urljoin(self.base_url, value)
  111. # Join this query string to the comments URL, and store this as
  112. # the comments URL of the current planning application
  113. comments_url = urlparse.urljoin(self.base_url, comments_url_end)
  114. self._current_application.comment_url = urlparse.urljoin(comments_url, query_string)
  115. # while we're here, let's follow some links to find the postcode...
  116. # the postcode is in an input tag in the property page. This page
  117. # can be found by following the info url.
  118. # The newlines in the info page need fixing.
  119. info_file_contents = fixNewlines(urllib2.urlopen(self._current_application.info_url).read())
  120. info_file_parser = PublicAccessInfoPageParser()
  121. info_file_parser.feed(info_file_contents)
  122. property_page_url = urlparse.urljoin(self._current_application.info_url, info_file_parser.property_page_url)
  123. # the newlines in this page need fixing
  124. property_file_contents = fixNewlines(urllib2.urlopen(property_page_url).read())
  125. property_file_parser = PublicAccessPropertyPageParser()
  126. property_file_parser.feed(property_file_contents)
  127. # Set the postcode on the current planning application from the
  128. # one found on the property page
  129. if property_file_parser.postcode is not None:
  130. self._current_application.postcode = property_file_parser.postcode
  131. else:
  132. # If there is no postcode in here, then we'll have to make do with regexing one out of the address.
  133. self._current_application.postcode = getPostcodeFromText(self._current_application.address)
  134. # There is no need for us to look at any more attributes.
  135. break
  136. def handle_data(self, data):
  137. if self._in_td:
  138. # The first td contains the reference
  139. if self._td_count == 1:
  140. self._current_application.council_reference = data
  141. # The second td contains the date the application was received
  142. elif self._td_count == 2:
  143. year, month, day = time.strptime(data, "%d/%m/%Y")[:3]
  144. received_date = datetime.date(year, month, day)
  145. self._current_application.date_received = received_date
  146. # The third td contains the address
  147. elif self._td_count == 3:
  148. #data = data.replace("^M","\n")
  149. self._current_application.address = data
  150. # The fourth td contains the description
  151. elif self._td_count == 4:
  152. self._current_application.description = data
  153. # 5 is status - we don't need it.
  154. # 6 is a button - this is where we will get our postcode,
  155. # comment_url, and info_url from (when handling the <a> tag).
  156. def getResultsByDayMonthYear(self, day, month, year):
  157. # First download the search form (in order to get a session cookie
  158. search_form_request = urllib2.Request(urlparse.urljoin(self.base_url, search_form_url_end))
  159. search_form_response = urllib2.urlopen(search_form_request)
  160. cookie_jar.extract_cookies(search_form_response, search_form_request)
  161. # We are only doing this first search in order to get a cookie
  162. # The paging on the site doesn't work with cookies turned off.
  163. search_data1 = urllib.urlencode({"searchType":"ADV",
  164. "caseNo":"",
  165. "PPReference":"",
  166. "AltReference":"",
  167. "srchtype":"",
  168. "srchstatus":"",
  169. "srchdecision":"",
  170. "srchapstatus":"",
  171. "srchappealdecision":"",
  172. "srchwardcode":"",
  173. "srchparishcode":"",
  174. "srchagentdetails":"",
  175. "srchDateReceivedStart":"%(day)02d/%(month)02d/%(year)d" %{"day":day ,"month": month ,"year": year},
  176. "srchDateReceivedEnd":"%(day)02d/%(month)02d/%(year)d" %{"day":day, "month":month, "year":year} })
  177. if self.debug:
  178. print search_data1
  179. search_url = urlparse.urljoin(self.base_url, search_results_url_end)
  180. request1 = urllib2.Request(search_url, search_data1)
  181. cookie_jar.add_cookie_header(request1)
  182. response1 = urllib2.urlopen(request1)
  183. # This search is the one we will actually use.
  184. # a maximum of 100 results are returned on this site,
  185. # hence setting "pagesize" to 100. I doubt there will ever
  186. # be more than 100 in one day in PublicAccess...
  187. # "currentpage" = 1 gets us to the first page of results
  188. # (there will only be one anyway, as we are asking for 100 results...)
  189. #http://planning.york.gov.uk/PublicAccess/tdc/DcApplication/application_searchresults.aspx?szSearchDescription=Applications%20received%20between%2022/02/2007%20and%2022/02/2007&searchType=ADV&bccaseno=&currentpage=2&pagesize=10&module=P3
  190. search_data2 = urllib.urlencode((("szSearchDescription","Applications received between %(day)02d/%(month)02d/%(year)d and %(day)02d/%(month)02d/%(year)d"%{"day":day ,"month": month ,"year": year}), ("searchType","ADV"), ("bccaseno",""), ("currentpage","1"), ("pagesize","100"), ("module","P3")))
  191. if self.debug:
  192. print search_data2
  193. # This time we want to do a get request, so add the search data into the url
  194. request2_url = urlparse.urljoin(self.base_url, search_results_url_end + "?" + search_data2)
  195. request2 = urllib2.Request(request2_url)
  196. # add the cookie we stored from our first search
  197. cookie_jar.add_cookie_header(request2)
  198. response2 = urllib2.urlopen(request2)
  199. contents = fixNewlines(response2.read())
  200. if self.debug:
  201. print contents
  202. self.feed(contents)
  203. return self._results
  204. def getResults(self, day, month, year):
  205. return self.getResultsByDayMonthYear(int(day), int(month), int(year)).displayXML()
  206. class PublicAccessInfoPageParser(HTMLParser.HTMLParser):
  207. """A parser to get the URL for the property details page out of the
  208. info page (this url is needed in order to get the postcode of the
  209. application.
  210. """
  211. def __init__(self):
  212. HTMLParser.HTMLParser.__init__(self)
  213. self.property_page_url = None
  214. def handle_starttag(self, tag, attrs):
  215. """The URL of the property details page is contained in an <a> tag in
  216. an attribute with key 'A_btnPropertyDetails'. There is some garbage on
  217. either side of it which we will have to clear up before storing it...
  218. We go through the <a> tags looking for one with an attribute with
  219. key 'id' and value 'A_btnPropertyDetails'. When we find it we go through
  220. its attributes looking for one with key 'href' - the value of this attribute
  221. contains the URL we want, after a bit of tidying up.
  222. Once we have got the URL, there is no need for us to look at any more <a> tags.
  223. """
  224. if tag == "a" and self.property_page_url is None:
  225. if attrs.count(("id","A_btnPropertyDetails")) > 0:
  226. for attr,value in attrs:
  227. if attr == "href":
  228. the_link = value
  229. # this has some garbage on either side of it...
  230. # let's strip that off
  231. # the garbage on the left is separated by whitespace.
  232. # the garbage on the right is separated by a "'".
  233. self.property_page_url = the_link.split()[1].split("'")[0]
  234. class PublicAccessPropertyPageParser(HTMLParser.HTMLParser):
  235. """A parser to get the postcode out of the property details page."""
  236. def __init__(self):
  237. HTMLParser.HTMLParser.__init__(self)
  238. self.postcode = None
  239. def handle_starttag(self, tag, attrs):
  240. """The postcode is contained in an <input> tag.
  241. This tag has an attribute 'name' with value postcode.
  242. It also has an attribute 'value' with value the postcode of this application.
  243. We go through the input tags looking for one with an attribute with
  244. key 'name' and value 'postcode'. When we find one,
  245. we look through its attributes for one with key 'value' - we store the value of this
  246. attribute as self.postcode.
  247. Once we have the postcode, there is no need to look at any more input tags.
  248. """
  249. if tag == "input" and self.postcode is None:
  250. if attrs.count(("name","postcode")) > 0:
  251. for attr,value in attrs:
  252. if attr == "value":
  253. self.postcode = value