Repository: alltheplaces/alltheplaces

Issue:
UPS Store
Not much to go on here:

https://www.theupsstore.com/tools/find-a-store


Discussion:
Will give this one a look. I'll post my findings.
![image](https://user-images.githubusercontent.com/552127/34183059-d2ca4b3a-e4e6-11e7-8f86-3ada5c6e00a2.png)

API works and returns 30 results at a time. Does not expect cookies. Looking for other choices besides zip code, or a way to widen the radius.  Thinking about the overall problem you guys are having here with the zip codes also. 

@iandees Permission to burn through zip codes? I could also experiment with the middleware idea from #538 
This issue hasn't been worked on in a while. I'm removing the assignment so others can work on it. If you're still working on it, please reassign yourself and reference a pull request or branch where you're doing your work.

Resolving patch:
diff --git a/locations/spiders/upsstore.py b/locations/spiders/upsstore.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/upsstore.py
@@ -0,0 +1,89 @@
+import scrapy
+import json
+from locations.items import GeojsonPointItem
+from locations.hours import OpeningHours
+
+DAY_MAPPING = {
+    "MONDAY": "Mo",
+    "TUESDAY": "Tu",
+    "WEDNESDAY": "We",
+    "THURSDAY": "Th",
+    "FRIDAY": "Fr",
+    "SATURDAY": "Sa",
+    "SUNDAY": "Su"
+}
+
+
+class UpsStoreSpider(scrapy.Spider):
+    name = "upsstore"
+    allowed_domains = ["theupsstore.com"]
+    download_delay = 0.1
+    start_urls = (
+        'https://locations.theupsstore.com/',
+    )
+
+    def parse_hours(self, hours):
+        """
+        :param hours:
+        :return:
+        """
+        hours = json.loads(hours)
+        o = OpeningHours()
+
+        for day in hours["hours"]["days"]:
+            if not day["isClosed"]:
+                interval = day["intervals"][0]
+
+                o.add_range(DAY_MAPPING[day["day"]],
+                            open_time=str(interval["start"]),
+                            close_time=str(interval["end"]),
+                            time_format="%H%M")
+        return o.as_opening_hours()
+
+    def parse_store(self, response):
+        properties = {
+            'name': response.xpath('//span[@class="LocationName-geo"]/text()').extract_first(),
+            'phone': response.xpath('//span[@itemprop="telephone"]/text()').extract_first(),
+            'addr_full': response.xpath('//meta[@itemprop="streetAddress"]/@content').extract_first(),
+            'city': response.xpath('//meta[@itemprop="addressLocality"]/@content').extract_first(),
+            'state': response.xpath('//abbr[@itemprop="addressRegion"]/text()').extract_first(),
+            'country': response.xpath('//abbr[@itemprop="addressCountry"]/text()').extract_first(),
+            'postcode': response.xpath('//span[@itemprop="postalCode"]/text()').extract_first(),
+            'ref': response.xpath('//input[@id="store_id"]/@value').extract_first(),
+            'website': response.url,
+            'lat': float(response.xpath('//meta[@itemprop="latitude"]/@content').extract_first()),
+            'lon': float(response.xpath('//meta[@itemprop="longitude"]/@content').extract_first()),
+        }
+
+        hours = response.xpath('//script[@id="location_info_hours"]/text()').extract_first()
+        try:
+            hours = self.parse_hours(hours)
+            if hours:
+                properties['opening_hours'] = hours
+        except:
+            pass
+
+        yield GeojsonPointItem(**properties)
+
+    def parse(self, response):
+        urls = response.xpath('//a[@class="Directory-listLink"]/@href').extract()
+
+        if urls:
+            for url in urls:
+                if len(url.split('/')) == 3:
+                    callback = self.parse_store
+                else:
+                    callback = self.parse
+
+                yield scrapy.Request(
+                    response.urljoin(url),
+                    callback=callback,
+                )
+
+        else:
+            urls = response.xpath('//a[@class="Link"]/@href').extract()
+            for url in urls:
+                yield scrapy.Request(
+                    response.urljoin(url),
+                    callback=self.parse_store,
+                )
\ No newline at end of file


Test patch: