Repository: alltheplaces/alltheplaces

Issue:
Spider banfield_pet_hospital is broken
During the global build at 2021-06-02-14-42-40, spider **banfield_pet_hospital** failed with **0 features** and **0 errors**.

Here's [the log](https://data.alltheplaces.xyz/runs/2021-06-02-14-42-40/logs/banfield_pet_hospital.txt) and [the output](https://data.alltheplaces.xyz/runs/2021-06-02-14-42-40/output/banfield_pet_hospital.geojson) ([on a map](https://data.alltheplaces.xyz/map.html?show=https://data.alltheplaces.xyz/runs/2021-06-02-14-42-40/output/banfield_pet_hospital.geojson))


Discussion:

Resolving patch:
diff --git a/locations/spiders/banfield_pet_hospital.py b/locations/spiders/banfield_pet_hospital.py
--- a/locations/spiders/banfield_pet_hospital.py
+++ b/locations/spiders/banfield_pet_hospital.py
@@ -1,72 +1,49 @@
+# -*- coding: utf-8 -*-
+import json
+
 import scrapy
-import re
-import urllib.parse
 
-from locations.items import GeojsonPointItem
 from locations.hours import OpeningHours
+from locations.items import GeojsonPointItem
 
 
 class BanfieldPetHospitalScraper(scrapy.Spider):
     name = "banfield_pet_hospital"
-    item_attributes = { 'brand': "Banfield Pet Hospital" }
+    item_attributes = {"brand": "Banfield Pet Hospital", "brand_wikidata": "Q2882416"}
     allowed_domains = ["www.banfield.com"]
     download_delay = 0.5
-    start_urls = (
-        'https://www.banfield.com/our-hospitals/hospital-locations/all-locations',
-    )
-
-    def parse_store(self, response):
-
-        elem = response.xpath('//div[contains(@class, "our-hospitals-location")]')
-
-        script_body = ' '.join(elem.xpath('.//script/text()').extract())
-        match = re.search(r'.*google.maps.LatLng\(([0-9.-]+),\s([0-9.-]+)\)', script_body)
-
-        lat, lon = match.groups()
-
-        # use last 3 elements of the store url as unique identifier (store number does not appear to be unique)
-        ref = "_".join(urllib.parse.urlsplit(response.url).path.split('/')[-3:])
-
-        number = elem.xpath('//div[@class="vcard"]/p[@id="hospitalAddressHospitalNumber"]/text()').extract_first()
-        number = re.search(r'Hospital\sNumber:\s+(\d+)', number).group(1)
-
-        properties = {
-            'name': elem.xpath('//div[@class="vcard"]/p[@class="fn"]/text()').extract_first(),
-            'addr_full': elem.xpath('//div[@class="vcard"]/span[@class="street-address"]/text()').extract_first(),
-            'phone': elem.xpath('//div[@class="vcard"]/p[@id="hospitalAddressPhone"]/text()').extract_first(),
-            'city': elem.xpath('//div[@class="vcard"]/span[@class="region"]/text()').extract_first(),
-            'state': elem.xpath('//div[@class="vcard"]/span[@class="state"]/text()').extract_first(),
-            'postcode': elem.xpath('//div[@class="vcard"]/span[@class="postal-code"]/text()').extract_first(),
-            'ref': ref,
-            'website': response.url,
-            'lat': lat,
-            'lon': lon,
-            'extras': {
-                'number': number
-            }
-        }
-
-        days = elem.xpath('//div[@class="hours"]/div[contains(@class, "day")]/@content').extract()
-        opening_hours = OpeningHours()
-
-        for d in days:
-            match = re.search(r'([A-Za-z]{2})\s([\d:]+)-([\d:]+)', d)
-            if match:
-                day, open, close = match.groups()
-                opening_hours.add_range(day=day, open_time=open, close_time=close)
-
-        hours = opening_hours.as_opening_hours()
-
-        if hours and hours != 'Mo-Su ':
-            properties['opening_hours'] = hours
-
-        yield GeojsonPointItem(**properties)
+    start_urls = ("https://www.banfield.com/locations/hospitals-by-state",)
 
     def parse(self, response):
-        stores = response.xpath('//li/table')
-        for store in stores:
-            elem = store.xpath('.//td[@class="hospname"]/a')
-            path = elem.xpath('.//@href').extract_first()
-            name = elem.xpath('.//text()').extract_first()
+        for href in response.xpath(
+            '//div[@class="state-hospital-name"]//@href'
+        ).extract():
+            yield scrapy.Request(response.urljoin(href), callback=self.parse_store)
 
-            yield scrapy.Request(response.urljoin(path), callback=self.parse_store)
+    def parse_store(self, response):
+        for ldjson in response.xpath(
+            '//script[@type="application/ld+json"]/text()'
+        ).extract():
+            data = json.loads(ldjson)
+            if data["@type"] != ["VeterinaryCare", "LocalBusiness"]:
+                continue
+            ref = response.url.rsplit("/", 1)[-1]
+            opening_hours = OpeningHours()
+            for spec in data["openingHoursSpecification"]:
+                opening_hours.add_range(
+                    spec["dayOfWeek"][:2], spec["opens"], spec["closes"]
+                )
+            properties = {
+                "ref": ref,
+                "website": response.url,
+                "lat": data["geo"]["latitude"],
+                "lon": data["geo"]["longitude"],
+                "name": data["name"],
+                "addr_full": data["address"]["streetAddress"],
+                "city": data["address"]["addressLocality"],
+                "state": data["address"]["addressRegion"],
+                "postcode": data["address"]["postalCode"],
+                "country": data["address"]["addressCountry"],
+                "phone": data["telephone"],
+            }
+            yield GeojsonPointItem(**properties)


Test patch: