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

Issue:
Huddle House
https://locations.huddlehouse.com/index.html


Discussion:
SitemapSpider of `https://www.huddlehouse.com/sitemap.xml` (filtering on `/locations/[a-z]{2}/[\w\-]+/{slug}/`) + StructuredDataSpider (LD+JSON)

Resolving patch:
diff --git a/locations/spiders/huddle_house_us.py b/locations/spiders/huddle_house_us.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/huddle_house_us.py
@@ -0,0 +1,52 @@
+from scrapy import Spider
+from scrapy.http import JsonRequest
+
+from locations.categories import Extras, PaymentMethods, apply_yes_no
+from locations.dict_parser import DictParser
+from locations.geo import point_locations
+from locations.hours import OpeningHours
+
+
+class HuddleHouseUSSpider(Spider):
+    name = "huddle_house_us"
+    item_attributes = {"brand": "Huddle House", "brand_wikidata": "Q5928324"}
+    allowed_domains = ["www.huddlehouse.com"]
+    start_urls = ["https://www.huddlehouse.com/api"]
+    download_delay = 0.2
+
+    def start_requests(self):
+        for url in self.start_urls:
+            for lat, lon in point_locations("us_centroids_iseadgg_48km_radius.csv"):
+                yield JsonRequest(
+                    url=f"{url}?lat={lat}&lng={lon}&radius=50",
+                    headers={"path": "locations/search", "client_type": "web"},
+                )
+
+    def parse(self, response):
+        if not response.json().get("locations"):
+            return
+        # A maximum of 25 locations are returned at once. The search radius is
+        # set to avoid receiving 25 locations in a single response. If 25
+        # locations were to be returned, it is a sign that some locations have
+        # most likely been truncated.
+        for location in response.json()["locations"]:
+            if not location.get("available"):
+                continue
+            item = DictParser.parse(location)
+            item["street_address"] = item.pop("addr_full", None)
+            if location.get("path"):
+                item["website"] = "https://www.huddlehouse.com/locations/" + location["path"]
+            item["opening_hours"] = OpeningHours()
+            for day_hours in location["hours"]:
+                if day_hours["closed"]:
+                    continue
+                item["opening_hours"].add_range(day_hours["day"].title(), day_hours["open"], day_hours["close"])
+            apply_yes_no(Extras.DRIVE_THROUGH, item, location["services"].get("drivethru"), False)
+            apply_yes_no(Extras.DELIVERY, item, location["services"].get("delivery"), False)
+            apply_yes_no(
+                PaymentMethods.AMERICAN_EXPRESS, item, "American Express" in location["payments"]["cardtypes"], False
+            )
+            apply_yes_no(PaymentMethods.DISCOVER_CARD, item, "Discover" in location["payments"]["cardtypes"], False)
+            apply_yes_no(PaymentMethods.MASTER_CARD, item, "MasterCard" in location["payments"]["cardtypes"], False)
+            apply_yes_no(PaymentMethods.VISA, item, "Visa" in location["payments"]["cardtypes"], False)
+            yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
dino
https://marketdino.pl/


Discussion:

