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

Issue:
Poundland spider returning Pep&Co only stores as both Poundland and Pep&Co
Poundland has a clothing brand called "Pep&Co", and typically their larger stores have a "Pep&Co" area inside, looking a bit like a concession in a department store. In their storefinder data, these stores are indicated by a "Pep Shop" flag. The ATP poundland.py spider looks for this, and when it's found generates an additional "Pep&Co" store item in addition to the "Poundland" one.

So far so good. However, it appears that there are also a few (about 14) "Pep&Co" stores that aren't also regular Poundland stores. For example, these can occur in shopping centres, where Poundland is renting two nearby units and using one as a regular Poundland store (without Pep&Co) and the other as a Pep&Co only store. Currently ATP is returning a "Poundland" entry for the  "Pep&Co" only store. Since there's a separate entry in the storefinder for the actual Poundland store, this leads to duplicates.

Here's an example mapped in OSM:
Poundland - https://www.openstreetmap.org/node/11293224534 - CV21 2JT
Pep&Co - https://www.openstreetmap.org/node/11293224520 - CV21 2JS

Another (unmapped example) is two stores with postcode TS8 0TJ.

I think these  "Pep&Co" only stores can be detected by the branch name (stored in item["branch"]) starting with "Pep & Co". I guess we should test for this, and then not yield the final item if it's found. (In case there's any inconsistency in their data, it would probably be good to add the same test to the` if "Pep Shop" in ` line as an alternative. This would ensure that we don't completely drop a branch who's name starts with "Pep&Co" but doesn't have the "Pep shop" flag set.


Discussion:

Resolving patch:
diff --git a/locations/spiders/poundland.py b/locations/spiders/poundland.py
--- a/locations/spiders/poundland.py
+++ b/locations/spiders/poundland.py
@@ -12,7 +12,7 @@ class PoundlandSpider(WoosmapSpider):
     def parse_item(self, item: Feature, feature: dict, **kwargs):
         item["branch"] = item.pop("name")
 
-        if "Pep Shop" in feature["properties"]["tags"]:
+        if "Pep Shop" in feature["properties"]["tags"] or item["branch"].startswith("Pep & Co "):
             pep = item.deepcopy()
 
             pep["ref"] = pep["ref"] + "_pep"
@@ -25,6 +25,9 @@ def parse_item(self, item: Feature, feature: dict, **kwargs):
 
             yield pep
 
+        if item["branch"].startswith("Pep & Co "):
+            return
+
         apply_yes_no(Extras.ATM, item, "ATM" in feature["properties"]["tags"])
         item["extras"]["icestore"] = "yes" if "Ice Store" in feature["properties"]["tags"] else "no"
 


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Anthropologie spider produces transposed coordinates
https://www.alltheplaces.xyz/map/#7.69/-75.171/39.95

