Repository: alltheplaces/alltheplaces

Issue:
Safelite
https://www.safelite.com/store-locator/store-locations-by-state


Discussion:
Hi @thekuiperbelt Can you pls let me know what needs to be done in this project? 
@Bharadwaja92 Hello, thanks for reaching out. I've labeled issues in the repository that are good options for creating your first spider. The ultimate goal is to scrape the URL linked above (or a better one if you find it) for [location information](https://github.com/alltheplaces/alltheplaces/blob/master/DATA_FORMAT.md) regarding the business. The project is built using [scrapy](https://docs.scrapy.org/en/latest/), so there is more documentation there the can also assist you as well. Feel free to comb through the spiders in the [locations folder](https://github.com/alltheplaces/alltheplaces/tree/master/locations) for guidance on completed spiders, see [university_maryland_medical_system.py](https://github.com/alltheplaces/alltheplaces/blob/43ae0183827f340925555029e26b3810730be268/locations/spiders/university_maryland_medical_system.py) and [credit_agricole.py](https://github.com/alltheplaces/alltheplaces/blob/c0b9dfe7379e8fef192fc319fcebe2b10b352204/locations/spiders/credit_agricole.py) as examples. If you find an issue that you think looks interesting, let me know and I will assign it to you, and feel free to reach out if you have more questions.
I'd like to try to do this one... can I take it?
Go for it!
I haven't been able to complete this one. I ended up submitting another one. If someone else wants to work on it, they can!
I'd like to work on this if it's available
Go for it

Resolving patch:
diff --git a/locations/spiders/safelite.py b/locations/spiders/safelite.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/safelite.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+import scrapy
+from locations.items import GeojsonPointItem
+from locations.hours import OpeningHours, DAYS_FULL
+
+
+class SafeliteSpider(scrapy.Spider):
+    name = "safelite"
+    allowed_domains = ["www.safelite.com"]
+    item_attributes = {"brand": "Safelite", "brand_wikidata": "Q28797369"}
+    start_urls = ("https://www.safelite.com/store-locator/store-locations-by-state",)
+
+    def parse(self, response):
+        state_urls = response.xpath(
+            './/div[@class="state-store-list"]/a/@href'
+        ).extract()
+        for state_url in state_urls:
+            yield scrapy.Request(
+                f"https://www.safelite.com{state_url}", callback=self.parse_location
+            )
+
+    def parse_location(self, response):
+        try:
+            addr_full = (
+                response.xpath('.//div[@class="content-block"]')[1]
+                .xpath(".//p/text()")
+                .extract_first()
+            )
+            addr_full = addr_full.split(",")
+            street_address = ", ".join(addr_full[:-2])
+            city = addr_full[-2].strip()
+            state_postcode = addr_full[-1].strip()
+            state, postcode = state_postcode.strip().split(" ")
+            state = state.strip()
+            addr_full = ", ".join(addr_full)
+            lat = response.xpath(
+                './/div[@class="store-map"]/@data-start-lat'
+            ).extract_first()
+            lon = response.xpath(
+                './/div[@class="store-map"]/@data-start-lon'
+            ).extract_first()
+        except:
+            addr_full = response.xpath(".//h1/text()").extract_first()
+            street_address = None
+            city, state = addr_full.split(",")
+            city = city.strip()
+            state = state.strip()
+            postcode = None
+            lat = None
+            lon = None
+        opening_hours = response.xpath(
+            './/div[@class="shop-container "]/p/text()'
+        ).extract()
+        opening_hours = [timing.strip() for timing in opening_hours]
+        properties = {
+            "ref": response.url,
+            "street_address": street_address,
+            "city": city,
+            "state": state,
+            "postcode": postcode,
+            "opening_hours": self.parse_opening_hours(opening_hours),
+            "website": response.url,
+            "lat": lat,
+            "lon": lon,
+        }
+        yield GeojsonPointItem(**properties)
+
+    def parse_opening_hours(self, timings):
+        timings = [timing for timing in timings if "closed" not in timing.lower()]
+        oh = OpeningHours()
+        for timing in timings:
+            timing = timing.strip()
+            timing = timing.replace(":", ",", 1)
+            days, times = timing.split(",")
+            start_day, end_day = (
+                days.split("-") if len(days.split("-")) == 2 else [days, days]
+            )
+            start_day, end_day = start_day.strip(), end_day.strip()
+            start_time, end_time = times.split("-")
+            start_time = self.parse_timings(start_time.strip())
+            end_time = self.parse_timings(end_time.strip())
+            curr_day_index = DAYS_FULL.index(start_day)
+            while curr_day_index <= DAYS_FULL.index(end_day):
+                curr_day_index += 1
+                oh.add_range(DAYS_FULL[curr_day_index - 1], start_time, end_time)
+
+        return oh.as_opening_hours()
+
+    def parse_timings(self, timing):
+        if timing[-3:] == " AM":
+            timing = timing[:-3]
+            if ":" not in timing:
+                timing = str(timing) + ":00"
+        elif timing[-3:] == " PM":
+            timing = timing[:-3]
+            if ":" in timing:
+                hours, mins = timing.split(":")
+                hours = int(hours) + 12
+                timing = str(hours) + ":" + str(mins)
+            else:
+                hours = int(timing) + 12
+                timing = str(hours) + ":00"
+        return timing


Test patch: