internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: alltheplaces/alltheplaces

Issue:
99 Cents Only Stores
https://99only.com/stores/near-me?map=31.781535980826575,-95.44439480489258,4z


Discussion:

Resolving patch:
diff --git a/locations/spiders/99centsonly.py b/locations/spiders/99centsonly.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/99centsonly.py
@@ -0,0 +1,79 @@
+import json
+import scrapy
+import re
+from scrapy.selector import Selector
+
+from locations.items import GeojsonPointItem
+from locations.hours import OpeningHours
+
+
+DAY_MAPPING = [
+    'Monday',
+    'Tuesday',
+    'Wednesday',
+    'Thursday',
+    'Friday',
+    'Saturday',
+    'Sunday'
+]
+
+
+class NinetyNineCentsOnlySpider(scrapy.Spider):
+    name = "99centsonly"
+    allowed_domains = ["99only.com"]
+
+    start_urls = [
+        "https://99only.com/sitemap.xml",
+    ]
+
+    def parse_hours(self, days, hours):
+        opening_hours = OpeningHours()
+
+        for d, h in zip(days, hours):
+            if d == 'Today':
+                # two days before the day after tomorrow
+                d = DAY_MAPPING[DAY_MAPPING.index(days[days.index('Tomorrow') + 1]) - 2]
+            elif d == 'Tomorrow':
+                # one day before the day after tomorrow
+                d = DAY_MAPPING[DAY_MAPPING.index(days[days.index('Tomorrow') + 1]) - 1]
+
+            open_time, close_time = h.split(' to ')
+            opening_hours.add_range(day=d[:2], open_time=open_time, close_time=close_time, time_format='%I:%M %p')
+
+        return opening_hours.as_opening_hours()
+
+    def parse_location(self, response):
+        data = response.xpath('//script[@type="application/ld+json"]/text()').extract_first()
+        data = json.loads(data)["@graph"][0]
+
+        properties = {
+            'ref': response.xpath('//span[@class="store-number"]/text()').extract_first().strip('#'),
+            'name': response.xpath('//div[@class="store-splash"]/h1/span/text()').extract_first(),
+            'addr_full': data["address"]["streetAddress"],
+            'city': data["address"]["addressLocality"],
+            'state': data["address"]["addressRegion"],
+            'postcode': data["address"]["postalCode"],
+            'country': data["address"]["addressCountry"],
+            'website': response.url,
+            'lat': float(data["geo"]["latitude"]),
+            'lon': float(data["geo"]["longitude"]),
+        }
+
+        hours = self.parse_hours(days=response.xpath('//div[@class="store-hours-list"]//td[@class="day"]/text()').extract(),
+                                 hours=response.xpath('//div[@class="store-hours-list"]//td[@class="hours"]/text()').extract())
+        if hours:
+            properties["opening_hours"] = hours
+
+        yield GeojsonPointItem(**properties)
+
+    def parse(self, response):
+        xml = Selector(response)
+        xml.remove_namespaces()
+
+        urls = xml.xpath('//loc/text()').extract()
+
+        for url in urls:
+            if re.match(r'^https://99only.com/stores/.+?$', url):
+                if 'near-me' in url:
+                    continue
+                yield scrapy.Request(url, callback=self.parse_location)


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Canadian Tire
https://www.canadiantire.ca/en/store-locator.html


Discussion:

