Scrapy 中间件的作用
中间件(Middleware)是一种可以介入到Scrapy的请求(request)-响应(response)处理过程中的钩子机制,允许开发者在执行特定任务时对请求或响应进行自定义处理。中间件可以看作是位于Scrapy引擎和爬虫、下载器之间的一个处理层,用于修改或扩展Scrapy的默认行为。
Scrapy 中间件的分类
1. 下载器中间件(Downloader Middleware)
下载器中间件用于处理发出的请求和下载的响应,它们在请求到达下载器之前或响应到达引擎之前执行。这类中间件可以用于:
- 修改请求头(比如User-Agent、Cookies等)
- 设置代理(如IP代理)
- 处理重定向
- 管理请求失败的重试机制
- 捕获和处理下载过程中发生的异常
2. 爬虫中间件(Spider Middleware)
爬虫中间件用于处理爬虫生成的请求以及接收到的响应。在引擎把响应交给爬虫处理之前,或者爬虫生成的请求交给引擎之前,爬虫中间件都有机会对这些对象进行处理。具体应用场景包括:
- 对爬虫产生的请求进行过滤或修改
- 处理从下载器返回的响应,比如进行一些前置的数据清理
- 修改从爬虫传出的项目(item)
下载器中间件方法
java
/**
* 处理每一个经过下载器的请求
*
* @param request 待处理的请求对象(scrapy.http.Request)
* @param spider 当前正在运行的爬虫实例
* @return
* --------------- 可以返回 None,表示继续处理这个请求。
* --------------- 可以返回 scrapy.http.Response,则中间件会返回这个响应对象并跳过下载过程,直接把响应传递给爬虫。
* --------------- 可以返回 scrapy.exceptions.IgnoreRequest 异常,则请求会被丢弃,触发 spider 的 request_dropped 信号。
*/
process_request(self, request, spider)
java
/**
* 处理下载器返回的响应
*
* @param request 产生这个响应的请求对象(scrapy.http.Request)
* @param response 当前的响应对象(scrapy.http.Response)
* @param spider 当前的爬虫实例
* @return
* --------------- 可以返回 response 对象(或者修改后的新 response 对象),继续交给下一个中间件处理或直接给爬虫。
* --------------- 可以返回一个新的 scrapy.http.Request,该请求会重新被调度和下载。
* --------------- 可以抛出 scrapy.exceptions.IgnoreRequest 异常,表示忽略此请求。
*/
process_response(self, request, response, spider)
java
/**
* 当下载器或者 process_request/process_response 方法抛出异常时,调用此方法处理异
*
* @param request 产生这个响应的请求对象(scrapy.http.Request)
* @param exception 抛出的异常对象
* @param spider 当前的爬虫实例
* @return
* --------------- 可以返回 None,继续交由其他中间件处理。
* --------------- 可以返回一个 scrapy.http.Response 对象,表示已经处理了该异常并提供了替代的响应。
* --------------- 可以返回一个 scrapy.http.Request 对象,以重新调度此请求。
*/
process_exception(self, request, exception, spider)
爬虫中间件方法
java
/**
* 当下载器将响应传递给爬虫之前,调用此方法处理响应
*
* @param response 下载器返回的响应对象
* @param spider 当前的爬虫实例
* @return
* --------------- 如果返回 None,响应将继续传递给爬虫处理。
* --------------- 可以抛出 scrapy.exceptions.IgnoreRequest,表示忽略此请求和响应,不会传递给爬虫。
*/
process_spider_input(self, response, spider)
java
/**
* 处理爬虫返回的结果(通常是 item 或新的 request)
*
* @param response 传递给爬虫的响应对象
* @param result 爬虫返回的结果,通常是生成器或列表,包含 Item 对象、Request 对象等
* @param spider 当前的爬虫实例
* @return 必须返回一个可迭代对象,包含 Item 对象或 Request 对象。如果需要,可以对结果进行过滤或修改。
*/
process_spider_output(self, response, result, spider)
java
/**
* 当爬虫在处理响应过程中抛出异常时,调用此方法处理异常
*
* @param response 导致异常的响应对象
* @param exception 抛出的异常对象
* @param spider 当前的爬虫实例
* @return
* --------------- 如果返回 None,Scrapy将继续处理该异常。
* --------------- 如果返回一个可迭代对象,将替代爬虫返回的结果。
*/
process_spider_exception(self, response, exception, spider)
java
/**
* 处理爬虫开始时生成的初始请求
*
* @param start_requests 爬虫开始时生成的初始请求(可迭代对象)
* @param spider 当前的爬虫实例
* @return 必须返回一个可迭代对象,包含 Request 对象。
*/
process_start_requests(self, start_requests, spider)
功能实现
实现随机 User-Agent
- 在 Scrapy 项目的 middlewares.py 文件中自定义一个下载器中间件类,在这个中间件中,process_request 方法会为每个请求随机选择一个 User-Agent。
python
import random
class RandomUserAgentMiddleware:
def __init__(self, user_agents):
self.user_agents = user_agents
@classmethod
def from_crawler(cls, crawler):
return cls(
# 获取 settings.py 中的配置
user_agents=crawler.settings.get('USER_AGENTS')
)
def process_request(self, request, spider):
# 从集合中随机取一个元素
user_agent = random.choice(self.user_agents)
if user_agent:
request.headers['User-Agent'] = user_agent
- 在 Scrapy 项目的 settings.py 文件中配置中间件,并提供一个 User-Agent 列表。
python
# 配置多个 USER_AGENT
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 SLBrowser/9.0.3.5211 SLBChan/25",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/57.0 Safari/537.3"
]
# 开启下载器中间件
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.RandomUserAgentMiddleware": 543,
}
# 关闭 robots.txt 检查
ROBOTSTXT_OBEY = False
- 验证是否成功
python
import scrapy
class BaiduSpider(scrapy.Spider):
name = "baidu"
allowed_domains = ["www.baidu.com"]
start_urls = ["https://www.baidu.com"]
def parse(self, response):
print("随机 User-Agent:", response.request.headers['User-Agent'])
实现随机 IP 代理
- 在 Scrapy 项目的 middlewares.py 文件中自定义一个下载器中间件类。
python
import random
import base64
class RandomProxyMiddleware:
def __init__(self, proxies):
self.proxies = proxies
@classmethod
def from_crawler(cls, crawler):
# 从 settings.py 中获取 PROXIES 列表
return cls(
proxies=crawler.settings.get('PROXIES')
)
def process_request(self, request, spider):
# 随机选择一个代理
proxy = random.choice(self.proxies)
if '@' in proxy:
# 配置带账号密码的
credentials, proxy_url = proxy.split('@')
proto, credentials = credentials.split('//')
proto = proto + "//"
auth = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
request.headers['Proxy-Authorization'] = 'Basic ' + auth
request.meta['proxy'] = proto + proxy_url
else:
# 配置无账号密码的
request.meta['proxy'] = proxy
- 在 Scrapy 项目的 settings.py 文件中配置中间件,并提供一个代理列表。
python
# 定义代理列表,代理太多可以存在数据库中
PROXIES = [
# 无密码的写法
'http://122.116.xxx.xxx:8888',
# 带账号密码的写法,代理ip需要在服务商处进行购买
'http://user:pass@14.29.xxx.xxx:16817',
]
# 开启下载器中间件
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.RandomProxyMiddleware": 543,
}
# 关闭 robots.txt 检查
ROBOTSTXT_OBEY = False
实现 selenium 动态数据加载渲染完成后再爬取数据
- 在 Scrapy 项目的 middlewares.py 文件中自定义一个下载器中间件类。
python
from selenium import webdriver
import time
from scrapy.http import HtmlResponse
class SeleniumMiddleware:
def __init__(self):
# 打开一个无头模式的谷歌浏览器
# options = webdriver.ChromeOptions()
# options.add_argument('--headless')
# options.add_argument('--disable-gpu') # 如果你使用的是 Windows 系统,需要加上这行
# driver = webdriver.Chrome(options=options)
# 打开一个 谷歌浏览器
self.driver = webdriver.Chrome()
def process_request(self, request, spider):
url = request.url
# 比如 http://abc.com/123?day=111 这个url中的数据要加载很久才能渲染完成显示出来,这时就需要对这个url使用 selenium 动态加载
if 'day' in url: # 判断url中包含的字符串,用于找到这个url地址
# 打开url
self.driver.get(url)
# 等待渲染完成时间
time.sleep(5)
# 拿到渲染完成后的页面数据
data = self.driver.page_source
# 关闭浏览器
self.driver.close()
# 封装新的response对象返回给爬虫,让爬虫从data中爬取数据
response = HtmlResponse(url=url, body=data, encoding='utf-8', request=request)
return response
- 在 Scrapy 项目的 settings.py 文件中配置中间件。
python
# 开启下载器中间件
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.SeleniumMiddleware": 543,
}
# 关闭 robots.txt 检查
ROBOTSTXT_OBEY = False