Source code for pyzillow.pyzillow

import requests

from xml.etree import cElementTree as ElementTree  # for zillow API

from .pyzillowerrors import ZillowError, ZillowFail, ZillowNoResults
from . import __version__


[docs]class ZillowWrapper(object): """This class provides an interface into the Zillow API. An API key is required to create an instance of this class: >>> from pyzillow.pyzillow import ZillowWrapper >>> zillow_data = ZillowWrapper(YOUR_ZILLOW_API_KEY) To request data from Zillow, you can choose between: 1. The GetDeepSearchResults API endpoint (:class:`pyzillow.pyzillow.GetDeepSearchResults`) which requires the following arguments: * A street address (e.g. ``'2114 Bigelow Ave'``) * A ZIP code or city and state combination (e.g. ``'98109'`` or ``'Seattle, WA'``) * Optional: Enabling or disabling Zillow Rentzestimate information in API results (``True``/``False``) Example: >>> from pyzillow.pyzillow import ZillowWrapper, GetDeepSearchResults >>> zillow_data = ZillowWrapper(YOUR_ZILLOW_API_KEY) >>> deep_search_response = zillow_data.get_deep_search_results(address, zipcode, rentzestimate) >>> result = GetDeepSearchResults(deep_search_response) 2. The GetUpdatedPropertyDetails API endpoint (:class:`pyzillow.pyzillow.GetUpdatedPropertyDetails`) which requires a Zillow Property ID (ZPID) as an argument. You can acquire this identifier by accessing ``.zillow_id`` from a :class:`pyzillow.pyzillow.GetDeepSearchResults` object. GetUpdatedPropertyDetails data is not available for all valid Zillow IDs. Example: >>> from pyzillow.pyzillow import ZillowWrapper, GetUpdatedPropertyDetails >>> zillow_data = ZillowWrapper(YOUR_ZILLOW_API_KEY) >>> updated_property_details_response = \ zillow_data.get_updated_property_details(zillow_id) >>> result = GetUpdatedPropertyDetails(updated_property_details_response) """ def __init__(self, api_key: str = None): """Constructor method """ self.api_key = api_key
[docs] def get_deep_search_results( self, address: str, zipcode: str, rentzestimate: bool = False ): """This method provides results from the GetDeepSearchResults API endpoint as an XML object. :param address: Street address to look up :type address: str :param zipcode: ZIP code to look up :type zipcode: str :param rentzestimate: Add Rent Zestimate information to result (True/False), defaults to False :type rentzestimate: bool, optional :return: Result from API query :rtype: xml.etree.ElementTree.Element """ url = "http://www.zillow.com/webservice/GetDeepSearchResults.htm" params = { "address": address, "citystatezip": zipcode, "rentzestimate": str(rentzestimate).lower(), "zws-id": self.api_key, } return self.get_data(url, params)
[docs] def get_updated_property_details(self, zpid: str): """This method provides results from the GetUpdatedPropertyDetails API endpoint as an XML object. :param zpid: Zillow Web Service Identifier :type zpid: str :return: Result from API query :rtype: xml.etree.ElementTree.Element """ url = "http://www.zillow.com/webservice/GetUpdatedPropertyDetails.htm" params = {"zpid": zpid, "zws-id": self.api_key} return self.get_data(url, params)
[docs] def get_data(self, url: str, params: dict): """This method requests data from the API endpoint specified in the url argument. It uses parameters from the params argument. :param url: URL of API endpoint :type url: str :param params: Parameters for API query :type params: dict :raises ZillowFail: The API endpoint could not be reached or the request did not return valid XML :raises ZillowError: The API endpoint responded with an error code :raises ZillowNoResults: The request did not return any results :return: Result from API query :rtype: xml.etree.ElementTree.Element """ try: request = requests.get( url=url, params=params, headers={ "User-Agent": "".join(["pyzillow/", __version__, " (Python)"]) }, ) except ( requests.exceptions.ConnectionError, requests.exceptions.TooManyRedirects, requests.exceptions.Timeout, ): raise ZillowFail try: request.raise_for_status() except requests.exceptions.HTTPError: raise ZillowFail try: response = ElementTree.fromstring(request.text) except ElementTree.ParseError: print("Zillow response is not a valid XML ({})".format(params["address"])) raise ZillowFail if response.findall("message/code")[0].text != "0": raise ZillowError(int(str(response.findall("message/code")[0].text))) else: if not response.findall("response"): print("Zillow returned no results for ({})".format(params["address"])) raise ZillowNoResults return response
class ZillowResults(object): """Base class for :class:`pyzillow.pyzillow.GetDeepSearchResults` and :class:`pyzillow.pyzillow.GetUpdatedPropertyDetails`. """ def __init__(self): self.attribute_mapping = {} def get_attr(self, attr): """ """ try: return self.data.find(self.attribute_mapping[attr]).text except AttributeError: return None def __str__(self): return self.zillow_id @property def area_unit(self): """ lotSizeSqFt """ return u"SqFt" @property def last_sold_price_currency(self): """ lastSoldPrice currency """ return self.data.find(self.attribute_mapping["last_sold_price"]).attrib[ "currency" ]
[docs]class GetDeepSearchResults(ZillowResults): """Maps results from the XML data array into attributes of an instance of GetDeepSearchResults. An instance of ``GetDeepSearchResults`` has the following attributes: ``.bathrooms`` ``.bedrooms`` ``.city`` ``.fips_county`` ``.graph_data_link`` ``.home_detail_link`` ``.home_size`` ``.home_type`` ``.last_sold_date`` ``.last_sold_price`` ``.latitude`` ``.longitude`` ``.map_this_home_link`` ``.property_size`` ``.rentzestimate_amount`` ``.rentzestimate_last_updated`` ``.rentzestimate_valuation_range_high`` ``.rentzestimate_valuation_range_low`` ``.rentzestimate_value_change`` ``.state`` ``.street`` ``.tax_value`` ``.tax_year`` ``.total_rooms`` ``.use_code`` ``.year_built`` ``.zestimate_amount`` ``.zestimate_last_updated`` ``.zestimate_percentile`` ``.zestimate_valuation_range_high`` ``.zestimate_valuation_range_low`` ``.zestimate_value_change`` ``.zillow_id`` ``.zipcode`` """ attribute_mapping = { "bathrooms": "result/bathrooms", "bedrooms": "result/bedrooms", "city": "result/address/city", "fips_county": "result/FIPScounty", "graph_data_link": "result/links/graphsanddata", "home_detail_link": "result/links/homedetails", "home_size": "result/finishedSqFt", "home_type": "result/useCode", "last_sold_date": "result/lastSoldDate", "last_sold_price": "result/lastSoldPrice", "latitude": "result/address/latitude", "longitude": "result/address/longitude", "map_this_home_link": "result/links/mapthishome", "property_size": "result/lotSizeSqFt", "rentzestimate_amount": "result/rentzestimate/amount", "rentzestimate_last_updated": "result/rentzestimate/last-updated", "rentzestimate_valuation_range_high": "result/rentzestimate/valuationRange/high", "rentzestimate_valuation_range_low": "result/rentzestimate/valuationRange/low", "rentzestimate_value_change": "result/rentzestimate/valueChange", "state": "result/address/state", "street": "result/address/street", "tax_value": "result/taxAssessment", "tax_year": "result/taxAssessmentYear", "total_rooms": "result/totalRooms", "use_code": "result/useCode", "year_built": "result/yearBuilt", "zestimate_amount": "result/zestimate/amount", "zestimate_last_updated": "result/zestimate/last-updated", "zestimate_percentile": "result/zestimate/percentile", "zestimate_valuation_range_high": "result/zestimate/valuationRange/high", "zestimate_valuation_range_low": "result/zestimate/valuationRange/low", "zestimate_value_change": "result/zestimate/valueChange", "zillow_id": "result/zpid", "zipcode": "result/address/zipcode", } def __init__(self, data, *args, **kwargs): """Constructor method """ self.data = data.findall("response/results")[0] for attr in self.attribute_mapping.__iter__(): try: self.__setattr__(attr, self.get_attr(attr)) except AttributeError: print("AttributeError with {}".format(attr)) @property def region_name(self): """ region name """ try: return self.data.find("result/localRealEstate/region").attrib["name"] except AttributeError: return None @property def region_id(self): """ region id """ try: return self.data.find("result/localRealEstate/region").attrib["id"] except AttributeError: return None @property def region_type(self): """ region type """ try: return self.data.find("result/localRealEstate/region").attrib["type"] except AttributeError: return None
[docs]class GetUpdatedPropertyDetails(ZillowResults): """Maps results from the XML data array into attributes of an instance of GetUpdatedPropertyDetails. An instance of ``GetUpdatedPropertyDetails`` has the following attributes: ``.agent_name`` ``.agent_profile_url`` ``.appliances`` ``.basement`` ``.bathrooms`` ``.bedrooms`` ``.brokerage`` ``.city`` ``.cooling_system`` ``.elementary_school`` ``.exterior_material`` ``.floor_material`` ``.heating_sources`` ``.heating_system`` ``.high_school`` ``.home_description`` ``.home_detail_link`` ``.home_info`` ``.home_size`` ``.home_type`` ``.latitude`` ``.longitude`` ``.middle_school`` ``.neighborhood`` ``.num_floors`` ``.num_rooms`` ``.page_view_count_this_month`` ``.page_view_count_total`` ``.parking_type`` ``.photo_gallery`` ``.posting_agent`` ``.posting_last_update`` ``.posting_mls`` ``.posting_status`` ``.posting_type`` ``.price`` ``.property_size`` ``.roof`` ``.rooms`` ``.school_district`` ``.state`` ``.street`` ``.view`` ``.year_built`` ``.year_updated`` ``.zillow_id`` ``.zipcode`` """ attribute_mapping = { # attributes in common with GetDeepSearchResults "bathrooms": "editedFacts/bathrooms", "bedrooms": "editedFacts/bedrooms", "city": "result/address/city", "home_detail_link": "links/homeDetails", "home_size": "editedFacts/finishedSqFt", "home_type": "editedFacts/useCode", "latitude": "address/latitude", "longitude": "address/longitude", "property_size": "editedFacts/lotSizeSqFt", "state": "result/address/state", "street": "result/address/street", "year_built": "editedFacts/yearBuilt", "zillow_id": "zpid", "zipcode": "result/address/zipcode", # new attributes in GetUpdatedPropertyDetails "agent_name": "posting/agentName", "agent_profile_url": "posting/agentProfileUrl", "appliances": "editedFacts/appliances", "basement": "editedFacts/basement", "brokerage": "posting/brokerage", "cooling_system": "editedFacts/coolingSystem", "elementary_school": "elementarySchool", "exterior_material": "editedFacts/exteriorMaterial", "floor_material": "editedFacts/floorCovering", "heating_sources": "editedFacts/heatingSources", "heating_system": "editedFacts/heatingSystem", "high_school": "highSchool", "home_description": "homeDescription", "home_info": "links/homeInfo", "middle_school": "middleSchool", "neighborhood": "neighborhood", "num_floors": "editedFacts/numFloors", "num_rooms": "editedFacts/numRooms", "page_view_count_this_month": "pageViewCount/currentMonth", "page_view_count_total": "pageViewCount/total", "parking_type": "editedFacts/parkingType", "photo_gallery": "links/photoGallery", "photo_gallery": "links/photoGallery", "posting_agent": "posting/agentName", "posting_last_update": "posting/lastUpdatedDate", "posting_mls": "posting/mls", "posting_status": "posting/status", "posting_type": "posting/type", "price": "price", "roof": "editedFacts/roof", "rooms": "editedFacts/rooms", "school_district": "schoolDistrict", "view": "editedFacts/view", "year_updated": "editedFacts/yearUpdated", } def __init__(self, data, *args, **kwargs): """Constructor method """ self.data = data.findall("response")[0] for attr in self.attribute_mapping.__iter__(): try: self.__setattr__(attr, self.get_attr(attr)) except AttributeError: print("AttributeError with {}".format(attr))