蜘蛛¶
蜘蛛是定义如何抓取特定网站(或一组网站)的类,包括如何执行爬取(例如,遵循链接)以及如何从其页面中提取结构化数据(例如,抓取项目)。换句话说,蜘蛛是在您为特定网站(或某些情况下,一组网站)的爬取和解析页面定义自定义行为的地方。
对于蜘蛛,抓取周期大致如下
您首先生成要爬取的初始 URL 的请求,并指定一个回调函数,该函数将在下载这些请求的响应时调用。
要执行的第一个请求是通过调用
start_requests()
方法获得的,该方法(默认情况下)为start_urls
中指定的 URL 生成Request
,并将parse
方法作为请求的回调函数。在回调函数中,您解析响应(网页)并返回 项目对象、
Request
对象或这些对象的迭代器。这些请求也将包含一个回调(可能是相同的),然后 Scrapy 将下载这些请求,然后由指定的回调处理其响应。在回调函数中,您解析页面内容,通常使用 选择器(但您也可以使用 BeautifulSoup、lxml 或您喜欢的任何机制)并使用解析的数据生成项目。
即使此循环适用于任何类型的蜘蛛(或多或少),但 Scrapy 中也捆绑了不同类型的默认蜘蛛以用于不同的目的。我们将在本文中讨论这些类型。
scrapy.Spider¶
- class scrapy.spiders.Spider¶
- class scrapy.Spider¶
这是最简单的蜘蛛,也是每个其他蜘蛛都必须继承的蜘蛛(包括与 Scrapy 捆绑在一起的蜘蛛,以及您自己编写的蜘蛛)。它不提供任何特殊功能。它只是提供了一个默认的
start_requests()
实现,该实现从start_urls
蜘蛛属性发送请求,并为每个生成的响应调用蜘蛛的方法parse
。- name¶
一个字符串,定义此蜘蛛的名称。蜘蛛名称是 Scrapy 如何定位(并实例化)蜘蛛的方式,因此它必须是唯一的。但是,没有任何内容阻止您实例化多个相同蜘蛛的实例。这是最重要的蜘蛛属性,并且是必需的。
如果蜘蛛抓取单个域,则一种常见做法是根据域命名蜘蛛,是否包含 TLD。因此,例如,抓取
mywebsite.com
的蜘蛛通常称为mywebsite
。
- allowed_domains¶
一个可选的字符串列表,包含此蜘蛛允许爬取的域。如果启用了
OffsiteMiddleware
,则不会遵循不属于此列表中指定的域名(或其子域)的 URL 的请求。假设您的目标 URL 是
https://www.example.com/1.html
,则将'example.com'
添加到列表中。
- start_urls¶
蜘蛛将从这里开始爬取的 URL 列表,当未指定特定 URL 时。因此,下载的第一批页面将是此处列出的页面。后续的
Request
将从 start URL 中包含的数据中依次生成。
- crawler¶
此属性由
from_crawler()
类方法在初始化类后设置,并链接到此蜘蛛实例绑定的Crawler
对象。爬虫封装了项目中的许多组件,以便于单一入口访问(例如扩展、中间件、信号管理器等)。请参阅 爬虫 API 以了解更多信息。
- state¶
一个字典,您可以使用它在批次之间保留一些蜘蛛状态。请参阅 在批次之间保持持久状态 以了解更多信息。
- from_crawler(crawler, *args, **kwargs)¶
这是 Scrapy 用于创建蜘蛛的类方法。
您可能不需要直接覆盖它,因为默认实现充当
__init__()
方法的代理,使用给定的参数args
和命名参数kwargs
调用它。但是,此方法在新的实例中设置了
crawler
和settings
属性,以便以后可以在蜘蛛代码中访问它们。2.11版本中变更: 现在可以在此方法中修改
crawler.settings
中的设置,如果您想根据参数修改它们,这将非常方便。因此,这些设置不是最终值,因为它们以后可以被例如插件修改。出于同样的原因,大多数Crawler
属性在此阶段尚未初始化。最终设置和初始化的
Crawler
属性可在start_requests()
方法、engine_started
信号的处理程序以及后续阶段获取。- 参数:
crawler (
Crawler
实例) – 爬虫将绑定到的爬虫args (列表) – 传递给
__init__()
方法的参数kwargs (字典) – 传递给
__init__()
方法的关键字参数
- 类方法 update_settings(settings)¶
update_settings()
方法用于修改爬虫的设置,并在爬虫实例初始化期间调用。它接收一个
Settings
对象作为参数,可以添加或更新爬虫的配置值。此方法是类方法,这意味着它在Spider
类上调用,并允许所有爬虫实例共享相同的配置。虽然可以在
custom_settings
中设置每个爬虫的设置,但使用update_settings()
允许您根据其他设置、爬虫属性或其他因素动态添加、删除或更改设置,并使用除'spider'
之外的设置优先级。此外,通过覆盖它,很容易在子类中扩展update_settings()
,而对custom_settings
执行相同的操作可能会很困难。例如,假设一个爬虫需要修改
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)
- start_requests()¶
此方法必须返回一个可迭代对象,其中包含要爬取的第一个请求和/或此爬虫的项目对象。当爬虫打开以进行抓取时,Scrapy 会调用它。Scrapy 只调用它一次,因此可以安全地将
start_requests()
实现为生成器。默认实现为
start_urls
中的每个URL生成Request(url, dont_filter=True)
。如果要更改用于开始抓取域的请求,则应覆盖此方法。例如,如果需要通过POST请求登录才能开始,则可以执行以下操作
import scrapy class MySpider(scrapy.Spider): name = "myspider" def start_requests(self): return [ scrapy.FormRequest( "http://www.example.com/login", formdata={"user": "john", "pass": "secret"}, callback=self.logged_in, ) ] def logged_in(self, response): # here you would extract links to follow and return Requests for # each of them, with another callback pass
- parse(response)¶
这是Scrapy用于处理已下载响应的默认回调,当它们的请求未指定回调时。
parse
方法负责处理响应并返回抓取的数据和/或要继续抓取的更多URL。其他请求回调与Spider
类具有相同的需求。此方法以及任何其他请求回调都必须返回一个
Request
对象、一个项目对象、一个Request
对象和/或项目对象的可迭代对象,或None
。- 参数:
response (
Response
) – 要解析的响应
- closed(reason)¶
当爬虫关闭时调用。此方法为
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)
从单个回调返回多个请求和项目
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_requests()
,而不是start_urls
;为了使数据具有更好的结构,您可以使用Item
对象
import scrapy
from myproject.items import MyItem
class MySpider(scrapy.Spider):
name = "example.com"
allowed_domains = ["example.com"]
def start_requests(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)
爬虫参数¶
爬虫可以接收修改其行为的参数。爬虫参数的一些常见用途是定义起始URL或将爬取限制到站点的某些部分,但它们可以用于配置爬虫的任何功能。
爬虫参数通过crawl
命令使用-a
选项传递。例如
scrapy crawl myspider -a category=electronics
爬虫可以在其__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__方法将获取任何爬虫参数并将它们复制到爬虫作为属性。上面的例子也可以写成如下形式
import scrapy
class MySpider(scrapy.Spider):
name = "myspider"
def start_requests(self):
yield scrapy.Request(f"http://www.example.com/categories/{self.category}")
如果您正在从脚本运行Scrapy,则可以在调用CrawlerProcess.crawl
或CrawlerRunner.crawl
时指定爬虫参数
process = CrawlerProcess()
process.crawl(MySpider, category="electronics")
请记住,爬虫参数仅为字符串。爬虫本身不会进行任何解析。如果要从命令行设置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
爬虫参数也可以通过Scrapyd schedule.json
API传递。请参阅Scrapyd文档。
通用爬虫¶
Scrapy附带了一些有用的通用爬虫,您可以使用它们作为爬虫的父类。它们的目标是为一些常见的抓取场景提供便利的功能,例如根据某些规则遵循站点上的所有链接,从站点地图抓取或解析XML/CSV提要。
对于以下爬虫中使用的示例,我们将假设您有一个项目,其中在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 继承的(您必须指定的)属性外,此类还支持一个新的属性
此爬虫还公开了一个可覆盖的方法
爬取规则¶
- 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
是一个 链接提取器 对象,它定义了如何从每个爬取的页面中提取链接。每个生成的链接都将用于生成一个Request
对象,该对象将在其meta
字典中(在link_text
键下)包含链接的文本。如果省略,则将使用不带参数创建的默认链接提取器,从而导致提取所有链接。callback
是一个可调用对象或字符串(在这种情况下,将使用具有该名称的爬虫对象中的方法),用于为使用指定的链接提取器提取的每个链接调用。此回调接收Response
作为其第一个参数,并且必须返回单个实例或 item 对象 和/或Request
对象(或它们的任何子类)的可迭代对象。如上所述,接收到的Response
对象将在其meta
字典中(在link_text
键下)包含生成Request
的链接的文本。cb_kwargs
是一个字典,其中包含要传递给回调函数的关键字参数。follow
是一个布尔值,它指定是否应从使用此规则提取的每个响应中跟踪链接。如果callback
为 None,则follow
默认为True
,否则默认为False
。process_links
是一个可调用对象或字符串(在这种情况下,将使用具有该名称的爬虫对象中的方法),它将针对使用指定的link_extractor
从每个响应中提取的每个链接列表调用。这主要用于过滤目的。process_request
是一个可调用对象(或字符串,在这种情况下,将使用具有该名称的爬虫对象中的方法),它将针对此规则提取的每个Request
调用。此可调用对象应将所述请求作为第一个参数,并将请求源自的Response
作为第二个参数。它必须返回一个Request
对象或None
(以过滤掉请求)。errback
是一个可调用对象或字符串(在这种情况下,将使用具有该名称的爬虫对象中的方法),如果在处理规则生成的请求时引发任何异常,则将调用它。它接收Twisted Failure
实例作为第一个参数。警告
由于其内部实现,在编写基于
CrawlSpider
的爬虫时,您必须显式设置新请求的回调;否则可能会发生意外行为。版本 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
此爬虫将从 example.com 的主页开始爬取,收集类别链接和项目链接,并使用 parse_item
方法解析后者。对于每个项目响应,将使用 XPath 从 HTML 中提取一些数据,并用它填充一个 Item
。
XMLFeedSpider¶
- class scrapy.spiders.XMLFeedSpider[source]¶
XMLFeedSpider 旨在通过根据特定节点名称迭代来解析 XML Feed。迭代器可以选择:
iternodes
、xml
和html
。出于性能考虑,建议使用iternodes
迭代器,因为xml
和html
迭代器会一次性生成整个 DOM 以进行解析。但是,当解析标记有错误的 XML 时,使用html
作为迭代器可能很有用。要设置迭代器和标签名称,您必须定义以下类属性
- iterator¶
定义要使用的迭代器的字符串。它可以是
'iternodes'
- 基于正则表达式的快速迭代器'html'
- 使用Selector
的迭代器。请记住,这使用 DOM 解析并且必须将所有 DOM 加载到内存中,这对于大型 Feed 来说可能是一个问题'xml'
- 使用Selector
的迭代器。请记住,这使用 DOM 解析并且必须将所有 DOM 加载到内存中,这对于大型 Feed 来说可能是一个问题
默认为:
'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 还具有以下可重写方法
- adapt_response(response)[source]¶
一个方法,在 Spider 开始解析之前,从 Spider 中间件收到响应后立即接收该响应。它可以用于在解析响应主体之前修改它。此方法接收一个响应并返回一个响应(它可以是相同的响应或另一个响应)。
- parse_node(response, selector)[source]¶
此方法针对与提供的标签名称 (
itertag
) 匹配的节点调用。接收响应和每个节点的Selector
。重写此方法是强制性的。否则,您的 Spider 将无法工作。此方法必须返回一个 item 对象、一个Request
对象或包含其中任何一个的可迭代对象。
- process_results(response, results)[source]¶
此方法针对 Spider 返回的每个结果(项目或请求)调用,旨在执行在将结果返回到框架核心之前所需的任何最后处理,例如设置项目 ID。它接收结果列表和生成这些结果的响应。它必须返回结果列表(项目或请求)。
警告
由于其内部实现,在编写基于
XMLFeedSpider
的 Spider 时,您必须显式设置新请求的回调;否则可能会出现意外行为。
XMLFeedSpider 示例¶
这些 Spider 使用起来非常简单,让我们看一个示例
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,它从给定的 start_urls
下载 Feed,然后遍历其每个 item
标签,打印它们,并在 Item
中存储一些随机数据。
CSVFeedSpider¶
- class scrapy.spiders.CSVFeedSpider[source]¶
此 Spider 与 XMLFeedSpider 非常相似,只是它迭代行而不是节点。每次迭代调用的方法是
parse_row()
。- delimiter¶
CSV 文件中每个字段的分隔符字符的字符串。默认为
','
(逗号)。
- quotechar¶
CSV 文件中每个字段的封闭字符的字符串。默认为
'"'
(引号)。
- headers¶
CSV 文件中列名的列表。
CSVFeedSpider 示例¶
让我们看一个类似于上一个示例的示例,但使用 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
SitemapSpider¶
- class scrapy.spiders.SitemapSpider[source]¶
SitemapSpider 允许您通过使用 站点地图 发现 URL 来抓取站点。
它支持嵌套站点地图并从 robots.txt 中发现站点地图 URL。
- sitemap_urls¶
指向您要抓取其 URL 的站点地图的 URL 列表。
您还可以指向 robots.txt,它将被解析以从中提取站点地图 URL。
- sitemap_rules¶
元组
(regex, callback)
列表,其中regex
是用于匹配从站点地图中提取的 URL 的正则表达式。regex
可以是 str 或已编译的正则表达式对象。callback
是用于处理与正则表达式匹配的 URL 的回调。callback
可以是字符串(指示 Spider 方法的名称)或可调用对象。
例如
sitemap_rules = [('/product/', 'parse_product')]
规则按顺序应用,并且只使用第一个匹配的规则。
如果省略此属性,则站点地图中找到的所有 URL 将使用
parse
回调进行处理。
- sitemap_alternate_links¶
指定是否应遵循一个
url
的备用链接。这些是在同一url
块中传递的,用于同一网站的不同语言的链接。例如
<url> <loc>http://example.com/</loc> <xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/> </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_filter
函数,根据日期过滤entries
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
这将仅检索 2005 年及以后修改的
entries
。Entries 是从站点地图文档中提取的字典对象。通常,键是标签名称,值是标签内的文本。
需要注意的是
由于 loc 属性是必需的,因此缺少此标签的条目将被丢弃
备用链接存储在键为
alternate
的列表中(参见sitemap_alternate_links
)命名空间被移除,因此命名为
{namespace}tagname
的 lxml 标签将仅变为tagname
如果您省略此方法,则将处理站点地图中找到的所有条目,并观察其他属性及其设置。
SitemapSpider 示例¶
最简单的示例:使用 parse
回调处理通过站点地图发现的所有 URL
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ["http://www.example.com/sitemap.xml"]
def parse(self, response):
pass # ... scrape item here ...
使用特定回调处理某些 URL,使用不同的回调处理其他 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 ...
遵循 robots.txt 文件中定义的站点地图,并且仅遵循 URL 包含 /sitemap_shop
的站点地图
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 ...
将 SitemapSpider 与其他 URL 源结合使用
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"]
def start_requests(self):
requests = list(super(MySpider, self).start_requests())
requests += [scrapy.Request(x, self.parse_other) for x in self.other_urls]
return requests
def parse_shop(self, response):
pass # ... scrape shop here ...
def parse_other(self, response):
pass # ... scrape other here ...