![image](https://github.com/alltheplaces/alltheplaces/assets/365751/549c74f8-f0ee-437d-aad5-b6c56cb747fb)

The cause is the upstream data:

https://www.anthropologie.com/stores/rittenhouse-square-philadelphia
![image](https://github.com/alltheplaces/alltheplaces/assets/365751/688ab3b4-18ed-435b-a6e1-733784d4c9a7)

It might be worth doing any of the following:

- Suspend the lat/long from the parser for now
- Contact the company (I'll probably do that shortly) about the bug
- Any kind of high level validations that can check the expected bounds for a scraper, vs the results?




Discussion:
We can switch them in the spider, it's possiby as easy as:
```python
    def pre_process_data(self, ld_data, **kwargs):
        ld_data["geo"]["latitude"], ld_data["geo"]["longitude"] = ld_data["geo"]["longitude"], ld_data["geo"]["latitude"]
```
I can't test right now as my VPN is broken, I'll take a look soon unless someone else does first.

> Contact the company (I'll probably do that shortly) about the bug

I don't have any positive experiences doing that, trying to find the right person is impossible. Good luck though, and if you manage, tell me your secrets! :)

> Any kind of high level validations that can check the expected bounds for a scraper, vs the results?

hmm, not currently.
Well, tried the contact the company route to no avail.

Time to write a tool to grab the Open Corporates ID from the wikidata and poke the CEO:
https://opencorporates.com/companies/us_pa/2077901


Resolving patch:
diff --git a/locations/spiders/anthropologie.py b/locations/spiders/anthropologie.py
--- a/locations/spiders/anthropologie.py
+++ b/locations/spiders/anthropologie.py
@@ -1,5 +1,6 @@
 from scrapy.spiders import SitemapSpider
 
+from locations.items import set_closed
 from locations.structured_data_spider import StructuredDataSpider
 
 
@@ -10,3 +11,17 @@ class AnthropologieSpider(SitemapSpider, StructuredDataSpider):
     sitemap_urls = ["https://www.anthropologie.com/store_sitemap.xml"]
     sitemap_rules = [("/stores/", "parse_sd")]
     requires_proxy = True
+
+    def pre_process_data(self, ld_data, **kwargs):
+        ld_data["geo"]["latitude"], ld_data["geo"]["longitude"] = (
+            ld_data["geo"]["longitude"],
+            ld_data["geo"]["latitude"],
+        )
+
+    def post_process_item(self, item, response, ld_data, **kwargs):
+        item["branch"] = item.pop("name").removeprefix(" - Anthropologie Store")
+
+        if item["branch"].startswith("Closed - ") or item["branch"].endswith(" - Closed"):
+            set_closed(item)
+
+        yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Rotten Robbie
### Brand name

Rotten Robbie

### Wikidata ID

Q87378070

### Store finder url(s)

https://www.rottenrobbie.com/locations-2/


Discussion:
All 37 locations appear to be returned via `https://www.rottenrobbie.com/wp-json/wpgmza/v1/features/base64eJyrVkrLzClJLVKyUqqOUcpNLIjPTIlRsopRMoxRqlWqBQCnUQoG`

Resolving patch:
diff --git a/locations/spiders/rotten_robbie_us.py b/locations/spiders/rotten_robbie_us.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/rotten_robbie_us.py
@@ -0,0 +1,15 @@
+from scrapy import Spider
+
+from locations.categories import Categories
+from locations.dict_parser import DictParser
+
+
+class RottenRobbieUSSpider(Spider):
+    name = "rotten_robbie_us"
+    item_attributes = {"brand": "Rotten Robbie", "brand_wikidata": "Q87378070", "extras": Categories.FUEL_STATION.value}
+    start_urls = [
+        "https://www.rottenrobbie.com/wp-json/wpgmza/v1/features/base64eJyrVkrLzClJLVKyUqqOUcpNLIjPTIlRsopRMoxRqlWqBQCnUQoG"
+    ]
+
+    def parse(self, response, **kwargs):
+        yield from map(DictParser.parse, response.json()["markers"])


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Aubert (FR) (shop/baby_goods) (Evermaps Storefinder)
### Brand name

Aubert

French baby goods retail chain

### Wikidata ID

Q62964657
https://www.wikidata.org/wiki/Q62964657
https://www.wikidata.org/wiki/Special:EntityData/Q62964657.json

### Store finder url(s)

https://magasins.aubert.com/fr/france-FR

Official Url(s): https://www.aubert.com/
- [x] I have run the below and if anything in generated, added it.
- [x] I have manually checked for a storefinder
- [x] This is not a manufacturer listing outlets operating under their own brands / this will not create duplicates
`pipenv run scrapy sf --brand-wikidata=Q62964657 https://www.aubert.com/`
```
```



Discussion:

Resolving patch:
diff --git a/locations/spiders/aubert_fr.py b/locations/spiders/aubert_fr.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/aubert_fr.py
@@ -0,0 +1,23 @@
+import scrapy
+from scrapy.spiders import SitemapSpider
+
+from locations.categories import Categories
+from locations.structured_data_spider import StructuredDataSpider
+
+
+class AubertFRSpider(SitemapSpider, StructuredDataSpider):
+    name = "aubert_fr"
+    item_attributes = {"brand": "Aubert", "brand_wikidata": "Q62964657", "extras": Categories.SHOP_BABY_GOODS.value}
+    allowed_domains = [
+        "magasins.aubert.com",
+    ]
+    sitemap_urls = ["https://magasins.aubert.com/sitemap_geo.xml"]
+    sitemap_rules = [
+        (r"magasins\.aubert\.com/.*/.*/.*/results$", "discover_store_details"),
+    ]
+
+    def discover_store_details(self, response):
+        urls = response.xpath('//a[contains(@href, "details")]/@href').extract()
+        for url in urls:
+            url = url.replace("\r\n", "")
+            yield scrapy.Request(response.urljoin(url), callback=self.parse_sd)


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Thirsty Camel (AU) (shop/alcohol) (350 stores)
### Brand name

Thirsty Camel

### Wikidata ID

Q113503937

### Store finder url(s)

https://www.thirstycamel.com.au/store-locator


Discussion:
All stores as JSON response: https://api.beta.thirstycamel.com.au/stores/all

Resolving patch:
diff --git a/locations/spiders/thirsty_camel_au.py b/locations/spiders/thirsty_camel_au.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/thirsty_camel_au.py
@@ -0,0 +1,39 @@
+from scrapy import Spider
+from scrapy.http import JsonRequest
+
+from locations.categories import Categories
+from locations.dict_parser import DictParser
+from locations.hours import OpeningHours
+
+
+class ThirstyCamelAUSpider(Spider):
+    name = "thirsty_camel_au"
+    item_attributes = {
+        "brand": "Thirsty Camel",
+        "brand_wikidata": "Q113503937",
+        "extras": Categories.SHOP_ALCOHOL.value,
+    }
+    allowed_domains = ["api.beta.thirstycamel.com.au"]
+    start_urls = ["https://api.beta.thirstycamel.com.au/stores/all"]
+
+    def start_requests(self):
+        for url in self.start_urls:
+            yield JsonRequest(url=url)
+
+    def parse(self, response):
+        for location in response.json():
+            if not location.get("published") or location.get("deletedAt"):
+                continue
+            item = DictParser.parse(location)
+            item["geometry"] = location["point"]
+            item["website"] = "https://www.thirstycamel.com.au/stores/" + location["slug"]
+            item["opening_hours"] = OpeningHours()
+            hours_string = ""
+            for day_name, day_hours in location["hours"].items():
+                if not day_hours:
+                    continue
+                open_time = day_hours[0]
+                close_time = day_hours[1]
+                hours_string = f"{hours_string} {day_name}: {open_time} - {close_time}"
+            item["opening_hours"].add_ranges_from_string(hours_string)
+            yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Prestons (ZA) (shop/alcohol) (22 stores)
### Brand name

Prestons

### Wikidata ID

Q116861728

### Store finder url(s)

https://prestonsliquors.co.za/wp-admin/admin-ajax.php?action=store_search&autoload=1


Discussion:

Resolving patch:
diff --git a/locations/spiders/prestons_za.py b/locations/spiders/prestons_za.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/prestons_za.py
@@ -0,0 +1,22 @@
+from html import unescape
+
+from locations.categories import Categories
+from locations.storefinders.wp_store_locator import WPStoreLocatorSpider
+
+
+class PrestonsZASpider(WPStoreLocatorSpider):
+    name = "prestons_za"
+    item_attributes = {
+        "brand": "Prestons",
+        "brand_wikidata": "Q116861728",
+        "extras": Categories.SHOP_ALCOHOL.value,
+    }
+    allowed_domains = ["prestonsliquors.co.za"]
+    time_format = "%I:%M %p"
+
+    def parse_item(self, item, location):
+        item["name"] = unescape(item["name"])
+        item["street_address"] = location.get("address")
+        item.pop("addr_full", None)
+        item["city"] = location.get("address2")
+        yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Liquorland (NZ) (shop/alcohol) (169 stores)
### Brand name

Liquorland

### Wikidata ID

Q110295342

### Store finder url(s)

https://www.liquorland.co.nz/store/GetStoreLocationsJsonFileForRegion?regionid=0


Discussion:

Resolving patch:
diff --git a/locations/spiders/liquorland_nz.py b/locations/spiders/liquorland_nz.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/liquorland_nz.py
@@ -0,0 +1,37 @@
+import re
+
+from scrapy import Selector, Spider
+from scrapy.http import JsonRequest
+
+from locations.categories import Categories
+from locations.dict_parser import DictParser
+from locations.hours import OpeningHours
+
+
+class LiquorlandNZSpider(Spider):
+    name = "liquorland_nz"
+    item_attributes = {"brand": "Liquorland", "brand_wikidata": "Q110295342", "extras": Categories.SHOP_ALCOHOL.value}
+    allowed_domains = ["www.liquorland.co.nz"]
+    start_urls = ["https://www.liquorland.co.nz/store/GetStoreLocationsJsonFileForRegion?regionid=0"]
+
+    def start_requests(self):
+        for url in self.start_urls:
+            yield JsonRequest(url=url)
+
+    def parse(self, response):
+        for location in response.json()["features"]:
+            item = DictParser.parse(location["properties"])
+            item["ref"] = location["properties"]["url"].split("?StoreId=", 1)[1]
+            item["geometry"] = location["geometry"]
+            item["addr_full"] = re.sub(
+                r"\s?,(?=[^\s])",
+                ", ",
+                re.sub(r"\s+", " ", location["properties"]["address"].replace("<br>", ",")).strip(),
+            )
+            item["website"] = "https://www.liquorland.co.nz" + location["properties"]["url"]
+            hours_string = " ".join(
+                filter(None, map(str.strip, Selector(text=location["properties"]["hours"]).xpath("//text()").getall()))
+            )
+            item["opening_hours"] = OpeningHours()
+            item["opening_hours"].add_ranges_from_string(hours_string)
+            yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
LiquorShop (ZA) (shop/alcohol) (210 stores)
### Brand name

LiquorShop

### Wikidata ID

Q5089126

### Store finder url(s)

https://www.liquorshop.co.za/occ/v2/liquorshop/stores?fields=stores(name%2CdisplayName%2CformattedDistance%2CopeningHours(weekDayOpeningList(FULL)%2CspecialDayOpeningList(FULL))%2CgeoPoint(latitude%2Clongitude)%2Caddress(line1%2Cline2%2Ctown%2Cregion(FULL)%2CpostalCode%2Cphone%2Ccountry%2Cemail)%2C%20features)%2Cpagination(DEFAULT)%2Csorts(DEFAULT)&query=&pageSize=-1&lang=en&curr=ZAR&region=ls_za



Discussion:
Note many of these are `located_in` Checkers (supermarket): https://github.com/alltheplaces/alltheplaces/issues/7016

Resolving patch:
diff --git a/locations/spiders/liquorshop_za.py b/locations/spiders/liquorshop_za.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/liquorshop_za.py
@@ -0,0 +1,43 @@
+from scrapy import Spider
+from scrapy.http import JsonRequest
+
+from locations.categories import Categories
+from locations.dict_parser import DictParser
+from locations.hours import OpeningHours
+
+
+class LiquorShopZASpider(Spider):
+    name = "liquorshop_za"
+    item_attributes = {"brand": "LiquorShop", "brand_wikidata": "Q5089126", "extras": Categories.SHOP_ALCOHOL.value}
+    allowed_domains = ["www.liquorshop.co.za"]
+    start_urls = [
+        "https://www.liquorshop.co.za/occ/v2/liquorshop/stores?fields=stores(name%2CdisplayName%2CformattedDistance%2CopeningHours(weekDayOpeningList(FULL)%2CspecialDayOpeningList(FULL))%2CgeoPoint(latitude%2Clongitude)%2Caddress(line1%2Cline2%2Ctown%2Cregion(FULL)%2CpostalCode%2Cphone%2Ccountry%2Cemail)%2C%20features)%2Cpagination(DEFAULT)%2Csorts(DEFAULT)&query=&pageSize=-1&lang=en&curr=ZAR&region=ls_za"
+    ]
+
+    def start_requests(self):
+        for url in self.start_urls:
+            yield JsonRequest(url)
+
+    def parse(self, response):
+        for location in response.json()["stores"]:
+            item = DictParser.parse(location)
+            item["ref"] = location["name"].replace("LS ", "").replace("LS", "").strip()
+            item["name"] = location["displayName"]
+            item["addr_full"] = ", ".join(
+                filter(None, [location["address"].get("line1"), location["address"].get("line2")])
+            )
+            if location["address"].get("region"):
+                item["state"] = location["address"]["region"]["name"]
+            item["phone"] = location["address"].get("phone")
+            item["website"] = "https://www.liquorshop.co.za/store-finder/country/ZA/" + item["ref"]
+            item["opening_hours"] = OpeningHours()
+            for day_hours in location["openingHours"]["weekDayOpeningList"]:
+                if day_hours["closed"]:
+                    continue
+                item["opening_hours"].add_range(
+                    day_hours["weekDay"],
+                    day_hours["openingTime"]["formattedHour"],
+                    day_hours["closingTime"]["formattedHour"],
+                    "%I:%M %p",
+                )
+            yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Bike Totaal (shop/bicycle)
### Brand name

Bike Totaal

Dutch bicycle retail chain

### Wikidata ID

Q123536506
https://www.wikidata.org/wiki/Q123536506
https://www.wikidata.org/wiki/Special:EntityData/Q123536506.json

### Store finder url(s)

https://www.biketotaal.nl/fietsenwinkels (js blob - script id="NEXT_DATA" type="application/json")


Official Url(s): https://www.biketotaal.nl/
- [x] I have run the below and if anything in generated, added it.
- [ ] 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=Q123536506 https://www.biketotaal.nl/`
```
```





Discussion:
147 (seemingly all) results returned with a POST to:

```https://txqn75ks6l-2.algolianet.com/1/indexes/*/queries?x-algolia-agent=Algolia for JavaScript (4.20.0); Browser (lite); instantsearch.js (4.60.0); react (18.3.0-canary-09285d5a7-20230925); react-instantsearch (7.3.0); react-instantsearch-core (7.3.0); next.js (13.5.4); JS Helper (3.15.0)&x-algolia-api-key=7b238970f2f25648d04318c495206253&x-algolia-application-id=TXQN75KS6L```

With search parameters:
```{"requests":[{"indexName":"live_nl_stores","params":"analytics=true&aroundLatLng=52.7712587%2C6.235939999999999&aroundRadius=1000000&facets=%5B%5D&getRankingInfo=true&hitsPerPage=1000&tagFilters="}]}```

Resolving patch:
diff --git a/locations/spiders/bike_totaal_nl.py b/locations/spiders/bike_totaal_nl.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/bike_totaal_nl.py
@@ -0,0 +1,16 @@
+from scrapy.spiders import SitemapSpider
+
+from locations.categories import Categories
+from locations.structured_data_spider import StructuredDataSpider
+
+
+class BikeTotaalNLSpider(SitemapSpider, StructuredDataSpider):
+    name = "bike_totaal_nl"
+    item_attributes = {"brand": "Bike Totaal", "brand_wikidata": "Q123536506", "extras": Categories.SHOP_BICYCLE.value}
+    allowed_domains = ["www.biketotaal.nl"]
+    sitemap_urls = ["https://www.biketotaal.nl/sitemap.xml"]
+    sitemap_rules = [(r"^https:\/\/www\.biketotaal\.nl\/fietsenwinkel\/bike-totaal-[^/]+$", "parse_sd")]
+
+    def post_process_item(self, item, response, ld_data):
+        item.pop("facebook", None)
+        yield item
diff --git a/locations/structured_data_spider.py b/locations/structured_data_spider.py
--- a/locations/structured_data_spider.py
+++ b/locations/structured_data_spider.py
@@ -152,6 +152,7 @@ class StructuredDataSpider(Spider):
         "AutoPartsStore",
         "GasStation",
         "LiquorStore",
+        "BikeStore",
     ]
     search_for_email = True
     search_for_phone = True


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Dirck III (NL) (shop/alcohol) (94 stores)
### Brand name

Dirck III

### Wikidata ID

Q109188079

### Store finder url(s)

https://www.dirckiii.nl/storelocator/index/ajax/


Discussion:

Resolving patch:
diff --git a/locations/spiders/dirck_iii_nl.py b/locations/spiders/dirck_iii_nl.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/dirck_iii_nl.py
@@ -0,0 +1,39 @@
+import json
+
+from scrapy import Spider
+from scrapy.http import JsonRequest
+
+from locations.categories import Categories
+from locations.dict_parser import DictParser
+from locations.hours import OpeningHours
+
+
+class DirckIIINLSpider(Spider):
+    name = "dirck_iii_nl"
+    item_attributes = {"brand": "Dirck III", "brand_wikidata": "Q109188079", "extras": Categories.SHOP_ALCOHOL.value}
+    allowed_domains = ["www.dirckiii.nl"]
+    start_urls = ["https://www.dirckiii.nl/storelocator/index/ajax/"]
+
+    def start_requests(self):
+        for url in self.start_urls:
+            yield JsonRequest(url=url)
+
+    def parse(self, response):
+        for location in response.json()["data"]:
+            if not location["is_active"]:
+                continue
+
+            item = DictParser.parse(location)
+            item["ref"] = location["storelocator_id"]
+            item["street_address"] = ", ".join(location["address"][1:])
+
+            item["opening_hours"] = OpeningHours()
+            hours_dict = json.loads(location["storetime"])
+            for record in hours_dict:
+                item["opening_hours"].add_range(
+                    record["days"],
+                    "{}:{}".format(record["open_hour"], record["open_minute"]),
+                    "{}:{}".format(record["close_hour"], record["close_minute"]),
+                )
+
+            yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Combi
https://www.combi.de/UnsereMaerkte/Marktsuche


Discussion:

Resolving patch:
diff --git a/locations/spiders/combi_de.py b/locations/spiders/combi_de.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/combi_de.py
@@ -0,0 +1,44 @@
+import re
+
+from scrapy.http import Response
+from scrapy.linkextractors import LinkExtractor
+from scrapy.spiders import CrawlSpider, Rule
+
+from locations.hours import DAYS_DE, OpeningHours
+from locations.items import Feature
+
+
+class CombiDESpider(CrawlSpider):
+    name = "combi_de"
+    item_attributes = {"brand": "Combi", "brand_wikidata": "Q1113618"}
+
+    start_urls = ["https://www.combi.de/unseremaerkte/marktsuche"]
+    rules = [Rule(LinkExtractor(allow="/marktauswahl/"), follow=False, callback="parse")]
+
+    address_pattern = r"\W*?^(\w.+\w)\W*,\W*(\d{5})\W*(\w.+\w)\W*?$"
+
+    def parse(self, response: Response):
+        address = response.xpath("//address/text()").get().strip()
+        street_address, postcode, city = re.findall(self.address_pattern, address)[0]
+        opening_hours = OpeningHours()
+        opening_hours.add_ranges_from_string(
+            response.xpath('string(//table[@class="opening-hours__table"])').get(), days=DAYS_DE
+        )
+        image_link = response.xpath('//img[contains(@src, "Marktbilder")]/@src').get()
+        image_link = "https://www.combi.de" + image_link if image_link else None
+        yield Feature(
+            ref=response.url.split("/")[-1],
+            website=response.url,
+            street_address=street_address,
+            lat=re.findall(r"var lat = Number\('(.+?)'\)", response.text, re.DOTALL)[0],
+            lon=re.findall(r"var lon = Number\('(.+?)'\)", response.text, re.DOTALL)[0],
+            postcode=postcode,
+            city=city,
+            addr_full=address,
+            opening_hours=opening_hours,
+            phone=response.xpath('string(//div[contains(@class, "contact__entry--phone")])')
+            .get()
+            .strip()
+            .replace("/", ""),
+            image=image_link,
+        )


Test patch: