在Python3中,使用scrapy爬取https网页数据过程中,遇到以下错误提示,经过不断调试,终于完成该问题的解决方式:
1、问题错误提示信息
Traceback (most recent call last):
File “/usr/lib/python3/dist-packages/scrapy/core/downloader/middleware.py”, line 44, in process_request
defer.returnValue((yield download_func(request=request, spider=spider)))
twisted.web._newclient.ResponseNeverReceived: [<twisted.python.failure.Failure OpenSSL.SSL.Error: [(‘SSL routines’, ‘tls_process_ske_dhe’, ‘dh key too small’)]>]
2、问题分析:
根据SSL实验室的测试,该网站确实使用了不安全的HTTPS配置,OpenSSL拒绝与之对话。
3、解决方案:
方案一:
将Scrapy.core.downloader.contextfactory.ScrapyClientContextFactory子类化,用可接受的密码替换默认的\u密码。从OpenSSLCipherString(‘DEFAULT:!DH’),然后将downloader\u CLIENTCONTEXTFACTORY设置设置为此新类。如果这样做有效的话,将密码字符串设置为一个粗略的设置可能是值得的,但是现在您需要这样做。加入以下代码:
from twisted.internet.ssl import AcceptableCiphers
from scrapy.core.downloader import contextfactory
contextfactory.DEFAULT_CIPHERS = AcceptableCiphers.fromOpenSSLCipherString('DEFAULT:!DH')
方案二:
修改 twisted 库里面的 _sslverify.py 文件
修改 C:\Users{你的用户名}\AppData\Local\Programs\Python\Python36-32\Lib\sitepackages\twisted\internet_sslverify.py (就是pip 安装插件的目录)
//需要添加的全局变量
_DEFAULT_CIPHERS = (
'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:DH+AES256:'
'ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:RSA+AESGCM:RSA+AES:RSA+HIGH:'
'!aNULL:!eNULL:!MD5:!3DES'
'HIGH:!DH:!aNULL'
)
//需要修改的方法
def _expandCipherString(cipherString, method, options):
ctx = SSL.Context(method)
ctx.set_options(options)
try:
//需要修改的地方,将原代码注释掉,在下面添加新代码
#ctx.set_cipher_list(cipherString.encode('ascii'))
ctx.set_cipher_list(_DEFAULT_CIPHERS)
except SSL.Error as e:
if e.args[0][0][2] == 'no cipher match':
return []
else:
raise
conn = SSL.Connection(ctx, None)
ciphers = conn.get_cipher_list()
if isinstance(ciphers[0], unicode):
return [OpenSSLCipher(cipher) for cipher in ciphers]
else:
return [OpenSSLCipher(cipher.decode('ascii')) for cipher in ciphers]
方案3:
修改 OpenSLL库里面 SLL.py 文件
修改 C:\Users{你的用户名}\AppData\Local\Programs\Python\Python36-32\Lib\site-packages\OpenSSL\SLL.py (就是pip 安装插件的目录)
将指定位置的代码修改。
这里的_DEFAULT_CIPHERS 变量需要自己定义一个全局变量
//需要添加的全局变量
_DEFAULT_CIPHERS = (
'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:DH+AES256:'
'ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:RSA+AESGCM:RSA+AES:RSA+HIGH:'
'!aNULL:!eNULL:!MD5:!3DES'
'HIGH:!DH:!aNULL'
)
//需要修改的方法
def set_cipher_list(self, cipher_list):
需要修改的地方,将原代码注释掉,在下面添加新代码
#cipher_list = _text_to_bytes_and_warn("cipher_list", cipher_list)
cipher_list = _text_to_bytes_and_warn("cipher_list", _DEFAULT_CIPHERS )
if not isinstance(cipher_list, bytes):
raise TypeError("cipher_list must be a byte string.")
_openssl_assert(
_lib.SSL_CTX_set_cipher_list(self._context, cipher_list) == 1
)
后两个方法也可以用,自己选择,记住一定不能删除原有代码,因为两个方法都是要修改库的源代码。这种修改可能在其他方面的使用时,会出现问题,所以不能删除源代码,以后出问题,可以再改回源代码。
这两个方法都不是很好,需要修改库源码,但可以临时用一下。
如有更好的方案,麻烦私信告诉我,谢谢