Scrapy 中间件
Scrapy 中间件的作用
中间件(Middleware)是一种可以介入到Scrapy的请求(request)-响应(response)处理过程中的钩子机制,允许开发者在执行特定任务时对请求或响应进行自定义处理。中间件可以看作是位于Scrapy引擎和爬虫、下载器之间的一个处理层,用于修改或扩展Scrapy的默认行为。
Scrapy 中间件的分类
1. 下载器中间件(Downloader Middleware)
下载器中间件用于处理发出的请求和下载的响应,它们在请求到达下载器之前或响应到达引擎之前执行。这类中间件可以用于:
- 修改请求头(比如User-Agent、Cookies等)
- 设置代理(如IP代理)
- 处理重定向
- 管理请求失败的重试机制
- 捕获和处理下载过程中发生的异常
2. 爬虫中间件(Spider Middleware)
爬虫中间件用于处理爬虫生成的请求以及接收到的响应。在引擎把响应交给爬虫处理之前,或者爬虫生成的请求交给引擎之前,爬虫中间件都有机会对这些对象进行处理。具体应用场景包括:
- 对爬虫产生的请求进行过滤或修改
- 处理从下载器返回的响应,比如进行一些前置的数据清理
- 修改从爬虫传出的项目(item)
下载器中间件方法
/*** 处理每一个经过下载器的请求** @param request 待处理的请求对象(scrapy.http.Request)* @param spider 当前正在运行的爬虫实例* @return * --------------- 可以返回 None,表示继续处理这个请求。* --------------- 可以返回 scrapy.http.Response,则中间件会返回这个响应对象并跳过下载过程,直接把响应传递给爬虫。* --------------- 可以返回 scrapy.exceptions.IgnoreRequest 异常,则请求会被丢弃,触发 spider 的 request_dropped 信号。*/
process_request(self, request, spider)
/*** 处理下载器返回的响应* * @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)
/*** 当下载器或者 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)
爬虫中间件方法
/*** 当下载器将响应传递给爬虫之前,调用此方法处理响应** @param response 下载器返回的响应对象* @param spider 当前的爬虫实例* @return * --------------- 如果返回 None,响应将继续传递给爬虫处理。* --------------- 可以抛出 scrapy.exceptions.IgnoreRequest,表示忽略此请求和响应,不会传递给爬虫。*/
process_spider_input(self, response, spider)
/*** 处理爬虫返回的结果(通常是 item 或新的 request)** @param response 传递给爬虫的响应对象* @param result 爬虫返回的结果,通常是生成器或列表,包含 Item 对象、Request 对象等* @param spider 当前的爬虫实例* @return 必须返回一个可迭代对象,包含 Item 对象或 Request 对象。如果需要,可以对结果进行过滤或修改。*/
process_spider_output(self, response, result, spider)
/*** 当爬虫在处理响应过程中抛出异常时,调用此方法处理异常** @param response 导致异常的响应对象* @param exception 抛出的异常对象* @param spider 当前的爬虫实例* @return * --------------- 如果返回 None,Scrapy将继续处理该异常。* --------------- 如果返回一个可迭代对象,将替代爬虫返回的结果。*/
process_spider_exception(self, response, exception, spider)
/*** 处理爬虫开始时生成的初始请求** @param start_requests 爬虫开始时生成的初始请求(可迭代对象)* @param spider 当前的爬虫实例* @return 必须返回一个可迭代对象,包含 Request 对象。*/
process_start_requests(self, start_requests, spider)
功能实现
实现随机 User-Agent
1. 在 Scrapy 项目的 middlewares.py 文件中自定义一个下载器中间件类,在这个中间件中,process_request 方法会为每个请求随机选择一个 User-Agent。
import randomclass RandomUserAgentMiddleware:def __init__(self, user_agents):self.user_agents = user_agents@classmethoddef 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
2. 在 Scrapy 项目的 settings.py 文件中配置中间件,并提供一个 User-Agent 列表。
# 配置多个 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
3. 验证是否成功
import scrapyclass 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 代理
1. 在 Scrapy 项目的 middlewares.py 文件中自定义一个下载器中间件类。
import random
import base64class RandomProxyMiddleware:def __init__(self, proxies):self.proxies = proxies@classmethoddef 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 ' + authrequest.meta['proxy'] = proto + proxy_urlelse:# 配置无账号密码的request.meta['proxy'] = proxy
2. 在 Scrapy 项目的 settings.py 文件中配置中间件,并提供一个代理列表。
# 定义代理列表,代理太多可以存在数据库中
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 动态数据加载渲染完成后再爬取数据
1. 在 Scrapy 项目的 middlewares.py 文件中自定义一个下载器中间件类。
from selenium import webdriver
import time
from scrapy.http import HtmlResponseclass 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地址# 打开urlself.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
2. 在 Scrapy 项目的 settings.py 文件中配置中间件。
# 开启下载器中间件
DOWNLOADER_MIDDLEWARES = {"myproject.middlewares.SeleniumMiddleware": 543,
}# 关闭 robots.txt 检查
ROBOTSTXT_OBEY = False
