We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Handle it gracefully when start_url is used instead of start_urls
Over the last year I’ve seen a few cases ([recent example](https://stackoverflow.com/q/58664004/939364)) of this, people missing the `s` at the end of the `start_urls`.

It may be nice to find a way to gracefully let the developer know where the issue is, why there is no crawling happening.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `scrapy/spiders/__init__.py`
Content:
```
1 """
2 Base class for Scrapy spiders
3 
4 See documentation in docs/topics/spiders.rst
5 """
6 import logging
7 import warnings
8 
9 from scrapy import signals
10 from scrapy.http import Request
11 from scrapy.utils.trackref import object_ref
12 from scrapy.utils.url import url_is_from_spider
13 from scrapy.exceptions import ScrapyDeprecationWarning
14 from scrapy.utils.deprecate import method_is_overridden
15 
16 
17 class Spider(object_ref):
18     """Base class for scrapy spiders. All spiders must inherit from this
19     class.
20     """
21 
22     name = None
23     custom_settings = None
24 
25     def __init__(self, name=None, **kwargs):
26         if name is not None:
27             self.name = name
28         elif not getattr(self, 'name', None):
29             raise ValueError("%s must have a name" % type(self).__name__)
30         self.__dict__.update(kwargs)
31         if not hasattr(self, 'start_urls'):
32             self.start_urls = []
33 
34     @property
35     def logger(self):
36         logger = logging.getLogger(self.name)
37         return logging.LoggerAdapter(logger, {'spider': self})
38 
39     def log(self, message, level=logging.DEBUG, **kw):
40         """Log the given message at the given log level
41 
42         This helper wraps a log call to the logger within the spider, but you
43         can use it directly (e.g. Spider.logger.info('msg')) or use any other
44         Python logger too.
45         """
46         self.logger.log(level, message, **kw)
47 
48     @classmethod
49     def from_crawler(cls, crawler, *args, **kwargs):
50         spider = cls(*args, **kwargs)
51         spider._set_crawler(crawler)
52         return spider
53 
54     def _set_crawler(self, crawler):
55         self.crawler = crawler
56         self.settings = crawler.settings
57         crawler.signals.connect(self.close, signals.spider_closed)
58 
59     def start_requests(self):
60         cls = self.__class__
61         if method_is_overridden(cls, Spider, 'make_requests_from_url'):
62             warnings.warn(
63                 "Spider.make_requests_from_url method is deprecated; it "
64                 "won't be called in future Scrapy releases. Please "
65                 "override Spider.start_requests method instead (see %s.%s)." % (
66                     cls.__module__, cls.__name__
67                 ),
68             )
69             for url in self.start_urls:
70                 yield self.make_requests_from_url(url)
71         else:
72             for url in self.start_urls:
73                 yield Request(url, dont_filter=True)
74 
75     def make_requests_from_url(self, url):
76         """ This method is deprecated. """
77         return Request(url, dont_filter=True)
78 
79     def parse(self, response):
80         raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))
81 
82     @classmethod
83     def update_settings(cls, settings):
84         settings.setdict(cls.custom_settings or {}, priority='spider')
85 
86     @classmethod
87     def handles_request(cls, request):
88         return url_is_from_spider(request.url, cls)
89 
90     @staticmethod
91     def close(spider, reason):
92         closed = getattr(spider, 'closed', None)
93         if callable(closed):
94             return closed(reason)
95 
96     def __str__(self):
97         return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))
98 
99     __repr__ = __str__
100 
101 
102 # Top-level imports
103 from scrapy.spiders.crawl import CrawlSpider, Rule
104 from scrapy.spiders.feed import XMLFeedSpider, CSVFeedSpider
105 from scrapy.spiders.sitemap import SitemapSpider
106
```
--- 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/scrapy/spiders/__init__.py b/scrapy/spiders/__init__.py
--- a/scrapy/spiders/__init__.py
+++ b/scrapy/spiders/__init__.py
@@ -58,6 +58,11 @@
 
     def start_requests(self):
         cls = self.__class__
+        if not self.start_urls and hasattr(self, 'start_url'):
+            raise AttributeError(
+                "Crawling could not start: 'start_urls' not found "
+                "or empty (but found 'start_url' attribute instead, "
+                "did you miss an 's'?)")
         if method_is_overridden(cls, Spider, 'make_requests_from_url'):
             warnings.warn(
                 "Spider.make_requests_from_url method is deprecated; it "

{"golden_diff": "diff --git a/scrapy/spiders/__init__.py b/scrapy/spiders/__init__.py\n--- a/scrapy/spiders/__init__.py\n+++ b/scrapy/spiders/__init__.py\n@@ -58,6 +58,11 @@\n \n     def start_requests(self):\n         cls = self.__class__\n+        if not self.start_urls and hasattr(self, 'start_url'):\n+            raise AttributeError(\n+                \"Crawling could not start: 'start_urls' not found \"\n+                \"or empty (but found 'start_url' attribute instead, \"\n+                \"did you miss an 's'?)\")\n         if method_is_overridden(cls, Spider, 'make_requests_from_url'):\n             warnings.warn(\n                 \"Spider.make_requests_from_url method is deprecated; it \"\n", "issue": "Handle it gracefully when start_url is used instead of start_urls\nOver the last year I\u2019ve seen a few cases ([recent example](https://stackoverflow.com/q/58664004/939364)) of this, people missing the `s` at the end of the `start_urls`.\r\n\r\nIt may be nice to find a way to gracefully let the developer know where the issue is, why there is no crawling happening.\n", "before_files": [{"content": "\"\"\"\nBase class for Scrapy spiders\n\nSee documentation in docs/topics/spiders.rst\n\"\"\"\nimport logging\nimport warnings\n\nfrom scrapy import signals\nfrom scrapy.http import Request\nfrom scrapy.utils.trackref import object_ref\nfrom scrapy.utils.url import url_is_from_spider\nfrom scrapy.exceptions import ScrapyDeprecationWarning\nfrom scrapy.utils.deprecate import method_is_overridden\n\n\nclass Spider(object_ref):\n    \"\"\"Base class for scrapy spiders. All spiders must inherit from this\n    class.\n    \"\"\"\n\n    name = None\n    custom_settings = None\n\n    def __init__(self, name=None, **kwargs):\n        if name is not None:\n            self.name = name\n        elif not getattr(self, 'name', None):\n            raise ValueError(\"%s must have a name\" % type(self).__name__)\n        self.__dict__.update(kwargs)\n        if not hasattr(self, 'start_urls'):\n            self.start_urls = []\n\n    @property\n    def logger(self):\n        logger = logging.getLogger(self.name)\n        return logging.LoggerAdapter(logger, {'spider': self})\n\n    def log(self, message, level=logging.DEBUG, **kw):\n        \"\"\"Log the given message at the given log level\n\n        This helper wraps a log call to the logger within the spider, but you\n        can use it directly (e.g. Spider.logger.info('msg')) or use any other\n        Python logger too.\n        \"\"\"\n        self.logger.log(level, message, **kw)\n\n    @classmethod\n    def from_crawler(cls, crawler, *args, **kwargs):\n        spider = cls(*args, **kwargs)\n        spider._set_crawler(crawler)\n        return spider\n\n    def _set_crawler(self, crawler):\n        self.crawler = crawler\n        self.settings = crawler.settings\n        crawler.signals.connect(self.close, signals.spider_closed)\n\n    def start_requests(self):\n        cls = self.__class__\n        if method_is_overridden(cls, Spider, 'make_requests_from_url'):\n            warnings.warn(\n                \"Spider.make_requests_from_url method is deprecated; it \"\n                \"won't be called in future Scrapy releases. Please \"\n                \"override Spider.start_requests method instead (see %s.%s).\" % (\n                    cls.__module__, cls.__name__\n                ),\n            )\n            for url in self.start_urls:\n                yield self.make_requests_from_url(url)\n        else:\n            for url in self.start_urls:\n                yield Request(url, dont_filter=True)\n\n    def make_requests_from_url(self, url):\n        \"\"\" This method is deprecated. \"\"\"\n        return Request(url, dont_filter=True)\n\n    def parse(self, response):\n        raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))\n\n    @classmethod\n    def update_settings(cls, settings):\n        settings.setdict(cls.custom_settings or {}, priority='spider')\n\n    @classmethod\n    def handles_request(cls, request):\n        return url_is_from_spider(request.url, cls)\n\n    @staticmethod\n    def close(spider, reason):\n        closed = getattr(spider, 'closed', None)\n        if callable(closed):\n            return closed(reason)\n\n    def __str__(self):\n        return \"<%s %r at 0x%0x>\" % (type(self).__name__, self.name, id(self))\n\n    __repr__ = __str__\n\n\n# Top-level imports\nfrom scrapy.spiders.crawl import CrawlSpider, Rule\nfrom scrapy.spiders.feed import XMLFeedSpider, CSVFeedSpider\nfrom scrapy.spiders.sitemap import SitemapSpider\n", "path": "scrapy/spiders/__init__.py"}], "after_files": [{"content": "\"\"\"\nBase class for Scrapy spiders\n\nSee documentation in docs/topics/spiders.rst\n\"\"\"\nimport logging\nimport warnings\n\nfrom scrapy import signals\nfrom scrapy.http import Request\nfrom scrapy.utils.trackref import object_ref\nfrom scrapy.utils.url import url_is_from_spider\nfrom scrapy.exceptions import ScrapyDeprecationWarning\nfrom scrapy.utils.deprecate import method_is_overridden\n\n\nclass Spider(object_ref):\n    \"\"\"Base class for scrapy spiders. All spiders must inherit from this\n    class.\n    \"\"\"\n\n    name = None\n    custom_settings = None\n\n    def __init__(self, name=None, **kwargs):\n        if name is not None:\n            self.name = name\n        elif not getattr(self, 'name', None):\n            raise ValueError(\"%s must have a name\" % type(self).__name__)\n        self.__dict__.update(kwargs)\n        if not hasattr(self, 'start_urls'):\n            self.start_urls = []\n\n    @property\n    def logger(self):\n        logger = logging.getLogger(self.name)\n        return logging.LoggerAdapter(logger, {'spider': self})\n\n    def log(self, message, level=logging.DEBUG, **kw):\n        \"\"\"Log the given message at the given log level\n\n        This helper wraps a log call to the logger within the spider, but you\n        can use it directly (e.g. Spider.logger.info('msg')) or use any other\n        Python logger too.\n        \"\"\"\n        self.logger.log(level, message, **kw)\n\n    @classmethod\n    def from_crawler(cls, crawler, *args, **kwargs):\n        spider = cls(*args, **kwargs)\n        spider._set_crawler(crawler)\n        return spider\n\n    def _set_crawler(self, crawler):\n        self.crawler = crawler\n        self.settings = crawler.settings\n        crawler.signals.connect(self.close, signals.spider_closed)\n\n    def start_requests(self):\n        cls = self.__class__\n        if not self.start_urls and hasattr(self, 'start_url'):\n            raise AttributeError(\n                \"Crawling could not start: 'start_urls' not found \"\n                \"or empty (but found 'start_url' attribute instead, \"\n                \"did you miss an 's'?)\")\n        if method_is_overridden(cls, Spider, 'make_requests_from_url'):\n            warnings.warn(\n                \"Spider.make_requests_from_url method is deprecated; it \"\n                \"won't be called in future Scrapy releases. Please \"\n                \"override Spider.start_requests method instead (see %s.%s).\" % (\n                    cls.__module__, cls.__name__\n                ),\n            )\n            for url in self.start_urls:\n                yield self.make_requests_from_url(url)\n        else:\n            for url in self.start_urls:\n                yield Request(url, dont_filter=True)\n\n    def make_requests_from_url(self, url):\n        \"\"\" This method is deprecated. \"\"\"\n        return Request(url, dont_filter=True)\n\n    def parse(self, response):\n        raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))\n\n    @classmethod\n    def update_settings(cls, settings):\n        settings.setdict(cls.custom_settings or {}, priority='spider')\n\n    @classmethod\n    def handles_request(cls, request):\n        return url_is_from_spider(request.url, cls)\n\n    @staticmethod\n    def close(spider, reason):\n        closed = getattr(spider, 'closed', None)\n        if callable(closed):\n            return closed(reason)\n\n    def __str__(self):\n        return \"<%s %r at 0x%0x>\" % (type(self).__name__, self.name, id(self))\n\n    __repr__ = __str__\n\n\n# Top-level imports\nfrom scrapy.spiders.crawl import CrawlSpider, Rule\nfrom scrapy.spiders.feed import XMLFeedSpider, CSVFeedSpider\nfrom scrapy.spiders.sitemap import SitemapSpider\n", "path": "scrapy/spiders/__init__.py"}]}