Repository: alltheplaces/alltheplaces

Issue:
Barnes and Noble
https://stores.barnesandnoble.com/stores?lng=-97.7429885864258&lat=30.2675895690918&searchText=Austin%2c+TX&storeFilter=all&v=1&view=map


I didn't assign myself on purpose this time. 


Discussion:
Can I assign my self?
If it doesn't have to be approved by @iandees I don't see why not
Yes, you can assign yourself.
@iandees 
I updated Qdoba and Tacobel spider already. Can you check them please?
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/barnesandnoble.py b/locations/spiders/barnesandnoble.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/barnesandnoble.py
@@ -0,0 +1,96 @@
+import scrapy
+import re
+import json
+from urllib.parse import urlencode
+from locations.items import GeojsonPointItem
+from locations.hours import OpeningHours
+
+
+DAY_MAPPING = {
+    'Sun': 'Su',
+    'Mon': 'Mo',
+    'Tue': 'Tu',
+    'Wed': 'We',
+    'Thu': 'Th',
+    'Fri': 'Fr',
+    'Sat': 'Sa'
+}
+DAY_ORDER = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
+
+
+class BarnesAndNobleSpider(scrapy.Spider):
+
+    name = "barnesandnoble"
+    download_delay = 0.5
+    allowed_domains = [
+        "stores.barnesandnoble.com",
+    ]
+
+    def start_requests(self):
+        base_url = "https://stores.barnesandnoble.com/stores?"
+
+        params = {
+            "storeFilter": "all",
+            "v": "1",
+            "view": "map"
+        }
+
+        with open('./locations/searchable_points/us_centroids_50mile_radius.csv') as points:
+            next(points)
+            for point in points:
+                _, lat, lon = point.strip().split(',')
+                params.update({"lat": lat, "lng": lon})
+                yield scrapy.Request(url=base_url + urlencode(params))
+
+    def parse_hours(self, hours):
+        """ Sun-Thu 9-9, Fri&Sat 9-10 """
+        o = OpeningHours()
+        ranges = hours.split(',')
+        for range in ranges:
+            pattern = r'(.{3})[-&](.{3})\s([\d:]+)-([\d:]+)'
+            start_day, end_day, start_time, end_time = re.search(pattern, range.strip()).groups()
+            if ':' not in start_time:
+                start_time += ":00 AM"
+            if ':' not in end_time:
+                end_time += ":00 PM"
+            for day in DAY_ORDER[DAY_ORDER.index(start_day): DAY_ORDER.index(end_day) + 1]:
+                o.add_range(day=DAY_MAPPING[day],
+                            open_time=start_time,
+                            close_time=end_time,
+                            time_format='%I:%M %p')
+        return o.as_opening_hours()
+
+    def parse(self, response):
+        data = response.xpath('//div[@id="mapDiv"]/script/text()').re(r"storesJson\s=\s(.*?);")
+        if not data:
+            if "No results found" in response.xpath('//div[@class="content"]/h3/text()').extract_first():
+                return
+
+        stores = json.loads(data[0])
+
+        for store in stores:
+            address = store.get("address2", None)
+            if not address:
+                address = store.get("address1")  # address1 is usually the venue/mall name
+
+            properties = {
+                'name': store["name"],
+                'addr_full': address,
+                'city': store["city"],
+                'state': store["state"],
+                'postcode': store["zip"],
+                'phone': store["phone"],
+                'ref': store['storeId'],
+                'website': "https://stores.barnesandnoble.com/store/{}".format(store["storeId"]),
+                'lat': float(store["location"][1]),
+                'lon': float(store["location"][0]),
+            }
+
+            try:
+                opening_hours = self.parse_hours(store['hours'])
+            except:
+                opening_hours = None
+            if opening_hours:
+                properties["opening_hours"] = opening_hours
+
+            yield GeojsonPointItem(**properties)


Test patch: