爬虫
Spider 是类,它们定义了如何抓取某个网站(或一组网站),包括如何执行抓取(即跟随链接)以及如何从页面中提取结构化数据(即抓取 Item)。换句话说,Spider 是定义特定网站(或在某些情况下,一组网站)的爬取和解析页面自定义行为的地方。
对于爬虫而言,抓取周期大致如下:
你首先生成初始请求来爬取第一个 URL,并指定一个回调函数来处理从这些请求下载到的响应。
要执行的第一个请求是通过迭代
start()
方法获得的,该方法默认会为start_urls
spider 属性中的每个 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 需要修改
FEEDS
import 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 ...