为何Scrapy爬虫使用OpenSSL同款配置仍SSL握手失败?
问题描述
需要爬取一台存在SSL配置缺陷的服务器(无法修改服务器配置),该服务器仅支持TLS 1.2,且要求客户端提供SHA-1签名算法,因此客户端必须设置SECLEVEL=0。
使用OpenSSL命令行工具和Python原生SSL模块配置相同参数时,SSL握手可成功,但通过Scrapy的custom_settings设置DOWNLOADER_CLIENT_TLS_CIPHERS = 'DEFAULT:@SECLEVEL=0'后,始终出现SSL握手失败。Scrapy日志显示配置已生效,但连接目标站点https://www.legislation.gov.au时反复重试均失败。
已尝试降级OpenSSL到1.1.1版本、在MacOS环境测试等排查操作,问题仍未解决。
环境版本信息
- Scrapy: 2.10.1
- Python: 3.9.16
- OpenSSL: 1.1.1w / 3.0.9(不同测试环境)
- 操作系统: macOS 13.5 / Ubuntu 22.04
复现步骤
- 创建Scrapy项目及爬虫:
scrapy startproject ssl_test cd ssl_test scrapy genspider legislation legislation.gov.au
- 修改爬虫文件
legislation.py,添加custom_settings:
import scrapy class LegislationSpider(scrapy.Spider): name = "legislation" allowed_domains = ["legislation.gov.au"] start_urls = ["https://www.legislation.gov.au"] custom_settings = { 'DOWNLOADER_CLIENT_TLS_CIPHERS': 'DEFAULT:@SECLEVEL=0', 'LOG_LEVEL': 'DEBUG' } def parse(self, response): self.logger.info(f"Response status: {response.status}")
- 运行爬虫:
scrapy crawl legislation
验证测试(成功案例)
Python原生SSL模块测试代码
import ssl import socket context = ssl.create_default_context() context.set_ciphers('DEFAULT:@SECLEVEL=0') context.options |= ssl.OP_NO_TLSv1_3 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1 with socket.create_connection(('www.legislation.gov.au', 443)) as sock: with context.wrap_socket(sock, server_hostname='www.legislation.gov.au') as ssock: print(f"TLS version: {ssock.version()}") print(f"Cipher: {ssock.cipher()}")
运行后可正常输出TLS版本和加密套件,握手成功。
OpenSSL命令行测试
openssl s_client -connect www.legislation.gov.au:443 -cipher 'DEFAULT:@SECLEVEL=0' -tls1_2
可成功完成SSL握手,获取服务器证书。
Scrapy错误日志片段
2024-05-20 14:32:15 [scrapy.downloadermiddlewares.retry] DEBUG: Retrying <GET https://www.legislation.gov.au> (failed 1 times): [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', '', 'sslv3 alert handshake failure')]>] 2024-05-20 14:32:15 [scrapy.core.engine] DEBUG: Crawled (None) <GET https://www.legislation.gov.au> (referer: None) 2024-05-20 14:32:16 [scrapy.downloadermiddlewares.retry] DEBUG: Retrying <GET https://www.legislation.gov.au> (failed 2 times): [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', '', 'sslv3 alert handshake failure')]>] 2024-05-20 14:32:16 [scrapy.core.engine] DEBUG: Crawled (None) <GET https://www.legislation.gov.au> (referer: None) 2024-05-20 14:32:17 [scrapy.downloadermiddlewares.retry] DEBUG: Gave up retrying <GET https://www.legislation.gov.au> (failed 3 times): [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', '', 'sslv3 alert handshake failure')]>] 2024-05-20 14:32:17 [scrapy.core.scraper] ERROR: Error downloading <GET https://www.legislation.gov.au> Traceback (most recent call last): ... OpenSSL.SSL.Error: [('SSL routines', '', 'sslv3 alert handshake failure')]
解决方案
问题根源在于Scrapy底层依赖的Twisted库,仅设置DOWNLOADER_CLIENT_TLS_CIPHERS不足以覆盖所有SSL配置,需额外强制指定TLS版本并调整SSL上下文的安全级别。
方法1:自定义下载中间件修改SSL上下文
- 在项目的
middlewares.py中添加自定义上下文工厂:
from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory from OpenSSL import SSL class CustomContextFactory(ScrapyClientContextFactory): def getContext(self): ctx = super().getContext() # 禁用TLS 1.0/1.1/1.3,仅保留TLS 1.2 ctx.set_options(SSL.OP_NO_TLSv1_3 | SSL.OP_NO_TLSv1_1 | SSL.OP_NO_TLSv1) # 设置安全级别和加密套件 ctx.set_cipher_list('DEFAULT:@SECLEVEL=0') return ctx
- 在
settings.py中启用该工厂,替换默认配置:
DOWNLOADER_CLIENTCONTEXTFACTORY = 'ssl_test.middlewares.CustomContextFactory'
方法2:补充Scrapy TLS版本配置
除了加密套件,强制指定TLS 1.2版本:
custom_settings = { 'DOWNLOADER_CLIENT_TLS_CIPHERS': 'DEFAULT:@SECLEVEL=0', 'DOWNLOADER_CLIENT_TLS_METHOD': 'TLSv1.2', 'LOG_LEVEL': 'DEBUG' }
注:部分Scrapy版本中DOWNLOADER_CLIENT_TLS_METHOD需使用Twisted常量值'TLSv1_2_METHOD',可根据实际版本调整。
验证
修改后重新运行爬虫,可成功完成SSL握手并获取站点响应。
内容的提问来源于stack exchange,提问作者Simon
相关产品推荐
相关产品推荐

