new_contents: # -*- coding: utf-8 -*-
  from datetime import datetime
  import json
  import re
  
  import scrapy
  
  from locations.items import GeojsonPointItem
  from locations.hours import OpeningHours
  
  DAY_MAPPING = [
      "Mo",
      "Tu",
      "We",
      "Th",
      "Fr",
      "Sa",
      "Su"
  ]
  
  
  class BJsWholesaleSpider(scrapy.Spider):
      name = "bjswholesale"
      allowed_domains = ["bjs.com"]
      start_urls = [
          'https://api.bjs.com/digital/live/apis/v1.0/clublocatorpage/statetowns/10201',
      ]
      headers = {"Content-Type": "application/json"}
  
      def parse_hours(self, hours):
          opening_hours = OpeningHours()
  
          if hours:
              hours = hours[0].get("value").split("<br>") or []
              for hour in hours:
                  try:
                      day, open_time, close_time = re.search(r'(.*?):\s(.*?)\s-\s(.*?)$', hour).groups()
                  except AttributeError:  # closed
                      continue
                  open_time = (datetime.strptime(open_time, '%I:%M %p')
                               if ":" in open_time
                               else datetime.strptime(open_time, '%I %p')).strftime('%H:%M')
                  close_time = (datetime.strptime(close_time, '%I:%M %p')
                                if ":" in close_time
                                else datetime.strptime(close_time, '%I %p')).strftime('%H:%M')
  
                  if '-' in day:
                      start_day, end_day = day.split('-')
                      start_day = start_day.strip()
                      end_day = end_day.strip()
                      for d in DAY_MAPPING[DAY_MAPPING.index(start_day[:2]):DAY_MAPPING.index(end_day[:2]) + 1]:
                          opening_hours.add_range(day=d,
                                                  open_time=open_time,
                                                  close_time=close_time,
                                                  time_format='%H:%M')
          return opening_hours.as_opening_hours()
  
      def parse_store(self, response):
          data = json.loads(response.body_as_unicode())["Stores"]["PhysicalStore"][0]
  
          properties = {
              'name': data["Description"][0]["displayStoreName"],
              'ref': data["storeName"],
              'addr_full': " ".join(data["addressLine"]).strip(),
              'city': data["city"].strip(),
              'state': data["stateOrProvinceName"].strip(),
              'postcode': data["postalCode"].strip(),
              'country': data["country"].strip(),
              'phone': data.get("telephone1", "").strip(),
              'website': "https://www.bjs.com/mapDetail;city={}".format(data["storeName"]),
              'lat': float(data["latitude"]),
              'lon': float(data["longitude"]),
          }
  
          hours = self.parse_hours([attr for attr in data["Attribute"] if attr["name"] == "Hours of Operation"])
          if hours:
              properties["opening_hours"] = hours
  
          yield GeojsonPointItem(**properties)
  
      def parse(self, response):
          data = json.loads(response.body_as_unicode())
  
          for state in data["clubLocatorStateTownList"]:
              for town in state["Towns"]:
                  code, _ = town.split('|')
                  payload = {"Town": code, "latitude": "", "longitude": "", "radius": "", "zipCode": ""}
                  yield scrapy.FormRequest("https://api.bjs.com/digital/live/api/v1.0/club/search/10201",
                                           method="POST",
                                           body=json.dumps(payload),
                                           headers=self.headers,
                                           callback=self.parse_store)
  
subject: Add spider for BJ's Wholesale; closes #898'