Resolving patch:
diff --git a/locations/spiders/dino_pl.py b/locations/spiders/dino_pl.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/dino_pl.py
@@ -0,0 +1,48 @@
+import json
+import re
+
+from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+from scrapy import Request, Spider
+
+from locations.dict_parser import DictParser
+from locations.hours import OpeningHours
+
+
+class DinoPLSpider(Spider):
+    name = "dino_pl"
+    item_attributes = {"brand": "Dino", "brand_wikidata": "Q11694239"}
+    allowed_domains = ["marketdino.pl", "api-dino.appchance.shop"]
+
+    def start_requests(self):
+        yield Request(
+            url="https://marketdino.pl/external/map/_next/static/chunks/pages/index-d599ac5e0a5fa37c.js",
+            callback=self.parse_decryption_params,
+        )
+
+    def parse_decryption_params(self, response):
+        if m := re.search(r'\.from\("([0-9a-f]{64})","hex"\)', response.text):
+            key = m.group(1)
+        if m := re.search(r'\.from\("([0-9a-f]{32})","hex"\)', response.text):
+            iv = m.group(1)
+        yield Request(
+            url="https://api-dino.appchance.shop/api/v1/dino_content/geofile/",
+            meta={"key": key, "iv": iv},
+            callback=self.parse_encrypted_geojson,
+        )
+
+    def parse_encrypted_geojson(self, response):
+        aesgcm = AESGCM(bytes.fromhex(response.meta["key"]))
+        geojson = json.loads(
+            aesgcm.decrypt(bytes.fromhex(response.meta["iv"]), bytes.fromhex(response.text), None).decode("utf-8")
+        )
+        for location in geojson["features"]:
+            if location["properties"]["status"] != "MARKET OTWARTY":  # "MARKET OPEN"
+                continue
+            item = DictParser.parse(location["properties"])
+            item["geometry"] = location["geometry"]
+            item["opening_hours"] = OpeningHours()
+            item["opening_hours"].add_days_range(
+                ["Mo", "Tu", "We", "Th", "Fr", "Sa"], *location["properties"]["weekHours"].split("-", 1)
+            )
+            item["opening_hours"].add_range("Su", *location["properties"]["sundayHours"].split("-", 1))
+            yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
name=Bankomat should not be added for amenity=atm
It is like `name=ATM`

The same goes for `name=Wpłatomat` (for ATM accepting cash)

https://www.alltheplaces.xyz/map/#16.82/50.072257/20.036549

![screen](https://github.com/alltheplaces/alltheplaces/assets/899988/404afa65-095e-42d7-8c58-0c8d77335426)



Discussion:

Resolving patch:
diff --git a/locations/spiders/santander_pl.py b/locations/spiders/santander_pl.py
--- a/locations/spiders/santander_pl.py
+++ b/locations/spiders/santander_pl.py
@@ -39,6 +39,9 @@ def parse_item(ref: str, data: dict, category) -> Feature:
                 start_time, end_time = hours.split("-")
                 item["opening_hours"].add_range(DAYS[int(day) - 2], start_time.strip(), end_time.strip())
 
+        if category == Categories.ATM:
+            item["name"] = None
+
         apply_category(category, item)
 
         return item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Pepco Europe
### Brand name

Pepco

### Wikidata ID

Q11815580

### Store finder url(s)

https://pepco.eu/find-store/

The coordinates can be retrieved from the map's json data (but no other information). For opening hours and store names, HTML parsing is required.


Discussion:
Only the first 50 load in the HTML, I can't get more to load, even with the "show more" button in a browser.

Resolving patch:
diff --git a/locations/spiders/pepco.py b/locations/spiders/pepco.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/pepco.py
@@ -0,0 +1,25 @@
+import json
+
+from scrapy import Spider
+
+from locations.items import Feature
+
+
+class PepcoSpider(Spider):
+    name = "pepco"
+    item_attributes = {"brand": "Pepco", "brand_wikidata": "Q11815580"}
+    start_urls = ["https://pepco.eu/find-store/"]
+
+    def parse(self, response, **kwargs):
+        for location in json.loads(response.xpath("//@shops-map-markers").get()):
+            yield Feature(
+                ref=location["shop_id"],
+                lat=location["coordinates"]["lat"],
+                lon=location["coordinates"]["lng"],
+                name=response.xpath(
+                    '//div[@shops-map-marker-anchor="{}"]//@data-shop-name'.format(location["shop_id"])
+                ).get(),
+                addr_full=response.xpath(
+                    '//div[@shops-map-marker-anchor="{}"]//@data-shop-address'.format(location["shop_id"])
+                ).get(),
+            )


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Castorama - Poland
### Brand name

Castorama

### Wikidata ID

Q966971

### Store finder url(s)

https://www.castorama.pl/informacje/sklepy


Discussion:

Resolving patch:
diff --git a/locations/spiders/castorama_pl.py b/locations/spiders/castorama_pl.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/castorama_pl.py
@@ -0,0 +1,18 @@
+from scrapy.linkextractors import LinkExtractor
+from scrapy.spiders import CrawlSpider, Rule
+
+from locations.structured_data_spider import StructuredDataSpider
+
+
+class CastoramaPLSpider(CrawlSpider, StructuredDataSpider):
+    name = "castorama_pl"
+    item_attributes = {"brand": "Castorama", "brand_wikidata": "Q966971"}
+    start_urls = ["https://www.castorama.pl/informacje/sklepy"]
+    rules = [Rule(LinkExtractor(allow=[r"/sklepy/[-\w]+.html$"]), callback="parse_sd")]
+    wanted_types = ["HardwareStore"]
+    time_format = "%H:%M:%S Europe/Warsaw"
+    search_for_facebook = False
+    search_for_email = False
+
+    def pre_process_data(self, ld_data, **kwargs):
+        ld_data["image"] = None


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Lewiatan
### Brand name

Lewiatan

### Wikidata ID

Q11755396

### Store finder url(s)

https://www.lewiatan.pl/znajdz-sklep


Discussion:

Resolving patch:
diff --git a/locations/spiders/lewiatan_pl.py b/locations/spiders/lewiatan_pl.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/lewiatan_pl.py
@@ -0,0 +1,20 @@
+import html
+
+from scrapy import Spider
+
+from locations.dict_parser import DictParser
+
+
+class LewiatanPLSpider(Spider):
+    name = "lewiatan_pl"
+    item_attributes = {"brand": "Lewiatan", "brand_wikidata": "Q11755396"}
+    start_urls = ["https://www.lewiatan.pl/api/stores"]
+
+    def parse(self, response, **kwargs):
+        for location in response.json()["data"]:
+            item = DictParser.parse(location)
+            item["extras"]["operator"] = html.unescape(item.pop("name"))
+            item["street_address"] = item.pop("addr_full")
+            item["website"] = response.urljoin(location["url"])
+
+            yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Rossmann Poland (1712 POI, rossmann_de spider may be a good base)
### Brand name

Rossmann

### Wikidata ID

Q316004

### Store finder url(s)

https://www.rossmann.pl/drogerie

https://github.com/alltheplaces/alltheplaces/blob/master/locations/spiders/rossmann_de.py


Discussion:
https://www.rossmann.pl/sitemap_shops.xml

```
pipenv run scrapy sd 'https://www.rossmann.pl/drogerie/Drogeria-Rossmann-handlowa-1-b,zawadzkie,16,1664'
{'city': 'Polska',
 'email': None,
 'extras': {},
 'lat': 50.604221,
 'lon': 18.486944,
 'name': 'Rossmann',
 'opening_hours': 'Mo-Fr 08:30-20:30; Sa 08:00-18:00',
 'phone': None,
 'postcode': '47-120',
 'ref': 'https://www.rossmann.pl/drogerie/Drogeria-Rossmann-handlowa-1-b,zawadzkie,16,1664',
 'state': 'PL',
 'street_address': 'ul. Handlowa 1 b',
 'website': 'https://www.rossmann.pl/drogerie/Drogeria-Rossmann-handlowa-1-b,zawadzkie,16,1664'}
```



Resolving patch:
diff --git a/locations/spiders/rossmann_pl.py b/locations/spiders/rossmann_pl.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/rossmann_pl.py
@@ -0,0 +1,14 @@
+from scrapy.spiders import SitemapSpider
+
+from locations.structured_data_spider import StructuredDataSpider
+
+
+class RossmannPLSpider(SitemapSpider, StructuredDataSpider):
+    name = "rossmann_pl"
+    item_attributes = {"brand": "Rossmann", "brand_wikidata": "Q316004"}
+    sitemap_urls = ["https://www.rossmann.pl/sitemap_shops.xml"]
+    sitemap_rules = [("/drogerie/Drogeria-Rossmann-", "parse_sd")]
+
+    def post_process_item(self, item, response, ld_data):
+        item["name"] = ld_data["description"]
+        yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Orlen amenity=parcel_locker (9699 POIs)
### Brand name

Orlen

### Wikidata ID

Q110457879

### Store finder url(s)

https://www.orlenpaczka.pl/pokaz-punkty/

Note that Orlen fuel station spider exists already


Discussion:
I think the brand is meant to be Q110457879 (Orlen Paczka) instead of Q971649 (Orlen). Looks simple to implement as all results returned with a single request to `https://ruch-osm.sysadvisors.pl/api.php`
switched to Q110457879 - I was unaware that someone bothered to consider it as a separate brand

Resolving patch:
diff --git a/locations/hours.py b/locations/hours.py
--- a/locations/hours.py
+++ b/locations/hours.py
@@ -231,15 +231,33 @@
 DAYS_PL = {
     "Pn": "Mo",
     "Po": "Mo",
+    "Pon": "Mo",
+    "Poniedziałek": "Mo",
     "Wt": "Tu",
+    "Wto": "Tu",
+    "Wtorek": "Tu",
     "Śr": "We",
+    "Sr": "We",
+    "Sro": "We",
+    "Śro": "We",
+    "Środa": "We",
     "Cz": "Th",
+    "Czw": "Th",
+    "Czwartek": "Th",
     "Pt": "Fr",
     "Pi": "Fr",
+    "Pia": "Fr",
+    "Piątek": "Fr",
+    "Piątek": "Fr",
     "Sb": "Sa",
     "So": "Sa",
+    "Sob": "Sa",
+    "Sobota": "Sa",
     "Nd": "Su",
     "Ni": "Su",
+    "Nie": "Su",
+    "Niedz": "Su",
+    "Niedzela": "Su",
 }
 DAYS_PT = {
     # "Se": "Mo",
diff --git a/locations/spiders/orlen_paczka_pl.py b/locations/spiders/orlen_paczka_pl.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/orlen_paczka_pl.py
@@ -0,0 +1,36 @@
+from scrapy import Request, Spider
+
+from locations.categories import Categories, apply_category
+from locations.hours import DAYS_PL, OpeningHours
+from locations.items import Feature
+
+
+class OrlenPaczkaPLSpider(Spider):
+    name = "orlen_paczka_pl"
+    item_attributes = {"brand": "Orlen Paczka", "brand_wikidata": "Q110457879"}
+    start_urls = ["https://ruch-osm.sysadvisors.pl/api.php"]
+    allowed_domains = ["ruch-osm.sysadvisors.pl"]
+
+    def start_requests(self):
+        for url in self.start_urls:
+            yield Request(
+                url=url,
+                body='{"a":"f","s":0}',
+                headers={"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
+                method="POST",
+            )
+
+    def parse(self, response):
+        for location in response.json()["pts"]:
+            properties = {
+                "ref": location["p"],
+                "lat": location["la"],
+                "lon": location["lo"],
+                "street_address": location["a_al"],
+                "city": location["a_c"],
+                "postcode": location["a_pc"],
+                "opening_hours": OpeningHours(),
+            }
+            properties["opening_hours"].add_ranges_from_string(location["o"], days=DAYS_PL)
+            apply_category(Categories.PARCEL_LOCKER, properties)
+            yield Feature(**properties)


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Delikatesy Centrum (1471 POIs)
### Brand name

Delikatesy Centrum

### Wikidata ID

Q11693824

### Store finder url(s)

https://www.delikatesy.pl/sklepy - note that one in South America, it is clearly misplaced


Discussion:
All locations returned via `https://www.delikatesy.pl/_next/data/8y1sYoIBRkA8Q7yTv3U32/sklepy.json`

There is a possibility that 8y1.. string changes regularly.

Resolving patch:
diff --git a/locations/spiders/delikatesy_centrum_pl.py b/locations/spiders/delikatesy_centrum_pl.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/delikatesy_centrum_pl.py
@@ -0,0 +1,30 @@
+from scrapy import Spider
+from scrapy.http import JsonRequest
+
+from locations.dict_parser import DictParser
+from locations.hours import OpeningHours
+
+
+class DelikatesyCentrumPLSpider(Spider):
+    name = "delikatesy_centrum_pl"
+    item_attributes = {"brand": "Delikatesy Centrum", "brand_wikidata": "Q11693824"}
+    start_urls = ["https://www.delikatesy.pl/_next/data/8y1sYoIBRkA8Q7yTv3U32/sklepy.json"]
+    allowed_domains = ["www.delikatesy.pl"]
+
+    def start_requests(self):
+        for url in self.start_urls:
+            yield JsonRequest(url=url)
+
+    def parse(self, response):
+        for location in response.json()["pageProps"]["shops"]:
+            item = DictParser.parse(location)
+            item["ref"] = location["shop_code"]
+            item["name"] = location["name"].split("\\", 1)[0].strip()
+            item["lat"] = location["address"]["lat"]
+            item["lon"] = location["address"]["lon"]
+            item["opening_hours"] = OpeningHours()
+            for day_hours in location["opening_hours"]:
+                if not day_hours["hours"] or day_hours["hours"] == "czynne":
+                    continue
+                item["opening_hours"].add_range(day_hours["day"].title(), *day_hours["hours"].split("-", 1), "%H:%M")
+            yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Ziko pharmacies
### Brand name

Ziko Apteka (or Ziko)

### Wikidata ID

Q63432892

### Store finder url(s)

https://zikoapteka.pl/lokalizator/


Discussion:

Resolving patch:
diff --git a/locations/spiders/ziko_apteka_pl.py b/locations/spiders/ziko_apteka_pl.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/ziko_apteka_pl.py
@@ -0,0 +1,32 @@
+import re
+
+from scrapy import Spider
+from scrapy.http import JsonRequest
+
+from locations.dict_parser import DictParser
+from locations.hours import DAYS_PL, OpeningHours
+
+
+class ZikoAptekaPLSpider(Spider):
+    name = "ziko_apteka_pl"
+    item_attributes = {"brand": "Ziko Apteka", "brand_wikidata": "Q63432892"}
+    allowed_domains = ["zikoapteka.pl"]
+    start_urls = ["https://zikoapteka.pl/wp-admin/admin-ajax.php?action=get_pharmacies"]
+
+    def start_requests(self):
+        for url in self.start_urls:
+            yield JsonRequest(url=url)
+
+    def parse(self, response):
+        for location in response.json().values():
+            item = DictParser.parse(location)
+            item["ref"] = location["mypostid"]
+            item["city"] = location["city_name"][0]
+            item.pop("state", None)
+            if location.get("link"):
+                item["website"] = "https://zikoapteka.pl/apteki" + location["link"]
+            item["opening_hours"] = OpeningHours()
+            item["opening_hours"].add_ranges_from_string(
+                re.sub(r"\s+", " ", location["hours"].replace(".", "")), days=DAYS_PL
+            )
+            yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Lilly Drogeries
### Brand name

Lilly

### Wikidata ID

Q111764460

### Store finder url(s)

Greece: https://lilidrogerie.gr/wp-json/v1/get-store/?lang=el
Serbia: https://www.lilly.rs/phpsqlsearch_genjson.php?lat=44.8019&lng=20.4671&radius=600&limit=200&keyword=none
Bulgaria: https://www.lillydrogerie.bg/bg/stores (look for `var locations = ` in the page source code, requires scraping HTML data in order to get opening hours)

It may be a good idea to have a separate spider for each country since all of them use diffrent systems and we don't know when (and if) they will standartise it.


Discussion:

Resolving patch:
diff --git a/locations/hours.py b/locations/hours.py
--- a/locations/hours.py
+++ b/locations/hours.py
@@ -111,12 +111,19 @@
 }
 DAYS_GR = {
     "Δε": "Mo",
+    "Δευτέρα": "Mo",
     "Τρ": "Tu",
+    "Τρίτη": "Tu",
     "Τε": "We",
+    "Τετάρτη": "We",
     "Πέ": "Th",
+    "Πέμπτη": "Th",
     "Πα": "Fr",
+    "Παρασκευή": "Fr",
     "Σά": "Sa",
+    "Σάββατο": "Sa",
     "Κυ": "Su",
+    "Κυριακή": "Su",
 }
 DAYS_HU = {
     "Hétfő": "Mo",
diff --git a/locations/spiders/lilly_gr.py b/locations/spiders/lilly_gr.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/lilly_gr.py
@@ -0,0 +1,27 @@
+import re
+
+from scrapy import Spider
+from scrapy.http import JsonRequest
+
+from locations.dict_parser import DictParser
+from locations.hours import DAYS_GR, OpeningHours
+
+
+class LillyGRSpider(Spider):
+    name = "lilly_gr"
+    item_attributes = {"brand": "Lilly", "brand_wikidata": "Q111764460"}
+    allowed_domains = ["lilidrogerie.gr"]
+    start_urls = ["https://lilidrogerie.gr/wp-json/v1/get-store/?lang=el"]
+    no_refs = True
+
+    def start_requests(self):
+        for url in self.start_urls:
+            yield JsonRequest(url=url)
+
+    def parse(self, response):
+        for location in response.json()[0]["store"]:
+            item = DictParser.parse(location)
+            item["addr_full"] = re.sub(r"\s+", " ", location["address"].strip())
+            item["opening_hours"] = OpeningHours()
+            item["opening_hours"].add_ranges_from_string(location["work_time"], DAYS_GR)
+            yield item


Test patch:
Repository: alltheplaces/alltheplaces

Issue:
Lilly Drogeries
### Brand name

Lilly

### Wikidata ID

Q111764460

### Store finder url(s)

Greece: https://lilidrogerie.gr/wp-json/v1/get-store/?lang=el
Serbia: https://www.lilly.rs/phpsqlsearch_genjson.php?lat=44.8019&lng=20.4671&radius=600&limit=200&keyword=none
Bulgaria: https://www.lillydrogerie.bg/bg/stores (look for `var locations = ` in the page source code, requires scraping HTML data in order to get opening hours)

It may be a good idea to have a separate spider for each country since all of them use diffrent systems and we don't know when (and if) they will standartise it.


Discussion:

Resolving patch:
diff --git a/locations/hours.py b/locations/hours.py
--- a/locations/hours.py
+++ b/locations/hours.py
@@ -378,6 +378,34 @@
     "Duminică": "Su",
 }
 
+DAYS_SR = {
+    "Pon": "Mo",
+    "Ponedjeljak": "Mo",
+    "Ponediljak": "Mo",
+    "Понедељак": "Mo",
+    "Понеделник": "Mo",
+    "Uto": "Tu",
+    "Utorak": "Tu",
+    "Уторак": "Tu",
+    "Sri": "We",
+    "Sreda": "We",
+    "Среда": "We",
+    "Cet": "Th",
+    "Čet": "Th",
+    "Četvrtak": "Th",
+    "Cetvrtak": "Th",
+    "Четвртак": "Th",
+    "Pet": "Fr",
+    "Petak": "Fr",
+    "Петак": "Fr",
+    "Sub": "Sa",
+    "Subota": "Sa",
+    "Субота": "Sa",
+    "Ned": "Su",
+    "Nedelja": "Su",
+    "Недеља": "Su",
+}
+
 NAMED_DAY_RANGES_EN = {
     "Daily": ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
     "All days": ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
diff --git a/locations/spiders/lilly_rs.py b/locations/spiders/lilly_rs.py
new file mode 100644
--- /dev/null
+++ b/locations/spiders/lilly_rs.py
@@ -0,0 +1,31 @@
+from scrapy import Spider
+from scrapy.http import JsonRequest
+
+from locations.dict_parser import DictParser
+from locations.hours import DAYS_SR, OpeningHours
+
+
+class LillyRSSpider(Spider):
+    name = "lilly_rs"
+    item_attributes = {"brand": "Lilly", "brand_wikidata": "Q111764460"}
+    allowed_domains = ["www.lilly.rs"]
+    start_urls = [
+        "https://www.lilly.rs/phpsqlsearch_genjson.php?lat=44.8019&lng=20.4671&radius=10000&limit=10000&keyword=none"
+    ]
+    requires_proxy = "US"  # Cloudflare bot blocking is in use
+
+    def start_requests(self):
+        for url in self.start_urls:
+            yield JsonRequest(url=url)
+
+    def parse(self, response):
+        for location in response.json():
+            item = DictParser.parse(location)
+            item["name"] = location["naziv_prodavnice"]
+            item["street_address"] = location["adresa"]
+            item["city"] = location["naziv_grada"]
+            item["phone"] = location["telefon"]
+            item["opening_hours"] = OpeningHours()
+            hours_string = "Pon-Pet: " + location["pon_pet"] + " Sub: " + location["sub"] + " Ned: " + location["ned"]
+            item["opening_hours"].add_ranges_from_string(hours_string, DAYS_SR)
+            yield item


Test patch: