We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Problems with Spiders built on YextSpider
I've noticed a few of the spiders whose results I was using in my OSM tool have recently stopped returning any results, including five_guys_de_es_fr_gb.py , matalan_gb.py , and three_gb.py .

A common feature of these spiders is that they use the YextSpider class. Checking some other spiders that are also based on this class, reveals they've also stopped working. The spider stats suggest 404 and 403 errors are preventing the data being retrieved.

If this is a general problem affecting multiple spiders, would someone be able to take a look and see if it can be fixed?

--- 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/spiders/tropical_smoothie_cafe_us.py`
Content:
```
1 from locations.categories import Extras, PaymentMethods, apply_yes_no
2 from locations.storefinders.yext import YextSpider
3 
4 
5 class TropicalSmoothieCafeUSSpider(YextSpider):
6     name = "tropical_smoothie_cafe_us"
7     item_attributes = {"brand": "Tropical Smoothie Cafe", "brand_wikidata": "Q7845817"}
8     api_key = "e00ed8254f827f6c73044941473bb9e9"
9     wanted_types = ["restaurant"]
10 
11     def parse_item(self, item, location):
12         item["email"] = location.get("c_cafeEmail")
13         apply_yes_no(Extras.DRIVE_THROUGH, item, "DRIVE_THRU" in location.get("c_locationPageServices", []), False)
14         apply_yes_no(Extras.DELIVERY, item, "DELIVERY" in location.get("c_locationPageServices", []), False)
15         apply_yes_no(Extras.WIFI, item, "WIFI" in location.get("c_locationPageServices", []), False)
16         apply_yes_no(
17             PaymentMethods.AMERICAN_EXPRESS, item, "AMERICANEXPRESS" in location.get("paymentOptions", []), False
18         )
19         apply_yes_no(PaymentMethods.DISCOVER_CARD, item, "DISCOVER" in location.get("paymentOptions", []), False)
20         apply_yes_no(PaymentMethods.MASTER_CARD, item, "MASTERCARD" in location.get("paymentOptions", []), False)
21         apply_yes_no(PaymentMethods.VISA, item, "VISA" in location.get("paymentOptions", []), False)
22         item["extras"].pop("contact:instagram", None)
23         item.pop("twitter", None)
24         yield item
25
```

Path: `locations/storefinders/yext_answers.py`
Content:
```
1 import json
2 from typing import Any, Iterable
3 from urllib.parse import urlencode
4 
5 from scrapy import Request
6 from scrapy.http import JsonRequest, Response
7 from scrapy.spiders import Spider
8 
9 from locations.dict_parser import DictParser
10 from locations.hours import OpeningHours
11 from locations.items import Feature
12 
13 
14 class YextAnswersSpider(Spider):
15     dataset_attributes = {"source": "api", "api": "yext"}
16 
17     endpoint: str = "https://liveapi.yext.com/v2/accounts/me/answers/vertical/query"
18     api_key: str = ""
19     experience_key: str = ""
20     api_version: str = "20220511"
21     page_limit: int = 50
22     locale: str = "en"
23 
24     def make_request(self, offset: int) -> JsonRequest:
25         return JsonRequest(
26             url="{}?{}".format(
27                 self.endpoint,
28                 urlencode(
29                     {
30                         "experienceKey": self.experience_key,
31                         "api_key": self.api_key,
32                         "v": self.api_version,
33                         "version": "PRODUCTION",
34                         "locale": self.locale,
35                         "verticalKey": "locations",
36                         "filters": json.dumps(
37                             {"builtin.location": {"$near": {"lat": 0, "lng": 0, "radius": 50000000}}}
38                         ),
39                         "limit": str(self.page_limit),
40                         "offset": str(offset),
41                         "source": "STANDARD",
42                     }
43                 ),
44             ),
45             meta={"offset": offset},
46         )
47 
48     def start_requests(self) -> Iterable[Request]:
49         yield self.make_request(0)
50 
51     def parse(self, response: Response, **kwargs: Any) -> Any:
52         for location in response.json()["response"]["results"]:
53             item = DictParser.parse(location["data"])
54             item["branch"] = location["data"].get("geomodifier")
55             item["extras"]["ref:google"] = location["data"].get("googlePlaceId")
56             item["facebook"] = location["data"].get("facebookPageUrl")
57 
58             if (
59                 not isinstance(item["lat"], float)
60                 or not isinstance(item["lon"], float)
61                 and location["data"].get("yextDisplayCoordinate")
62             ):
63                 item["lat"] = location["data"]["yextDisplayCoordinate"].get("latitude")
64                 item["lon"] = location["data"]["yextDisplayCoordinate"].get("longitude")
65 
66             item["opening_hours"] = self.parse_opening_hours(location)
67 
68             yield from self.parse_item(location, item) or []
69 
70         if len(response.json()["response"]["results"]) == self.page_limit:
71             yield self.make_request(response.meta["offset"] + self.page_limit)
72 
73     def parse_opening_hours(self, location: dict, **kwargs: Any) -> OpeningHours | None:
74         oh = OpeningHours()
75         hours = location["data"].get("hours")
76         if not hours:
77             return None
78         for day, rule in hours.items():
79             if not isinstance(rule, dict):
80                 continue
81             if day == "holidayHours":
82                 continue
83             if rule.get("isClosed") is True:
84                 continue
85             for time in rule["openIntervals"]:
86                 oh.add_range(day, time["start"], time["end"])
87 
88         return oh
89 
90     def parse_item(self, location: dict, item: Feature) -> Iterable[Feature]:
91         yield item
92
```
--- 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/spiders/tropical_smoothie_cafe_us.py b/locations/spiders/tropical_smoothie_cafe_us.py
--- a/locations/spiders/tropical_smoothie_cafe_us.py
+++ b/locations/spiders/tropical_smoothie_cafe_us.py
@@ -1,24 +1,21 @@
-from locations.categories import Extras, PaymentMethods, apply_yes_no
-from locations.storefinders.yext import YextSpider
+from locations.categories import Categories, Extras, apply_yes_no
+from locations.storefinders.yext_answers import YextAnswersSpider
 
 
-class TropicalSmoothieCafeUSSpider(YextSpider):
+class TropicalSmoothieCafeUSSpider(YextAnswersSpider):
     name = "tropical_smoothie_cafe_us"
-    item_attributes = {"brand": "Tropical Smoothie Cafe", "brand_wikidata": "Q7845817"}
+    item_attributes = {
+        "brand": "Tropical Smoothie Cafe",
+        "brand_wikidata": "Q7845817",
+        "extras": Categories.FAST_FOOD.value,
+    }
     api_key = "e00ed8254f827f6c73044941473bb9e9"
-    wanted_types = ["restaurant"]
+    experience_key = "answers"
+    environment = "STAGING"
+    feature_type = "restaurants"
 
-    def parse_item(self, item, location):
-        item["email"] = location.get("c_cafeEmail")
-        apply_yes_no(Extras.DRIVE_THROUGH, item, "DRIVE_THRU" in location.get("c_locationPageServices", []), False)
-        apply_yes_no(Extras.DELIVERY, item, "DELIVERY" in location.get("c_locationPageServices", []), False)
-        apply_yes_no(Extras.WIFI, item, "WIFI" in location.get("c_locationPageServices", []), False)
-        apply_yes_no(
-            PaymentMethods.AMERICAN_EXPRESS, item, "AMERICANEXPRESS" in location.get("paymentOptions", []), False
-        )
-        apply_yes_no(PaymentMethods.DISCOVER_CARD, item, "DISCOVER" in location.get("paymentOptions", []), False)
-        apply_yes_no(PaymentMethods.MASTER_CARD, item, "MASTERCARD" in location.get("paymentOptions", []), False)
-        apply_yes_no(PaymentMethods.VISA, item, "VISA" in location.get("paymentOptions", []), False)
-        item["extras"].pop("contact:instagram", None)
-        item.pop("twitter", None)
+    def parse_item(self, location, item):
+        item["email"] = location["data"].get("c_franchiseeEmail")
+        if amenities := location["data"].get("c_locationPageServices"):
+            apply_yes_no(Extras.WIFI, item, "Wifi" in amenities, False)
         yield item
diff --git a/locations/storefinders/yext_answers.py b/locations/storefinders/yext_answers.py
--- a/locations/storefinders/yext_answers.py
+++ b/locations/storefinders/yext_answers.py
@@ -20,6 +20,8 @@
     api_version: str = "20220511"
     page_limit: int = 50
     locale: str = "en"
+    environment: str = "PRODUCTION"  # "STAGING" also used
+    feature_type: str = "locations"  # "restaurants" also used
 
     def make_request(self, offset: int) -> JsonRequest:
         return JsonRequest(
@@ -30,9 +32,9 @@
                         "experienceKey": self.experience_key,
                         "api_key": self.api_key,
                         "v": self.api_version,
-                        "version": "PRODUCTION",
+                        "version": self.environment,
                         "locale": self.locale,
-                        "verticalKey": "locations",
+                        "verticalKey": self.feature_type,
                         "filters": json.dumps(
                             {"builtin.location": {"$near": {"lat": 0, "lng": 0, "radius": 50000000}}}
                         ),

{"golden_diff": "diff --git a/locations/spiders/tropical_smoothie_cafe_us.py b/locations/spiders/tropical_smoothie_cafe_us.py\n--- a/locations/spiders/tropical_smoothie_cafe_us.py\n+++ b/locations/spiders/tropical_smoothie_cafe_us.py\n@@ -1,24 +1,21 @@\n-from locations.categories import Extras, PaymentMethods, apply_yes_no\n-from locations.storefinders.yext import YextSpider\n+from locations.categories import Categories, Extras, apply_yes_no\n+from locations.storefinders.yext_answers import YextAnswersSpider\n \n \n-class TropicalSmoothieCafeUSSpider(YextSpider):\n+class TropicalSmoothieCafeUSSpider(YextAnswersSpider):\n     name = \"tropical_smoothie_cafe_us\"\n-    item_attributes = {\"brand\": \"Tropical Smoothie Cafe\", \"brand_wikidata\": \"Q7845817\"}\n+    item_attributes = {\n+        \"brand\": \"Tropical Smoothie Cafe\",\n+        \"brand_wikidata\": \"Q7845817\",\n+        \"extras\": Categories.FAST_FOOD.value,\n+    }\n     api_key = \"e00ed8254f827f6c73044941473bb9e9\"\n-    wanted_types = [\"restaurant\"]\n+    experience_key = \"answers\"\n+    environment = \"STAGING\"\n+    feature_type = \"restaurants\"\n \n-    def parse_item(self, item, location):\n-        item[\"email\"] = location.get(\"c_cafeEmail\")\n-        apply_yes_no(Extras.DRIVE_THROUGH, item, \"DRIVE_THRU\" in location.get(\"c_locationPageServices\", []), False)\n-        apply_yes_no(Extras.DELIVERY, item, \"DELIVERY\" in location.get(\"c_locationPageServices\", []), False)\n-        apply_yes_no(Extras.WIFI, item, \"WIFI\" in location.get(\"c_locationPageServices\", []), False)\n-        apply_yes_no(\n-            PaymentMethods.AMERICAN_EXPRESS, item, \"AMERICANEXPRESS\" in location.get(\"paymentOptions\", []), False\n-        )\n-        apply_yes_no(PaymentMethods.DISCOVER_CARD, item, \"DISCOVER\" in location.get(\"paymentOptions\", []), False)\n-        apply_yes_no(PaymentMethods.MASTER_CARD, item, \"MASTERCARD\" in location.get(\"paymentOptions\", []), False)\n-        apply_yes_no(PaymentMethods.VISA, item, \"VISA\" in location.get(\"paymentOptions\", []), False)\n-        item[\"extras\"].pop(\"contact:instagram\", None)\n-        item.pop(\"twitter\", None)\n+    def parse_item(self, location, item):\n+        item[\"email\"] = location[\"data\"].get(\"c_franchiseeEmail\")\n+        if amenities := location[\"data\"].get(\"c_locationPageServices\"):\n+            apply_yes_no(Extras.WIFI, item, \"Wifi\" in amenities, False)\n         yield item\ndiff --git a/locations/storefinders/yext_answers.py b/locations/storefinders/yext_answers.py\n--- a/locations/storefinders/yext_answers.py\n+++ b/locations/storefinders/yext_answers.py\n@@ -20,6 +20,8 @@\n     api_version: str = \"20220511\"\n     page_limit: int = 50\n     locale: str = \"en\"\n+    environment: str = \"PRODUCTION\"  # \"STAGING\" also used\n+    feature_type: str = \"locations\"  # \"restaurants\" also used\n \n     def make_request(self, offset: int) -> JsonRequest:\n         return JsonRequest(\n@@ -30,9 +32,9 @@\n                         \"experienceKey\": self.experience_key,\n                         \"api_key\": self.api_key,\n                         \"v\": self.api_version,\n-                        \"version\": \"PRODUCTION\",\n+                        \"version\": self.environment,\n                         \"locale\": self.locale,\n-                        \"verticalKey\": \"locations\",\n+                        \"verticalKey\": self.feature_type,\n                         \"filters\": json.dumps(\n                             {\"builtin.location\": {\"$near\": {\"lat\": 0, \"lng\": 0, \"radius\": 50000000}}}\n                         ),\n", "issue": "Problems with Spiders built on YextSpider\nI've noticed a few of the spiders whose results I was using in my OSM tool have recently stopped returning any results, including five_guys_de_es_fr_gb.py , matalan_gb.py , and three_gb.py .\r\n\r\nA common feature of these spiders is that they use the YextSpider class. Checking some other spiders that are also based on this class, reveals they've also stopped working. The spider stats suggest 404 and 403 errors are preventing the data being retrieved.\r\n\r\nIf this is a general problem affecting multiple spiders, would someone be able to take a look and see if it can be fixed?\n", "before_files": [{"content": "from locations.categories import Extras, PaymentMethods, apply_yes_no\nfrom locations.storefinders.yext import YextSpider\n\n\nclass TropicalSmoothieCafeUSSpider(YextSpider):\n    name = \"tropical_smoothie_cafe_us\"\n    item_attributes = {\"brand\": \"Tropical Smoothie Cafe\", \"brand_wikidata\": \"Q7845817\"}\n    api_key = \"e00ed8254f827f6c73044941473bb9e9\"\n    wanted_types = [\"restaurant\"]\n\n    def parse_item(self, item, location):\n        item[\"email\"] = location.get(\"c_cafeEmail\")\n        apply_yes_no(Extras.DRIVE_THROUGH, item, \"DRIVE_THRU\" in location.get(\"c_locationPageServices\", []), False)\n        apply_yes_no(Extras.DELIVERY, item, \"DELIVERY\" in location.get(\"c_locationPageServices\", []), False)\n        apply_yes_no(Extras.WIFI, item, \"WIFI\" in location.get(\"c_locationPageServices\", []), False)\n        apply_yes_no(\n            PaymentMethods.AMERICAN_EXPRESS, item, \"AMERICANEXPRESS\" in location.get(\"paymentOptions\", []), False\n        )\n        apply_yes_no(PaymentMethods.DISCOVER_CARD, item, \"DISCOVER\" in location.get(\"paymentOptions\", []), False)\n        apply_yes_no(PaymentMethods.MASTER_CARD, item, \"MASTERCARD\" in location.get(\"paymentOptions\", []), False)\n        apply_yes_no(PaymentMethods.VISA, item, \"VISA\" in location.get(\"paymentOptions\", []), False)\n        item[\"extras\"].pop(\"contact:instagram\", None)\n        item.pop(\"twitter\", None)\n        yield item\n", "path": "locations/spiders/tropical_smoothie_cafe_us.py"}, {"content": "import json\nfrom typing import Any, Iterable\nfrom urllib.parse import urlencode\n\nfrom scrapy import Request\nfrom scrapy.http import JsonRequest, Response\nfrom scrapy.spiders import Spider\n\nfrom locations.dict_parser import DictParser\nfrom locations.hours import OpeningHours\nfrom locations.items import Feature\n\n\nclass YextAnswersSpider(Spider):\n    dataset_attributes = {\"source\": \"api\", \"api\": \"yext\"}\n\n    endpoint: str = \"https://liveapi.yext.com/v2/accounts/me/answers/vertical/query\"\n    api_key: str = \"\"\n    experience_key: str = \"\"\n    api_version: str = \"20220511\"\n    page_limit: int = 50\n    locale: str = \"en\"\n\n    def make_request(self, offset: int) -> JsonRequest:\n        return JsonRequest(\n            url=\"{}?{}\".format(\n                self.endpoint,\n                urlencode(\n                    {\n                        \"experienceKey\": self.experience_key,\n                        \"api_key\": self.api_key,\n                        \"v\": self.api_version,\n                        \"version\": \"PRODUCTION\",\n                        \"locale\": self.locale,\n                        \"verticalKey\": \"locations\",\n                        \"filters\": json.dumps(\n                            {\"builtin.location\": {\"$near\": {\"lat\": 0, \"lng\": 0, \"radius\": 50000000}}}\n                        ),\n                        \"limit\": str(self.page_limit),\n                        \"offset\": str(offset),\n                        \"source\": \"STANDARD\",\n                    }\n                ),\n            ),\n            meta={\"offset\": offset},\n        )\n\n    def start_requests(self) -> Iterable[Request]:\n        yield self.make_request(0)\n\n    def parse(self, response: Response, **kwargs: Any) -> Any:\n        for location in response.json()[\"response\"][\"results\"]:\n            item = DictParser.parse(location[\"data\"])\n            item[\"branch\"] = location[\"data\"].get(\"geomodifier\")\n            item[\"extras\"][\"ref:google\"] = location[\"data\"].get(\"googlePlaceId\")\n            item[\"facebook\"] = location[\"data\"].get(\"facebookPageUrl\")\n\n            if (\n                not isinstance(item[\"lat\"], float)\n                or not isinstance(item[\"lon\"], float)\n                and location[\"data\"].get(\"yextDisplayCoordinate\")\n            ):\n                item[\"lat\"] = location[\"data\"][\"yextDisplayCoordinate\"].get(\"latitude\")\n                item[\"lon\"] = location[\"data\"][\"yextDisplayCoordinate\"].get(\"longitude\")\n\n            item[\"opening_hours\"] = self.parse_opening_hours(location)\n\n            yield from self.parse_item(location, item) or []\n\n        if len(response.json()[\"response\"][\"results\"]) == self.page_limit:\n            yield self.make_request(response.meta[\"offset\"] + self.page_limit)\n\n    def parse_opening_hours(self, location: dict, **kwargs: Any) -> OpeningHours | None:\n        oh = OpeningHours()\n        hours = location[\"data\"].get(\"hours\")\n        if not hours:\n            return None\n        for day, rule in hours.items():\n            if not isinstance(rule, dict):\n                continue\n            if day == \"holidayHours\":\n                continue\n            if rule.get(\"isClosed\") is True:\n                continue\n            for time in rule[\"openIntervals\"]:\n                oh.add_range(day, time[\"start\"], time[\"end\"])\n\n        return oh\n\n    def parse_item(self, location: dict, item: Feature) -> Iterable[Feature]:\n        yield item\n", "path": "locations/storefinders/yext_answers.py"}], "after_files": [{"content": "from locations.categories import Categories, Extras, apply_yes_no\nfrom locations.storefinders.yext_answers import YextAnswersSpider\n\n\nclass TropicalSmoothieCafeUSSpider(YextAnswersSpider):\n    name = \"tropical_smoothie_cafe_us\"\n    item_attributes = {\n        \"brand\": \"Tropical Smoothie Cafe\",\n        \"brand_wikidata\": \"Q7845817\",\n        \"extras\": Categories.FAST_FOOD.value,\n    }\n    api_key = \"e00ed8254f827f6c73044941473bb9e9\"\n    experience_key = \"answers\"\n    environment = \"STAGING\"\n    feature_type = \"restaurants\"\n\n    def parse_item(self, location, item):\n        item[\"email\"] = location[\"data\"].get(\"c_franchiseeEmail\")\n        if amenities := location[\"data\"].get(\"c_locationPageServices\"):\n            apply_yes_no(Extras.WIFI, item, \"Wifi\" in amenities, False)\n        yield item\n", "path": "locations/spiders/tropical_smoothie_cafe_us.py"}, {"content": "import json\nfrom typing import Any, Iterable\nfrom urllib.parse import urlencode\n\nfrom scrapy import Request\nfrom scrapy.http import JsonRequest, Response\nfrom scrapy.spiders import Spider\n\nfrom locations.dict_parser import DictParser\nfrom locations.hours import OpeningHours\nfrom locations.items import Feature\n\n\nclass YextAnswersSpider(Spider):\n    dataset_attributes = {\"source\": \"api\", \"api\": \"yext\"}\n\n    endpoint: str = \"https://liveapi.yext.com/v2/accounts/me/answers/vertical/query\"\n    api_key: str = \"\"\n    experience_key: str = \"\"\n    api_version: str = \"20220511\"\n    page_limit: int = 50\n    locale: str = \"en\"\n    environment: str = \"PRODUCTION\"  # \"STAGING\" also used\n    feature_type: str = \"locations\"  # \"restaurants\" also used\n\n    def make_request(self, offset: int) -> JsonRequest:\n        return JsonRequest(\n            url=\"{}?{}\".format(\n                self.endpoint,\n                urlencode(\n                    {\n                        \"experienceKey\": self.experience_key,\n                        \"api_key\": self.api_key,\n                        \"v\": self.api_version,\n                        \"version\": self.environment,\n                        \"locale\": self.locale,\n                        \"verticalKey\": self.feature_type,\n                        \"filters\": json.dumps(\n                            {\"builtin.location\": {\"$near\": {\"lat\": 0, \"lng\": 0, \"radius\": 50000000}}}\n                        ),\n                        \"limit\": str(self.page_limit),\n                        \"offset\": str(offset),\n                        \"source\": \"STANDARD\",\n                    }\n                ),\n            ),\n            meta={\"offset\": offset},\n        )\n\n    def start_requests(self) -> Iterable[Request]:\n        yield self.make_request(0)\n\n    def parse(self, response: Response, **kwargs: Any) -> Any:\n        for location in response.json()[\"response\"][\"results\"]:\n            item = DictParser.parse(location[\"data\"])\n            item[\"branch\"] = location[\"data\"].get(\"geomodifier\")\n            item[\"extras\"][\"ref:google\"] = location[\"data\"].get(\"googlePlaceId\")\n            item[\"facebook\"] = location[\"data\"].get(\"facebookPageUrl\")\n\n            if (\n                not isinstance(item[\"lat\"], float)\n                or not isinstance(item[\"lon\"], float)\n                and location[\"data\"].get(\"yextDisplayCoordinate\")\n            ):\n                item[\"lat\"] = location[\"data\"][\"yextDisplayCoordinate\"].get(\"latitude\")\n                item[\"lon\"] = location[\"data\"][\"yextDisplayCoordinate\"].get(\"longitude\")\n\n            item[\"opening_hours\"] = self.parse_opening_hours(location)\n\n            yield from self.parse_item(location, item) or []\n\n        if len(response.json()[\"response\"][\"results\"]) == self.page_limit:\n            yield self.make_request(response.meta[\"offset\"] + self.page_limit)\n\n    def parse_opening_hours(self, location: dict, **kwargs: Any) -> OpeningHours | None:\n        oh = OpeningHours()\n        hours = location[\"data\"].get(\"hours\")\n        if not hours:\n            return None\n        for day, rule in hours.items():\n            if not isinstance(rule, dict):\n                continue\n            if day == \"holidayHours\":\n                continue\n            if rule.get(\"isClosed\") is True:\n                continue\n            for time in rule[\"openIntervals\"]:\n                oh.add_range(day, time[\"start\"], time[\"end\"])\n\n        return oh\n\n    def parse_item(self, location: dict, item: Feature) -> Iterable[Feature]:\n        yield item\n", "path": "locations/storefinders/yext_answers.py"}]}