爬虫¶
蜘蛛是定义如何抓取某个站点(或一组站点)的类,包括如何执行抓取(即跟踪链接)以及如何从其页面中提取结构化数据(即抓取项目)。换句话说,蜘蛛是你为特定站点(或在某些情况下,一组站点)定义自定义抓取和解析页面行为的地方。
对于爬虫来说,抓取周期大致如下:
你首先生成初始请求来爬取第一个URL,并指定一个回调函数,该函数将在从这些请求下载的响应中被调用。
首先执行的请求是通过调用
start_requests()方法获得的,该方法(默认情况下)为start_urls中指定的URL生成Request,并将parse方法作为请求的回调函数。在回调函数中,您解析响应(网页)并返回 项目对象,
Request对象,或这些对象的可迭代集合。 这些请求还将包含一个回调(可能是相同的),然后由Scrapy下载,然后它们的响应由指定的回调处理。在回调函数中,您通常使用Selectors(但您也可以使用BeautifulSoup、lxml或任何您喜欢的机制)来解析页面内容,并使用解析后的数据生成项目。
最后,从爬虫返回的项目通常会持久化到数据库中(在某些Item Pipeline中)或使用Feed exports写入文件。
尽管这个周期(或多或少)适用于任何类型的蜘蛛,但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请求。假设你的目标网址是
https://www.example.com/1.html, 然后将'example.com'添加到列表中。
- start_urls¶
当没有指定特定URL时,蜘蛛开始爬取的URL列表。因此,首先下载的页面将是此处列出的页面。随后的
Request将依次从起始URL中包含的数据生成。
- crawler¶
此属性由
from_crawler()类方法在初始化类后设置,并链接到Crawler对象,该对象与此蜘蛛实例绑定。爬虫在项目中封装了许多组件,以便通过单一入口访问(如扩展、中间件、信号管理器等)。更多信息请参见Crawler API。
- logger¶
使用Spider的
name创建的Python logger。你可以通过它发送日志消息,如从Spider记录日志中所述。
- state¶
一个字典,您可以使用它在批次之间保持一些爬虫状态。 有关更多信息,请参见在批次之间保持持久状态。
- from_crawler(crawler, *args, **kwargs)¶
这是Scrapy用来创建你的爬虫的类方法。
你可能不需要直接覆盖这个,因为默认实现充当了
__init__()方法的代理,使用给定的参数args和命名参数kwargs调用它。尽管如此,此方法在新实例中设置了
crawler和settings属性,以便稍后在蜘蛛的代码中可以访问它们。在版本2.11中更改:现在可以在该方法中修改
crawler.settings中的设置,如果您想根据参数修改它们,这将非常方便。因此,这些设置不是最终值,因为它们可以在以后由例如附加组件修改。出于同样的原因,大多数Crawler属性在此阶段未初始化。最终的设置和初始化的
Crawler属性在start_requests()方法中可用,以及engine_started信号的处理程序和之后。- Parameters:
爬虫 (
Crawler实例) – 蜘蛛将绑定到的爬虫args (list) – 传递给
__init__()方法的参数kwargs (dict) – 传递给
__init__()方法的关键字参数
- classmethod 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。其他Requests回调与Spider类有相同的要求。此方法,以及任何其他请求回调,必须返回一个
Request对象,一个 item object, 一个可迭代的Request对象和/或 item objects,或者None。- Parameters:
response (
Response) – 要解析的响应
- log(message[, level, component])¶
通过Spider的
logger发送日志消息的包装器,为了向后兼容而保留。更多信息请参见从Spider记录日志。
- 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或将爬网限制在站点的某些部分,但它们可以用于配置蜘蛛的任何功能。
Spider参数通过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
Spider 参数也可以通过 Scrapyd 的 schedule.json API 传递。
参见 Scrapyd 文档。
通用爬虫¶
Scrapy 提供了一些有用的通用蜘蛛,您可以使用它们来子类化您的蜘蛛。它们的目的是为一些常见的抓取情况提供便利的功能,比如根据某些规则跟踪网站上的所有链接,从Sitemaps抓取,或解析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[源代码]¶
这是最常用于爬取常规网站的蜘蛛,因为它通过定义一组规则提供了一种方便的链接跟踪机制。它可能不是最适合您的特定网站或项目,但对于多种情况来说足够通用,因此您可以从中开始,并根据需要覆盖它以获得更多自定义功能,或者直接实现您自己的蜘蛛。
除了从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)[源代码]¶
link_extractor是一个 Link Extractor 对象,它定义了如何从每个爬取的页面中提取链接。每个生成的链接将用于生成一个Request对象,该对象将在其meta字典中包含链接的文本(在link_text键下)。如果省略,将使用没有参数创建的默认链接提取器,结果将提取所有链接。callback是一个可调用对象或字符串(如果是字符串,则使用蜘蛛对象中具有该名称的方法),用于为每个使用指定链接提取器提取的链接调用。此回调接收一个Response作为其第一个参数,并且必须返回单个实例或可迭代的 项目对象 和/或Request对象(或它们的任何子类)。如上所述,接收到的Response对象将在其meta字典中包含生成Request的链接文本(在link_text键下)。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[源代码]¶
XMLFeedSpider 旨在通过按特定节点名称迭代来解析 XML 源。迭代器可以从以下选项中选择:
iternodes、xml和html。出于性能考虑,建议使用iternodes迭代器,因为xml和html迭代器会一次性生成整个 DOM 以便解析。然而,当解析标记不良的 XML 时,使用html作为迭代器可能会很有用。要设置迭代器和标签名称,您必须定义以下类属性:
- iterator¶
一个字符串,用于定义要使用的迭代器。它可以是以下之一:
'iternodes'- 基于正则表达式的快速迭代器'html'- 一个使用Selector的迭代器。 请记住,这使用DOM解析并且必须将所有DOM加载到内存中, 这对于大型feed可能是一个问题'xml'- 一个使用Selector的迭代器。 请记住,这使用了DOM解析,并且必须将所有DOM加载到内存中, 这对于大型数据源可能是一个问题
默认值为:
'iternodes'。
- itertag¶
一个包含要迭代的节点(或元素)名称的字符串。示例:
itertag = 'product'
- namespaces¶
一个包含
(prefix, uri)元组的列表,这些元组定义了在该文档中可用的命名空间,这些命名空间将由此爬虫处理。prefix和uri将用于通过register_namespace()方法自动注册命名空间。然后你可以在
itertag属性中指定带有命名空间的节点。示例:
class YourSpider(XMLFeedSpider): namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')] itertag = 'n:url' # ...
除了这些新属性外,这个爬虫还有以下可重写的方法:
- adapt_response(response)[source]¶
一种方法,一旦从蜘蛛中间件接收到响应,就在蜘蛛开始解析之前接收它。它可以用于在解析之前修改响应体。此方法接收一个响应并返回一个响应(可能是相同的或另一个)。
- parse_node(response, selector)[来源]¶
此方法用于匹配提供的标签名称的节点 (
itertag)。接收响应和每个节点的Selector。重写此方法是强制性的。否则,你的爬虫将无法工作。此方法 必须返回一个项目对象,一个Request对象,或包含其中任何一个的可迭代对象。
- process_results(response, results)[源代码]¶
此方法针对蜘蛛返回的每个结果(项目或请求)调用,旨在执行返回结果到框架核心之前所需的任何最后处理,例如设置项目ID。它接收一个结果列表和产生这些结果的响应。它必须返回一个结果列表(项目或请求)。
警告
由于其内部实现,在编写基于
XMLFeedSpider的爬虫时,必须显式设置新请求的回调;否则可能会出现意外行为。
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
基本上,我们在上面所做的是创建一个蜘蛛,它从给定的start_urls下载一个feed,然后遍历每个item标签,打印它们,并将一些随机数据存储在Item中。
CSVFeedSpider¶
- class scrapy.spiders.CSVFeedSpider[source]¶
这个爬虫与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
站点地图爬虫¶
- class scrapy.spiders.SitemapSpider[源代码]¶
SitemapSpider 允许您通过使用 Sitemaps 发现 URL 来爬取网站。
它支持嵌套的站点地图,并可以从 robots.txt中发现站点地图的URL。
- sitemap_urls¶
指向您想要抓取的站点地图的URL列表。
你也可以指向一个robots.txt,它将被解析以从中提取站点地图URL。
- sitemap_rules¶
一个元组列表
(regex, callback)其中:regex是一个用于匹配从站点地图中提取的URL的正则表达式。regex可以是一个字符串或一个已编译的正则表达式对象。callback 是用于处理与正则表达式匹配的 URL 的回调函数。
callback可以是一个字符串(表示蜘蛛方法的名称)或一个可调用对象。
例如:
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。条目是从站点地图文档中提取的字典对象。 通常,键是标签名称,值是其中的文本。
重要的是要注意到:
由于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文件中定义的站点地图,并且只跟踪包含/sitemap_shop的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 ...
将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 ...