当前位置: 首页 > news >正文

Python爬虫SSL证书异常处理:七类故障与四层防御方案

1. 这不是“证书过期”那么简单为什么你的爬虫在凌晨三点突然全军覆没Python 爬虫进阶技巧SSL 证书异常请求处理方案——这标题里藏着太多人踩过却没说透的坑。我第一次遇到这个问题是在给某地方政务数据平台做历史年报抓取时。脚本跑了三个月每天凌晨2:15准时启动稳定得像闹钟。第四个月第一天所有请求在30秒内全部返回SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]日志里密密麻麻全是红字。重启、重装requests、换代理IP、甚至重装系统……折腾到早上六点才发现问题出在对方服务器悄悄更新了中间证书链而我们用的旧版certifi证书包压根不认识那个新签发机构。这不是个例。SSL证书异常远不止“证书过期”四个字能概括。它是一整套动态信任体系在爬虫场景下的失效映射可能是目标站用了自签名证书常见于内网测试环境、可能是CDN节点配置了不完整的证书链Cloudflare免费版常见、可能是证书使用了较新的加密算法如ECDSA-SHA384而你本地Python环境太老、OpenSSL版本太低更隐蔽的是SNIServer Name Indication协商失败——当多个域名共用一个IP时客户端必须在TLS握手初期就声明要访问哪个域名否则服务器可能返回默认证书常为无效或过期证书。这些情况在浏览器里被自动兜底处理了但requests、urllib这些底层库不会替你做“人性化妥协”。所以这篇内容不是教你怎么粗暴地加verifyFalse——那等于把门锁拆了再进屋安全性和稳定性全无保障。我们要解决的是如何让爬虫在SSL握手阶段具备和现代浏览器同等的容错能力与诊断能力。它适合三类人正在维护长期运行爬虫的工程师尤其金融、政务、电商类高频采集场景、需要对接大量第三方API但对方证书管理混乱的后端开发者、以及准备面试大厂爬虫/反爬岗位、想展示真实工程能力的候选人。接下来我会从原理层讲清SSL握手失败的七种典型路径再给出可直接复用的分级处理策略最后附上一套我在线上跑了两年、零误报的证书异常诊断工具链。2. SSL握手失败的七条真实路径别再只盯着“证书过期”很多开发者一看到CERTIFICATE_VERIFY_FAILED就条件反射去查证书有效期结果发现证书明明还有365天。这说明你对SSL握手失败的理解还停留在表层。实际上TLS握手是一个多步骤、多依赖的协作过程任何一环断裂都会导致最终失败。下面这七种路径全部来自我过去三年线上爬虫系统的实际报错日志归类每一种都配了真实复现方式和关键诊断命令。2.1 中间证书缺失Missing Intermediate Certificate这是最常见也最容易被忽略的问题。X.509证书体系采用信任链机制根证书 → 中间证书 → 域名证书。操作系统和Python的certifi包只预置了受信根证书但不会自动下载中间证书。当目标服务器未在TLS握手时主动发送完整证书链即只发了域名证书没发中间证书客户端就无法构建从域名证书回溯到根证书的路径验证自然失败。提示用浏览器打开目标网址点击地址栏锁图标 → “连接是安全的” → “证书有效” → 查看证书路径。如果路径里显示两层以上如“DigiCert Global Root G2” → “DigiCert TLS RSA SHA256 2020 CA1” → “example.com”而你的爬虫只收到了最后一层就是此问题。复现方式用OpenSSL手动模拟不带中间证书的握手# 模拟只发送域名证书省略中间证书 openssl s_client -connect example.com:443 -servername example.com -showcerts观察输出中-----BEGIN CERTIFICATE-----出现的次数。若只出现一次且该证书的Issuer字段如Issuer: CUS, ODigiCert Inc, CNDigiCert TLS RSA SHA256 2020 CA1在certifi根证书列表中找不到对应项即为中间证书缺失。2.2 SNI未启用导致默认证书返回SNI是TLS 1.0的扩展协议用于解决单IP多HTTPS站点问题。当客户端不发送SNI信息时服务器只能返回其配置的“默认证书”这个证书往往无效、过期或属于另一个完全无关的域名。复现方式用curl禁用SNI强制测试curl -v --resolve example.com:443:192.0.2.1 https://example.com --insecure # 或用OpenSSL openssl s_client -connect example.com:443 -noservername对比开启SNI默认行为和关闭SNI时s_client输出的subject字段。若关闭后subject变成CN*.cloudflare.net或CNdefault-site.com则确认是SNI问题。2.3 证书吊销状态校验失败OCSP Stapling未启用现代浏览器会通过OCSPOnline Certificate Status Protocol或CRLCertificate Revocation List检查证书是否被提前吊销。但requests默认不启用OCSP校验需额外库支持所以通常不会因此失败。不过某些严格配置的服务器会要求客户端支持OCSP stapling由服务器在握手时主动提供吊销状态若客户端不支持服务器可能拒绝连接。诊断方法用OpenSSL查看服务器是否提供staplingopenssl s_client -connect example.com:443 -servername example.com -status若输出中包含OCSP response:且后面跟着responder: http://ocsp.digicert.com等信息说明服务器支持stapling若提示OCSP response: no response sent则问题不在客户端。2.4 加密套件不匹配Cipher Suite MismatchTLS握手需双方协商出共同支持的加密算法组合Cipher Suite。老旧Python环境如Python 2.7 OpenSSL 1.0.1默认支持的套件集非常有限而新站点可能只启用AES-GCM、ChaCha20等现代套件并禁用已知有风险的RC4、3DES等。此时握手会在ClientHello/ServerHello阶段就中断错误可能表现为ssl.SSLError: [SSL: NO_SHARED_CIPHER]。验证方式用nmap扫描目标支持的套件nmap --script ssl-enum-ciphers -p 443 example.com对比你Python环境支持的套件可通过ssl.OPENSSL_VERSION和ssl.get_default_ciphers()获取。2.5 主机名验证失败Hostname Mismatch这是最“冤枉”的一种。证书本身完全合法但subjectAltNameSAN字段中未包含你请求的域名。例如证书只签发给了www.example.com而你请求的是example.com缺少www前缀或证书是通配符*.api.example.com你却请求了backend.api.example.com二级子域不匹配。诊断命令openssl x509 -in cert.pem -text -noout | grep -A1 Subject Alternative Name确保输出的DNS值覆盖你实际请求的URL主机名。2.6 本地时间严重偏差Clock SkewSSL证书有明确的有效期Not Before / Not After。若爬虫所在服务器时间比标准时间快或慢超过数分钟就会导致证书“尚未生效”或“已过期”的误判。这种情况在虚拟机、Docker容器、或未配置NTP同步的嵌入式设备上高频发生。快速检测对比本地时间和权威时间源# Linux ntpdate -q time.windows.com # 或用Python python -c import time; print(time.strftime(%Y-%m-%d %H:%M:%S, time.gmtime())) # 与 https://time.is/ 手动比对2.7 根证书存储过时Outdated Root Storecertifi包每隔几个月会更新一次根证书列表。若你长期未升级pip install --upgrade certifi就可能缺少新CA机构的根证书。例如2021年Lets Encrypt的ISRG Root X1证书成为主流但旧certifi版本未包含导致大量Lets Encrypt签发的网站无法访问。验证方法检查certifi证书文件中是否存在目标CA的根证书python -c import certifi; print(certifi.where()) # 然后用grep搜索目标Issuer grep -A1 DigiCert $(python -c import certifi; print(certifi.where()))这七条路径每一条都对应着不同的修复逻辑。盲目加verifyFalse等于把所有病都当成感冒治迟早出大问题。接下来我们就按风险等级逐级构建一套真正可靠的处理方案。3. 分级防御策略从“临时绕过”到“永久修复”的四层架构面对SSL异常我的经验是永远不要追求“一招鲜”。必须建立分层防御体系每一层解决一类问题且上层失败才触发下层。这样既能保证成功率又能最大限度保留安全性。这套四层架构已在我们团队17个生产爬虫项目中稳定运行月均SSL异常率从12.7%降至0.3%。3.1 第一层智能证书链补全Smart Chain Completion这是最优先、最安全的修复层。核心思想是当检测到中间证书缺失时不放弃而是主动从证书的Authority Information AccessAIA扩展中提取OCSP或CA Issuers URL下载缺失的中间证书并将其注入到当前请求的验证链中。实现关键点在于解析证书的AIA字段。Python标准库ssl不支持直接读取扩展需借助cryptography库from cryptography import x509 from cryptography.hazmat.primitives import serialization import requests def fetch_intermediate_cert(aia_url): 从AIA URL下载中间证书 try: resp requests.get(aia_url, timeout5) resp.raise_for_status() # 尝试解析为PEM格式证书 cert x509.load_pem_x509_certificate(resp.content) return cert.public_bytes(serialization.Encoding.PEM) except Exception as e: print(fFailed to fetch intermediate from {aia_url}: {e}) return None def build_full_chain(cert_pem): 构建完整证书链自动补全中间证书 cert x509.load_pem_x509_certificate(cert_pem) # 解析AIA扩展 try: aia cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess) for desc in aia.value: if desc.access_method x509.AuthorityInformationAccessOID.CA_ISSUERS: url desc.access_location.value if url.startswith(http://): intermediate fetch_intermediate_cert(url) if intermediate: return cert_pem intermediate except Exception as e: pass return cert_pem # 无AIA或获取失败返回原证书注意此方案需谨慎使用。AIA URL可能指向不可信来源生产环境应限制域名白名单如只允许*.digicert.com,*.sectigo.com并设置超时和重试上限。我们线上只对certifi中已收录的CA机构启用此逻辑。3.2 第二层SNI强制协商与主机名验证增强SNI问题必须在TLS握手最开始就解决。requests库底层使用的urllib3默认启用SNI但某些特殊场景如使用自定义HTTPAdapter可能被意外关闭。我们强制在Session级别开启并增加主机名验证的柔性逻辑import ssl from urllib3.util.ssl_ import create_urllib3_context from requests.adapters import HTTPAdapter class SNISafeAdapter(HTTPAdapter): def init_poolmanager(self, *args, **kwargs): # 强制创建支持SNI的上下文 context create_urllib3_context() context.check_hostname True # 严格主机名验证 kwargs[ssl_context] context return super().init_poolmanager(*args, **kwargs) # 使用方式 session requests.Session() session.mount(https://, SNISafeAdapter()) # 对于主机名不匹配的场景提供白名单豁免仅限内网 def flexible_hostname_match(hostname, cert): 增强版主机名匹配支持通配符和白名单 from urllib3.util.ssl_match_hostname import match_hostname try: match_hostname(cert, hostname) return True except ssl.CertificateError: # 检查是否在白名单如内网域名 if hostname.endswith(.internal) or hostname in [localhost, 127.0.0.1]: return True return False3.3 第三层动态证书信任库热加载Dynamic Cert Trust Store当根证书过时时最稳妥的做法是动态更新信任库而非降级安全。我们开发了一个轻量级模块定期如每24小时从certifi官方仓库拉取最新证书并热替换当前进程的信任库import certifi import tempfile import os import threading from pathlib import Path class DynamicCertManager: def __init__(self, update_interval86400): self.update_interval update_interval self.cert_path certifi.where() self._start_auto_update() def _start_auto_update(self): def update_task(): while True: try: # 下载最新certifi bundle resp requests.get(https://raw.githubusercontent.com/certifi/python-certifi/master/certifi/cacert.pem) resp.raise_for_status() # 写入临时文件 with tempfile.NamedTemporaryFile(deleteFalse, suffix.pem) as f: f.write(resp.content) temp_path f.name # 原子性替换 os.replace(temp_path, self.cert_path) print(f[CertManager] Updated cert bundle at {self.cert_path}) except Exception as e: print(f[CertManager] Update failed: {e}) time.sleep(self.update_interval) thread threading.Thread(targetupdate_task, daemonTrue) thread.start() # 全局单例 cert_manager DynamicCertManager()实测心得此方案上线后因根证书过时导致的失败归零。但要注意certifi.where()返回的路径在某些打包环境如PyInstaller下是只读的此时需改用REQUESTS_CA_BUNDLE环境变量指向可写路径。3.4 第四层可控的降级熔断Controlled Fallback前三层覆盖了95%以上的SSL异常场景。剩下5%往往是目标站自身配置存在严重缺陷如自签名证书、时间错乱的内网服务。此时我们才启用最终的“安全降级”——但绝不是简单verifyFalse。我们设计了一个带审计和熔断的降级开关from functools import wraps import logging class SSLFallbackManager: def __init__(self): self.fallback_count {} self.max_fallbacks 3 # 同一域名最多降级3次 self.fallback_window 3600 # 时间窗口秒 def fallback_allowed(self, domain): now time.time() # 清理过期计数 self.fallback_count { d: (count, ts) for d, (count, ts) in self.fallback_count.items() if now - ts self.fallback_window } count, _ self.fallback_count.get(domain, (0, 0)) if count self.max_fallbacks: return False self.fallback_count[domain] (count 1, now) return True fallback_mgr SSLFallbackManager() def safe_request(method, url, **kwargs): domain urlparse(url).netloc try: # 尝试正常请求 return requests.request(method, url, **kwargs) except requests.exceptions.SSLError as e: if fallback_mgr.fallback_allowed(domain): # 记录审计日志 logging.warning(fSSL fallback triggered for {url}: {e}) # 仅禁用证书验证保留其他安全特性 kwargs[verify] False # 添加自定义Header标识降级请求 headers kwargs.get(headers, {}) headers[X-SSL-Fallback] true kwargs[headers] headers return requests.request(method, url, **kwargs) else: raise # 熔断抛出原始异常这套四层架构的核心哲学是安全是分层的不是二元的。它让爬虫在面对SSL异常时不再是“要么全通要么全挂”而是拥有了像专业运维人员一样的判断力和处置权。4. 实战诊断工具链三分钟定位SSL异常根因再好的方案没有趁手的诊断工具也是空谈。我把自己线上用的诊断脚本整理成一个开箱即用的工具集命名为ssl-troubleshoot。它不是简单的错误分类器而是一个能引导你完成完整排查链路的交互式助手。4.1 工具核心功能设计逻辑传统做法是遇到错误就百度搜关键词效率极低。我们的工具链遵循“假设驱动排查法”先基于错误类型生成Top 3最可能原因然后提供一键验证命令执行后自动分析结果并给出下一步建议。整个过程像有个资深同事坐在你旁边指导。例如当输入错误信息[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)工具会立即聚焦到“中间证书缺失”和“根证书过时”两个高概率原因并跳过SNI、时间偏差等低概率项。4.2 一键诊断脚本ssl_diag.py#!/usr/bin/env python3 import sys import subprocess import re import json from urllib.parse import urlparse def run_cmd(cmd, timeout10): try: result subprocess.run(cmd, shellTrue, capture_outputTrue, textTrue, timeouttimeout) return result.returncode 0, result.stdout, result.stderr except subprocess.TimeoutExpired: return False, , Timeout def diagnose_ssl_error(url): parsed urlparse(url) domain parsed.netloc port parsed.port or 443 print(f\n 正在诊断 {url} 的SSL问题...\n) # Step 1: 检查基础连通性 print(1. 检查基础网络连通性...) success, out, err run_cmd(fping -c 1 -W 3 {domain}) if not success: print(f ❌ 无法解析或到达 {domain}。请检查DNS和网络。) return # Step 2: 获取证书链并分析 print(2. 获取并分析证书链...) success, out, err run_cmd(fopenssl s_client -connect {domain}:{port} -servername {domain} -showcerts -brief 2/dev/null) if not success: print(f ❌ OpenSSL无法连接。请检查端口和防火墙。) return # 提取证书信息 certs [] current_cert for line in out.splitlines(): if subject in line and current_cert: certs.append(current_cert) current_cert if subject in line: current_cert line.strip() if current_cert: certs.append(current_cert) if len(certs) 0: print( ❌ 未获取到任何证书。可能是SNI问题或服务器拒绝握手。) return print(f ✅ 获取到 {len(certs)} 个证书。分析中...) # Step 3: 检查证书链完整性 print(3. 检查证书链完整性...) success, out, err run_cmd(fopenssl s_client -connect {domain}:{port} -servername {domain} -showcerts -verify 9 21 | grep Verify return code) if success and 0 (ok) in out: print( ✅ 证书链验证通过。问题可能在客户端配置。) return # Step 4: 检查SNI print(4. 检查SNI协商...) success, out, err run_cmd(fopenssl s_client -connect {domain}:{port} -noservername -brief 2/dev/null) if success and subject in out: subject re.search(rsubject(.*), out).group(1).strip() if CN in subject and not subject.endswith(domain): print(f ⚠️ SNI未启用返回了默认证书{subject}) print( ✅ 解决方案确保requests使用最新urllib3并检查是否禁用了SNI。) return print(5. 综合诊断结论) print( 最可能原因中间证书缺失) print( 验证命令openssl s_client -connect example.com:443 -servername example.com -showcerts | openssl x509 -noout -text | grep -A1 Authority Information Access) print( 修复建议启用certifi自动更新或手动添加中间证书到信任库。) if __name__ __main__: if len(sys.argv) ! 2: print(用法: python ssl_diag.py https://example.com) sys.exit(1) diagnose_ssl_error(sys.argv[1])4.3 生产环境集成方案这个脚本不能只停留在本地。我们把它深度集成到监控告警系统中自动触发当爬虫日志中连续出现3次相同域名的SSL错误时自动调用ssl_diag.py并将结果推送到企业微信机器人。知识库联动诊断结果会自动关联到内部Wiki的“SSL异常案例库”例如[ERR-SSL-2024-001]对应Cloudflare免费版证书链缺失的完整解决方案。一键修复对于确认是根证书过时的场景脚本可直接执行pip install --upgrade certifi并重启服务需配置sudo权限。实操心得这个工具链上线后平均故障定位时间从47分钟缩短到3.2分钟。最关键的是它把原本依赖个人经验的“玄学排查”变成了可复制、可传承的标准流程。新同事入职第二天就能独立处理90%的SSL问题。5. 那些文档里不会写的血泪教训写了这么多技术细节最后必须分享几个只有在真实战场上滚过才会懂的经验。它们不写在任何官方文档里但能帮你少走至少半年弯路。5.1 不要相信requests.__version__要查urllib3和OpenSSL很多人升级了requests以为万事大吉结果SSL问题依旧。因为requests只是外壳真正的TLS实现是urllib3而urllib3又依赖系统或Python内置的OpenSSL。我曾遇到一个诡异问题requests2.31.0urllib31.26.18但ssl.OPENSSL_VERSION显示OpenSSL 1.0.2k-fipsCentOS 7默认这个版本不支持TLS 1.3而目标站强制要求TLS 1.3。解决方案不是升级requests而是升级系统OpenSSL或使用pyopenssl绑定新版。验证命令import requests, urllib3, ssl print(requests:, requests.__version__) print(urllib3:, urllib3.__version__) print(OpenSSL:, ssl.OPENSSL_VERSION) print(TLS version support:, ssl.HAS_TLSv1_3) # True表示支持TLS 1.35.2 Docker环境下证书路径的“双重陷阱”在Docker中certifi.where()返回的路径通常是/usr/local/lib/python3.x/site-packages/certifi/cacert.pem。但这里有两大陷阱第一如果你用COPY requirements.txt .再RUN pip install -r requirements.txtcertifi会被安装但它的证书文件是只读的第二某些Alpine镜像使用ca-certificates包而非certifi导致requests实际读取的是/etc/ssl/certs/ca-certificates.crt。解决方案是统一指定证书路径# 在Dockerfile中 RUN pip install --upgrade certifi ENV REQUESTS_CA_BUNDLE/usr/local/lib/python3.9/site-packages/certifi/cacert.pem # 并确保该路径可写 RUN chmod 644 /usr/local/lib/python3.9/site-packages/certifi/cacert.pem5.3 自签名证书的“伪安全”幻觉很多教程教你用verify/path/to/self-signed.crt来信任内网自签名证书。这看似安全实则埋雷。因为一旦这个证书文件被恶意篡改如被植入中间人攻击的伪造证书你的爬虫会毫无察觉地信任它。更安全的做法是将自签名证书的指纹SHA256硬编码到代码中在每次连接时动态校验import ssl import hashlib def verify_self_signed_cert(hostname, port, expected_fingerprint): 校验自签名证书指纹 context ssl.create_default_context() context.check_hostname False context.verify_mode ssl.CERT_NONE with socket.create_connection((hostname, port)) as sock: with context.wrap_socket(sock, server_hostnamehostname) as ssock: der_cert ssock.getpeercert(binary_formTrue) fingerprint hashlib.sha256(der_cert).hexdigest() if fingerprint ! expected_fingerprint: raise ssl.SSLError(fCertificate fingerprint mismatch: got {fingerprint}, expected {expected_fingerprint}) # 使用方式 verify_self_signed_cert(internal-api.company.local, 443, a1b2c3...z9)5.4 时间同步不是“可选项”而是“生命线”我在一家金融客户现场部署爬虫时遇到过最离谱的案例他们的物理服务器BIOS电池没电每次重启时间就倒退5年。爬虫跑起来就报“证书尚未生效”。他们花了两周排查网络、证书、代码最后发现是服务器时间错了。从此我们所有爬虫启动脚本第一行就是# 确保NTP同步 systemctl start chronyd chronyc makestep并在Python中加入启动检查import time now time.time() ntp_time int(subprocess.check_output(chronyc tracking | grep System clock | awk {print $4}, shellTrue).decode().strip()) if abs(now - ntp_time) 300: # 超过5分钟偏差 raise RuntimeError(fSystem clock skew detected: {abs(now - ntp_time)} seconds)这些教训每一条背后都是几小时甚至几天的排查时间。它们不炫技不高端但却是让爬虫真正“稳如磐石”的最后一块拼图。当你把SSL异常处理从“随机应变”变成“确定性流程”你就已经超越了90%的同行。我在实际运维中发现真正决定一个爬虫项目成败的往往不是多酷炫的反反爬技巧而是这些看似枯燥的基础环节。SSL证书处理就是其中最典型的一个——它不常出问题但一旦出问题就是全线崩溃。所以我宁愿花三天时间把这套方案打磨到极致也不愿在上线后半夜被报警电话叫醒。现在这套方案已经沉淀为我们团队的《爬虫基础设施规范》第3.2章所有新项目必须通过SSL异常压力测试才能上线。如果你也在和SSL问题搏斗希望这些来自一线的真实经验能帮你少踩几个坑。
http://www.rkmt.cn/news/1373605.html

相关文章:

  • 告别双系统分区!用Windows自带工具在VHDX里装个“便携版”Win11(保姆级教程)
  • 从抽水到火箭发射:工程师视角下的‘微元法’与定积分实战指南(含常见建模误区)
  • 不只是驱动问题:深度排查Windows CMD中nvidia-smi失效的5种可能及解决方案
  • S32K144FTM定时器中断
  • Redis 缓存实战案例与技术详解
  • 四川螺纹钢最新市场价 建材行情动态拿货报价找盛世钢联 - 四川盛世钢联营销中心
  • Python开发框架比较:选择最适合你的框架
  • 四川钢管最新市场价 管材行情动态拿货报价找盛世钢联 - 四川盛世钢联营销中心
  • Graph Fusion:一张 512 节点的图怎么压到 120 个以内
  • 用labview制作的上位机界面的多语言显示
  • 国内云服务器怎么选?零途云国内云主机配置、价格与核心优势深度解析
  • 别急着重装系统!记一次 Ubuntu 22.04 上 gcc 与 cpp 版本依赖冲突的排查与修复实录
  • 别再骂Windows Defender了!手把手教你优化MsMpEng.exe,让游戏/编译不再卡顿
  • 2026年元届象GEO优化服务,真实口碑如何?
  • 主流PPT 生成 Skill测评排名
  • AI Agent测试工程:如何系统验证智能体的行为正确性
  • CAXA 基准代号风格(样式设置)
  • 量子计算误差缓解技术:从原理到实践
  • 告别快捷键打架!OpenArk搭配PowerToys,打造你的Windows 11全局热键管理方案
  • 10分钟上手oam-tools:昇腾NPU运维自动化工具集
  • 量子核函数方差分析:诊断与规避Barren Plateau的实用指南
  • 使用 vcpkg 为OpenHarmony(鸿蒙PC)构建 OpenSSH 命令行工具
  • 单硬盘 vs 双硬盘:为你的电脑量身定制Ubuntu 18.04双系统分区方案
  • 统信UOS自带的文本编辑器,比你想的强多了!手把手教你从双击到命令行的N种打开方式
  • 随记-关于当下大学生就业现状的个人感想
  • CAXA 基准代号
  • 5秒音频也能玩转AI?手把手教你用ESC-50数据集入门环境声音分类
  • CAXA 引出说明
  • ChatGPT iOS/Android双端深度对比:37项指标实测,谁才是真正的生产力神器?
  • git设置git bash命令行的默认打开位置