We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Spider ace_hardware is broken
During the global build at 2021-05-21-20-28-08, spider **ace_hardware** failed with **0 features** and **0 errors**.

Here's [the log](https://data.alltheplaces.xyz/runs/2021-05-21-20-28-08/logs/ace_hardware.log) and [the output](https://data.alltheplaces.xyz/runs/2021-05-21-20-28-08/output/ace_hardware.geojson) ([on a map](https://data.alltheplaces.xyz/map.html?show=https://data.alltheplaces.xyz/runs/2021-05-21-20-28-08/output/ace_hardware.geojson))

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `locations/settings.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 import os
3 
4 # Scrapy settings for locations project
5 #
6 # For simplicity, this file contains only settings considered important or
7 # commonly used. You can find more settings consulting the documentation:
8 #
9 #     http://doc.scrapy.org/en/latest/topics/settings.html
10 #     http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
11 #     http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
12 
13 BOT_NAME = 'locations'
14 
15 SPIDER_MODULES = ['locations.spiders']
16 NEWSPIDER_MODULE = 'locations.spiders'
17 
18 
19 
20 # Obey robots.txt rules
21 ROBOTSTXT_OBEY = False
22 
23 FEED_URI = os.environ.get('FEED_URI')
24 FEED_FORMAT = os.environ.get('FEED_FORMAT')
25 FEED_EXPORTERS = {
26     'geojson': 'locations.exporters.GeoJsonExporter',
27     'ndgeojson': 'locations.exporters.LineDelimitedGeoJsonExporter',
28 }
29 
30 # Configure maximum concurrent requests performed by Scrapy (default: 16)
31 #CONCURRENT_REQUESTS = 32
32 
33 # Configure a delay for requests for the same website (default: 0)
34 # See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
35 # See also autothrottle settings and docs
36 #DOWNLOAD_DELAY = 3
37 # The download delay setting will honor only one of:
38 #CONCURRENT_REQUESTS_PER_DOMAIN = 16
39 #CONCURRENT_REQUESTS_PER_IP = 16
40 
41 # Disable cookies (enabled by default)
42 #COOKIES_ENABLED = False
43 
44 # Disable Telnet Console (enabled by default)
45 TELNETCONSOLE_ENABLED = False
46 
47 # Override the default request headers:
48 #DEFAULT_REQUEST_HEADERS = {
49 #   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
50 #   'Accept-Language': 'en',
51 #}
52 
53 # Enable or disable spider middlewares
54 # See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
55 #SPIDER_MIDDLEWARES = {
56 #    'locations.middlewares.MyCustomSpiderMiddleware': 543,
57 #}
58 
59 # Enable or disable downloader middlewares
60 # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
61 DOWNLOADER_MIDDLEWARES = {
62     'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
63     'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
64     'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 400,
65     'scrapy_fake_useragent.middleware.RetryUserAgentMiddleware': 401,
66 }
67 
68 FAKEUSERAGENT_PROVIDERS = [
69     'scrapy_fake_useragent.providers.FakeUserAgentProvider',
70     'scrapy_fake_useragent.providers.FakerProvider',
71     'scrapy_fake_useragent.providers.FixedUserAgentProvider',
72 ]
73 USER_AGENT = 'Mozilla/5.0 (Linux; rv:1.0) https://github.com/alltheplaces/alltheplaces'
74 
75 # Enable or disable extensions
76 # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
77 #EXTENSIONS = {
78 #    'scrapy.extensions.telnet.TelnetConsole': None,
79 #}
80 
81 EXTENSIONS = {
82     'locations.extensions.LogStatsExtension': 101,
83 }
84 
85 # Configure item pipelines
86 # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
87 ITEM_PIPELINES = {
88    'locations.pipelines.DuplicatesPipeline': 200,
89    'locations.pipelines.ApplySpiderNamePipeline': 250,
90    'locations.pipelines.ApplySpiderLevelAttributesPipeline': 300
91 }
92 
93 
94 
95 # Enable and configure the AutoThrottle extension (disabled by default)
96 # See http://doc.scrapy.org/en/latest/topics/autothrottle.html
97 #AUTOTHROTTLE_ENABLED = True
98 # The initial download delay
99 #AUTOTHROTTLE_START_DELAY = 5
100 # The maximum download delay to be set in case of high latencies
101 #AUTOTHROTTLE_MAX_DELAY = 60
102 # The average number of requests Scrapy should be sending in parallel to
103 # each remote server
104 #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
105 # Enable showing throttling stats for every response received:
106 #AUTOTHROTTLE_DEBUG = False
107 
108 # Enable and configure HTTP caching (disabled by default)
109 # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
110 # HTTPCACHE_ENABLED = True
111 #HTTPCACHE_EXPIRATION_SECS = 0
112 #HTTPCACHE_DIR = 'httpcache'
113 #HTTPCACHE_IGNORE_HTTP_CODES = []
114 #HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
115
```

Path: `locations/spiders/ace_hardware.py`
Content:
```
1 import scrapy
2 import re
3 import json
4 from locations.items import GeojsonPointItem
5 from locations.hours import OpeningHours
6 
7 DAY_MAPPING = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
8 
9 
10 class AceHardwareSpider(scrapy.Spider):
11     name = "ace_hardware"
12     item_attributes = { 'brand': "Ace Hardware" }
13     allowed_domains = ["www.acehardware.com"]
14     download_delay = 0.1
15     start_urls = (
16         'https://www.acehardware.com/store-directory',
17     )
18 
19     def parse_hours(self, lis):
20         o = OpeningHours()
21 
22         for day in DAY_MAPPING:
23             d = day.title()[:2]
24 
25             label = lis[day]['label']
26             if label == '0000 - 0000':
27                 continue
28 
29             start, end = label.split(' - ')
30             start = '%s:%s' % (start[:2], start[2:])
31             end = '%s:%s' % (end[:2], end[2:])
32 
33             o.add_range(d, start, end)
34         return o.as_opening_hours()
35 
36     def parse_store(self, response):
37         store_data = response.xpath('//script[@id="data-mz-preload-store"]/text()').extract_first()
38 
39         if not store_data:
40             return
41 
42         store_data = json.loads(store_data)
43 
44         properties = {
45             'name': store_data['StoreName'],
46             'phone': store_data['Phone'],
47             'addr_full': store_data['StoreAddressLn1'],
48             'city': store_data['StoreCityNm'],
49             'state': store_data['StoreStateCd'],
50             'postcode': store_data['StoreZipCd'],
51             'ref': store_data['StoreNumber'],
52             'website': response.url,
53             'lat': float(store_data['Latitude']),
54             'lon': float(store_data['Longitude']),
55         }
56 
57         hours = self.parse_hours(store_data['RegularHours'])
58         if hours:
59             properties['opening_hours'] = hours
60 
61         yield GeojsonPointItem(**properties)
62 
63     def parse(self, response):
64         for store_url in response.css('div.store-directory-list-item').xpath('div/a/@href').extract():
65             yield scrapy.Request(
66                 response.urljoin(store_url),
67                 callback=self.parse_store,
68             )
69
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/locations/settings.py b/locations/settings.py
--- a/locations/settings.py
+++ b/locations/settings.py
@@ -16,6 +16,8 @@
 NEWSPIDER_MODULE = 'locations.spiders'
 
 
+# Crawl responsibly by identifying yourself (and your website) on the user-agent
+USER_AGENT = 'Mozilla/5.0 (Linux; rv:1.0) https://github.com/alltheplaces/alltheplaces'
 
 # Obey robots.txt rules
 ROBOTSTXT_OBEY = False
@@ -58,19 +60,9 @@
 
 # Enable or disable downloader middlewares
 # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
-DOWNLOADER_MIDDLEWARES = {
-    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
-    'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
-    'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 400,
-    'scrapy_fake_useragent.middleware.RetryUserAgentMiddleware': 401,
-}
-
-FAKEUSERAGENT_PROVIDERS = [
-    'scrapy_fake_useragent.providers.FakeUserAgentProvider',
-    'scrapy_fake_useragent.providers.FakerProvider',
-    'scrapy_fake_useragent.providers.FixedUserAgentProvider',
-]
-USER_AGENT = 'Mozilla/5.0 (Linux; rv:1.0) https://github.com/alltheplaces/alltheplaces'
+#DOWNLOADER_MIDDLEWARES = {
+#    'locations.middlewares.MyCustomDownloaderMiddleware': 543,
+#}
 
 # Enable or disable extensions
 # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
diff --git a/locations/spiders/ace_hardware.py b/locations/spiders/ace_hardware.py
--- a/locations/spiders/ace_hardware.py
+++ b/locations/spiders/ace_hardware.py
@@ -9,12 +9,13 @@
 
 class AceHardwareSpider(scrapy.Spider):
     name = "ace_hardware"
-    item_attributes = { 'brand': "Ace Hardware" }
+    item_attributes = {'brand': "Ace Hardware", 'brand_wikidata': 'Q4672981'}
     allowed_domains = ["www.acehardware.com"]
-    download_delay = 0.1
+    download_delay = 0.7
     start_urls = (
         'https://www.acehardware.com/store-directory',
     )
+    user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
 
     def parse_hours(self, lis):
         o = OpeningHours()
@@ -22,15 +23,10 @@
         for day in DAY_MAPPING:
             d = day.title()[:2]
 
-            label = lis[day]['label']
-            if label == '0000 - 0000':
+            if lis[day]['label'] == '0000 - 0000':
                 continue
 
-            start, end = label.split(' - ')
-            start = '%s:%s' % (start[:2], start[2:])
-            end = '%s:%s' % (end[:2], end[2:])
-
-            o.add_range(d, start, end)
+            o.add_range(d, lis[day]['openTime'], lis[day]['closeTime'])
         return o.as_opening_hours()
 
     def parse_store(self, response):
@@ -50,8 +46,8 @@
             'postcode': store_data['StoreZipCd'],
             'ref': store_data['StoreNumber'],
             'website': response.url,
-            'lat': float(store_data['Latitude']),
-            'lon': float(store_data['Longitude']),
+            'lat': store_data['Latitude'],
+            'lon': store_data['Longitude'],
         }
 
         hours = self.parse_hours(store_data['RegularHours'])

{"golden_diff": "diff --git a/locations/settings.py b/locations/settings.py\n--- a/locations/settings.py\n+++ b/locations/settings.py\n@@ -16,6 +16,8 @@\n NEWSPIDER_MODULE = 'locations.spiders'\n \n \n+# Crawl responsibly by identifying yourself (and your website) on the user-agent\n+USER_AGENT = 'Mozilla/5.0 (Linux; rv:1.0) https://github.com/alltheplaces/alltheplaces'\n \n # Obey robots.txt rules\n ROBOTSTXT_OBEY = False\n@@ -58,19 +60,9 @@\n \n # Enable or disable downloader middlewares\n # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html\n-DOWNLOADER_MIDDLEWARES = {\n-    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,\n-    'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,\n-    'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 400,\n-    'scrapy_fake_useragent.middleware.RetryUserAgentMiddleware': 401,\n-}\n-\n-FAKEUSERAGENT_PROVIDERS = [\n-    'scrapy_fake_useragent.providers.FakeUserAgentProvider',\n-    'scrapy_fake_useragent.providers.FakerProvider',\n-    'scrapy_fake_useragent.providers.FixedUserAgentProvider',\n-]\n-USER_AGENT = 'Mozilla/5.0 (Linux; rv:1.0) https://github.com/alltheplaces/alltheplaces'\n+#DOWNLOADER_MIDDLEWARES = {\n+#    'locations.middlewares.MyCustomDownloaderMiddleware': 543,\n+#}\n \n # Enable or disable extensions\n # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html\ndiff --git a/locations/spiders/ace_hardware.py b/locations/spiders/ace_hardware.py\n--- a/locations/spiders/ace_hardware.py\n+++ b/locations/spiders/ace_hardware.py\n@@ -9,12 +9,13 @@\n \n class AceHardwareSpider(scrapy.Spider):\n     name = \"ace_hardware\"\n-    item_attributes = { 'brand': \"Ace Hardware\" }\n+    item_attributes = {'brand': \"Ace Hardware\", 'brand_wikidata': 'Q4672981'}\n     allowed_domains = [\"www.acehardware.com\"]\n-    download_delay = 0.1\n+    download_delay = 0.7\n     start_urls = (\n         'https://www.acehardware.com/store-directory',\n     )\n+    user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'\n \n     def parse_hours(self, lis):\n         o = OpeningHours()\n@@ -22,15 +23,10 @@\n         for day in DAY_MAPPING:\n             d = day.title()[:2]\n \n-            label = lis[day]['label']\n-            if label == '0000 - 0000':\n+            if lis[day]['label'] == '0000 - 0000':\n                 continue\n \n-            start, end = label.split(' - ')\n-            start = '%s:%s' % (start[:2], start[2:])\n-            end = '%s:%s' % (end[:2], end[2:])\n-\n-            o.add_range(d, start, end)\n+            o.add_range(d, lis[day]['openTime'], lis[day]['closeTime'])\n         return o.as_opening_hours()\n \n     def parse_store(self, response):\n@@ -50,8 +46,8 @@\n             'postcode': store_data['StoreZipCd'],\n             'ref': store_data['StoreNumber'],\n             'website': response.url,\n-            'lat': float(store_data['Latitude']),\n-            'lon': float(store_data['Longitude']),\n+            'lat': store_data['Latitude'],\n+            'lon': store_data['Longitude'],\n         }\n \n         hours = self.parse_hours(store_data['RegularHours'])\n", "issue": "Spider ace_hardware is broken\nDuring the global build at 2021-05-21-20-28-08, spider **ace_hardware** failed with **0 features** and **0 errors**.\n\nHere's [the log](https://data.alltheplaces.xyz/runs/2021-05-21-20-28-08/logs/ace_hardware.log) and [the output](https://data.alltheplaces.xyz/runs/2021-05-21-20-28-08/output/ace_hardware.geojson) ([on a map](https://data.alltheplaces.xyz/map.html?show=https://data.alltheplaces.xyz/runs/2021-05-21-20-28-08/output/ace_hardware.geojson))\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nimport os\n\n# Scrapy settings for locations project\n#\n# For simplicity, this file contains only settings considered important or\n# commonly used. You can find more settings consulting the documentation:\n#\n#     http://doc.scrapy.org/en/latest/topics/settings.html\n#     http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html\n#     http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html\n\nBOT_NAME = 'locations'\n\nSPIDER_MODULES = ['locations.spiders']\nNEWSPIDER_MODULE = 'locations.spiders'\n\n\n\n# Obey robots.txt rules\nROBOTSTXT_OBEY = False\n\nFEED_URI = os.environ.get('FEED_URI')\nFEED_FORMAT = os.environ.get('FEED_FORMAT')\nFEED_EXPORTERS = {\n    'geojson': 'locations.exporters.GeoJsonExporter',\n    'ndgeojson': 'locations.exporters.LineDelimitedGeoJsonExporter',\n}\n\n# Configure maximum concurrent requests performed by Scrapy (default: 16)\n#CONCURRENT_REQUESTS = 32\n\n# Configure a delay for requests for the same website (default: 0)\n# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay\n# See also autothrottle settings and docs\n#DOWNLOAD_DELAY = 3\n# The download delay setting will honor only one of:\n#CONCURRENT_REQUESTS_PER_DOMAIN = 16\n#CONCURRENT_REQUESTS_PER_IP = 16\n\n# Disable cookies (enabled by default)\n#COOKIES_ENABLED = False\n\n# Disable Telnet Console (enabled by default)\nTELNETCONSOLE_ENABLED = False\n\n# Override the default request headers:\n#DEFAULT_REQUEST_HEADERS = {\n#   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\n#   'Accept-Language': 'en',\n#}\n\n# Enable or disable spider middlewares\n# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html\n#SPIDER_MIDDLEWARES = {\n#    'locations.middlewares.MyCustomSpiderMiddleware': 543,\n#}\n\n# Enable or disable downloader middlewares\n# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html\nDOWNLOADER_MIDDLEWARES = {\n    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,\n    'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,\n    'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 400,\n    'scrapy_fake_useragent.middleware.RetryUserAgentMiddleware': 401,\n}\n\nFAKEUSERAGENT_PROVIDERS = [\n    'scrapy_fake_useragent.providers.FakeUserAgentProvider',\n    'scrapy_fake_useragent.providers.FakerProvider',\n    'scrapy_fake_useragent.providers.FixedUserAgentProvider',\n]\nUSER_AGENT = 'Mozilla/5.0 (Linux; rv:1.0) https://github.com/alltheplaces/alltheplaces'\n\n# Enable or disable extensions\n# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html\n#EXTENSIONS = {\n#    'scrapy.extensions.telnet.TelnetConsole': None,\n#}\n\nEXTENSIONS = {\n    'locations.extensions.LogStatsExtension': 101,\n}\n\n# Configure item pipelines\n# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html\nITEM_PIPELINES = {\n   'locations.pipelines.DuplicatesPipeline': 200,\n   'locations.pipelines.ApplySpiderNamePipeline': 250,\n   'locations.pipelines.ApplySpiderLevelAttributesPipeline': 300\n}\n\n\n\n# Enable and configure the AutoThrottle extension (disabled by default)\n# See http://doc.scrapy.org/en/latest/topics/autothrottle.html\n#AUTOTHROTTLE_ENABLED = True\n# The initial download delay\n#AUTOTHROTTLE_START_DELAY = 5\n# The maximum download delay to be set in case of high latencies\n#AUTOTHROTTLE_MAX_DELAY = 60\n# The average number of requests Scrapy should be sending in parallel to\n# each remote server\n#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0\n# Enable showing throttling stats for every response received:\n#AUTOTHROTTLE_DEBUG = False\n\n# Enable and configure HTTP caching (disabled by default)\n# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings\n# HTTPCACHE_ENABLED = True\n#HTTPCACHE_EXPIRATION_SECS = 0\n#HTTPCACHE_DIR = 'httpcache'\n#HTTPCACHE_IGNORE_HTTP_CODES = []\n#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'\n", "path": "locations/settings.py"}, {"content": "import scrapy\nimport re\nimport json\nfrom locations.items import GeojsonPointItem\nfrom locations.hours import OpeningHours\n\nDAY_MAPPING = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']\n\n\nclass AceHardwareSpider(scrapy.Spider):\n    name = \"ace_hardware\"\n    item_attributes = { 'brand': \"Ace Hardware\" }\n    allowed_domains = [\"www.acehardware.com\"]\n    download_delay = 0.1\n    start_urls = (\n        'https://www.acehardware.com/store-directory',\n    )\n\n    def parse_hours(self, lis):\n        o = OpeningHours()\n\n        for day in DAY_MAPPING:\n            d = day.title()[:2]\n\n            label = lis[day]['label']\n            if label == '0000 - 0000':\n                continue\n\n            start, end = label.split(' - ')\n            start = '%s:%s' % (start[:2], start[2:])\n            end = '%s:%s' % (end[:2], end[2:])\n\n            o.add_range(d, start, end)\n        return o.as_opening_hours()\n\n    def parse_store(self, response):\n        store_data = response.xpath('//script[@id=\"data-mz-preload-store\"]/text()').extract_first()\n\n        if not store_data:\n            return\n\n        store_data = json.loads(store_data)\n\n        properties = {\n            'name': store_data['StoreName'],\n            'phone': store_data['Phone'],\n            'addr_full': store_data['StoreAddressLn1'],\n            'city': store_data['StoreCityNm'],\n            'state': store_data['StoreStateCd'],\n            'postcode': store_data['StoreZipCd'],\n            'ref': store_data['StoreNumber'],\n            'website': response.url,\n            'lat': float(store_data['Latitude']),\n            'lon': float(store_data['Longitude']),\n        }\n\n        hours = self.parse_hours(store_data['RegularHours'])\n        if hours:\n            properties['opening_hours'] = hours\n\n        yield GeojsonPointItem(**properties)\n\n    def parse(self, response):\n        for store_url in response.css('div.store-directory-list-item').xpath('div/a/@href').extract():\n            yield scrapy.Request(\n                response.urljoin(store_url),\n                callback=self.parse_store,\n            )\n", "path": "locations/spiders/ace_hardware.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nimport os\n\n# Scrapy settings for locations project\n#\n# For simplicity, this file contains only settings considered important or\n# commonly used. You can find more settings consulting the documentation:\n#\n#     http://doc.scrapy.org/en/latest/topics/settings.html\n#     http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html\n#     http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html\n\nBOT_NAME = 'locations'\n\nSPIDER_MODULES = ['locations.spiders']\nNEWSPIDER_MODULE = 'locations.spiders'\n\n\n# Crawl responsibly by identifying yourself (and your website) on the user-agent\nUSER_AGENT = 'Mozilla/5.0 (Linux; rv:1.0) https://github.com/alltheplaces/alltheplaces'\n\n# Obey robots.txt rules\nROBOTSTXT_OBEY = False\n\nFEED_URI = os.environ.get('FEED_URI')\nFEED_FORMAT = os.environ.get('FEED_FORMAT')\nFEED_EXPORTERS = {\n    'geojson': 'locations.exporters.GeoJsonExporter',\n    'ndgeojson': 'locations.exporters.LineDelimitedGeoJsonExporter',\n}\n\n# Configure maximum concurrent requests performed by Scrapy (default: 16)\n#CONCURRENT_REQUESTS = 32\n\n# Configure a delay for requests for the same website (default: 0)\n# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay\n# See also autothrottle settings and docs\n#DOWNLOAD_DELAY = 3\n# The download delay setting will honor only one of:\n#CONCURRENT_REQUESTS_PER_DOMAIN = 16\n#CONCURRENT_REQUESTS_PER_IP = 16\n\n# Disable cookies (enabled by default)\n#COOKIES_ENABLED = False\n\n# Disable Telnet Console (enabled by default)\nTELNETCONSOLE_ENABLED = False\n\n# Override the default request headers:\n#DEFAULT_REQUEST_HEADERS = {\n#   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\n#   'Accept-Language': 'en',\n#}\n\n# Enable or disable spider middlewares\n# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html\n#SPIDER_MIDDLEWARES = {\n#    'locations.middlewares.MyCustomSpiderMiddleware': 543,\n#}\n\n# Enable or disable downloader middlewares\n# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html\n#DOWNLOADER_MIDDLEWARES = {\n#    'locations.middlewares.MyCustomDownloaderMiddleware': 543,\n#}\n\n# Enable or disable extensions\n# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html\n#EXTENSIONS = {\n#    'scrapy.extensions.telnet.TelnetConsole': None,\n#}\n\nEXTENSIONS = {\n    'locations.extensions.LogStatsExtension': 101,\n}\n\n# Configure item pipelines\n# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html\nITEM_PIPELINES = {\n   'locations.pipelines.DuplicatesPipeline': 200,\n   'locations.pipelines.ApplySpiderNamePipeline': 250,\n   'locations.pipelines.ApplySpiderLevelAttributesPipeline': 300\n}\n\n\n\n# Enable and configure the AutoThrottle extension (disabled by default)\n# See http://doc.scrapy.org/en/latest/topics/autothrottle.html\n#AUTOTHROTTLE_ENABLED = True\n# The initial download delay\n#AUTOTHROTTLE_START_DELAY = 5\n# The maximum download delay to be set in case of high latencies\n#AUTOTHROTTLE_MAX_DELAY = 60\n# The average number of requests Scrapy should be sending in parallel to\n# each remote server\n#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0\n# Enable showing throttling stats for every response received:\n#AUTOTHROTTLE_DEBUG = False\n\n# Enable and configure HTTP caching (disabled by default)\n# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings\n# HTTPCACHE_ENABLED = True\n#HTTPCACHE_EXPIRATION_SECS = 0\n#HTTPCACHE_DIR = 'httpcache'\n#HTTPCACHE_IGNORE_HTTP_CODES = []\n#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'\n", "path": "locations/settings.py"}, {"content": "import scrapy\nimport re\nimport json\nfrom locations.items import GeojsonPointItem\nfrom locations.hours import OpeningHours\n\nDAY_MAPPING = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']\n\n\nclass AceHardwareSpider(scrapy.Spider):\n    name = \"ace_hardware\"\n    item_attributes = {'brand': \"Ace Hardware\", 'brand_wikidata': 'Q4672981'}\n    allowed_domains = [\"www.acehardware.com\"]\n    download_delay = 0.7\n    start_urls = (\n        'https://www.acehardware.com/store-directory',\n    )\n    user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'\n\n    def parse_hours(self, lis):\n        o = OpeningHours()\n\n        for day in DAY_MAPPING:\n            d = day.title()[:2]\n\n            if lis[day]['label'] == '0000 - 0000':\n                continue\n\n            o.add_range(d, lis[day]['openTime'], lis[day]['closeTime'])\n        return o.as_opening_hours()\n\n    def parse_store(self, response):\n        store_data = response.xpath('//script[@id=\"data-mz-preload-store\"]/text()').extract_first()\n\n        if not store_data:\n            return\n\n        store_data = json.loads(store_data)\n\n        properties = {\n            'name': store_data['StoreName'],\n            'phone': store_data['Phone'],\n            'addr_full': store_data['StoreAddressLn1'],\n            'city': store_data['StoreCityNm'],\n            'state': store_data['StoreStateCd'],\n            'postcode': store_data['StoreZipCd'],\n            'ref': store_data['StoreNumber'],\n            'website': response.url,\n            'lat': store_data['Latitude'],\n            'lon': store_data['Longitude'],\n        }\n\n        hours = self.parse_hours(store_data['RegularHours'])\n        if hours:\n            properties['opening_hours'] = hours\n\n        yield GeojsonPointItem(**properties)\n\n    def parse(self, response):\n        for store_url in response.css('div.store-directory-list-item').xpath('div/a/@href').extract():\n            yield scrapy.Request(\n                response.urljoin(store_url),\n                callback=self.parse_store,\n            )\n", "path": "locations/spiders/ace_hardware.py"}]}