前言

换用mac之后主要在写java,python虽然刚拿到机子就装到了电脑上但却一直没怎么用过。今晚做东西需要用到nltk,但是下载nltk_data时却报错connect refuse,多番排查之后发现是ssl证书的问题。

解决

问题原因

从python 3.6起,python有一个内置的OpenSSL,没有使用mac自带的OpenSSL,所以导致了python的ssl证书和系统的证书并不一致,发起ssl连接时因证书问题而出错。

解决方案

既然定位到了问题,解决起来还是很简单的,我们只要让python内置的OpenSSL该用系统证书即可。

  1. Install Certificates.command
    为了解决这个问题,官方为我们提供了一个工具Install Certificates.command

    cd "/Applications/Python 3.7/"
    sudo "./Install Certificates.command"

    但是我的python是用brew装的,并没有安装python GUI,所以也不存在上述的目录,自然是无法解决问题的。

  2. 手动执行Install Certificates.command
    我在github上找到了Install Certificates.command的源码,阅读理解了其中的逻辑之后发现其实手动执行也是可行的,所以就复制到了本地打算手动执行。
    Install Certificates.command

    # install_certifi.py
    #
    # sample script to install or update a set of default Root Certificates
    # for the ssl module.  Uses the certificates provided by the certifi package:
    #       https://pypi.python.org/pypi/certifi
    import os
    import os.path
    import ssl
    import stat
    import subprocess
    import sys
    STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
                 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
                 | stat.S_IROTH |                stat.S_IXOTH )
    def main():
        openssl_dir, openssl_cafile = os.path.split(
            ssl.get_default_verify_paths().openssl_cafile)
        print(" -- pip install --upgrade certifi")
        subprocess.check_call([sys.executable,
            "-E", "-s", "-m", "pip", "install", "--upgrade", "certifi"])
        import certifi
        # change working directory to the default SSL directory
        os.chdir(openssl_dir)
        relpath_to_certifi_cafile = os.path.relpath(certifi.where())
        print(" -- removing any existing file or link")
        try:
            os.remove(openssl_cafile)
        except FileNotFoundError:
            pass
        print(" -- creating symlink to certifi certificate bundle")
        os.symlink(relpath_to_certifi_cafile, openssl_cafile)
        print(" -- setting permissions")
        os.chmod(openssl_cafile, STAT_0o775)
        print(" -- update complete")
    if __name__ == '__main__':
        main()

    写成python脚本直接sudo执行其实也是可以的,但是我又遇到了新问题:openssl_dir指向的目录又不存在,脚本异常退出。

  3. 最终成功方案
    首先,我在终端打印出来了openssl_dir, openssl_cafile两个变量的值
    openssl_dir
    打印出来的路径是/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/etc/ssl
    一找才发现这个目录到/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7就断了,etc/ssl根本不存在,于是就有了下面的解决办法。
    这个目录时python内置ssl证书的默认路径,那我其实可以创建好这个路径然后把它软链到系统证书路径,这样两边的证书就统一了。

    # 确保这个目录存在
    cd /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/
    sudo mkdir etc
    # 软链接到系统的ssl证书
    sudo ln -s /etc/ssl ./etc

    经过测试,可以正常使用。

后记

一般来说,这种问题的解决过程我是不会去专门写东西的(主要是懒!)。但是这次这个问题直接搜索基本上只能得到解决方案1,然后大概又要像我一样绕这么一圈才能解决问题。为了方便之后遇到问题的人(当然也包括我自己),在此记录。

Last modification:February 20, 2021
如果觉得我的文章对你有用,请随意赞赏