爬虫
Spider 是类,它们定义了如何抓取某个网站(或一组网站),包括如何执行抓取(即跟随链接)以及如何从页面中提取结构化数据(即抓取 Item)。换句话说,Spider 是定义特定网站(或在某些情况下,一组网站)的爬取和解析页面自定义行为的地方。
对于爬虫而言,抓取周期大致如下:
你首先生成初始请求来爬取第一个 URL,并指定一个回调函数来处理从这些请求下载到的响应。
要执行的第一个请求是通过迭代
start()方法获得的,该方法默认会为start_urlsspider 属性中的每个 URL 生成一个Request对象,并将parse方法设置为callback函数来处理每个Response。在回调函数中,你解析响应(网页)并返回 Item 对象、
Request对象,或者这些对象的迭代器。这些 Request 也将包含一个回调(可能是同一个),然后由 Scrapy 下载,其响应再由指定的回调处理。在回调函数中,你解析页面内容,通常使用 Selector(但你也可以使用 BeautifulSoup、lxml 或任何你喜欢的机制),并用解析后的数据生成 Item。
最后,从 spider 返回的 Item 通常会持久化到数据库(在某些 Item Pipeline 中)或使用 Feed 导出 写入文件。
尽管这个周期(或多或少)适用于任何类型的 spider,但 Scrapy 内置了不同类型的默认 spider,用于不同的目的。我们将在这里讨论这些类型。
scrapy.Spider
- class scrapy.spiders.Spider
- class scrapy.Spider(*args: Any, **kwargs: Any)[source]
所有 spider 都必须继承的基类。
它提供了一个默认的
start()实现,根据start_urls类属性发送请求,并为每个响应调用parse()方法。- name
一个字符串,定义了该 spider 的名称。spider 名称是 Scrapy 定位(并实例化) spider 的方式,因此它必须是唯一的。但是,这并不妨碍你实例化同一个 spider 的多个实例。这是最重要的 spider 属性,并且是必需的。
如果 spider 抓取单个域,一个常见的做法是将 spider 命名为该域的名称,可以包含或不包含 TLD (顶级域名)。例如,一个抓取
mywebsite.com的 spider 通常会被命名为mywebsite。
- allowed_domains
一个可选的字符串列表,包含此 spider 允许爬取的域名。如果启用了
OffsiteMiddleware,则属于此列表中指定域名(或其子域名)之外的 URL 请求将不会被跟随。假设你的目标 url 是
https://www.example.com/1.html,那么将'example.com'添加到此列表。
- crawler
此属性由
from_crawler()类方法在初始化类后设置,并链接到此 spider 实例所绑定的Crawler对象。Crawler 封装了项目中的许多组件,以便进行单一入口访问(例如扩展、中间件、信号管理器等)。要了解更多信息,请参见 Crawler API。
- logger
使用 Spider 的
name创建的 Python logger。你可以使用它来发送日志消息,详见 从 Spider 记录日志。
- state
一个字典,你可以用来在批次之间持久化一些 spider 状态。要了解更多信息,请参见 在批次之间保持持久状态。
- from_crawler(crawler, *args, **kwargs)[source]
这是 Scrapy 用来创建你的 spider 的类方法。
你可能不需要直接覆盖此方法,因为默认实现充当
__init__()方法的代理,并使用给定的位置参数args和命名参数kwargs调用它。尽管如此,此方法在新实例中设置了
crawler和settings属性,以便之后在 spider 代码中访问它们。版本 2.11 中的变化:
crawler.settings中的设置现在可以在此方法中修改,这对于你想基于参数修改设置时很方便。因此,这些设置不是最终值,因为它们可能在之后被例如 附加组件 修改。出于同样的原因,此时大多数Crawler属性尚未初始化。最终设置和初始化的
Crawler属性可在start()方法、engine_started信号的处理程序以及更晚的时候使用。- 参数:
crawler (
Crawler实例) – spider 将绑定到的 crawlerargs (list) – 传递给
__init__()方法的参数kwargs (dict) – 传递给
__init__()方法的关键字参数
- classmethod update_settings(settings)[source]
update_settings()方法用于修改 spider 的设置,并在初始化 spider 实例时调用。它接受一个
Settings对象作为参数,可以添加或更新 spider 的配置值。此方法是一个类方法,意味着它在Spider类上调用,并允许所有 spider 实例共享相同的配置。虽然可以在
custom_settings中设置每个 spider 的设置,但使用update_settings()可以让你根据其他设置、spider 属性或其他因素动态添加、删除或更改设置,并使用'spider'之外的设置优先级。此外,在子类中通过覆盖update_settings()可以轻松地扩展它,而对custom_settings做同样的事情可能会很困难。例如,假设一个 spider 需要修改
FEEDSimport scrapy class MySpider(scrapy.Spider): name = "myspider" custom_feed = { "/home/user/documents/items.json": { "format": "json", "indent": 4, } } @classmethod def update_settings(cls, settings): super().update_settings(settings) settings.setdefault("FEEDS", {}).update(cls.custom_feed)
- async start() AsyncIterator[Any][source]
生成要发送的初始
Request对象。版本 2.13 中新增。
例如
from scrapy import Request, Spider class MySpider(Spider): name = "myspider" async def start(self): yield Request("https://toscrape.com/")
默认实现从
start_urls读取 URL,并为每个 URL 生成一个启用dont_filter的请求。它在功能上等同于async def start(self): for url in self.start_urls: yield Request(url, dont_filter=True)
你也可以生成 Item。例如
async def start(self): yield {"foo": "bar"}
要编写适用于 Scrapy 2.13 之前版本的 spider,还需要定义一个返回可迭代对象的同步
start_requests()方法。例如def start_requests(self): yield Request("https://toscrape.com/")
另请参阅
- parse(response)[source]
这是 Scrapy 用来处理下载的响应的默认回调,当它们的请求未指定回调时。
parse方法负责处理响应并返回抓取的数据和/或更多要跟随的 URL。其他 Request 回调的要求与Spider类相同。此方法以及任何其他 Request 回调,必须返回一个
Request对象、一个 Item 对象、包含任何这些对象的迭代器,或者None。- 参数:
response (
Response) – 要解析的响应
- log(message[, level, component])[source]
通过 Spider 的
logger发送日志消息的包装器,保留用于向后兼容。要了解更多信息,请参见 从 Spider 记录日志。
- closed(reason)
在 spider 关闭时调用。此方法为
spider_closed信号提供了 signals.connect() 的快捷方式。
让我们看一个例子
import scrapy
class MySpider(scrapy.Spider):
name = "example.com"
allowed_domains = ["example.com"]
start_urls = [
"http://www.example.com/1.html",
"http://www.example.com/2.html",
"http://www.example.com/3.html",
]
def parse(self, response):
self.logger.info("A response from %s just arrived!", response.url)
从单个回调返回多个 Request 和 Item
import scrapy
class MySpider(scrapy.Spider):
name = "example.com"
allowed_domains = ["example.com"]
start_urls = [
"http://www.example.com/1.html",
"http://www.example.com/2.html",
"http://www.example.com/3.html",
]
def parse(self, response):
for h3 in response.xpath("//h3").getall():
yield {"title": h3}
for href in response.xpath("//a/@href").getall():
yield scrapy.Request(response.urljoin(href), self.parse)
你可以直接使用 start() 而不是 start_urls;为了给数据更多结构,你可以使用 Item 对象
import scrapy
from myproject.items import MyItem
class MySpider(scrapy.Spider):
name = "example.com"
allowed_domains = ["example.com"]
async def start(self):
yield scrapy.Request("http://www.example.com/1.html", self.parse)
yield scrapy.Request("http://www.example.com/2.html", self.parse)
yield scrapy.Request("http://www.example.com/3.html", self.parse)
def parse(self, response):
for h3 in response.xpath("//h3").getall():
yield MyItem(title=h3)
for href in response.xpath("//a/@href").getall():
yield scrapy.Request(response.urljoin(href), self.parse)
Spider 参数
Spider 可以接收修改其行为的参数。spider 参数的一些常见用途是定义起始 URL 或将爬取限制在网站的某些部分,但它们可以用于配置 spider 的任何功能。
spider 参数通过 crawl 命令使用 -a 选项传递。例如
scrapy crawl myspider -a category=electronics
Spider 可以在它们的 __init__ 方法中访问参数
import scrapy
class MySpider(scrapy.Spider):
name = "myspider"
def __init__(self, category=None, *args, **kwargs):
super(MySpider, self).__init__(*args, **kwargs)
self.start_urls = [f"http://www.example.com/categories/{category}"]
# ...
默认的 __init__ 方法将接受任何 spider 参数并将它们作为属性复制到 spider 中。上面的例子也可以写成如下形式
import scrapy
class MySpider(scrapy.Spider):
name = "myspider"
async def start(self):
yield scrapy.Request(f"http://www.example.com/categories/{self.category}")
如果你是从脚本运行 Scrapy,可以在调用 CrawlerProcess.crawl 或 CrawlerRunner.crawl 时指定 spider 参数
process = CrawlerProcess()
process.crawl(MySpider, category="electronics")
请记住,spider 参数仅为字符串。spider 本身不会进行任何解析。如果你要从命令行设置 start_urls 属性,则必须使用 ast.literal_eval() 或 json.loads() 等方法将其解析为列表,然后将其设置为属性。否则,你将会遍历 start_urls 字符串(一个非常常见的 Python 陷阱),导致每个字符被视为一个单独的 url。
一个有效的用例是设置由 HttpAuthMiddleware 使用的 http 认证凭据或由 UserAgentMiddleware 使用的用户代理。
scrapy crawl myspider -a http_user=myuser -a http_pass=mypassword -a user_agent=mybot
Spider 参数也可以通过 Scrapyd schedule.json API 传递。参见 Scrapyd documentation。
起始请求
起始请求是 从 spider 的 start() 方法或 spider 中间件 的 process_start() 方法生成的 Request 对象。
另请参阅
延迟起始请求迭代
你可以按如下方式覆盖 start() 方法,以便在有计划中的请求时暂停其迭代
async def start(self):
async for item_or_request in super().start():
if self.crawler.engine.needs_backoff():
await self.crawler.signals.wait_for(signals.scheduler_empty)
yield item_or_request
这有助于在任何给定时间最小化调度器中的请求数量,从而最小化资源使用(内存或磁盘,取决于 JOBDIR)。
通用爬虫
Scrapy 内置了一些有用的通用爬虫,你可以继承它们来创建你的 spider。它们旨在为一些常见的抓取场景提供便利功能,例如根据某些规则跟随网站上的所有链接,从 Sitemaps 爬取,或解析 XML/CSV feed。
对于以下 spider 中使用的示例,我们假设你有一个项目,其中在 myproject.items 模块中声明了一个 TestItem
import scrapy
class TestItem(scrapy.Item):
id = scrapy.Field()
name = scrapy.Field()
description = scrapy.Field()
CrawlSpider
- class scrapy.spiders.CrawlSpider[source]
这是最常用的爬虫,用于抓取常规网站,因为它通过定义一组规则提供了一种方便的跟随链接机制。它可能不是最适合你的特定网站或项目,但它对于多种情况来说足够通用,因此你可以从它开始,并根据需要覆盖它以实现更自定义的功能,或者直接实现你自己的 spider。
除了从 Spider 继承的属性(你必须指定)之外,此类还支持一个新属性
- rules
它是一个包含一个(或多个)
Rule对象的列表。每个Rule定义了爬取网站的特定行为。Rule 对象在下面描述。如果多个规则匹配同一个链接,将根据它们在此属性中定义的顺序使用第一个匹配的规则。
此 spider 还公开了一个可覆盖的方法
爬取规则
- class scrapy.spiders.Rule(link_extractor: LinkExtractor | None = None, callback: CallbackT | str | None = None, cb_kwargs: dict[str, Any] | None = None, follow: bool | None = None, process_links: ProcessLinksT | str | None = None, process_request: ProcessRequestT | str |None = None, errback: Callable[[Failure], Any] | str |None = None)[source]
link_extractor是一个 Link Extractor (链接提取器) 对象,它定义了如何从每个爬取的页面提取链接。每个提取的链接都将用于生成一个Request对象,该对象将在其meta字典中包含链接的文本(键为link_text)。如果省略,将使用一个不带参数创建的默认链接提取器,这将提取所有链接。callback是一个可调用对象或一个字符串(在这种情况下,将使用 spider 对象中同名的方法),用于处理使用指定链接提取器提取的每个链接。此回调函数接收一个Response作为其第一个参数,并且必须返回单个实例或一个由 item 对象 和/或Request对象(或它们的任何子类)组成的可迭代对象。如上所述,收到的Response对象将在其meta字典中包含生成Request的链接文本(键为link_text)。cb_kwargs是一个字典,包含要传递给回调函数的关键字参数。follow是一个布尔值,指定是否应从使用此规则提取的每个响应中跟踪链接。如果callback为 None,则follow默认为True,否则默认为False。process_links是一个可调用对象或一个字符串(在这种情况下,将使用 spider 对象中同名的方法),它将针对使用指定的link_extractor从每个响应中提取的链接列表调用。这主要用于过滤目的。process_request是一个可调用对象(或一个字符串,在这种情况下,将使用 spider 对象中同名的方法),它将针对此规则提取的每个Request调用。此可调用对象应将该请求作为第一个参数,并将生成该请求的Response作为第二个参数。它必须返回一个Request对象或None(以过滤掉请求)。errback是一个可调用对象或一个字符串(在这种情况下,将使用 spider 对象中同名的方法),用于在处理规则生成的请求时发生任何异常时调用。它接收一个Twisted Failure实例作为第一个参数。警告
由于其内部实现,在编写基于
CrawlSpider的 spider 时,您必须为新请求显式设置回调函数;否则可能发生意外行为。添加到 2.0 版本: errback 参数。
CrawlSpider 示例
现在让我们来看一个带有规则的 CrawlSpider 示例
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class MySpider(CrawlSpider):
name = "example.com"
allowed_domains = ["example.com"]
start_urls = ["http://www.example.com"]
rules = (
# Extract links matching 'category.php' (but not matching 'subsection.php')
# and follow links from them (since no callback means follow=True by default).
Rule(LinkExtractor(allow=(r"category\.php",), deny=(r"subsection\.php",))),
# Extract links matching 'item.php' and parse them with the spider's method parse_item
Rule(LinkExtractor(allow=(r"item\.php",)), callback="parse_item"),
)
def parse_item(self, response):
self.logger.info("Hi, this is an item page! %s", response.url)
item = scrapy.Item()
item["id"] = response.xpath('//td[@id="item_id"]/text()').re(r"ID: (\d+)")
item["name"] = response.xpath('//td[@id="item_name"]/text()').get()
item["description"] = response.xpath(
'//td[@id="item_description"]/text()'
).get()
item["link_text"] = response.meta["link_text"]
url = response.xpath('//td[@id="additional_data"]/@href').get()
return response.follow(
url, self.parse_additional_page, cb_kwargs=dict(item=item)
)
def parse_additional_page(self, response, item):
item["additional_data"] = response.xpath(
'//p[@id="additional_data"]/text()'
).get()
return item
此 spider 将开始爬取 example.com 的主页,收集类别链接和商品链接,并使用 parse_item 方法解析商品链接。对于每个商品响应,将使用 XPath 从 HTML 中提取一些数据,并使用这些数据填充一个 Item 对象。
XMLFeedSpider
- class scrapy.spiders.XMLFeedSpider[source]
XMLFeedSpider 设计用于通过按某个节点名称迭代来解析 XML feeds。迭代器可以选择:
iternodes、xml和html。建议出于性能原因使用iternodes迭代器,因为xml和html迭代器会一次性生成整个 DOM 以进行解析。但是,在解析标记错误的 XML 时,使用html作为迭代器可能会很有用。要设置迭代器和标签名称,必须定义以下类属性:
- iterator
一个字符串,定义要使用的迭代器。可以是以下之一:
默认为:
'iternodes'。
- itertag
一个字符串,包含要迭代的节点(或元素)的名称。示例:
itertag = 'product'
- 命名空间
namespaces
一个
(prefix, uri)元组列表,定义将由该 spider 处理的文档中可用的命名空间。prefix和uri将用于使用register_namespace()方法自动注册命名空间。然后可以在
itertag属性中指定带命名空间的节点。class YourSpider(XMLFeedSpider): namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')] itertag = 'n:url' # ...
示例:
- 一个方法,在 spider 开始解析之前,收到响应后立即被调用。它可以用于在解析响应体之前修改它。此方法接收一个响应,并返回一个响应(可以是同一个或另一个)。
-
此方法针对匹配提供的标签名称 (
itertag) 的节点调用。接收响应和每个节点的Selector对象。覆盖此方法是强制性的。否则,您的 spider 将无法工作。此方法必须返回一个 item 对象、一个Request对象,或包含它们中任何一个的可迭代对象。
警告
此方法针对 spider 返回的每个结果(item 或 request)调用,其目的是在将结果返回给框架核心之前执行任何最后的处理,例如设置 item ID。它接收结果列表和生成这些结果的响应。它必须返回结果(item 或 request)列表。
由于其内部实现,在编写基于 XMLFeedSpider 的 spider 时,您必须为新请求显式设置回调函数;否则可能发生意外行为。
XMLFeedSpider 示例
from scrapy.spiders import XMLFeedSpider
from myproject.items import TestItem
class MySpider(XMLFeedSpider):
name = "example.com"
allowed_domains = ["example.com"]
start_urls = ["http://www.example.com/feed.xml"]
iterator = "iternodes" # This is actually unnecessary, since it's the default value
itertag = "item"
def parse_node(self, response, node):
self.logger.info(
"Hi, this is a <%s> node!: %s", self.itertag, "".join(node.getall())
)
item = TestItem()
item["id"] = node.xpath("@id").get()
item["name"] = node.xpath("name").get()
item["description"] = node.xpath("description").get()
return item
这些 spider 非常易于使用,让我们看一个示例:
基本上,我们在上面做的是创建一个 spider,它从给定的 start_urls 下载一个 feed,然后迭代其每个 item 标签,将它们打印出来,并将一些随机数据存储在 Item 中。
- CSVFeedSpider
class scrapy.spiders.CSVFeedSpider[source]
-
此 spider 与 XMLFeedSpider 非常相似,不同之处在于它迭代行,而不是节点。每次迭代中调用的方法是
parse_row()。 delimiter
-
一个字符串,表示 CSV 文件中每个字段的分隔符。默认为
','(逗号)。 quotechar
-
一个字符串,表示 CSV 文件中每个字段的包围字符。默认为
'"'(引号)。 headers
-
此 spider 与 XMLFeedSpider 非常相似,不同之处在于它迭代行,而不是节点。每次迭代中调用的方法是
接收一个响应和一个字典(代表每一行),字典的每个键对应于 CSV 文件中提供的(或检测到的)每个头部。此 spider 还提供了覆盖 adapt_response 和 process_results 方法以进行预处理和后处理的机会。
CSVFeedSpider 示例
from scrapy.spiders import CSVFeedSpider
from myproject.items import TestItem
class MySpider(CSVFeedSpider):
name = "example.com"
allowed_domains = ["example.com"]
start_urls = ["http://www.example.com/feed.csv"]
delimiter = ";"
quotechar = "'"
headers = ["id", "name", "description"]
def parse_row(self, response, row):
self.logger.info("Hi, this is a row!: %r", row)
item = TestItem()
item["id"] = row["id"]
item["name"] = row["name"]
item["description"] = row["description"]
return item
让我们看一个与前面类似的示例,但使用 CSVFeedSpider:
- SitemapSpider
class scrapy.spiders.SitemapSpider[source]
SitemapSpider 允许您通过使用 Sitemaps (网站地图) 发现 URL 来爬取网站。
- 它支持嵌套的 sitemaps 和从 robots.txt 中发现 sitemap url。
sitemap_urls
指向您希望爬取的 sitemaps 的 URL 列表。
- 您也可以指向 robots.txt 文件,它将被解析以从中提取 sitemap url。
sitemap_rules
一个元组列表
(regex, callback),其中:regex是一个正则表达式,用于匹配从 sitemaps 中提取的 URL。regex可以是字符串或已编译的正则表达式对象。
例如
sitemap_rules = [('/product/', 'parse_product')]
callback是用于处理与正则表达式匹配的 URL 的回调函数。callback可以是字符串(表示 spider 方法的名称)或可调用对象。规则按顺序应用,只使用第一个匹配的规则。
-
如果省略此属性,则在 sitemaps 中找到的所有 URL 都将使用
parse回调函数进行处理。 sitemap_follow
应该遵循的 sitemap 的正则表达式列表。这仅适用于使用 Sitemap 索引文件 指向其他 sitemap 文件的网站。
- 默认情况下,所有 sitemaps 都会被遵循。
sitemap_alternate_links
例如
<url> <loc>http://example.com/</loc> <xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/> </url>
指定是否应遵循同一
url的备用链接。这些是同一网站的在其他语言的链接,在同一个url块中传递。如果设置了
sitemap_alternate_links,将检索这两个 URL。如果禁用sitemap_alternate_links,则只会检索http://example.com/。
-
默认情况下
sitemap_alternate_links被禁用。 sitemap_filter(entries)[source]
例如
<url> <loc>http://example.com/</loc> <lastmod>2005-01-01</lastmod> </url>
这是一个过滤器函数,可以被覆盖以根据其属性选择 sitemap 条目。
from datetime import datetime from scrapy.spiders import SitemapSpider class FilteredSitemapSpider(SitemapSpider): name = "filtered_sitemap_spider" allowed_domains = ["example.com"] sitemap_urls = ["http://example.com/sitemap.xml"] def sitemap_filter(self, entries): for entry in entries: date_time = datetime.strptime(entry["lastmod"], "%Y-%m-%d") if date_time.year >= 2005: yield entry
我们可以定义一个
sitemap_filter函数来按日期过滤entries:这将只检索在 2005 年及以后修改的
entries。Entries是从 sitemap 文档中提取的字典对象。通常,键是标签名,值是标签内的文本。需要注意的重要事项:
由于 loc 属性是必需的,没有此标签的条目将被丢弃。
备用链接存储在键为
alternate的列表中(参见sitemap_alternate_links)。
命名空间被移除,因此 lxml 中名为
{namespace}tagname的标签仅变为tagname。
如果省略此方法,将处理在 sitemaps 中找到的所有条目,同时遵循其他属性及其设置。
SitemapSpider 示例
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ["http://www.example.com/sitemap.xml"]
def parse(self, response):
pass # ... scrape item here ...
最简单的示例:使用 parse 回调函数处理通过 sitemaps 发现的所有 URL:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ["http://www.example.com/sitemap.xml"]
sitemap_rules = [
("/product/", "parse_product"),
("/category/", "parse_category"),
]
def parse_product(self, response):
pass # ... scrape product ...
def parse_category(self, response):
pass # ... scrape category ...
使用特定的回调函数处理某些 URL,使用不同的回调函数处理其他 URL:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ["http://www.example.com/robots.txt"]
sitemap_rules = [
("/shop/", "parse_shop"),
]
sitemap_follow = ["/sitemap_shops"]
def parse_shop(self, response):
pass # ... scrape shop here ...
遵循 robots.txt 文件中定义的 sitemaps,并且只遵循 URL 中包含 /sitemap_shop 的 sitemaps:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ["http://www.example.com/robots.txt"]
sitemap_rules = [
("/shop/", "parse_shop"),
]
other_urls = ["http://www.example.com/about"]
async def start(self):
async for item_or_request in super().start():
yield item_or_request
for url in self.other_urls:
yield Request(url, self.parse_other)
def parse_shop(self, response):
pass # ... scrape shop here ...
def parse_other(self, response):
pass # ... scrape other here ...