Resolving patch:
diff --git a/locations/spiders/canadiantire.py b/locations/spiders/canadiantire.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/canadiantire.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+import json
+
+import scrapy
+from scrapy import Selector
+
+from locations.items import GeojsonPointItem
+from locations.hours import OpeningHours
+
+
+DAYS = [
+    'Mo',
+    'Tu',
+    'We',
+    'Th',
+    'Fr',
+    'Sa',
+    'Su'
+]
+
+
+class CanadianTireSpider(scrapy.Spider):
+    name = "canadiantire"
+    allowed_domains = ['canadiantire.ca']
+    start_urls = [
+        'https://www.canadiantire.ca/sitemap_0_4.xml.gz',
+    ]
+
+    def parse_hours(self, hours):
+        opening_hours = OpeningHours()
+        for hour, day in zip(hours, DAYS):
+            if hour == 'Closed':
+                continue
+            hour = json.loads(hour)
+            opening_hours.add_range(day, open_time=hour["open"], close_time=hour["close"])
+
+        return opening_hours.as_opening_hours()
+
+    def parse(self, response):
+        xml = Selector(response)
+        xml.remove_namespaces()
+
+        urls = xml.xpath('//loc/text()').extract()
+        urls = [url.strip() for url in urls]
+
+        for url in urls:
+            if 'store-details' in url:
+                yield scrapy.Request(url, callback=self.parse_store)
+
+    def parse_store(self, response):
+        check = response.xpath('//tr[contains(@class, "hours-table")]/@data-working-hours').extract_first()
+        if not check or check == "12:00 AM - 12:00 AM":
+            # there's some empty/placeholder/test store pages
+            return
+
+        data = json.loads(response.xpath('//div[@data-component="StoreLocatorPage"]/@data-config').extract_first())
+
+        properties = {
+            'brand': 'Canadian Tire',
+            'name': data["storeName"],
+            'ref': data["storeNumber"],
+            'addr_full': data["storeAddress1"],
+            'city': data["storeCityName"],
+            'state': data["storeProvince"],
+            'postcode': data["storePostalCode"],
+            'country': "CA",
+            'phone': data["storeTelephone"],
+            'website': response.url,
+            'lat': float(data["storeLatitude"]),
+            'lon': float(data["storeLongitude"]),
+        }
+
+        hours = self.parse_hours(response.xpath('//tr[contains(@class, "hours-table")]/@data-working-hours').extract())
+        if hours:
+            properties["opening_hours"] = hours
+
+        yield GeojsonPointItem(**properties)


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Petsmart Canada
https://www.petsmart.ca/store-locator/all/


Discussion:

Resolving patch:
diff --git a/locations/spiders/petsmart.py b/locations/spiders/petsmart.py
--- a/locations/spiders/petsmart.py
+++ b/locations/spiders/petsmart.py
@@ -32,9 +32,10 @@ class PetSmartSpider(scrapy.Spider):
     download_delay = 0.2
     name = "petsmart"
     item_attributes = { 'brand': "Petsmart" }
-    allowed_domains = ["petsmart.com"]
+    allowed_domains = ["petsmart.com", "petsmart.ca"]
     start_urls = (
         'https://www.petsmart.com/store-locator/all/',
+        'https://www.petsmart.ca/store-locator/all/'
     )
 
     def parse(self, response):
@@ -50,6 +51,10 @@ def parse(self, response):
 
     def parse_store(self, response):
         ref = re.search(r'.+/?\?(.+)', response.url).group(1)
+        if 'petsmart.ca' in response.url:
+            country = 'CA'
+        elif 'petsmart.com' in response.url:
+            country = 'US'
 
         properties = {
             'name': response.xpath('//span[@itemprop="name"]/text()').extract_first().strip(),
@@ -60,6 +65,7 @@ def parse_store(self, response):
             'lat': float(response.xpath('//input[@name="storeLatitudeVal"]/@value').extract_first()),
             'lon': float(response.xpath('//input[@name="storeLongitudeVal"]/@value').extract_first()),
             'phone': response.xpath('//a[@class="store-contact-info"]/text()').extract_first(),
+            'country': country,
             'ref': ref,
             'website': response.url
         }


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
CGI Group
https://www.cgi.com/en/offices?field_address_country_code=CA&field_address_administrative_area=All&field_address_locality=All


Discussion:

Resolving patch:
diff --git a/locations/spiders/cgi_group.py b/locations/spiders/cgi_group.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/cgi_group.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+import re
+import scrapy
+import json
+
+from locations.items import GeojsonPointItem
+
+
+class CGISpider(scrapy.Spider):
+    name = "cgi_group"
+    allowed_domains = ["cgi.com"]
+    download_delay = 0.5
+    start_urls = (
+        'https://www.cgi.com/en/offices?field_address_country_code=All&field_address_administrative_area=All&field_address_locality=All',
+    )
+
+    def checklist(self, mylist):
+        try:
+            string = mylist[0]
+            return string
+        except IndexError:
+            return None
+
+    def parse_country(self, response):
+        data = json.loads(response.xpath('//script[@type="application/json"]/text()').extract_first())
+
+        offices = data['geofield_google_map']['geofield-map-view-newoffices-block-1']['data']['features']
+
+        for office in offices:
+
+            properties = {
+                'name': re.findall('(?<=<span>).*?(?=<)', office['properties']['description'])[0],
+                'addr_full': re.findall('(?<=-line1">).*?(?=<)', office['properties']['description'])[0],
+                'city': re.findall('(?<=locality">).*?(?=<)', office['properties']['description'])[0],
+                'state': self.checklist(re.findall('(?<=administrative-area">).*?(?=<)',
+                                                   office['properties']['description'])),
+                'postcode': self.checklist(re.findall('(?<=postal-code">).*?(?=<)',
+                                                      office['properties']['description'])),
+                'lat': float(office['geometry']['coordinates'][1]),
+                'lon': float(office['geometry']['coordinates'][0]),
+                'country': re.findall('(?<=country">).*?(?=<)', office['properties']['description'])[0],
+                'ref': re.findall('(?<=<span>).*?(?=<)', office['properties']['description'])[0],
+                'website': response.url
+            }
+
+            yield GeojsonPointItem(**properties)
+
+    def parse(self, response):
+        countries = response.xpath('//select[@data-drupal-selector="edit-field-address-country-code"]/option').extract()
+        for country in countries:
+            country = re.findall(r'"([^"]*)"', country)
+            if country[0] != "All":
+                url = 'https://www.cgi.com/en/offices?field_address_country_code={country}&field_address_administrative_area=All&field_address_locality=All'.format(country=country[0])
+                yield scrapy.Request(url, callback=self.parse_country)


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Earth Fare
https://www.earthfare.com/rs/StoreLocator


Discussion:

Resolving patch:
diff --git a/locations/spiders/earth_fare.py b/locations/spiders/earth_fare.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/earth_fare.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+import json
+import re
+
+import scrapy
+
+from locations.items import GeojsonPointItem
+from locations.hours import OpeningHours
+
+DAY_MAPPING = {
+    'Mon': 'Mo',
+    'Tue': 'Tu',
+    'Wed': 'We',
+    'Thu': 'Th',
+    'Fri': 'Fr',
+    'Sat': 'Sa',
+    'Sun': 'Su'
+}
+
+
+class EarthFareSpider(scrapy.Spider):
+    name = "earth_fare"
+    item_attributes = {'brand': "Earth Fare"}
+    allowed_domains = ['www.earthfare.com']
+    start_urls = [
+        'https://www.earthfare.com/rs/StoreLocator',
+    ]
+
+    def parse_hours(self, store_hours):
+        opening_hours = OpeningHours()
+
+        days = list(DAY_MAPPING.keys())
+        day_range, open_close = re.split(r', | ', store_hours)
+        start_day, end_day = day_range.split('-')
+        start_time, end_time = open_close.split('-')
+
+        for day in days[days.index(start_day):days.index(end_day) + 1]:
+            opening_hours.add_range(day=DAY_MAPPING[day],
+                                    open_time=start_time,
+                                    close_time=end_time,
+                                    time_format='%I%p')
+
+        return opening_hours.as_opening_hours()
+
+    def parse(self, response):
+        stores = response.xpath('//script[@type="text/javascript" and contains(text(), "storeDetail")]/text()').extract_first()
+        store_data = re.findall(r'(\{\\\"Distance\\\".*?\"\})[,\]]', stores)
+
+        for store in store_data:
+            store_details = json.loads(store.replace('\\', ''))
+
+            properties = {
+                'name': store_details["StoreName"],
+                'ref': store_details["CS_StoreID"],
+                'addr_full': store_details["Address1"],
+                'city': store_details["City"],
+                'state': store_details["State"],
+                'postcode': store_details["Zipcode"],
+                'phone': store_details.get("PhoneNumber"),
+                'website': 'https://www.earthfare.com/rs/StoreLocator?id={0}'.format(store_details["CS_StoreID"]),
+                'lat': store_details.get("Latitude"),
+                'lon': store_details.get("Longitude"),
+            }
+
+            hours = store_details.get("StoreHours")
+            if hours:
+                properties["opening_hours"] = self.parse_hours(hours)
+
+            yield GeojsonPointItem(**properties)


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
B & Q
B & Q is a hardware store chain in the UK. Clearly they were quick to register their domain name...

https://www.diy.com/find-a-store



Discussion:
No activity in several days, so I'm un-assigning. Please re-assign yourself if you're actively working on it.
Again search by ZIP or City.
Can't find any other way to search. :-(

Resolving patch:
diff --git a/locations/spiders/bq.py b/locations/spiders/bq.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/bq.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+import scrapy
+import json
+import re
+
+from locations.items import GeojsonPointItem
+
+
+class BQSpider(scrapy.Spider):
+    name = "bq"
+    item_attributes = { 'brand': "B&Q" }
+    allowed_domains = ['www.diy.com']
+    # To get a new atmosphere_app_id key, check Network calls within https://www.diy.com/find-a-store/ (call to api.kingfisher.com)
+    start_urls = (
+        'https://api.kingfisher.com/v1/mobile/stores/BQUK?nearLatLong=51.515617%2C-0.091998&page[size]=500&atmosphere_app_id=kingfisher-7c4QgmLEROp4PUh0oUebbI94',
+    )
+
+
+    def parse(self, response):
+        response.selector.remove_namespaces()
+
+        data = json.loads( response.xpath('//text()').get() )['data']
+
+        for store in data:
+            if store['type'] == 'store':
+                try:
+
+                    country = store['attributes']['store']['geoCoordinates']['country']
+                    country = 'Ireland' if country == 'Eire' else country
+
+                    properties = {
+                        'name': store['attributes']['store']['name'],
+                        'ref': store['id'],
+                        'website': 'https://www.diy.com{}'.format( store['attributes']['seoPath'] ),
+                        'postcode': store['attributes']['store']['geoCoordinates']['postalCode'],
+                        'city': store['attributes']['store']['geoCoordinates']['address']['lines'][-2],
+                        'country': country,
+                        'addr_full': store['attributes']['store']['geoCoordinates']['address']['lines'][0],
+                        'phone': store['attributes']['store']['contactPoint']['telephone'],
+                        'lat': float(store['attributes']['store']['geoCoordinates']['latitude']),
+                        'lon': float(store['attributes']['store']['geoCoordinates']['longitude'])
+                    }
+
+                    yield GeojsonPointItem(**properties)
+
+                except:
+                    break
\ No newline at end of file


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Extended Stay America
https://www.extendedstayamerica.com/hotels


Discussion:

Resolving patch:
diff --git a/locations/spiders/extended_stay_america.py b/locations/spiders/extended_stay_america.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/extended_stay_america.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+import json
+import re
+
+import scrapy
+
+from locations.items import GeojsonPointItem
+from locations.hours import OpeningHours
+
+
+class ExtendedStayAmericaSpider(scrapy.Spider):
+    name = "extended_stay_america"
+    item_attributes = {'brand': 'Extended Stay America'}
+    allowed_domains = ["extendedstayamerica.com"]
+    start_urls = [
+        'https://www.extendedstayamerica.com/hotels',
+    ]
+
+    def parse(self, response):
+        urls = response.xpath('//tr/td/a/@href').extract()
+        for url in urls:
+            yield scrapy.Request(response.urljoin(url), callback=self.parse_state)
+
+    def parse_state(self, response):
+        urls = response.xpath('//div[@class="links"]//td/a/@href').extract()
+        for url in urls:
+            yield scrapy.Request(response.urljoin(url), callback=self.parse_hotel_list)
+
+    def parse_hotel_list(self, response):
+        script = response.xpath('//script[contains(text(), "pinList")]/text()').extract_first()
+        hotels = json.loads(re.search(r'var pinList =\s(\{.*\});', script).groups()[0])
+
+        for hotel in hotels["PushPins"]:
+            properties = {
+                'name': hotel["HotelName"],
+                'ref': hotel["HotelId"],
+                'addr_full': hotel["Address"],
+                'city': hotel["HotelCity"],
+                'state': hotel["HotelState"],
+                'postcode': hotel["HotelZip"],
+                'country': 'US',
+                'website': hotel["MinisiteUrl"],
+                'lat': float(hotel["Latitude"]),
+                'lon': float(hotel["Longitude"]),
+            }
+
+            yield GeojsonPointItem(**properties)


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Valero Gas Stations
No list, but does have a maps search that does POST requests:

https://www.valero.com/en-us/ProductsAndServices/Consumers/StoreLocator

Does POST requests against:

https://valeromaps.valero.com/Home/Search?SPHostUrl=https%3A%2F%2Fwww.valero.com%2Fen-us

to return json search results.

https://www.wikidata.org/wiki/Q1283291


Discussion:

Resolving patch:
diff --git a/locations/spiders/valero.py b/locations/spiders/valero.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/valero.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+import scrapy
+import json
+from locations.items import GeojsonPointItem
+
+
+class ValeroSpider(scrapy.Spider):
+    name = "valero"
+    item_attributes = {'brand': "Valero", 'brand_wikidata': 'Q1283291'}
+    allowed_domains = ["valeromaps.valero.com"]
+
+    def start_requests(self):
+        yield scrapy.FormRequest(
+            'https://valeromaps.valero.com/Home/Search?SPHostUrl=https:%2F%2Fwww.valero.com%2Fen-us',
+            method='POST',
+            headers={
+                'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
+            },
+            formdata={
+                'NEBound_Lat': '90',
+                'NEBound_Long': '180',
+                'SWBound_Lat': '-90',
+                'SWBound_Long': '-180',
+                'center_Lat': '0',
+                'center_Long': '0'
+            }
+        )
+
+    def parse(self, response):
+        result = json.loads(response.body_as_unicode())
+        for store in result['StoreList']:
+            details = ', '.join([d['DetailName'] for d in store['Details']])
+            yield GeojsonPointItem(
+                lon=store['Longitude'],
+                lat=store['Latitude'],
+                ref=store['UniqueID'],
+                name=store['StationName'],
+                addr_full=store['Address'],
+                phone=store['Phone'],
+                opening_hours='24/7' if '24 Hours' in details else None,
+                extras={
+                    'amenity:fuel': True,
+                    'amenity:toilets': 'Restroom' in details or None,
+                    'atm': 'ATM' in details,
+                    'car_wash': 'Car Wash' in details,
+                    'fuel:diesel': 'Diesel' in details or None,
+                    'fuel:e85': 'E-85' in details or None,
+                }
+            )


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Auchan/Auchan Supermarché
https://www.auchan.fr/magasins/votremagasin


Discussion:

Resolving patch:
diff --git a/locations/spiders/auchan.py b/locations/spiders/auchan.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/auchan.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+import json
+import re
+
+import scrapy
+
+from locations.items import GeojsonPointItem
+from locations.hours import OpeningHours
+
+DAYS = {
+    "1": "Mo",
+    "2": "Tu",
+    "3": "We",
+    "4": "Th",
+    "5": "Fr",
+    "6": "Sa",
+    "7": "Su"
+}
+
+
+class AuchanSpider(scrapy.Spider):
+    name = "auchan"
+    item_attributes = {'brand': 'Auchan', 'brand_wikidata': 'Q758603'}
+    allowed_domains = ['auchan.fr', "woosmap.com"]
+    start_urls = [
+        'https://www.auchan.fr/magasins/votremagasin?store_id=152',
+    ]
+
+    def parse(self, response):
+        yield scrapy.Request('https://api.woosmap.com/stores/?key=auchan-woos&page=1', callback=self.parse_api)
+
+    def parse_hours(self, hours):
+        opening_hours = OpeningHours()
+        for day, times in hours.items():
+            if day == "default":
+                continue
+            opening_hours.add_range(day=DAYS[day],
+                                    open_time=times[0]["start"],
+                                    close_time=times[-1]["end"]
+                                    )
+
+        return opening_hours.as_opening_hours()
+
+    def parse_api(self, response):
+        data = json.loads(response.body_as_unicode())
+
+        stores = data['features']
+
+        for store in stores:
+            properties = {
+                'name': store["properties"]["name"],
+                'ref': store["properties"]["store_id"],
+                'addr_full': " ".join(store["properties"]["address"]["lines"]),
+                'city': store["properties"]["address"]["city"],
+                'postcode': store["properties"]["address"]["zipcode"],
+                'country': store["properties"]["address"]["country_code"],
+                'phone': store["properties"]["contact"]["phone"],
+                'website': store["properties"]["contact"]["website"],
+                'lat': float(store["geometry"]["coordinates"][1]),
+                'lon': float(store["geometry"]["coordinates"][0]),
+            }
+
+            hours = self.parse_hours(store["properties"]["opening_hours"]["usual"])
+            if hours:
+                properties["opening_hours"] = hours
+
+            yield GeojsonPointItem(**properties)
+
+        if data['pagination']['page'] < data['pagination']['pageCount']:
+            page = data['pagination']['page'] + 1
+            yield scrapy.Request(
+                url='https://api.woosmap.com/stores/?key=auchan-woos&page={page}'.format(page=page),
+                callback=self.parse_api
+            )


Test patch: