
在构建解决方案之前,我们首先需要理解敌人。服务器返回403通常基于以下几点:
Referer头。Scrapy的架构之美在于其高度的可扩展性。下载器中间件是位于Scrapy引擎和下载器之间的钩子框架,用于全局处理请求和响应。这正是我们统一处理403状态的理想场所。
我们的核心思路是:创建一个自定义中间件,捕获所有状态码为403的响应,并按照预设策略自动重试该请求,同时在重试前对请求进行“修饰”以绕过检测。
我们将创建一个名为 Http403RetryMiddleware 的中间件。
假设你已经有一个Scrapy项目。如果没有,可以通过 scrapy startproject myproject 创建。我们在项目的 middlewares.py 文件中定义我们的中间件。
python
# middlewares.py
import random
import logging
from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message
logger = logging.getLogger(__name__)
class Http403RetryMiddleware(RetryMiddleware):
"""
自定义403重试中间件,继承自Scrapy内置的RetryMiddleware。
这样我们可以复用其重试逻辑和设置(如重试次数、重试延迟)。
"""
def process_response(self, request, response, spider):
# 核心方法:处理响应
# 如果响应状态码不是403,交给父类处理(处理429、500等)
if response.status != 403:
return super().process_response(request, response, spider)
# 记录警告日志
logger.warning(f"Intercepted 403 Forbidden for: {request.url}")
# 调用重试方法
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response
def process_request(self, request, spider):
"""
在请求发送前,对其进行“修饰”,增加反反爬虫措施。
这里会在每次重试(包括第一次)时被调用。
"""
self._enhance_request(request)
# 返回None,Scrapy会继续处理这个请求
return None
def _enhance_request(self, request):
"""
增强请求,添加或修改请求头,模拟浏览器行为。
"""
# 1. 设置一个常见的、真实的User-Agent池
user_agent_list = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36'
]
# 如果请求没有设置User-Agent,或者我们想要在重试时更换,可以在这里设置
if not request.headers.get('User-Agent'):
request.headers['User-Agent'] = random.choice(user_agent_list)
# 2. 设置Referer头,可以设置为同域名下的一个安全页面,或者谷歌
if not request.headers.get('Referer'):
# 这里简单设置为同域名的根目录,实际项目中可以根据情况动态设置
domain = request.url.split('/')[2] # 获取域名
request.headers['Referer'] = f'https://{domain}/'
# 3. 设置其他常见的请求头,使其更像浏览器
request.headers.setdefault('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8')
request.headers.setdefault('Accept-Language', 'zh-CN,zh;q=0.9,en;q=0.8')
request.headers.setdefault('Accept-Encoding', 'gzip, deflate, br')
request.headers.setdefault('DNT', '1')
request.headers.setdefault('Connection', 'keep-alive')
request.headers.setdefault('Upgrade-Insecure-Requests', '1')
logger.debug(f"Enhanced request headers for: {request.url}")仅仅创建中间件是不够的,我们需要在Scrapy项目的设置文件中启用它,并调整相关配置。
打开 settings.py 文件,进行如下修改:
python
# settings.py
# 下载器中间件配置
DOWNLOADER_MIDDLEWARES = {
# 首先停用内置的RetryMiddleware,因为我们自定义的中间件继承了它并会替代其功能。
'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
# 然后添加我们自定义的403重试中间件,优先级数字可以自定义(500-600是常用范围)
'myproject.middlewares.Http403RetryMiddleware': 550,
# 其他中间件...
}
# 重试设置
# 总的重试次数(包括第一次请求)
RETRY_TIMES = 3
# 需要重试的HTTP状态码,确保403在其中
RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429, 403] # 重点:加入了403
# 随机下载延迟,避免请求过于规律
DOWNLOAD_DELAY = 1
AUTOTHROTTLE_ENABLED = True # 推荐启用自动限速
# 并发请求数,根据目标网站承受能力调整
CONCURRENT_REQUESTS = 16
# 为特定网站设置自定义配置(可选)
CUSTOM_USER_AGENT = 'Mozilla/5.0 (compatible; MyBot/1.0; +http://mycompany.com)'你还可以在Spider内部根据特定站点的需求,微调中间件的行为。例如,为某个特定的难啃的网站准备一个独立的User-Agent列表。
python
# spiders/__init__.py
# 假设我们在spider中设置了自定义的meta来指导中间件
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
urls = ['https://example.com/page1', 'https://example-harder-site.com/page2']
for url in urls:
# 对于难处理的网站,可以设置一个标志,让中间件使用更激进的策略
meta = {}
if 'harder-site' in url:
meta['use_aggressive_retry'] = True
yield scrapy.Request(url=url, callback=self.parse, meta=meta)
def parse(self, response):
# ... 你的解析逻辑
pass然后,在中间件中,我们可以读取这个 meta 信息:
python
# 在 middlewares.py 的 process_request 方法中添加
def process_request(self, request, spider):
# 检查spider的meta中是否有特殊指令
if request.meta.get('use_aggressive_retry'):
# 例如,更换一个不同的、更复杂的User-Agent池
aggressive_agents = [...]
request.headers['User-Agent'] = random.choice(aggressive_agents)
else:
self._enhance_request(request) # 使用默认的增强方法
return None通过上述实现,我们成功地将403错误处理工程化:
RetryMiddleware,使我们能无缝接入Scrapy的重试、记录日志和统计系统。process_request 中,当重试次数超过一定阈值时,为请求设置代理 request.meta['proxy'] = some_proxy_url。curl_cffi 或 selenium)结合,在特定条件下使用它们来执行请求。结论:
在爬虫开发中,处理反爬虫机制不应是事后补救的散兵游勇,而应是项目初期就纳入设计的系统工程。利用Scrapy中间件对403状态码进行统一、智能化的处理,是迈向稳健、可维护、高效率的数据采集系统的关键一步。本文提供的方案不仅解决了403问题,更展示了一种工程化的思维模式,可举一反三应用于处理其他如429(请求过多)、JS挑战等复杂的爬虫挑战。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。