Spider 会话¶
前提条件
- 已阅读入门,知道如何创建并运行一个简单的 Spider。
- 熟悉 Fetcher 基础,了解 HTTP 会话和 Stealthy 会话的区别。
一个 Spider 可以同时使用多个 Fetcher 会话。例如,用快速的 HTTP 会话获取简单页面,用隐身浏览器会话获取受保护页面。本页介绍如何配置和使用会话。
什么是会话?¶
会话是一个预先配置好的 Fetcher 实例,会在整个抓取过程中保持运行。Spider 会复用会话,而不是为每个请求创建新连接或浏览器。这样速度更快,也更节省资源。
默认情况下,每个 Spider 都会创建一个 FetcherSession。重写 configure_sessions() 可以添加更多会话,或替换默认会话。这里只能使用各会话的异步版本,如下表所示:
| 会话类型 | 适用场景 |
|---|---|
| FetcherSession | 快速 HTTP 请求,不运行 JavaScript |
| AsyncStealthySession | JavaScript、浏览器自动化、绕过反机器人保护 |
配置会话¶
在 Spider 中重写 configure_sessions() 来配置会话。参数 manager 是一个 SessionManager 实例。使用 manager.add() 注册会话:
from scrapling.spiders import Spider, Response
from scrapling.fetchers import FetcherSession
class MySpider(Spider):
name = "my_spider"
start_urls = ["https://example.com"]
def configure_sessions(self, manager):
manager.add("default", FetcherSession())
async def parse(self, response: Response):
yield {"title": response.css("title::text").get("")}
manager.add() 方法支持以下参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
session_id |
str |
必填 | 在请求中引用此会话的名称 |
session |
Session |
必填 | 会话实例 |
default |
bool |
False |
将此会话设为默认会话 |
lazy |
bool |
False |
第一次使用时才启动会话 |
注意事项
- 如果请求没有指定会话,就会使用默认会话。默认会话通过以下两种方式之一确定:
- 第一个加入管理器的会话会自动成为默认会话。
- 添加会话时传入
default=True。
- 不需要自行提前启动传入的会话实例。Spider 会检查所有会话,并启动尚未运行的会话。
- 如果希望某个会话只在需要时启动,添加时使用
lazy参数即可。例如,让浏览器在真正需要时才启动,而不是随 Spider 一起启动。
多会话 Spider¶
下面是一个实际例子:用快速 HTTP 会话获取列表页,用隐身浏览器获取有反机器人保护的详情页:
from scrapling.spiders import Spider, Response
from scrapling.fetchers import FetcherSession, AsyncStealthySession
class ProductSpider(Spider):
name = "products"
start_urls = ["https://shop.example.com/products"]
def configure_sessions(self, manager):
# Fast HTTP for listing pages (default)
manager.add("http", FetcherSession())
self.browser_session = AsyncStealthySession(
headless=True,
network_idle=True,
record_requests=True,
)
manager.add("stealth", self.browser_session)
async def parse(self, response: Response):
for link in response.css("a.product::attr(href)").getall():
# Route product pages through the stealth session
yield response.follow(link, sid="stealth", callback=self.parse_product)
next_page = response.css("a.next::attr(href)").get()
if next_page:
yield response.follow(next_page)
async def parse_product(self, response: Response):
yield {
"name": response.css("h1::text").get(""),
"price": response.css(".price::text").get(""),
}
async def on_close(self):
for api_response in self.browser_session.network.search(
url_pattern=r"https://api\.shop\.example\.com/.*", limit=None
):
self.logger.info(f"Saved API response: {api_response.url} ({api_response.status})")
示例保留了浏览器实例,并在会话关闭前,通过 on_close() 读取一次已保存的 API 历史记录。记录涵盖所有商品页面,关闭后仍可读取。尚未完成的记录不会被等待,记录数量限制仍然有效。
关键是 sid 参数,它告诉 Spider 每个请求应该使用哪个会话。调用 response.follow() 时如果未指定 sid,就会继承原请求的会话 ID。
会话不必来自不同的类。也可以使用同一个类的多个实例,并为它们设置不同的配置:
from scrapling.spiders import Spider, Response
from scrapling.fetchers import FetcherSession
class ProductSpider(Spider):
name = "products"
start_urls = ["https://shop.example.com/products"]
def configure_sessions(self, manager):
chrome_requests = FetcherSession(impersonate="chrome")
firefox_requests = FetcherSession(impersonate="firefox")
manager.add("chrome", chrome_requests)
manager.add("firefox", firefox_requests)
async def parse(self, response: Response):
for link in response.css("a.product::attr(href)").getall():
yield response.follow(link, callback=self.parse_product)
next_page = response.css("a.next::attr(href)").get()
if next_page:
yield response.follow(next_page, sid="firefox")
async def parse_product(self, response: Response):
yield {
"name": response.css("h1::text").get(""),
"price": response.css(".price::text").get(""),
}
也可以为某类请求保留一个独立会话,让它保有自己的 Cookie 和状态。
会话参数¶
传给 Request 或通过 response.follow(**kwargs) 传入的额外关键字参数,会交给会话的获取方法。这样就能单独调整请求,而不必改变会话配置:
async def parse(self, response: Response):
# Pass extra headers for this specific request
yield Request(
"https://api.example.com/data",
headers={"Authorization": "Bearer token123"},
callback=self.parse_api,
)
# Use a different HTTP method
yield Request(
"https://example.com/submit",
method="POST",
data={"field": "value"},
sid="firefox",
callback=self.parse_result,
)
Warning
通常使用 FetcherSession、Fetcher 或 AsyncFetcher 时,会通过 .get()、.post() 等方法选择 HTTP 方法。但在 Spider 中使用 FetcherSession 时不能这样做。请求默认为 HTTP GET;若要使用其他 HTTP 方法,必须通过 method 参数指定,如上例所示。这是为了让所有会话类型使用统一的 Request 接口。
对于上面多会话示例中注册的 "stealth" 会话(AsyncStealthySession),可以传入 wait_selector、page_action 或 extra_headers 等浏览器专用参数:
async def parse(self, response: Response):
# Use Cloudflare solver with the `AsyncStealthySession` we configured above
yield Request(
"https://nopecha.com/demo/cloudflare",
sid="stealth",
callback=self.parse_result,
solve_cloudflare=True,
block_webrtc=True,
hide_canvas=True,
google_search=True,
)
yield response.follow(
"/dynamic-page",
sid="stealth",
callback=self.parse_dynamic,
wait_selector="div.loaded",
network_idle=True,
)
Warning
response.follow() 会继承原请求的会话参数(**kwargs),但不包括 method、params、data、json、files 和 multipart。因此,POST 请求之后跟随链接时,会发送普通 GET 请求。新传入的参数优先于继承的参数。
from scrapling.spiders import Spider, Response
from scrapling.fetchers import FetcherSession
class ProductSpider(Spider):
name = "products"
start_urls = ["https://shop.example.com/products"]
def configure_sessions(self, manager):
manager.add("http", FetcherSession(impersonate='chrome'))
async def parse(self, response: Response):
# I don't want the follow request to impersonate a desktop Chrome like the previous request, but a mobile one
# so I override it like this
for link in response.css("a.product::attr(href)").getall():
yield response.follow(link, impersonate="chrome131_android", callback=self.parse_product)
next_page = response.css("a.next::attr(href)").get()
if next_page:
yield response.follow(next_page)
async def parse_product(self, response: Response):
yield {
"name": response.css("h1::text").get(""),
"price": response.css(".price::text").get(""),
}
Info
Spider 关闭时,管理器会自动检查是否还有运行中的会话,并在 Spider 结束前关闭它们。