Repository: alltheplaces/alltheplaces

Issue:
Cycle Spot (JP) (shop/bicycle) (WPStoreLocator) (More than 100 bicycle specialty stores in the Kanto area)
### Brand name

Cycle Spot

Japanese cycling company

### Wikidata ID

Q93620124
https://www.wikidata.org/wiki/Q93620124
https://www.wikidata.org/wiki/Special:EntityData/Q93620124.json

### Store finder url(s)

https://www.cyclespot.net/shops-list

Official Url(s): https://www.cyclespot.net/
- [x] I have run the below and if anything in generated, added it.
- [x] I have manually checked for a storefinder
- [ ] This is not a manufacturer listing outlets operating under their own brands / this will not create duplicates
`pipenv run scrapy sf --brand-wikidata=Q93620124 https://www.cyclespot.net/`
```

from locations.storefinders.wp_store_locator import WPStoreLocatorSpider


class CycleSpotJPSpider(WPStoreLocatorSpider):
        name = "_jp"
        item_attributes = {
                "brand_wikidata": "Q93620124",
                "brand": "サイクルスポット",
        }
        allowed_domains = [
                "www.cyclespot.net",
        ]
```


Discussion:
Requires a different approach:
https://github.com/alltheplaces/alltheplaces/pull/7132
Can alternatively be implemented with a CrawlSpider starting at https://www.cyclespot.net/shops-list plus HTML extraction of each location page (example: https://www.cyclespot.net/shops/cyclespotooi/)

Resolving patch:
diff --git a/locations/spiders/cycle_spot_jp.py b/locations/spiders/cycle_spot_jp.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/cycle_spot_jp.py
@@ -0,0 +1,49 @@
+import re
+
+from scrapy.linkextractors import LinkExtractor
+from scrapy.spiders import CrawlSpider, Rule
+
+from locations.categories import Categories
+from locations.google_url import extract_google_position
+from locations.items import Feature
+
+
+class CycleSpotJPSpider(CrawlSpider):
+    name = "cycle_spot_jp"
+    item_attributes = {"brand": "Cycle Spot", "brand_wikidata": "Q93620124", "extras": Categories.SHOP_BICYCLE.value}
+    allowed_domains = ["www.cyclespot.net"]
+    start_urls = ["https://www.cyclespot.net/shops-list"]
+    rules = [
+        Rule(
+            LinkExtractor(
+                allow=r"^https:\/\/www\.cyclespot\.net\/[^/]+\/?$", restrict_xpaths='//div[@id="prefecture"]//a'
+            ),
+            callback=None,
+            follow=True,
+        ),
+        Rule(LinkExtractor(allow=r"^https:\/\/www\.cyclespot\.net\/shops\/[^/]+\/?$"), callback="parse", follow=False),
+    ]
+
+    def parse(self, response):
+        if not response.xpath('//div[@class="cs_wpsl_shopinfomation"]'):
+            # Some location pages are blank and need skipping
+            return
+        properties = {
+            "ref": response.url.split("/(?!$)")[-1],
+            "name": response.xpath('//div[@class="cs_wpsl_shopinfomation"]/p[1]/b/text()').get().strip(),
+            "addr_full": re.sub(
+                r"\s+", " ", response.xpath('//div[@class="cs_wpsl_shopinfomation"]/p[2]/text()').get().strip()
+            ),
+            "phone": re.search(
+                r"([\d\-.]{8,})",
+                " ".join(
+                    response.xpath(
+                        '//div[@class="cs_wpsl_shopinfomation"]/p/i[contains(@class, "fa-phone")]/parent::p//text()'
+                    ).getall()
+                ),
+            ).group(1),
+            "website": response.url,
+        }
+        extract_google_position(properties, response)
+
+        yield Feature(**properties)


Test patch: