Python请求在转换为.exe后无法找到具有证书的文件夹

26

我有一个从不同的营销系统汇总广告数据的程序,在转换成.exe格式并运行后,一切都正常工作,直到出现问题。

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\tkinter\__init__.py", line 1549, in __call__
    return self.func(*args)
  File "C:\Users\user\Desktop\alg\TSK_7. Marketing\report_gui.py", line 24, in <lambda>
    ok = tk.Button(root, text="DO NOT PRESS", bg="red", command=lambda: self.run())
  File "C:\Users\user\Desktop\alg\TSK_7. Marketing\report_gui.py", line 43, in run
    report.merge_all()
  File "C:\Users\user\Desktop\alg\TSK_7. Marketing\process_data.py", line 400, in merge_all
    fb_df     = self.fetch_fb()
  File "C:\Users\user\Desktop\alg\TSK_7. Marketing\process_data.py", line 156, in fetch_fb
    fb_campaigns = from_fb.run_fb(self.start_date, self.end_date)  # in JSON format
  File "C:\Users\user\Desktop\alg\TSK_7. Marketing\from_fb.py", line 110, in run_fb
    return s.get_stats()
  File "C:\Users\user\Desktop\alg\TSK_7. Marketing\from_fb.py", line 84, in get_stats
    params=params,
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\facebookads\adobjects\adaccount.py", line 1551, in get_insights
    return request.execute()
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\facebookads\api.py", line 653, in execute
    cursor.load_next_page()
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\facebookads\api.py", line 797, in load_next_page
    params=self.params,
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\facebookads\api.py", line 305, in call
    timeout=self._session.timeout
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\requests\sessions.py", line 508, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\requests\sessions.py", line 618, in send
    r = adapter.send(request, **kwargs)
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\requests\adapters.py", line 407, in send
    self.cert_verify(conn, request.url, verify, cert)
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\requests\adapters.py", line 226, in cert_verify
    "invalid path: {0}".format(cert_loc))
OSError: Could not find a suitable TLS CA certificate bundle, invalid path: C:\Users\user\AppData\Local\Temp\_MEI253762\facebookads\fb_ca_chain_bundle.crt

我尝试使用这段代码来修复,但每次运行此代码时,MEI文件夹的数字都会更改,所以没用。

dst = r'C:\Users\user\AppData\Local\Temp\_MEI120642\facebookads'
file = 'fb_ca_chain_bundle.crt'

try:
    os.makedirs(dst);  ## it creates the destination folder
except:
    pass

shutil.move(file, dst)

我访问了这个文件:

C:\Users\user\AppData\Local\Programs\Python\Python35\Lib\site-packages\requests\adapters.py

并尝试注释掉引发错误的 if 语句,但出现了 SSL 错误。我找不到生成那些 MEI 数字的代码片段。

def cert_verify(self, conn, url, verify, cert):
    """Verify a SSL certificate. This method should not be called from user
    code, and is only exposed for use when subclassing the
    :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

    :param conn: The urllib3 connection object associated with the cert.
    :param url: The requested URL.
    :param verify: Either a boolean, in which case it controls whether we verify
        the server's TLS certificate, or a string, in which case it must be a path
        to a CA bundle to use
    :param cert: The SSL certificate to verify.
    """
    if url.lower().startswith('https') and verify:

        cert_loc = None

        # Allow self-specified cert location.
        if verify is not True:
            cert_loc = verify

        if not cert_loc:
            cert_loc = DEFAULT_CA_BUNDLE_PATH

        if not cert_loc or not os.path.exists(cert_loc):
            raise IOError("Could not find a suitable TLS CA certificate bundle, "
                          "invalid path: {0}".format(cert_loc))

        conn.cert_reqs = 'CERT_REQUIRED'

        if not os.path.isdir(cert_loc):
            conn.ca_certs = cert_loc
        else:
            conn.ca_cert_dir = cert_loc
    else:
        conn.cert_reqs = 'CERT_NONE'
        conn.ca_certs = None
        conn.ca_cert_dir = None

    if cert:
        if not isinstance(cert, basestring):
            conn.cert_file = cert[0]
            conn.key_file = cert[1]
        else:
            conn.cert_file = cert
            conn.key_file = None
        if conn.cert_file and not os.path.exists(conn.cert_file):
            raise IOError("Could not find the TLS certificate file, "
                          "invalid path: {0}".format(conn.cert_file))
        if conn.key_file and not os.path.exists(conn.key_file):
            raise IOError("Could not find the TLS key file, "
                          "invalid path: {0}".format(conn.key_file))

我不得不在cert_verify()函数的开头放置一个return语句。这很糟糕,但它起作用了。 - Superbman
5个回答

18

我也遇到了这个问题。看起来是因为编译程序时没有在requests包目录中包含证书束cacert.pemrequests模块使用函数certifi.core.where来确定cacert.pem的位置。覆盖此函数和此函数设置的变量似乎可以解决该问题。

我将以下代码添加到我的程序开头:

import sys, os


def override_where():
    """ overrides certifi.core.where to return actual location of cacert.pem"""
    # change this to match the location of cacert.pem
    return os.path.abspath("cacert.pem")


# is the program compiled?
if hasattr(sys, "frozen"):
    import certifi.core

    os.environ["REQUESTS_CA_BUNDLE"] = override_where()
    certifi.core.where = override_where

    # delay importing until after where() has been replaced
    import requests.utils
    import requests.adapters
    # replace these variables in case these modules were
    # imported before we replaced certifi.core.where
    requests.utils.DEFAULT_CA_BUNDLE_PATH = override_where()
    requests.adapters.DEFAULT_CA_BUNDLE_PATH = override_where()

1
我看了很多“解决方案”,但只有这个有效!!!谢谢...所有其他的解决方案都会误导企业代理和证书。 - Vamsi Nerella
我遇到了相同的错误,但这个解决方案对我没有用。有什么帮助吗? - Isukali
我在 Databricks 中遇到了相同的错误,但这个解决方案对我没有起作用。有什么建议可以解决这个错误吗? - Pushkar

5

我通过输入以下代码解决了这个问题:

import os

import sys

import certifi

os.environ['REQUESTS_CA_BUNDLE'] = 
os.path.join(os.path.dirname(sys.argv[0]), certifi.where())

我通过这个方法检测“cacert.pem”文件的当前路径,并将其放入环境变量中


3
这可能与requests包有关。
我通过手动将cacert.pem文件从/lib/site-packages/certifi复制到/lib/site-packages/requests来解决这个问题。
如果您想修复这个与.exe相关的问题,请将cacert.pem文件从/lib/site-packages/certifi复制到dist/library.zip/certifi/
我想你使用了py2exe创建了exe,其中py2exe将在dist/下创建一个library.zip,它包含所有脚本依赖项。 我不知道其他exe转换器是否会创建library.zip

如果 exe 文件没有使用 library.zip 库进行构建会怎么样? - Haibrayn González

3
我在使用 PyInstaller 将程序转换为 .exe 时,使用 conda 的虚拟环境开发时遇到了与 requests 包相同的问题。后来,我改为使用Python 的虚拟环境(因为我的同事也这样做了,但没有遇到相同的错误),像往常一样安装了我的包,在使用 PyInstaller 创建 .exe 时没有遇到相同的错误。
我不确定 cacert.pem 文件是否被正确地打包到 Python 虚拟环境中的 .exe 中,但在 conda 虚拟环境中却被忽略了。也许其他人可以澄清这种情况,但我希望这能有所帮助!

1
这解决了我的问题!我实际上不需要使用 venv,只需创建一个空的 conda 环境,并使用 pip 安装所有所需的软件包即可。在该环境中运行 PyInstallercacert.pem 被正确地捆绑了。 - Erlend Magnus Viggen

-4

如果您想禁用证书验证,可以在requests.get()中使用verify=False参数:

requests.get('https://example.com', verify=False)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接