public async Task GetProspectsForTrade() { var trade = new Trade() { Id = "-12039", Name = "Handyman Services" }; var ha = new HomeAdvisor(); var prospects = await ha.GetProspectsForTrade(trade, "10707"); Assert.NotNull(prospects); Assert.NotEqual(0, prospects.Count); }
public void GetProspectsForTradesWithZipCodes() { var tradeHandyman = new Trade() { Id = "-12039", Name = "Handyman Services" }; var tradePlumbing = new Trade() { Id = "-12058", Name = "Plumbing" }; var trades = new List<Trade>(); trades.Add(tradeHandyman); trades.Add(tradePlumbing); var zipCodes = new List<string>(); zipCodes.Add("10707"); zipCodes.Add("11782"); var ha = new HomeAdvisor(); var results = ha.GetProspectsForTradesWithZipCodes(trades, zipCodes); using (var file = new System.IO.StreamWriter("prospects.txt")) { foreach (var result in results) { file.WriteLine($"{result.Name} - {result.Service.Name} - {result.PostalCode} - {result.StreetAddress}"); } } Assert.NotNull(results); Assert.NotEqual(0, results.Count); }
public async Task<List<Prospect>> GetProspectsForTrade(Trade trade, string postalCode) { List<Prospect> prospects = null; // URL to POST to, requiring following form data: // - findContractor: searchByZip // - catOid: [trade ID here] // - zip: [zip code here] var values = new List<KeyValuePair<string, string>>(); values.Add(new KeyValuePair<string,string>("findContractor", "searchByZip")); values.Add(new KeyValuePair<string,string>("catOid", trade.Id)); values.Add(new KeyValuePair<string,string>("zip", postalCode)); using (var postContent = new FormUrlEncodedContent(values)) using (var client = new HttpClient()) using (var response = await client.PostAsync(_proReviewsUrl, postContent)) using (var content = response.Content) { // Now have first page with the review content var xhtml = await content.ReadAsStringAsync(); // Parse the XHTML result var doc = new HtmlDocument(); doc.LoadHtml(xhtml); // Look for the review pane containing prospects var reviewPane = doc.DocumentNode.SelectSingleNode("//div[@class='xmd-content-main']"); var prospectDivClass = string.Format(".//div[@data-srzip='{0}']", postalCode); foreach (var child in reviewPane.SelectNodes(prospectDivClass)) { var prospect = new Prospect() { Service = trade, RatingAvailable = false, Id = child.Attributes["data-spid"].Value }; // Parse the itemprops foreach (var span in child.SelectNodes(".//span[@itemprop]")) { var attrValue = span.GetAttributeValue("itemprop", "").ToLower(); switch (attrValue) { case "name": prospect.Name = span.InnerText; break; case "telephone": prospect.Phone = span.InnerText; break; case "streetaddress": prospect.StreetAddress = span.InnerText; break; case "addresslocality": prospect.AddressLocality = span.InnerText; break; case "addressregion": prospect.AddressRegion = span.InnerText; break; case "postalcode": prospect.PostalCode = span.InnerText; break; default: break; } } // Get the URL for the detail page of the prospect var urlNode = child.SelectNodes(".//a[@itemprop='url']").FirstOrDefault(); if (urlNode != null) { prospect.Url = new Uri(UrlCombine(_rootUrl, urlNode.Attributes["href"].Value)); } var ratingRefNodes = child.SelectNodes(".//div[@class='l-column ratings-reference']/div"); if (ratingRefNodes != null && ratingRefNodes.First().Attributes["class"].Value.Equals("t-stars-small t-stars-rating")) { prospect.RatingAvailable = true; // Parse star percentage var reviewRatingNode = ratingRefNodes[0].ChildNodes.Where(x => x.Name.Equals("div")).FirstOrDefault(); if (reviewRatingNode != null) { // TODO: Move cleaning double to utility class // Replace non digit and non decimal characters with nothing var ratingCleaned = Regex.Replace(reviewRatingNode.Attributes["style"].Value, "[^0-9.]", ""); var starRating = 0.0; if (double.TryParse(ratingCleaned, out starRating)) { prospect.StarRating = starRating; } } // Parse review count var reviewCountNode = ratingRefNodes.Where(x => x.Attributes["class"].Value.Contains("verified-reviews")).FirstOrDefault(); if (reviewCountNode != null) { // TODO: Move cleaning integer to utility class // Replace non digit characters with nothing var reviewCountCleaned = Regex.Replace(reviewCountNode.InnerText, "[^0-9]", ""); var reviewCount = 0; if (int.TryParse(reviewCountCleaned, out reviewCount)) { prospect.ReviewCount = reviewCount; } } } // Finished parsing itemprops, add the object to the list if (prospects == null) prospects = new List<Prospect>(); prospects.Add(prospect); } } return prospects; }