阅读视图

发现新文章,点击刷新页面。

南墙 WAF 系列(三)– API SDK

手里乱七八糟的域名实在是有点多,而这些域名,多数都解析到了同样的站点。或者说是回源是同样的站点,最开始其实并没有套 cdn,中间有段时间连这种 301 302 跳转都被打,就变得很尴尬。301 跳转都能让沙雕给打挂,也的确什么好办法了。

后来发现盾云的 scdn性价比的确不错,于是一股脑的全部套了 scdn,不过,有的也的确没什么流量,于是一些没那么重要的东西放哪里就有些浪费:

所以,思索再三,几天申请了一台 99 块钱的海外轻量,每个月 1t 的流量,感觉应该足够应付这些乱七八糟的服务了。当然,最主要的还是要套上南墙 waf,直接裸奔感觉也不大行。

当然,waf 后面的回源多数还是家里的服务器。这就需要有个工具来更新 waf 的回源地址,今天又完善了一下南墙的 api 接口,代码直接提交 github 的,有需要可以自取。

功能特性

 

  • 证书管理
    • 获取证书列表
    • 检查证书
    • 提交证书配置
    • 删除证书
  • 站点配置
    • 获取站点列表
    • 更新站点配置
  • 用户认证
    • 用户名密码登录
    • 支持双因素认证(OTP)

环境要求

 

  • Python 3.x
  • 必需的 Python 包:
    • requests
    • jwt
    • urllib3

安装说明

 

  1. 克隆仓库:
git clone https://github.com/obaby/baby-nanqiang-waf-api-toos.git 
cd baby-nanqiang-waf-api-toos
  1. 安装依赖包:
pip install -r requirements.txt

 

使用说明

基础用法

from baby_nanqiang_api_tools import NanQiangAPI

# 初始化 API 客户端
api = NanQiangAPI(base_url="https://lang.bi:443")

# 登录
result = api.login(username="your_username", password="your_password")
if result:
    print("登录成功")
    
    # 获取证书列表
    cert_list = api.get_cert_list()
    if cert_list:
        parsed_certs = api.parse_cert_list(cert_list)
        print("证书列表:", parsed_certs)
证书管理
# 从文件检查证书
cert_result = api.check_cert_from_files("cert.pem", "key.pem")
if cert_result:
    # 提交证书配置
    submit_result = api.submit_cert_config(cert_result)
    
# 删除证书
delete_result = api.delete_cert(cert_id=123)

站点配置

 

# 获取站点列表
site_list = api.get_site_list()
if site_list:
    parsed_sites = api.parse_site_list(site_list)
    print("站点列表:", parsed_sites)

# 更新站点配置
update_result = api.update_site_config(
    ip="192.168.1.1",
    port=443,
    site_id=123,
    uid=456,
    description="我的网站"
)

仓库地址:
https://github.com/obaby/baby-nanqiang-waf-api-tools

The post 南墙 WAF 系列(三)– API SDK appeared first on obaby@mars.

南墙 WAF 系列(二)– 网站证书自动更新

相对管理后台的 ssl来说,其实网站的 ssl 证书才是正事,毕竟这个关系到网站的访问。按照官方的说法在开放 80 端口的情况下,南墙可以自动申请更新证书,不过后台没找到配置的地方,我的 v4 的 80 也是不通的,所以就需要自己去维护管理证书了。

然而,上午在问了管理之后,得到的答复是没有 api,可以自己抓包进行修改。

嗐,这么看来其实也没啥,最起码说明后台的 api 接口是可以直接拿来用的。即使是有 api 文档,也是得自己去看,去写,没有的话 curl 抓包一样能解决问题。按照之前的方法,只直接复制 curl 给 cursor 就可以了。

api 文件baby_nanqiang_api_tools.py内容:

#!/usr/bin/env python3
import requests
import json
import jwt
from datetime import datetime
import os
import urllib3

# 禁用 SSL 验证警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class NanQiangAPI:
    def __init__(self, base_url="https://lang.bi:443"):
        self.base_url = base_url
        self.session = requests.Session()
        self.session.verify = False  # 忽略SSL证书验证
        self.token = None
        self._setup_headers()

    def _setup_headers(self):
        """设置请求头"""
        self.headers = {
            'accept': 'application/json, text/plain, */*',
            'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
            'cache-control': 'no-cache',
            'content-type': 'application/json',
            'origin': self.base_url,
            'pragma': 'no-cache',
            'priority': 'u=1, i',
            'referer': f'{self.base_url}/',
            'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"macOS"',
            'sec-fetch-dest': 'empty',
            'sec-fetch-mode': 'cors',
            'sec-fetch-site': 'same-origin',
            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
        }

    def _update_headers_with_token(self):
        """更新请求头,添加token"""
        if self.token:
            self.headers['Authorization'] = self.token  # 直接使用token,不添加'Bearer '前缀

    def delete_cert(self, cert_id):
        """
        删除指定ID的证书
        :param cert_id: 证书ID
        :return: 删除结果
        """
        if not self.is_logged_in():
            print("请先登录")
            return None

        url = f"{self.base_url}/api/v1/certs/{cert_id}"

        try:
            response = self.session.delete(
                url,
                headers=self.headers
            )
            
            response_data = response.json()
            
            if 'err' in response_data:
                print(f"删除证书失败: {response_data['err']}")
                return None
            
            # 检查删除是否成功
            if response_data.get('result') == 'success' and response_data.get('RowsAffected') > 0:
                print(f"证书 {cert_id} 删除成功")
                return True
            else:
                print(f"证书 {cert_id} 删除失败: 未找到证书或删除操作未生效")
                return False
            
        except requests.exceptions.RequestException as e:
            print(f"删除证书请求失败: {str(e)}")
            return None
        except json.JSONDecodeError as e:
            print(f"解析响应数据失败: {str(e)}")
            return None

    def parse_cert_list(self, cert_list):
        """
        解析证书列表数据
        :param cert_list: 证书列表数据
        :return: 解析后的证书信息列表
        """
        if not cert_list:
            return None

        parsed_certs = []
        for cert in cert_list:
            try:
                # 解析SNI字段(JSON字符串)
                sni_list = json.loads(cert.get('sni', '[]'))
                
                parsed_cert = {
                    'id': cert.get('id'),
                    'sni': sni_list,
                    'expire_time': cert.get('expire_time'),
                    'update_time': cert.get('update_time')
                }
                parsed_certs.append(parsed_cert)
            except json.JSONDecodeError as e:
                print(f"解析SNI字段失败: {str(e)}")
                continue
            except Exception as e:
                print(f"解析证书数据失败: {str(e)}")
                continue

        return parsed_certs

    def get_cert_list(self):
        """
        获取证书列表
        :return: 证书列表
        """
        if not self.is_logged_in():
            print("请先登录")
            return None

        url = f"{self.base_url}/api/v1/certs/"

        try:
            response = self.session.get(
                url,
                headers=self.headers
            )
            
            response_data = response.json()
            
            if 'err' in response_data:
                print(f"获取证书列表失败: {response_data['err']}")
                return None
                
            return response_data
            
        except requests.exceptions.RequestException as e:
            print(f"获取证书列表请求失败: {str(e)}")
            return None
        except json.JSONDecodeError as e:
            print(f"解析响应数据失败: {str(e)}")
            return None

    def login(self, username, password, otp=""):
        """
        登录接口
        :param username: 用户名
        :param password: 密码
        :param otp: 双因素认证码(可选)
        :return: 登录响应
        """
        url = f"{self.base_url}/api/v1/users/login"
        data = {
            "usr": username,
            "pwd": password,
            "otp": otp
        }

        try:
            response = self.session.post(
                url,
                headers=self.headers,
                json=data
            )
            
            # 获取响应数据
            response_data = response.json()
            
            # 检查是否有错误信息
            if 'err' in response_data:
                print(f"登录失败: {response_data['err']}")
                return None
            
            # 保存token
            if 'token' in response_data:
                self.token = response_data['token']
                self._update_headers_with_token()
                
                # # 解析token信息
                # try:
                #     # 使用 jwt.decode 替代 jwt.decode_complete
                #     token_data = jwt.decode(self.token, options={"verify_signature": False})
                #     exp_timestamp = token_data.get('exp')
                #     if exp_timestamp:
                #         exp_date = datetime.fromtimestamp(exp_timestamp)
                #         print(f"Token 有效期至: {exp_date}")
                # except Exception as e:
                #     print(f"无法解析token信息: {str(e)}")
                
            return response_data
            
        except requests.exceptions.RequestException as e:
            print(f"登录请求失败: {str(e)}")
            return None
        except json.JSONDecodeError as e:
            print(f"解析响应数据失败: {str(e)}")
            return None

    def check_cert(self, cert_content, key_content, mode=0):
        """
        检查证书
        :param cert_content: 证书内容
        :param key_content: 私钥内容
        :param mode: 模式,默认为0
        :return: 检查结果
        """
        if not self.is_logged_in():
            print("请先登录")
            return None

        url = f"{self.base_url}/api/v1/certs/check"
        
        # 准备multipart/form-data数据
        files = {
            'mode': (None, str(mode)),
            'cert': (None, cert_content),
            'key': (None, key_content)
        }

        try:
            # 临时移除content-type,让requests自动设置
            headers = self.headers.copy()
            headers.pop('content-type', None)
            
            response = self.session.post(
                url,
                headers=headers,
                files=files
            )
            
            response_data = response.json()
            
            if 'err' in response_data:
                print(f"证书检查失败: {response_data['err']}")
                return None
                
            return response_data
            
        except requests.exceptions.RequestException as e:
            print(f"证书检查请求失败: {str(e)}")
            return None
        except json.JSONDecodeError as e:
            print(f"解析响应数据失败: {str(e)}")
            return None

    def check_cert_from_files(self, cert_file_path, key_file_path, mode=0):
        """
        从文件检查证书
        :param cert_file_path: 证书文件路径
        :param key_file_path: 私钥文件路径
        :param mode: 模式,默认为0
        :return: 检查结果
        """
        try:
            with open(cert_file_path, 'r') as f:
                cert_content = f.read()
            with open(key_file_path, 'r') as f:
                key_content = f.read()
                
            return self.check_cert(cert_content, key_content, mode)
            
        except FileNotFoundError as e:
            print(f"文件不存在: {str(e)}")
            return None
        except Exception as e:
            print(f"读取文件失败: {str(e)}")
            return None

    def submit_cert_config(self, check_result):
        """
        提交证书配置
        :param check_result: 证书检查的结果数据
        :return: 提交结果
        """
        if not self.is_logged_in():
            print("请先登录")
            return None

        if not check_result:
            print("无效的证书检查结果")
            return None

        url = f"{self.base_url}/api/v1/certs/config"
        
        # 准备提交数据
        data = {
            "id": check_result.get("id", 0),
            "sni": check_result.get("sni", "[]"),
            "cert": check_result.get("cert", ""),
            "key": check_result.get("key", ""),
            "expire_time": check_result.get("expire_time", ""),
            "update_time": check_result.get("update_time", "")
        }

        try:
            response = self.session.post(
                url,
                headers=self.headers,
                json=data
            )
            
            response_data = response.json()
            
            if 'err' in response_data:
                print(f"证书配置提交失败: {response_data['err']}")
                return None
                
            return response_data
            
        except requests.exceptions.RequestException as e:
            print(f"证书配置提交请求失败: {str(e)}")
            return None
        except json.JSONDecodeError as e:
            print(f"解析响应数据失败: {str(e)}")
            return None

    def is_logged_in(self):
        """
        检查是否已登录
        :return: bool
        """
        return self.token is not None

def main():
    # 使用示例
    api = NanQiangAPI()
    
    # 登录信息
    username = "obaby"
    password = "obaby@mars"
    
    # 执行登录
    result = api.login(username, password)
    
    if result:
        print("登录成功:")
        print(json.dumps(result, indent=2, ensure_ascii=False))
        print(f"Token: {api.token}")
        
        # 获取证书列表
        cert_list = api.get_cert_list()
        if cert_list:
            # 解析证书列表
            parsed_certs = api.parse_cert_list(cert_list)
            if parsed_certs:
                print("解析后的证书列表:")
                print(json.dumps(parsed_certs, indent=2, ensure_ascii=False))
                
                # # 删除证书示例
                # cert_id = 4  # 要删除的证书ID
                # delete_result = api.delete_cert(cert_id)
                # if delete_result:
                #     print(f"证书 {cert_id} 删除成功")
                # else:
                #     print(f"证书 {cert_id} 删除失败")
        
        # 证书检查示例
        cert_file = "path/to/cert.pem"
        key_file = "path/to/key.pem"
        
        if os.path.exists(cert_file) and os.path.exists(key_file):
            return
            # 先检查证书
            cert_result = api.check_cert_from_files(cert_file, key_file)
            if cert_result:
                print("证书检查结果:")
                print(json.dumps(cert_result, indent=2, ensure_ascii=False))
                
                # 提交证书配置
                submit_result = api.submit_cert_config(cert_result)
                if submit_result:
                    print("证书配置提交成功:")
                    print(json.dumps(submit_result, indent=2, ensure_ascii=False))
                else:
                    print("证书配置提交失败")
    else:
        print("登录失败")

if __name__ == "__main__":
    main()

账号不要设置动态密码,如果设置了,那就创建一个新账号。

获取证书的脚本参考上一篇文章,对应的路径自己调整。更新证书的代码site_cert_auto_update_tool.py:

#!/usr/bin/env python3
import os
import subprocess
import hashlib
import json
from datetime import datetime
import logging
from baby_nanqiang_api_tools import NanQiangAPI

# Configuration
CERT_SOURCE_DIR = "/root/.acme.sh/h4ck.org.cn_ecc"
CERT_FILE = "fullchain.cer"
KEY_FILE = "h4ck.org.cn.key"
HASH_FILE = "web_cert_hash.json"
CERT_SCRIPT = "get_web_cert.sh"

def setup_logging():
    """设置日志"""
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler('web_cert_update.log'),
            logging.StreamHandler()
        ]
    )

def get_file_hash(file_path):
    """计算文件的SHA-256哈希值"""
    sha256_hash = hashlib.sha256()
    with open(file_path, "rb") as f:
        for byte_block in iter(lambda: f.read(4096), b""):
            sha256_hash.update(byte_block)
    return sha256_hash.hexdigest()

def save_cert_hash(cert_hash, key_hash):
    """保存证书和私钥的哈希值到JSON文件"""
    with open(HASH_FILE, 'w') as f:
        json.dump({
            'cert_hash': cert_hash,
            'key_hash': key_hash
        }, f)

def load_cert_hash():
    """从JSON文件加载证书和私钥的哈希值"""
    try:
        with open(HASH_FILE, 'r') as f:
            data = json.load(f)
            return data.get('cert_hash'), data.get('key_hash')
    except (FileNotFoundError, json.JSONDecodeError):
        return None, None

def run_get_cert_script(script_path=None):
    """
    执行获取证书的脚本
    :param script_path: 脚本路径,如果为None则使用默认的get_web_cert.sh
    :return: bool 是否执行成功
    """
    try:
        # 如果没有指定脚本路径,使用默认的get_web_cert.sh
        if script_path is None:
            script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), CERT_SCRIPT)
        
        # 检查脚本是否存在
        if not os.path.exists(script_path):
            logging.error(f"错误: 脚本文件 {script_path} 不存在")
            return False
            
        # 检查脚本是否可执行
        if not os.access(script_path, os.X_OK):
            logging.error(f"错误: 脚本文件 {script_path} 没有执行权限")
            return False
            
        # 执行脚本
        result = subprocess.run(['sh', script_path], 
                              capture_output=True, 
                              text=True)
        
        # 检查执行结果
        if result.returncode == 0:
            logging.info("证书获取脚本执行成功")
            if result.stdout:
                logging.info("脚本输出:\n%s", result.stdout)
            return True
        else:
            logging.error(f"证书获取脚本执行异常,返回码: {result.returncode}")
            if result.stderr:
                logging.error("异常输出:\n%s", result.stderr)
            return True
            
    except Exception as e:
        logging.error(f"执行证书获取脚本时发生错误: {str(e)}")
        return False

def read_file_content(file_path):
    """读取文件内容"""
    try:
        with open(file_path, 'r') as f:
            return f.read()
    except Exception as e:
        logging.error(f"读取文件 {file_path} 失败: {str(e)}")
        return None

def is_cert_expired(expire_time_str):
    """
    检查证书是否过期或即将过期(7天内)
    :param expire_time_str: 过期时间字符串
    :return: bool 是否过期或即将过期
    """
    try:
        expire_time = datetime.strptime(expire_time_str, "%Y-%m-%d %H:%M:%S")
        now = datetime.now()
        days_until_expire = (expire_time - now).days
        return days_until_expire <= 7
    except Exception as e:
        logging.error(f"解析过期时间失败: {str(e)}")
        return False

def process_same_sni_certs(api, parsed_certs, current_sni, current_cert_id):
    """
    处理具有相同SNI的证书
    :param api: API实例
    :param parsed_certs: 解析后的证书列表
    :param current_sni: 当前证书的SNI
    :param current_cert_id: 当前证书的ID
    :return: None
    """
    # 筛选出相同SNI的证书
    same_sni_certs = [cert for cert in parsed_certs 
                     if cert['sni'] == current_sni and cert['id'] != current_cert_id]
    
    if not same_sni_certs:
        return
        
    # 按过期时间排序(从早到晚)
    same_sni_certs.sort(key=lambda x: datetime.strptime(x['expire_time'], "%Y-%m-%d %H:%M:%S"))
    
    # 检查是否有过期或即将过期的证书
    for cert in same_sni_certs:
        if is_cert_expired(cert['expire_time']):
            logging.info(f"删除过期证书 ID: {cert['id']}")
            if not api.delete_cert(cert['id']):
                logging.error(f"删除证书 {cert['id']} 失败")
    
    # 检查是否有过期时间相同的证书
    if len(same_sni_certs) > 1:
        # 获取第一个证书的过期时间作为基准
        base_expire_time = same_sni_certs[0]['expire_time']
        
        # 删除过期时间相同的证书(保留第一个)
        for cert in same_sni_certs[1:]:
            if cert['expire_time'] == base_expire_time:
                logging.info(f"删除重复过期时间的证书 ID: {cert['id']}")
                if not api.delete_cert(cert['id']):
                    logging.error(f"删除证书 {cert['id']} 失败")

def main():
    # 设置日志
    setup_logging()
    
    try:
        # 执行证书获取脚本
        if not run_get_cert_script():
            logging.error("获取证书失败,退出程序")
            return
            
        # 检查证书文件是否存在
        cert_path = os.path.join(CERT_SOURCE_DIR, CERT_FILE)
        key_path = os.path.join(CERT_SOURCE_DIR, KEY_FILE)
        
        if not (os.path.exists(cert_path) and os.path.exists(key_path)):
            logging.error("证书文件不存在,退出程序")
            return
            
        # 计算新文件的哈希值
        new_cert_hash = get_file_hash(cert_path)
        new_key_hash = get_file_hash(key_path)
        
        # 获取旧的哈希值
        old_cert_hash, old_key_hash = load_cert_hash()
        
        # 检查文件是否发生变化
        if new_cert_hash != old_cert_hash or new_key_hash != old_key_hash:
            logging.info("证书文件已发生变化,开始更新流程")
            
            # 读取证书和私钥内容
            cert_content = read_file_content(cert_path)
            key_content = read_file_content(key_path)
            
            if not cert_content or not key_content:
                logging.error("读取证书文件失败")
                return
                
            # 初始化API
            api = NanQiangAPI()
            
            # 登录
            if not api.login("obaby", "obaby@mars"):
                logging.error("登录失败")
                return
                
            # 检查证书
            check_result = api.check_cert(cert_content, key_content)
            if not check_result:
                logging.error("证书检查失败")
                return
                
            # 提交证书配置
            if not api.submit_cert_config(check_result):
                logging.error("提交证书配置失败")
                return
                
            # 获取证书列表
            cert_list = api.get_cert_list()
            if not cert_list:
                logging.error("获取证书列表失败")
                return
                
            # 解析证书列表
            parsed_certs = api.parse_cert_list(cert_list)
            if not parsed_certs:
                logging.error("解析证书列表失败")
                return
                
            # 获取当前证书的SNI
            current_sni = check_result.get('sni', '[]')
            try:
                current_sni = json.loads(current_sni)
            except json.JSONDecodeError:
                logging.error("解析当前证书SNI失败")
                return
                
            # 处理相同SNI的证书
            process_same_sni_certs(api, parsed_certs, current_sni, check_result.get('id'))
            
            # 保存新的哈希值
            save_cert_hash(new_cert_hash, new_key_hash)
            logging.info("证书更新完成")
        else:
            logging.info("证书文件未发生变化,无需更新")
            
    except Exception as e:
        logging.error(f"程序执行出错: {str(e)}", exc_info=True)

if __name__ == "__main__":
    main()

添加定时任务,每天,或者每几天:

0 2 * * * /usr/bin/python3 /home/soft/baby-nanqiang-cert-tools/site_cert_auto_update_tool.py >> /home/soft/baby-nanqiang-cert-tools/web_cert_manager.log 2>&1

最终效果:

The post 南墙 WAF 系列(二)– 网站证书自动更新 appeared first on obaby@mars.

再谈 Python自动生成 pdf 文件

pdf 这个东西,不得不说,真的是受人欢迎,任何时候下载个文件或者报告之类的,都想弄个 pdf 版本。当然,这个东西的好处是不管在哪里看,样式基本都是一样的。

然而,缺点也很明显,没有办法直接生成 pdf 文件,当然,通过各种库可以直接将图片转为 pdf。然而,对于复杂格式或者需要使用模板来创建 pdf 的时候,就变得有些麻烦了。

def converImageToPdf(img_list):
    pdf = fitz.open() PyMuPDF
    pdf_document = fitz.open()  Creates a new PDF
    #遍历图片文件夹中的所有图片文件
    for img_url in img_list:
        img_local_file = download_image(img_url, 'confirmd_images')
        img_path = os.path.join(img_folder, img_file)
        img = fitz.open(img_local_file)
        img_rect = img[0].rect  Get the rectangle of the first page of the image
    #Create a new page with the same dimensions as the image
        pdf_page = pdf_document.new_page(width=img_rect.width, height=img_rect.height)
    #Insert the image into the new page
        pdf_page.insert_image(pdf_page.rect, filename=img_local_file)
    #保存PDF文件
        img.close()
    
    file_name = random_file_name('pdf')
    if not os.path.exists('confirmd_receipt'):
        os.mkdir('confirmd_receipt')
    pdf_document.save(os.path.join('confirmd_receipt/') + file_name)
    pdf_document.close()

依赖于fitz

pip install fitz

之前写过基于 oss 的:

Python生成Pdf报告

那么没有 oss 呢?其实此时最简单的办法就是基于 liboffice 了。

# 安装 LibreOffice(Ubuntu/Debian)
sudo apt-get install libreoffice

# 验证安装
libreoffice --version

代码:

import subprocess
import os

def convert_to_pdf(input_docx, output_dir):
    try:
        # 创建输出目录(如果不存在)
        os.makedirs(output_dir, exist_ok=True)
        
        # 执行转换命令
        cmd = [
            'libreoffice', '--headless', '--convert-to', 'pdf',
            '--outdir', output_dir, input_docx
        ]
        result = subprocess.run(cmd, check=True, capture_output=True, text=True)
        
        print(f"转换成功: {input_docx} → {output_dir}")
        return True
    except subprocess.CalledProcessError as e:
        print(f"转换失败: {e.stderr}")
        return False
    except Exception as e:
        print(f"发生错误: {str(e)}")
        return False

# 使用示例
convert_to_pdf(
    input_docx="/path/to/document.docx",
    output_dir="/path/to/output"
)

不多此时大概率得到的 PDF 文件会是乱码:

这一堆框就很专业,应该是没有字体导致的,安装字体文件:

# Ubuntu/Debian
sudo apt-get install fonts-wqy-zenhei fonts-wqy-microhei fonts-noto-cjk

# CentOS/RHEL
sudo yum install wqy-zenhei-fonts wqy-microhei-fonts google-noto-cjk-fonts

# 刷新字体缓存
sudo fc-cache -fv

验证安装:

# 查看已安装的中文字体
fc-list :lang=zh | grep -E "WenQuanYi|Noto"

# 预期输出示例(显示已安装字体路径):
# /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc: WenQuanYi Zen Hei,文泉驛正黑:style=Regular
# /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc: Noto Sans CJK JP,Noto Sans CJK JP Regular:style=Regular

如果依然有问题,那就修改系统配置:

# 检查当前 locale
locale

# 生成中文环境配置(如未安装)
sudo locale-gen zh_CN.UTF-8

# 临时设置环境变量(测试用)
export LANG=zh_CN.UTF-8

# 永久设置(修改 /etc/default/locale)
sudo nano /etc/default/locale
# 添加内容:
LANG="zh_CN.UTF-8"
LC_ALL="zh_CN.UTF-8"

或者转换的之后指定字体:

def convert_with_font(input_docx, output_dir):
    cmd = [
        'libreoffice', '--headless',
        '--env:UserInstallation=file:///tmp/libreoffice-altprofile', # 使用独立配置
        '--convert-to', 'pdf:writer_pdf_Export:{"Watermark":{"type":"string","value":" "},'
                         '"SelectPdfVersion":{"type":"long","value":"1"},'
                         '"UseTaggedPDF":{"type":"boolean","value":"true"},'
                         '"ExportBookmarks":{"type":"boolean","value":"true"},'
                         '"EmbedStandardFonts":{"type":"boolean","value":"true"}}',
        '--outdir', output_dir,
        input_docx
    ]
    subprocess.run(cmd, check=True)

此时多数就能解决问题了:

微博图片拯救 — 妈妈再也不用担心图片被夹看不到啦!🤓

过了这好几年之后,总感觉自己已经从一个技术博主,变成了一个生活博主。

年龄越来越大了之后,探索能力,学习能力逐渐的下降。接受新事物的能力也日渐式微,总感觉想做一些东西而力不从心。

很多东西多年以前就知道了,但是想自己去做的时候却总感觉没什么头绪,不知道该从哪里开始。

今天又看到教主转的微博,同样原内容图片被夹了。

教主发的那个这就发挥作用了,是一张截图:

这么个东西。

至于原理,很久之前教主就大概提过,说出来也简单,就是利用cdn的缓存删除时间差,在节点未删除之前遍历所有的cdn节点去搜索图片。知道原理之后,要实现也简单,目前微博图片主要有四个域名+两个alias:

weibo_cdn_domain_list = [
    'wx1.sinaimg.cn',
    'wx2.sinaimg.cn',
    'wx3.sinaimg.cn',
    'wx4.sinaimg.cn',
    'weiboimgwx.gslb.sinaedge.com',
    'weiboimgwx.grid.sinaedge.com'
]

既然有了域名,那么也简单,通过python库直接解析所有的地址即可:

def get_ipv4_ips(domain_name):
    try:
        ipv4_addresses = []
        answers = dr.resolve(domain_name, "A")
        for rdata in answers:
            if str(rdata).startswith("192."):
                continue
            else:
                ipv4_addresses.append(str(rdata))
        return ipv4_addresses
    except Exception as e:
        print(e)
        return None


def get_ipv6_ips(domain_name):
    try:
        ipv6_addresses = []
        answers = dr.resolve(domain_name, "AAAA")
        for rdata in answers:
            if str(rdata).startswith("::"):
                continue
            else:
                ipv6_addresses.append(str(rdata))
        return ipv6_addresses
    except Exception as e:
        print(e)
        return None


def get_all_ips():
    ip_dict_list = []

    for domain in weibo_cdn_domain_list:
        ips = get_ipv4_ips(domain)
        v6_ips = get_ipv6_ips(domain)
        print(domain, ips)
        domain_ips = {
            'domain': domain,
            'ipv4': ips,
            'ipv6': v6_ips
        }
        ip_dict_list.append(domain_ips)
    return ip_dict_list

然而,这么高却也存在问题,就是拿到的ip地址都是国内解析到的,与命令查询到的一致:

四个域名加起来不多几十个ip地址,然而,仔细观察教主的图片会发现,解析出来的ip大约有2000+按照图片进度猜测。

即使加上ipv6的也远远少于教主的ip地址数量。

并且尝试下载的时候全部失败了,无法遍历到删除的文件,再次查看教主的图片,搜了下ip地址,并不是国内的:

那么,可能的原因在于,教主拿到了所有的ip地址,包括海外的,并且海外节点的删除时间会更晚,这样能找到被夹的图片的概率自然也越大。

那么直接去itdog.cn拉取所有的ip地址列表:

一个域名对应800+ip地址,那么这么看来基本跟教主的数量就能对上了。剩下的就简单了,告知思路,剩下的大家可以自由发挥了,主要代码可以暂停录像看屏幕代码:

1.将所有的域名解析为ip
2.讲ip与域名组装为:
domain_ips = {
            'domain': domain,
            'ipv4': ips,
            'ipv6': v6_ips
        }
格式。
3.遍历域名下的所有ip地址,拼接请求链接指定host。
4.针对请求数据进行处理,目前已知默认的占位符图片长度为:6067, 8308, 8844这几个,对于返回长度10000以下的,可以直接抛弃掉。
5.请求到数据之后保存为文件即可。

效果图:

视频演示:

 

如何快速清空七牛的存储空间中的所有文件,并删除存储空间

如果你和我一样,有很多历史的文件存储在七牛上,但如今已经不再需要使用,那么就可以考虑删除七牛的存储空间,来节省费用。

但七牛为了保证安全,所以要求必须删除所有的文件后才能删除空间,以避免误删除,所以需要一个个删除所有的文件。为了快速删除七牛存储空间的文件,我写了个简单的脚本,帮助你快速删除七牛空间下的所有文件。

具体操作可参考如下脚本,你只需要

  1. 在本地安装七牛 SDK :pip install qiniu
  2. 创建一个新文件 run.py 并复制下方的代码,修改其中的访问密钥和存储空间名称
  3. 执行 python run.py 就可以了。
# -*- coding: utf-8 -*-
# 导入七牛云 SDK 所需的模块
from qiniu import Auth
from qiniu import BucketManager, build_batch_delete
# 七牛云账号的访问密钥
access_key = '你的 ACCESS Key'
secret_key = '你的 Secret Key'
# 要清理的存储空间名称
bucket_name = '你要清空的空间名称'


# 使用 AK、SK 初始化授权对象
q = Auth(access_key, secret_key)
# 初始化存储空间管理器
bucket = BucketManager(q)

# 设置每次列举的最大条目数
limit = 1000

# 循环列举并删除存储空间中的文件
while True:
    # 列举存储空间中的文件
    # ret: 包含文件信息的字典
    # eof: 是否已列举完所有文件
    # info: 请求的状态信息
    ret, eof, info = bucket.list(bucket=bucket_name)
    # 从返回结果中提取文件名列表
    keys = [item['key'] for item in ret['items']]
    # 构建批量删除操作
    ops = build_batch_delete(bucket_name, keys)
    # 执行批量删除操作
    ret, info = bucket.batch(ops)
    # 检查删除操作是否成功
    if info.status_code == 200:
        print(f"success delete {len(keys)} files!")
    
    # 判断是否已经列举完所有文件
    if eof:
        break
    else:
        continue
# 输出清理完成的提示信息
print(f"delete all files in {bucket_name}")

执行成功后,你会看到如下面这样的命令,接下来等他自动执行即可,你就不用做任何事情了。

image

当工具提示你 delete all files in 你的 kodo 名时,你就可以回到七牛控制台,删除掉空的 kodo 了。

image

django 直接运行目录下py 文件

为了处理数据,直接写了一个文件用来处理解析数据。然而比较诡异的一点是,使用 pycharm 可以直接运行这个文件,不会报错。但是,如果用命令运行就直接报错了。

上面是 pycharm 的运行效果,下面是直接命令运行的效果。

(venv) PS E:\Pycharm_Projects\powersystem> E:\Pycharm_Projects\powersystem\venv\Scripts\python.exe E:\Pycharm_Projects\powersystem\application\data_process_test.py 
Traceback (most recent call last):
  File "E:\Pycharm_Projects\powersystem\application\data_process_test.py", line 17, in <module>
    django.setup()
  File "E:\Pycharm_Projects\powersystem\venv\lib\site-packages\django\__init__.py", line 19, in setup
    configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
  File "E:\Pycharm_Projects\powersystem\venv\lib\site-packages\django\conf\__init__.py", line 82, in __getattr__
    self._setup(name)
  File "E:\Pycharm_Projects\powersystem\venv\lib\site-packages\django\conf\__init__.py", line 69, in _setup
    self._wrapped = Settings(settings_module)
  File "E:\Pycharm_Projects\powersystem\venv\lib\site-packages\django\conf\__init__.py", line 170, in __init__
    mod = importlib.import_module(self.SETTINGS_MODULE)
  File "G:\Python3.10.6\lib\importlib\__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 992, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'application'

提示的错误信息是找不到 application,但是这个文件是作为 django 的一部分存在的,按理也不需要去设置什么东西。之前的时候不能运行也就算了,但是现在有台服务器在内网,无法链接内网的的数据库进行数据处理,这就比较麻烦。

不过既然 pycharm 能运行,那肯定是有些东西不一样,猜测是 pycharm 将当前的目录加入 lib 目录了。添加下面的代码重新运行。

import os,sys

if __name__ == '__main__':
    # 获取当前脚本所在目录的绝对路径
    current_directory = os.path.abspath(os.path.dirname(__file__))

    # 将当前目录添加到sys.path
    sys.path.append("E:/Pycharm_Projects/powersystem/")
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "application.settings")
    import django

    django.setup()
    label =get_device_label(msg['devvar'])
    new_msg = rebuild_msg(msg, label)
    print(new_msg)

现在一切就 ok 了。

其他的运行脚本方式:

https://django-extensions-zh.readthedocs.io/zh-cn/latest/runscript.html

https://www.jb51.net/article/236739.htm

Python 中寻找数据的众数: mode vs multimode


在 Python 中寻找数据的众数

statistics.mode() 函数是 Python 中 statistics 模块的一部分,它返回数据集中出现次数最多的单个值(众数)。与 multimode() 不同,mode() 如果数据集包含多个众数(即多模态数据)或数据为空,则会引发错误。

以下是一些示例来说明 mode() 的行为:

mode() 的语法

statistics.mode(data)

data: 一个序列(例如 list、tuple),其中的元素是可散列的,用于确定众数。

示例

单一众数(单模态数据)
from statistics import mode
data = [1, 2, 2, 3, 4]
result = mode(data)
print(result)  # 输出: 2
字符串作为数据
from statistics import mode
data = ["apple", "banana", "apple", "cherry"]
result = mode(data)
print(result)  # 输出: "apple"
多模态数据(引发错误)

如果有多个众数,mode() 会引发 StatisticsError。

from statistics import mode
data = [1, 1, 2, 2, 3]
try:
    result = mode(data)
except StatisticsError as e:
    print(e)  # 输出: "no unique mode; found 2 equally common values"
无重复值(引发错误)

如果数据集中没有值重复,mode() 会引发 StatisticsError。

from statistics import mode
data = [1, 2, 3, 4, 5]
try:
    result = mode(data)
except StatisticsError as e:
    print(e)  # 输出: "no unique mode; found 5 equally common values"
空数据集(引发错误)

如果数据集为空,mode() 会引发 StatisticsError。

from statistics import mode
data = []
try:
    result = mode(data)
except StatisticsError as e:
    print(e)  # 输出: "no mode for empty data"

在 Python 中寻找多众数

在 Python 中,术语 multimode 通常指 statistics.multimode() 函数,这是 Python 3.8 中 statistics 模块的一部分。此函数用于找到数据集中出现次数最多的值(众数)。与 statistics.mode() 不同,后者仅返回单个众数(如果数据集是多模态的会引发错误),而 multimode() 可以处理包含多个众数的多模态数据集。

语法

statistics.multimode(data)

data: 一个序列(例如 list、tuple),其中的元素是可散列的,用于查找众数。

行为

返回输入数据中所有众数的列表。如果没有元素重复,则返回所有唯一值的列表,因为在这种情况下每个值都是众数。

示例

单一众数
from statistics import multimode
data = [1, 2, 2, 3, 4]
result = multimode(data)
print(result)  # 输出: [2]
多个众数
from statistics import multimode
data = [1, 1, 2, 2, 3]
result = multimode(data)
print(result)  # 输出: [1, 2]
无重复值
from statistics import multimode
data = [1, 2, 3, 4, 5]
result = multimode(data)
print(result)  # 输出: [1, 2, 3, 4, 5]

主要特性

多模态支持:可以处理包含多个同频值的数据集。
优雅地处理唯一数据:如果没有重复值,则返回所有唯一值。
灵活的输入类型:适用于任何可散列对象的序列,包括字符串和元组。

字符串示例

data = ["apple", "banana", "apple", "cherry", "banana", "banana"]
result = multimode(data)
print(result)  # 输出: ['banana']

使用场景

  • 分析调查结果或投票中具有多个最受欢迎选项的情况。
  • 识别数据集中可能共享最高频率的频繁模式。

局限性

如果数据集很大,计算众数可能会消耗大量计算资源,因为它需要统计所有元素的出现次数。

mode 与 multimode 的比较

特性 mode() multimode()
返回值 单个最频繁的值 所有最频繁值的列表
多模态数据行为 引发 StatisticsError 返回所有众数
空数据集行为 引发 StatisticsError 返回空列表
最佳用途 适用于期望唯一众数的单模态数据 适用于包含多个众数的多模态数据或任意数据

如果不确定数据是否包含多个众数或无重复值,multimode() 是更安全的选择。

英文:The mode vs multimode in Python

本文一共 702 个汉字, 你数一下对不对.
Python 中寻找数据的众数: mode vs multimode. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c Python 中寻找数据的众数: mode vs multimode Python Python 学习笔记 数学 程序设计 计算机
The post Python 中寻找数据的众数: mode vs multimode first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. 西瓜视频再也上传不了视频了(字节头条: 抖西合并) 上两个月,我再也没法在西瓜视频上上传我的视频了,也从此少了一个同步视频备份的地方了。现在登陆西瓜平台,要发视频的话会立马转到抖音创作者中心,然后我尝试通过手机号+SMS验证码的方式登录,提示我“请使用抖音手机APP登录” 原来西瓜视频国外的用户,可以通过手机号SMS+验证码的方式登陆网站,上传视频的。由于我人在英国,装的是英国的App Store,无法安装国内的西瓜视频APP,使用上有点不方便的。后来为了同步到国内的抖音平台,让我姐帮我绑定了她的抖音账号,可能就是因为这样,现在强制我得通过抖音平台来发视频了。等哪天有空我让我姐试试是否可以解绑。 西瓜视频已经合并到抖音平台上了,字节头条也不再单独运营西瓜视频了。 我发了邮件问西瓜视频,得到的回复:“您好,若您可以登录账号,请前往抖音,在设置内选择【我的客服】在线咨询,详细描述您的问题并提供问题界面截屏。若您无法登录账号,您可以拨打 95152 热线咨询,热线接听时间为8:30-22:00” 再问:您好,我们国外的用户没法装抖音啊。。。之前西瓜视频一直是 国外手机号登录的。 答:您好,您可以通过以下几种方法找到西瓜客服帮你解决问题:1.手机端入口:【西瓜视频App】更新至最新版本 【我的】-【创作中心】-【问题反馈】2.手机端入口:【西瓜视频App】-【我的】-【反馈与帮助】【意见反馈】3.电脑端入口:登陆【西瓜创作平台西瓜创作平台】点击右下角【问题咨询】。 西瓜视频是由字节跳动公司推出的一款视频分享平台,旨在提供用户一个便捷的观看和创作短视频的空间。西瓜视频自推出以来,凭借其丰富的内容、强大的推荐算法以及用户互动功能,逐渐成为中国大陆地区受欢迎的视频平台之一。 主要特点: 内容丰富:西瓜视频涵盖了多种类型的视频内容,包括娱乐、搞笑、影视、音乐、游戏、教育、科技等,满足不同用户的兴趣需求。 个性化推荐:依托字节跳动强大的机器学习和人工智能技术,西瓜视频通过分析用户的观看历史和行为习惯,提供精准的个性化推荐,提升用户的观看体验。...
  4. MySQL参数一键配置脚本: 有效提升数据库性能 我一直是自己租用VPS服务器,然后搭建各种服务,比如博客就是Apache2+MySQL数据库。一般来说就是默认参数,没有去管,不过最近发现MySQL的性能参数都很保守,不能发挥整个服务器的性能。 然后我就网上搜索了一下,根据参数配置建议,用ChatGPT写了以下Python和BASH脚本。只需要在需要优化的服务器上,跑一下该脚本,然后就会显示参数配置,然后直接把参数添加到MySQL数据库配置参数文件上: /etc/mysql/mysql.conf.d/mysqld.cnf 然后运行: service mysql restart 重启MySQL服务器。 运行了几周,发现效果很好,博客反应速度也快了很多,这很大原因是根据了内存增加了MySQL缓存大小。 Python脚本优化MySQL数据库参数 把下面的Python脚本存成 mysql_config.py 然后运行 python3 mysql_config.py...
  5. 世界再无OneKey币圈美元虚拟卡了 我前两年就了解到OneKey这个币圈虚拟货币出金卡,不过去年年底才注册使用的。当时还花了99美元一步升级到顶级黑卡。然后这一年陆陆续续用了这卡,但用得不多,主要就用于支持一些VPS主机费还有CloudFlare,ChatGPT Pro等。 这个卡是美国地址,卡号有两个段,Visa 和 Mastercard,不过由于地址是美国的,刷卡可能会有问题。比如我ChatGPT Pro注册帐号是英国的,然后用这卡支付了几个月,突然有一天帐号就被封,被告知:您的付款记录很可疑。 印象中,用这虚拟货币Crypto Card美元出金卡有手续费,但是并没有啥Cash Back返现卡,如果是非美元购物则会有另一笔手续费,所以我很少用这卡出金变现。 前两个月,OneKey宣布关闭: 关于 OneKey Card 服务停用通知 尊敬的用户,为提高服务质量和优化产品供应,我们将按照以下时间表停用...
  6. 负电价活久见: 安装Octopus智能电表省电费甚至赚钱 前几周我的电气公司 Octopus 终于来装智能电表了(Smart Meter),虽然是免费安装的,但是排队排了有两三年了吧。因为之前一直写邮件催的时候就老是说 Not Ready。 收到邮件说可以安装智能电表我还是相当开心和期待的,因为已经听说这玩意好,但是还是得亲身体验一下。工程师来安装大概不到2小时,其中需要停电闸一会儿,重新接下线。装好后,给了个小册子,自动切换到了 Agile 的电价,也就是每半小时的电价都不一样,提前一天可以在手机App和网站上查得。 正好在原来的电价计费合同快要结束前2天换到了智能电表计价 Octopus Agile方式,但是系统还是扣了我75英镑 Exit Fee (提前合同结束得交违约费),不过我一个电话打过去,公司很爽快就给我退了。...
  7. 步步高多媒体学生电脑 汇编程序设计 – 1 英文同步 90年代后期步步高生产的软驱一号(又称步步高多媒体学生电脑)和98型学生电脑都带了软驱,一按电源件, 都从软盘启动(98型可以从内置的电子盘启动) 步步高提供了直接在学习机上写汇编开发的工具 BASM. BASM 可以用来写 6502 汇编,并可以编译成 CMD 小型可执行程序 不支持 EXC 程序. CMD...
  8. 力扣刷题获得一件衣服奖励(Leetcode DCC Winner) 我每天都在力扣上刷题。力扣有国服和美服,我两边都会打卡。每天打卡可以获得积分,而积分可以兑换各种礼物,比如T恤、卫衣、水壶,甚至可以用来抵扣一个月的会员费用。 我从2018年8月开始刷题找工作(当时去伦敦面试亚马逊),从那时起每年都会续费会员,费用是159美元,相当于每月13.25美元。我觉得这是对自己最值得的投资。买了力扣会员,就会有动力刷题、思考,通过不断练习让自己熟能生巧,保持一定的竞争力。 到目前为止,我已经用积分兑换了7-8件力扣的衣服,其中有2-3件是卫衣。国内的礼物我都寄到姐姐家。 前不久,我收到了力扣的邮件,说我获得了DCC奖励。我也不知道为什么会获得这个奖,随手回了邮件。没多久,就收到了一件新版的力扣衬衫。 英文:Leetcode DCC Winner T-shirt 本文一共 291 个汉字, 你数一下对不对. 力扣刷题获得一件衣服奖励(Leetcode DCC Winner)....

MySQL参数一键配置脚本: 有效提升数据库性能


我一直是自己租用VPS服务器,然后搭建各种服务,比如博客就是Apache2+MySQL数据库。一般来说就是默认参数,没有去管,不过最近发现MySQL的性能参数都很保守,不能发挥整个服务器的性能。

然后我就网上搜索了一下,根据参数配置建议,用ChatGPT写了以下Python和BASH脚本。只需要在需要优化的服务器上,跑一下该脚本,然后就会显示参数配置,然后直接把参数添加到MySQL数据库配置参数文件上: /etc/mysql/mysql.conf.d/mysqld.cnf

然后运行: service mysql restart 重启MySQL服务器。

运行了几周,发现效果很好,博客反应速度也快了很多,这很大原因是根据了内存增加了MySQL缓存大小。

Python脚本优化MySQL数据库参数

把下面的Python脚本存成 mysql_config.py 然后运行 python3 mysql_config.py

def get_total_ram():
    with open('/proc/meminfo', 'r') as f:
        for line in f:
            if line.startswith("MemTotal:"):
                total_ram_kb = int(line.split()[1])
                return total_ram_kb * 1024  # 转换为字节(bytes)
    return 0  # 如果未找到 MemTotal,则返回 0

def calculate_mysql_settings():
    # 获取总内存(以字节为单位)
    total_ram = get_total_ram()

    # 根据总内存(以字节为单位)计算 MySQL 配置
    innodb_buffer_pool_size = int(total_ram * 0.3)  # 使用内存的 30%
    key_buffer_size = min(total_ram * 20 // 100, 512 * 1024 * 1024)  # 使用内存的 20%,最大限制为 512MB
    sort_buffer_size = min(total_ram * 25 // 1000, 4 * 1024 * 1024)  # 使用内存的 0.25%,最大限制为 4MB
    read_rnd_buffer_size = min(total_ram * 625 // 100000, 512 * 1024)  # 使用内存的 0.0625%,最大限制为 512KB
    tmp_table_size = max_heap_table_size = min(total_ram * 5 // 100, 64 * 1024 * 1024)  # 使用内存的 5%,最大限制为 64MB
    join_buffer_size = min(total_ram * 2 // 1000, 4 * 1024 * 1024)  # 使用内存的 0.2%,最大限制为 4MB
    table_open_cache = min(400 + (total_ram // 64), 2000)  # 根据内存动态计算,最大限制为 2000
    thread_cache_size = min(total_ram * 15 // 1000, 100)  # 使用内存的 1.5%,最大限制为 100
    innodb_log_buffer_size = min(total_ram * 5 // 100, 16 * 1024 * 1024)  # 使用内存的 5%,最大限制为 16MB

    # 以字节为单位打印配置
    print(f"MySQL 配置(基于总内存 {total_ram / (1024 * 1024):.2f} MB):")
    print("将以下内容添加到 /etc/mysql/mysql.conf.d/mysqld.cnf 的末尾\n")
    
    print(f"innodb_buffer_pool_size = {innodb_buffer_pool_size}")
    print(f"key_buffer_size = {key_buffer_size}")
    print(f"sort_buffer_size = {sort_buffer_size}")
    print(f"read_rnd_buffer_size = {read_rnd_buffer_size}")
    print(f"tmp_table_size = {tmp_table_size}")
    print(f"max_heap_table_size = {max_heap_table_size}")
    print(f"join_buffer_size = {join_buffer_size}")
    print(f"table_open_cache = {table_open_cache}")
    print(f"thread_cache_size = {thread_cache_size}")
    print(f"innodb_log_buffer_size = {innodb_log_buffer_size}")

    # 打印自定义设置
    print("expire_logs_days = 3")
    print("max_binlog_size = 100M")

if __name__ == "__main__":
    calculate_mysql_settings()

会打印出类似以下的配置:

innodb_buffer_pool_size = 626468044
key_buffer_size = 417645363
sort_buffer_size = 4194304
read_rnd_buffer_size = 524288
tmp_table_size = 67108864
max_heap_table_size = 67108864
join_buffer_size = 4176453
table_open_cache = 2000
thread_cache_size = 100
innodb_log_buffer_size = 16777216
expire_logs_days = 3
max_binlog_size = 100M

添加到MySQL的配置文件:/etc/mysql/mysql.conf.d/mysqld.cnf 然后重启数据库即可:service mysql restart

BASH脚本优化MySQL数据库参数

以下是完成同样功能的BASH脚本。

#!/bin/bash

# 获取总内存大小(以字节为单位)
get_total_ram() {
    # 从 /proc/meminfo 中提取总内存(以 kB 为单位)
    total_ram_kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)
    if [[ -z "$total_ram_kb" ]]; then
        echo 0  # 如果未找到 MemTotal,则返回 0
    else
        echo $((total_ram_kb * 1024))  # 将 kB 转换为字节
    fi
}

# 根据总内存大小计算 MySQL 配置
calculate_mysql_settings() {
    # 获取总内存(以字节为单位)
    total_ram=$(get_total_ram)

    # 计算 MySQL 配置参数
    innodb_buffer_pool_size=$((total_ram * 30 / 100))  # 使用内存的 30%
    key_buffer_size=$(($((total_ram * 20 / 100)) < $((512 * 1024 * 1024)) ? $((total_ram * 20 / 100)) : $((512 * 1024 * 1024))))  # 使用内存的 20%,最大限制为 512MB
    sort_buffer_size=$(($((total_ram * 25 / 1000)) < $((4 * 1024 * 1024)) ? $((total_ram * 25 / 1000)) : $((4 * 1024 * 1024))))  # 使用内存的 0.25%,最大限制为 4MB
    read_rnd_buffer_size=$(($((total_ram * 625 / 100000)) < $((512 * 1024)) ? $((total_ram * 625 / 100000)) : $((512 * 1024))))  # 使用内存的 0.0625%,最大限制为 512KB
    tmp_table_size=$((total_ram * 5 / 100 < 64 * 1024 * 1024 ? total_ram * 5 / 100 : 64 * 1024 * 1024))  # 使用内存的 5%,最大限制为 64MB
    max_heap_table_size=$tmp_table_size  # 临时表大小等于最大堆表大小
    join_buffer_size=$(($((total_ram * 2 / 1000)) < $((4 * 1024 * 1024)) ? $((total_ram * 2 / 1000)) : $((4 * 1024 * 1024))))  # 使用内存的 0.2%,最大限制为 4MB
    table_open_cache=$(($((400 + total_ram / 64)) < 2000 ? $((400 + total_ram / 64)) : 2000))  # 根据内存动态计算,最大限制为 2000
    thread_cache_size=$(($((total_ram * 15 / 1000)) < 100 ? $((total_ram * 15 / 1000)) : 100))  # 使用内存的 1.5%,最大限制为 100
    innodb_log_buffer_size=$(($((total_ram * 5 / 100)) < $((16 * 1024 * 1024)) ? $((total_ram * 5 / 100)) : $((16 * 1024 * 1024))))  # 使用内存的 5%,最大限制为 16MB

    # 打印配置(以字节为单位)
    echo "MySQL 配置(基于总内存 $((total_ram / (1024 * 1024))) MB):"
    echo "将以下内容添加到 /etc/mysql/mysql.conf.d/mysqld.cnf 的末尾"
    echo
    echo "innodb_buffer_pool_size = $innodb_buffer_pool_size"
    echo "key_buffer_size = $key_buffer_size"
    echo "sort_buffer_size = $sort_buffer_size"
    echo "read_rnd_buffer_size = $read_rnd_buffer_size"
    echo "tmp_table_size = $tmp_table_size"
    echo "max_heap_table_size = $max_heap_table_size"
    echo "join_buffer_size = $join_buffer_size"
    echo "table_open_cache = $table_open_cache"
    echo "thread_cache_size = $thread_cache_size"
    echo "innodb_log_buffer_size = $innodb_log_buffer_size"
    echo
    echo "expire_logs_days = 3"  # 日志过期天数设置为 3 天
    echo "max_binlog_size = 100M"  # 最大二进制日志大小设置为 100M
}

# 主函数调用
calculate_mysql_settings

需要注意的是,我在脚本后面加入了一些我自定义的配置,根据需求自行修改即可。在配置文件里,后面定义的会覆盖前面的,这就是为什么要添加到文件尾的原因。

其中最关键的配置 innodb_buffer_pool_size 我设置为使用当前内存的30%,如果服务器只有数据库/博客这个功能,可以适当的提高比例,比如60%-80%。

英文:Python/Bash Script to Print the Optimized Parameters for MySQL Servers

运维/DevOps

本文一共 812 个汉字, 你数一下对不对.
MySQL参数一键配置脚本: 有效提升数据库性能. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c MySQL参数一键配置脚本: 有效提升数据库性能 MySQL 学习笔记 数据库 计算机 计算机 运维 运维 DevOps
The post MySQL参数一键配置脚本: 有效提升数据库性能 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 步步高学生电脑上 Basic 编程语言 peek 用法示例 步步高学生电脑 是8位FC机的经典之作.它上面的BASIC有三个版本 1.0, 2.0 和 2.1 2.1 版本有个在线帮助,实际上是 help.cmd 1.0 是用 Esc 键退回到 DOS 的,...
  2. 一张图告诉你北京的雾霾有多严重 一北京的朋友朋友圈发的: 左上为全新口罩;右上为全新口罩本周一到周五每天室外戴20分钟左右;左下为全新口罩今早室外+公交车戴一个半小时;右下为全新口罩今早开车戴一小时左右. 还有这图 空气污染 – 红色的是严重的.中国,尤其是华北地区,是全球最红的地区,没有”之一”. 本文一共 113 个汉字, 你数一下对不对. 一张图告诉你北京的雾霾有多严重. (AMP 移动加速版本) 赞赏我的几个理由. ¥...
  3. 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
  4. 穷举算法的应用 – 去除EXCEL文件中的保护 EXCEL 是可以用密码来保护的. 比如 这个EXCEL 就用了密码保护. 打开EXCEL文件 你会注意到 无法编辑 无法查看宏(VBA)的代码. 去除保护很简单 第一步先编辑宏 VBA 把下面的VBA代码拷贝到VBA编辑器里 并按下F5运行 1...
  5. 谈谈 Utopian 成立公司 就在刚刚 Utopian 的老板 @elear 在 帖子和 discord 上宣布在 意大利成立 Utopian 公司. 可喜可贺! 这开始只是 Steem 上的一个小项目,...
  6. 步步高多媒体学生电脑 汇编程序设计 – 1 英文同步 90年代后期步步高生产的软驱一号(又称步步高多媒体学生电脑)和98型学生电脑都带了软驱,一按电源件, 都从软盘启动(98型可以从内置的电子盘启动) 步步高提供了直接在学习机上写汇编开发的工具 BASM. BASM 可以用来写 6502 汇编,并可以编译成 CMD 小型可执行程序 不支持 EXC 程序. CMD...
  7. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  8. 舍得给员工培训的公司是好公司 最近出差比较多, 很多人问我都干嘛去. 各种开会, 各种培训. 公司从剑桥一个软件公司请了一个专业的软件专家来做软件工程上的顾问. 我是 R&D 研发经理, 所以很自然的就要和他经常讨论, 然后目标是把当前公司的软件开发流程给完善, 提高, 把以前做的不对的得整对了. 培训的内容很多, 让我觉得以前公司完全就是在瞎搞, 什么...

SCDN 放弃 DDNS 回源 — IP 地址回源更新工具 V1.0【开源】

前端时间家里的路由器坏了,导致一些服务挂掉了,包括自己的博客,闺蜜圈 wiki 等。然后就是每天收到各种离线消息。

终于网络恢复之后,感觉一切正常然而,通过 cdn 反问就是回源错误。于是跟技术沟通了一下下下,发现盾云的域名回源竟然存在延迟,并且这个延迟好几个小时,这就比较蛋疼了。所以,每次换了 ip 首先要去设置 ip 地址才能正常回源。

既然,这个东西存在延迟,那么 dnspod 的 vip 的动态 ddns 解析也就没有任何的用了,延迟已经不是动态域名的问题了,是 cdn 域名刷新的问题。

今天早上忽然想到,既然域名有延迟,那就直接 ip 地址回源吧,不过域名太多了也不能每次都改啊。直接尝试代码解决吧,下面就是全部的代码了:

# 盾云SCDN 接口工具
# by:obaby
# https://h4ck.org.cn
# https://oba.by
from datetime import datetime

import requests

username = '邮箱地址'
password = '密码,先设置用户邮箱地址和密码'

def login():
    url = "https://scdn.ddunyun.com/prod-api/login"

    payload = "{\"email\":\""+username+"\",\"password\":\""+password+"\"}"
    headers = {
        'accept': 'application/json, text/plain, */*',
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'content-type': 'application/json;charset=UTF-8',
        'origin': 'https://scdn.ddunyun.com',
        'priority': 'u=1, i',
        'referer': 'https://scdn.ddunyun.com/',
        'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"macOS"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }

    response = requests.request("POST", url, headers=headers, data=payload)

    # print(response.json())
    js = response.json()
    if js['code'] == 0:
        return js['data']['access_token']
    else:
        print('[E] 发生错误:', js['message'])
    return None
    # return None


def get_site_list(token):
    url = "https://scdn.ddunyun.com/prod-api/site?domain_name=&sub_domain_name=&cname=&status=&current_page=1&total=0&page_size=20"

    payload = {}
    headers = {
        'accept': 'application/json, text/plain, */*',
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'authorization': 'Bearer ' + token,
        # 'cookie': 'LeCDN-Client=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbGllbnQiLCJleHAiOjE3MzMyOTM5NDcsIm5iZiI6MTczMzI3MTM0NywianRpIjoiNDEifQ.lpSIjvtVGE7wcxZgNF2upZiZ8QsPh2CYajevHqgsjzg; sidebarStatus=0',
        'priority': 'u=1, i',
        'referer': 'https://scdn.ddunyun.com/',
        'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"macOS"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }

    response = requests.request("GET", url, headers=headers, data=payload)

    # print(response.text)
    js = response.json()
    if js['code'] == 0:
        return js['data']['data']
    else:
        print('[E] 发生错误:', js['message'])
    return None


def get_site_source(access_token, site_id):
    url = "https://scdn.ddunyun.com/prod-api/site_source?site_id=" + str(site_id)

    payload = {}
    headers = {
        'accept': 'application/json, text/plain, */*',
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'authorization': 'Bearer ' + access_token,
        # 'cookie': 'LeCDN-Client=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbGllbnQiLCJleHAiOjE3MzMyOTM5NDcsIm5iZiI6MTczMzI3MTM0NywianRpIjoiNDEifQ.lpSIjvtVGE7wcxZgNF2upZiZ8QsPh2CYajevHqgsjzg; sidebarStatus=0',
        'priority': 'u=1, i',
        'referer': 'https://scdn.ddunyun.com/',
        'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"macOS"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }

    response = requests.request("GET", url, headers=headers, data=payload)

    js = response.json()
    if js['code'] == 0:
        return js['data']['data']
    else:
        print('[E] 发生错误:', js['message'])
    return None


def delete_source(access_token, site_id, source_id):
    # url = "https://scdn.ddunyun.com/prod-api/site_source/593?site_id=382"
    url = "https://scdn.ddunyun.com/prod-api/site_source/" + str(source_id) + "?site_id=" + str(site_id)

    payload = {}
    headers = {
        'accept': 'application/json, text/plain, */*',
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'authorization': 'Bearer ' + access_token,
        # 'cookie': 'LeCDN-Client=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbGllbnQiLCJleHAiOjE3MzMyOTM5NDcsIm5iZiI6MTczMzI3MTM0NywianRpIjoiNDEifQ.lpSIjvtVGE7wcxZgNF2upZiZ8QsPh2CYajevHqgsjzg; sidebarStatus=0',
        'origin': 'https://scdn.ddunyun.com',
        'priority': 'u=1, i',
        'referer': 'https://scdn.ddunyun.com/',
        'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"macOS"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }

    response = requests.request("DELETE", url, headers=headers, data=payload)

    js = response.json()
    if js['code'] == 0:
        return js['data']
    else:
        print('[E] 发生错误:', js['message'])
    return None


def edit_source(access_token, site_id, source_id, ip_address):
    url = "https://scdn.ddunyun.com/prod-api/site_source/" + str(source_id)

    payload = "{\"id\":" + str(source_id) + ",\"site_id\":" + str(
        site_id) + ",\"type\":\"ipaddr\",\"content\":\"" + ip_address + "\",\"priority\":\"20\",\"weight\":15,\"created_at\":\"2024-11-25 13:21:23\",\"updated_at\":\"2024-12-01 13:39:13\",\"isEdit\":true}"
    headers = {
        'accept': 'application/json, text/plain, */*',
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'authorization': 'Bearer ' + access_token,
        'content-type': 'application/json;charset=UTF-8',
        # 'cookie': 'LeCDN-Client=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbGllbnQiLCJleHAiOjE3MzMyOTM5NDcsIm5iZiI6MTczMzI3MTM0NywianRpIjoiNDEifQ.lpSIjvtVGE7wcxZgNF2upZiZ8QsPh2CYajevHqgsjzg; sidebarStatus=0',
        'origin': 'https://scdn.ddunyun.com',
        'priority': 'u=1, i',
        'referer': 'https://scdn.ddunyun.com/',
        'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"macOS"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }

    response = requests.request("PUT", url, headers=headers, data=payload)

    js = response.json()
    if js['code'] == 0:
        return js['data']
    else:
        print('[E] 发生错误:', js['message'])
    return None


def add_source(access_token, site_id, ip_address):
    import requests

    url = "https://scdn.ddunyun.com/prod-api/site_source"

    payload = "{\"type\":\"ipaddr\",\"content\":\"" + ip_address + "\",\"priority\":\"20\",\"weight\":15,\"isEdit\":true,\"site_id\":" + str(
        site_id) + "}"
    headers = {
        'accept': 'application/json, text/plain, */*',
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'authorization': 'Bearer ' + access_token,
        'content-type': 'application/json;charset=UTF-8',
        # 'cookie': 'LeCDN-Client=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbGllbnQiLCJleHAiOjE3MzMyOTM5NDcsIm5iZiI6MTczMzI3MTM0NywianRpIjoiNDEifQ.lpSIjvtVGE7wcxZgNF2upZiZ8QsPh2CYajevHqgsjzg; sidebarStatus=0',
        'origin': 'https://scdn.ddunyun.com',
        'priority': 'u=1, i',
        'referer': 'https://scdn.ddunyun.com/',
        'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"macOS"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }

    response = requests.request("POST", url, headers=headers, data=payload)

    js = response.json()
    if js['code'] == 0:
        return js['data']
    else:
        print('[E] 发生错误:', js['message'])
    return None


def update_domain_source(domain_list, new_ip_address):
    token = login()
    print('[T] 获取到 token:', token)
    # 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助
    site_list = get_site_list(token)
    # new_ip_address = '123.234.187.19'
    # print(site_list)
    for s in site_list:
        # print(s['domain_name'])
        dn = s['domain_name']
        site_id = s['id']
        if dn in domain_list:
            source_list = s['sources']
            # print(source_list)
            print('-' * 100)
            print('[U] 时间:', datetime.now())
            print('[U] 更新域名配置:')
            print('[U] 域名:', dn)
            ip_count = 0
            for sl in source_list:
                source_id = sl['id']
                # site_id = sl['site_id']
                ip_address = sl['content']
                try:
                    if sl['type'] == 'ipaddr':
                        print('[U] 旧 IP 地址:', sl['content'])

                        # edit_source(token, site_id, source_id, ip_address)
                        if ip_address != new_ip_address:
                            print('[U] 更新:正在更新 ip 地址:123.234.187.19')
                            edit_source(token, site_id, source_id, new_ip_address)
                        else:
                            print('[U] 忽略:IP地址无变化')
                        ip_count += 1
                    else:
                        print('[D] 删除:删除域名回源:', ip_address)
                        delete_source(token, site_id, source_id)
                except Exception as e:
                    print('[E] 发生异常:', e)
            # 如果不存在 IP 地址回源 添加 ip 回源
            if ip_count == 0:
                print('[U] 添加:添加IP地址回源:', new_ip_address)
                add_source(token, site_id, new_ip_address)
            print('*' * 100)

    print('[U] 更新完成')
    print('#' * 100)


def print_usage():
    print('*' * 100)
    print('盾云 SCDN 回源地址配置工具')
    print('说明:用于更新盾云 ip 地址回源,解决域名回源导致的延迟问题')
    print('by: obaby')
    print('https://oba.by')
    print('https://h4ck.org.cn')
    print('#' * 100)

if __name__ == '__main__':
    print('*' * 100)
    print('盾云 SCDN 回源地址配置工具')
    print('by: obaby')
    print('https://oba.by')
    print('https://h4ck.org.cn')

然后在需要更新 ip 地址的地方通过下面的代码调用即可:

from baby_dunyun import *

if __name__ == '__main__':
    domain_list = [
        "image.dlj.kim",
        "nai.dog",
        "image.h4ck.org.cn",
        "wiki.guimiquan.cn",
        "baby.lc",
        "danteng.me",
        "loli.gifts",
        "lang.bi",
        "da.bi",
        "zhongxiaojie.cn",
        "oba.by",
        "zhongxiaojie.com",
        "lang.ma",
        "dlj.kim",
        "h4ck.org.cn",
    ]
    new_ip_address = '123.234.187.19'
    update_domain_source(domain_list, new_ip_address)

运行效果:

此工具仅支持盾云 scdn,https://scdn.ddunyun.com/#/dashboard

教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型


视频:油管/Youtube | B站/小破站 | 微博视频 | 西瓜视频 | 微信视频号 | X/推特 | 小红书

马斯克的x.ai到年底有免费的25美元的credit可以使用Grok大语言模型

前不久(今年初),伊隆·马斯克/Elon Musk的X公司开源了Grok大语言模型,并且给免费提供了25美元的credit可以调用。可以在其官网x.ai注册一个帐号,申请API KEY即可,官网还贴心的的给出了调用的例子。

curl https://api.x.ai/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer xai-......" -d '{
  "messages": [
    {
      "role": "system",
      "content": "You are a test assistant."
    },
    {
      "role": "user",
      "content": "Testing. Just say hi and hello world and nothing else."
    }
  ],
  "model": "grok-beta",
  "stream": false,
  "temperature": 0
}'

孩子们由于未成年,所以无法申请ChatGPT、X AI等大语言模式的帐号,平时他们只能在免费的微软冰/BING搜索引擎上使用集成的免费Copilot。不过今天听弟弟说,ChatGPT现在已经不需要登陆就可以使用,不过他说这个版本有点受限制。

平均长度来算的话,一句话的Prompt大概是0.0012美元。当然越长的句子花费越贵,毕竟价格是按Token来算的。可以粗略的估计一个单词是一个Token。

x.ai-usage 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

目测每条Prompt的费用是0.0012美元,25美元可以使用大概2万次

x.ai-free-25-credit 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

每个帐号有免费的25美元API费用

x.ai-dashboard 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

X.AI 的API网站界面

x.ai-api-models 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

API 的 X AI 模型:grok-beta 和 grok-vision-beta

grok-x-ai-api-keys 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

由 X AI 为 Grok LLM 创建 API 密钥。 Create API Keys for Grok LLM by X AI.

ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型

反正是免费的25美元,于是想着给娃做一个简单的PYTHON程序,然后人机交互,每次调用x.ai的Grok大语言模式,也正好让娃学一学实际的编程应用。于是让ChatGPT写了个程序,这种简单的程序ChatGPT基本上是Bug Free,生成的代码拿来就能用。

import requests
import json

api_key = "x_ai ..."

# Define the API endpoint and headers
url = "https://api.x.ai/v1/chat/completions"
headers = {
    "Content-Type": "application/json",
    f"Authorization": "Bearer {api_key}",
}

# Define a system message for context
system_message = {"role": "system", "content": "You are a test assistant."}

print("Welcome to the Grok, an AI chatbot. Type 'bye' to exit.\n")

while True:
    # Prompt the user for input
    user_input = input("You: ").strip()

    # Check if the user wants to exit
    if user_input.lower() == "bye":
        print("Goodbye!")
        break

    if user_input == "":
        continue

    # Define the payload
    payload = {
        "messages": [
            system_message,
            {"role": "user", "content": user_input}
        ],
        "model": "grok-beta",
        "stream": False,
        "temperature": 0
    }

    try:
        # Make the request
        response = requests.post(url, headers=headers, json=payload)

        # Check the response status
        if response.status_code == 200:
            data = response.json()
            assistant_response = data["choices"][0]["message"]["content"]
            print(f"Grok: {assistant_response}\n")
        else:
            print(f"Error: {response.status_code} - {response.text}")
    except Exception as e:
        print(f"An error occurred: {e}")

之后 简单做了些修改,比如避免空的Prompt,并且用strip函数去除句首和句尾的空格。娃使用的是Mac苹果电脑,还得在Terminal装个Homebrew,然后安装Python,并且用 pip3 install requests 安装上请求包,就可以使用了。

x-ai-prompt-python-2024-12-01-12.53.31-scaled 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

虽然界面有点素,也就是个简单的终端,但是对于孩子来说已经是个很强大的软件了。

kid-plays-x-ai-grok-llm-2024-12-01-12.53.26-scaled 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

弟弟在苹果电脑上成功跑起了x.ai的大语言模式 Grok

ChatGPT 可以拿来做什么?

ChatGPT 通用人工智能

英文:ChatGPT writes a Python Script to Interact with Grok LLM from x.ai (Free $25 Credit)

本文一共 743 个汉字, 你数一下对不对.
教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计
The post 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. HPZ800服务器主板太老不支持超过2TB的大硬盘 我家里一直用的是HPZ800服务器, 很吵, 很老, 虽然这台服务器已经有十年之久(我在EBAY上买来用了五年多了), 但是即使放到今天, 这服务器速度依旧很快, 很稳定. 由于服务器用的是ECC较验内存, 所以基本上不重启关机. HPZ800主机有两个硬核CPU – 因特志强 X5650 – 每个CPU是12核....
  2. 给孩子零花钱培养孩子正确的金钱观价值观 两个娃已经不知不觉7岁8岁了. 媳妇和我商量一下决定给孩子每人每周5英镑的零花钱(Pocket Money). 这样他们慢慢的就有自己的小积蓄备将来不时之需: 比如朋友聚会生日啥的需要准备礼物. 同时, 我们决定不再给孩子买零食(薯片啥的). 孩子一天好几餐, 晚上睡觉前还得吃零食, 我们就多买了很多水果面包, 健康的食物多吃一些总不是啥坏事. 孩子可以用这些零钱买自己想要的东西, 我们也不再过问. 孩子有自己的决定权. 第一周的时候,...
  3. 测测你的幸运 – Linux Fortune-Teller LINUX 下有很好很好玩的命令,之前已经介绍过: figlet, rig, curl. 现在推荐另一个 命令 fortune 是用来随机显示一段(句)话的.fortune 在英文里就是幸运的意思. 这个命令可以不需要 参数 如果没有 可以通过 apt-get...
  4. 负电价活久见: 安装Octopus智能电表省电费甚至赚钱 前几周我的电气公司 Octopus 终于来装智能电表了(Smart Meter),虽然是免费安装的,但是排队排了有两三年了吧。因为之前一直写邮件催的时候就老是说 Not Ready。 收到邮件说可以安装智能电表我还是相当开心和期待的,因为已经听说这玩意好,但是还是得亲身体验一下。工程师来安装大概不到2小时,其中需要停电闸一会儿,重新接下线。装好后,给了个小册子,自动切换到了 Agile 的电价,也就是每半小时的电价都不一样,提前一天可以在手机App和网站上查得。 正好在原来的电价计费合同快要结束前2天换到了智能电表计价 Octopus Agile方式,但是系统还是扣了我75英镑 Exit Fee (提前合同结束得交违约费),不过我一个电话打过去,公司很爽快就给我退了。...
  5. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  6. 你要找什么样的老婆? 找媳妇的标准 昨天和网友在剑桥面基, 网友奔现, 他从爱尔兰过来, 小我12岁, 就聊到了找对象的标准. TLDR; 找老婆不要(只)看颜值, 而要注重性格, 为人处事和顾家等更重要的品质, 当然性和谐也很重要. 在当今社会, 人们对于找伴侣的标准有所不同. 有些人认为颜值是最重要的, 因为外貌吸引力可以让人在日常生活中感到愉悦, 这是人的本性,...
  7. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  8. 给STEEM中文微信群加了个机器人 之前说到我的公众号 justyyuk 可以查询几种虚拟货币的实时价钱, 但是有点不方便, 因为很多朋友在群里聊天得切换到公众号, 这下好了, 今天往STEEM中文微信群(还有编程群)加了个机器人, 在聊天的时候想了解价钱就直接输入货币代号即可, 如: 既方便自己, 又能方便别人(省事, 价格信息会同时显示给其它成员). 注: 这机器人不是我做的, 只是我拉进来的,...

Django 打包为 docker 镜像

之前也想过将 django 项目打包成 docker 部署,但是由于之前的项目过于庞大,用到了系统的定时任务等各种系统服务,不知道打包成 docker 之后相关的服务是否依然能够启动,所以并未实施。

前几天做的我的足迹地图,项目相对来说比较独立,没有其他的依赖项,正好可以尝试一下。

首先在项目下创建 Dockerfile,写入以下内容:

# 使用官方Python运行时作为父镜像
FROM python:3.8.18-slim

# 设置工作目录
WORKDIR /app

# 将当前目录内容复制到位于/app中的容器中
COPY . /app


# 安装项目依赖
RUN pip install --no-cache-dir -r requirements.pip -i https://pypi.tuna.tsinghua.edu.cn/simple

# 暴露端口8000,与Django的默认运行端口一致
EXPOSE 10086

# 定义环境变量
ENV NAME=Django

# 在容器启动时运行Django的manage.py命令
CMD ["python", "manage.py", "runserver", "0.0.0.0:10086"]

网上代码来回抄,第二行都是FROM python:3.8-slim 如果这么写会导致下面的错误:

PS E:\Pycharm_Projects\BabyFootprintV2> docker build -t baby-footprint:1.0 .
[+] Building 21.2s (2/2) FINISHED docker:desktop-linux
 => [internal] load build definition from Dockerfile 0.0s
 => => transferring dockerfile: 568B 0.0s
 => ERROR [internal] load metadata for docker.io/library/python:3.8-slim 21.1s
------
 > [internal] load metadata for docker.io/library/python:3.8-slim:
------
Dockerfile:2
--------------------
   1 | # 使用官方Python运行时作为父镜像
   2 | >>> FROM python:3.8-slim
   3 |
   4 | # 设置工作目录
--------------------
ERROR: failed to solve: python:3.8-slim: failed to resolve source metadata for docker.io/library/python:3.8-slim: failed to do request: Head "https://registry-1.docker.io/v2/library/python/manifests/3.8-slim": dialing registry-1.docker.io:443 container via direct connection because has no HTTPS proxy: connecting to registry-1.docker.io:443: dial tcp 69.63.186.31:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.

直接访问上面的网址docker.io/library/python:3.8-slim会发现根本没这么东西,所以要改成FROM python:3.8.18-slim

搜索一下,会有教程提示先下载 python3.8的 docker:

PS E:\Pycharm_Projects\BabyFootprintV2> docker pull python:3.8.18-slim
3.8.18-slim: Pulling from library/python
8a1e25ce7c4f: Pull complete
1103112ebfc4: Pull complete
b7d41b19b655: Pull complete
6a1ad0671ce8: Pull complete
de92c59aadaa: Pull complete
Digest: sha256:e796941013b10bb53a0924d8705485a1afe654bbbc6fe71d32509101e44b6414
Status: Downloaded newer image for python:3.8.18-slim
docker.io/library/python:3.8.18-slim

3.8.18是 ok 的,此时重新 build 即可:

PS E:\Pycharm_Projects\BabyFootprintV2> docker build -t baby-footprint:1.0 .
[+] Building 214.6s (9/9) FINISHED docker:desktop-linux
 => [internal] load build definition from Dockerfile 0.0s
 => => transferring dockerfile: 571B 0.0s
 => [internal] load metadata for docker.io/library/python:3.8.18-slim 0.0s
 => [internal] load .dockerignore 0.0s
 => => transferring context: 2B 0.0s
 => [1/4] FROM docker.io/library/python:3.8.18-slim 0.1s
 => [internal] load build context 0.9s
 => => transferring context: 43.30MB 0.8s
 => [2/4] WORKDIR /app 0.1s
 => [3/4] COPY . /app 0.2s
 => [4/4] RUN pip install --no-cache-dir -r requirements.pip -i https://pypi.tuna.tsinghua.edu.cn/simple 212.0s
 => exporting to image 1.4s
 => => exporting layers 1.4s
 => => writing image sha256:cba073b574f88f19be7487b29612e19b9826ab99e7b54ea748bd5df22e83e1a0 0.0s
 => => naming to docker.io/library/baby-footprint:1.0 0.0s

编译变成,就可以像 docker hub 推送镜像了,不过首先需要设置 tag,如果直接推送会提示下面的错误:

PS E:\Pycharm_Projects\BabyFootprintV2> docker push baby-footprint:1.0
The push refers to repository [docker.io/library/baby-footprint]
04013169f44d: Preparing
f7c443286fad: Retrying in 5 seconds
fd749af069d5: Retrying in 5 seconds
3482d4cd60de: Retrying in 5 seconds
370c0e78e3ea: Retrying in 5 seconds
a74bee0a48a5: Waiting
c8f253aef560: Waiting
a483da8ab3e9: Waiting
denied: requested access to the resource is denied

这个提示也比较坑人,由于 docker 被屏蔽,我一直以为是网络连接问题,直到后来才发现是路径问题。

通过下面的命令设置 tag 后 push:

docker tag baby-footprint:1.0 obaby/baby-footprint:1.0
PS E:\Pycharm_Projects\BabyFootprintV2> docker push obaby/baby-footprint:1.0
The push refers to repository [docker.io/obaby/baby-footprint]
04013169f44d: Pushed
f7c443286fad: Pushed
fd749af069d5: Pushed
3482d4cd60de: Pushed
370c0e78e3ea: Layer already exists
a74bee0a48a5: Pushed
c8f253aef560: Pushed
a483da8ab3e9: Layer already exists
1.0: digest: sha256:0d0c0989a64cc3f3e192e5c8e7bc4931676d49ab66d810061a1daec6b1a6af58 size: 2000

受限于网络问题,可能会 push 失败,多重试几次就 ok 了。

最后就可以直接 docker 安装运行啦:

docker push obaby/baby-footprint:tagname

创建一个我的足迹地图

关于我页面,有个个人足迹的图片。

这个图片最早是通过google maps的足迹地图来生成的。

谷歌地图

然而,好景不常,这个东西现在也下架了,今年又去了很多地方,但是这个图片要更新就变得非常麻烦,总不能用ps往上打点吧?这也有点太智障了。于是想着通过地图来实现这个功能,高德或者百度,上午试了一下高德,发现免费的静态地图,最多只能添加10个marker,这尼玛是出来恶心姐姐我的吗?就离谱。

下午看了下百度的要好很多,于是下午折腾了一下,把足迹点,通过代码拼成url直接添加图片就能显示了,这个还是不错的,最终效果如下:

我的足迹

其中粉色点点是最起码呆过一段时间的,黄色的点点是途径点。

处理的代码也比较简单,不过最开始用的高德,于是gps坐标获取用的高德的api,需要提供高德的key。

静态地图用的百度的,所以又需要百度地图的ak,这个ak必须用服务端的ak哈。全部代码如下,没做配置功能,需要的自己改改代码就行了(运行前 pip install requests 依赖库):

import time

import requests


def amap_geodecode(addr):
    try:
        para = {
            'key': '高德的key',  # 高德地图开放平台申请的key
            'address': addr  # 传入地址参数
        }
        url = 'https://restapi.amap.com/v3/geocode/geo?'  # 高德地图API接口
        req = requests.get(url, para)
        req = req.json()
        print('-' * 30)
        print(req)
        m = req['geocodes'][0]['location']
        print(addr)
        print(m)
    except:
        return None
    return m


# 按装订区域中的绿色按钮以运行脚本。
if __name__ == '__main__':
    url_bast = 'https://api.map.baidu.com/staticimage/v2?ak=<百度的ak>&width=900&height=800&zoom=5&center=103.8319522831,36.0615585627&markerStyles=0xFF0000|0x808000&markers='
    city_list = ['北京','上海','深圳', '东营','连云港','锡林郭勒盟','赤峰','承德','济南','泰安','枣庄','昆明','长沙','湘潭','株洲','日照','威海','烟台','深圳','广州','西安','临沂','潍坊','青岛','大理','清迈','海口','三亚','蜈支洲岛','清莱']
    pass_city_list = ['天津','德州','景德镇','衢州','佛山','无锡']

    print('添加点亮城市:')
    cord_list = []
    visited_marker_style_list = []
    for c in city_list:
        m = amap_geodecode(c)
        if m:
            v = '' + m
            cord_list.append(v)
            visited_marker_style_list.append('m,V,0xFF69B4')
        time.sleep(2)
    print('添加途径城市:')
    for c in pass_city_list:
        m = amap_geodecode(c)
        if m:
            v = '' + m
            cord_list.append(v)
            visited_marker_style_list.append('s,P,0xFFFF00')
        time.sleep(2)

    print(cord_list)
    print('|'.join(cord_list))
    print('最终url:')
    print(url_bast + '&markerStyles=' + '|'.join(visited_marker_style_list) + '&markers=' + '|'.join(cord_list))

别问为啥这么搞,问就是懒得改了,哈哈哈。添加图片直接使用最后的url即可。以后足迹点多了之后,修改list重新生成url即可。

唯一的缺点,不支持国外的坐标,我的泰国的坐标标不上,不过也无所谓了,反正就一个泰国而已。

更多定制化功能,参考百度地图的相关开发文档:

https://lbsyun.baidu.com/faq/api?title=static/prepare

https://lbsyun.baidu.com/faq/api?title=static/markerStatic

https://lbsyun.baidu.com/faq/api?title=static/heightStatic

Python 解析 DLT645 协议数据

DL/T 645是中国国家电网公司制定的一种用于电能表通信的协议,全称为《多功能电能表通信协议》。该协议主要用于电能表与数据采集终端(如集中器、抄表器等)之间的通信,以实现电能数据的采集、传输和管理。

主要特点

  1. 多功能性:支持多种电能参数的读取和设置,如电压、电流、功率、电能、功率因数等。
  2. 灵活性:支持多种通信方式,如RS-485、红外、无线等。
  3. 安全性:支持数据加密和身份验证,确保数据传输的安全性。
  4. 标准化:符合国家电网公司的标准,便于大规模部署和维护。

协议结构

DL/T 645协议的数据帧结构通常包括以下几个部分:

  1. 帧起始符:标识数据帧的开始,通常为0x68。
  2. 地址域:标识电能表的地址,通常为6字节。
  3. 控制码:标识命令类型,如读取数据、写入数据等。
  4. 数据域:包含具体的命令数据或返回的数据。
  5. 校验码:用于校验数据帧的完整性,通常为1字节。
  6. 帧结束符:标识数据帧的结束,通常为0x16。

常用命令

  • 读取数据:用于读取电能表的各种参数,如电压、电流、功率等。
  • 写入数据:用于设置电能表的参数,如时间、费率等。
  • 广播校时:用于同步电能表的时间。
  • 冻结命令:用于冻结电能表的当前数据,便于后续读取。

应用场景

DL/T 645协议广泛应用于智能电网、电力监控系统、远程抄表系统等领域。通过该协议,可以实现电能数据的实时采集、远程监控和自动化管理,提高电力系统的运行效率和管理水平。

数据报文格式:

数据报文解析相对来说并不复杂,所有的数据都是流式,直接按照顺序进行读取即可。不过里面数据的内容,并不需要在此进行数值转换(电力数据需要处理),基本读到什么内容就是什么内容。

例如原始数据:

message = b'hw8\x06(\x15Dh\x00\x02\x01\x1f(\x16'

解析数据可以通过下面的方法:

def process_645_data(message):
    print('message in hex=', message.hex())
    start_pos = message[0:1]
    # print('start_code = ', start_pos.hex())
    hid = message[1:7]
    # print(hid)
    # int_value = int.from_bytes(hid,byteorder='little')  # 默认使用大端模式
    # print(int_value)
    # int_value = int.from_bytes(hid,byteorder='big')  # 默认使用大端模式
    # print(int_value)
    print('hid = ', hid[::-1].hex())
    hid_hex = hid[::-1].hex()
    # print(hid.hex())
    data_pos = message[7:8]
    # print('data_pos = ', data_pos.hex())

    control_code = message[8:9]
    print('control_code = ', control_code.hex())
    data_length = message[9:10]
    print('data_length =', data_length.hex())
    data_lenth_int = int.from_bytes(data_length, byteorder='little')
    data = message[10:10 + data_lenth_int]
    print('data = ', data)
    crc_code = message[10 + data_lenth_int:11 + data_lenth_int]

    # crc_source = message[0:10 + data_lenth_int]

    # calced_crc = calc_crc(crc_source)
    # print('calced crc = ', calced_crc)

    print('crc_code = ', crc_code.hex())
    end_pos = message[11 + data_lenth_int:12 + data_lenth_int]
    # print('end_pos = ', end_pos.hex())

    return hid, hid_hex, control_code, data_lenth_int, crc_code, data

接收到的数据解析出来之后不需要再进行转换int.from_bytes(hid,byteorder=’little’) 不管是大端还是小端模式,转出来都是错的,直接将高低位倒序输出即可:hid[::-1].hex()

解析后的数据:

message in hex= 68773806281544680002011f2816
hid =  441528063877
control_code =  00
data_length = 02
data =  b'\x01\x1f'
crc_code =  28

对于数据上报的内容,例如电量,电报上报数据为下面的报文:

# 电量上报数据
data_msg = b'\x68\x77\x38\x06\x28\x15\x44\x68\x91\x08\x33\x33\x34\x33\x33\x33\x33\x33\x38\x16'

解析数据内容:

data_msg = b'\x68\x77\x38\x06\x28\x15\x44\x68\x91\x08\x33\x33\x34\x33\x33\x33\x33\x33\x38\x16'

hid, hid_hex, control_code, data_lenth_int, crc_code, data = process_654_data(data_msg)
print(control_code.hex())
print(data.hex())

data_type = data[0:4]
data_source = data[4:]
process_data_type = bytes(byte - 0x33 for byte in data_type)
print(process_data_type[::-1].hex())
process_data_data = bytes(byte - 0x33 for byte in data_source)
print(process_data_data[::-1].hex())
print(int(process_data_data[::-1].hex(), 16) / 100)

解析后的数据:

message in hex= 6877380628154468910833333433333333333816
hid =  441528063877
control_code =  91
data_length = 08
data =  b'33433333'
crc_code =  38
91
3333343333333333
00010000
00000000
0.0

crc 计算方法:

def calc_crc(src):
    sum = 0
    for i in range(len(src)):
        sum += src[i]
    crc = sum % 256
    return crc

 

数据解析处理参考:https://blog.csdn.net/m0_37651448/article/details/143100598

迭代幂运算/重幂的介绍与其Python代码实现


数学中的迭代幂运算/重幂是什么?

迭代幂运算(重幂)是数学中的一种运算,涉及到反复进行幂次运算。它是超运算序列的一部分,该序列延伸了加法、乘法和幂运算。在迭代幂运算中,一个数自乘多次,直到达到指定的次数。

一个数a迭代幂的高度n通常表示为:tex_7f275feba9caa33491cc739d97613e41 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 ,也就是把n写在a的左上角,(也可以记作:a↑↑n)这表示a被迭代n次。

例如:

  • tex_809d19495ee2ad967edb956694773d96 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 (简单恒等式)
  • tex_b2971689df7256a7c315e159e8dceca6 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 (a自乘一次)
  • tex_185fcc3b7fe6daf47068d87ffd22f670 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 (a的幂次为a自乘)
  • tex_a932b7bb96225dc665bbe571f816002a 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 ,依此类推。

在迭代幂运算的上下文中,tex_b3ef97b6eba2428ee919c02c89d2c9ea 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 通常未定义或没有普遍共识。然而,一些数学惯例建议对于任何 tex_58c6653dfed174ea991f702adfb3e6f4 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 tex_2f1f5ed0eeff6d95cf9b145624dfb6af 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 ,类似于在幂运算中对任何非零的 tex_58c6653dfed174ea991f702adfb3e6f4 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 tex_5c135adeedf02dca7953a9719fb38fa2 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 的情况。

迭代幂运算示例

让我们评估 tex_a5f6729c6edbc3df5dae3c81efe128b2 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 (读作“2迭代到高度3”):

tex_c3974a6b51c4029486462ba28d7f5c17 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机
tex_38f3272f3cc8a02e84ceed576663756c 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机
因此 tex_a79a23ebbcc28f19e40a7b5604f3e748 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机

迭代幂/重幂运算的通用性质

  • 非交换性:迭代幂运算不是交换的,这意味着 tex_2bb7d37b96ba358da2a2c8024d02fe57 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机
  • 增长速度非常快:迭代幂运算增长非常快。即使是小数也会因为幂运算的快速增长而导致非常大的结果。

迭代幂运算/重幂在基础数学中较少见,但在某些高级数学领域中发挥作用,特别是在涉及极大数的领域,如大数理论和计算机科学中。

用 Python 计算迭代幂运算

以下是两个计算迭代幂运算的Python函数。第一个使用递归,第二个使用迭代。

在两个函数中,我们在开始时添加了对 n = 0 的检查。如果 n 为 0,则函数返回 1,否则继续处理。这种方式使函数能够按照任意数的迭代幂高为0时为1的惯例处理 n=0 的情况。

递归函数计算迭代幂

递归函数:此函数将自己调用,n 的高度递减1,直到达到1,此时返回基数a。这就实现了从上到下构建指数链的效果。

@lru.cache(None)  ## 缓存函数
def tetration_recursive(a, n):
    if n == 0:
        return 1
    if n == 1:
        return a
    return a ** tetration_recursive(a, n - 1)

递归计算迭代幂的函数理论上可以进行尾优化。在尾递归中,递归调用是函数中的最后一个操作,这样某些编译器或解释器可以通过重用相同的堆栈帧来优化调用堆栈的使用。这可以通过消除每个递归调用的额外堆栈帧需求来将空间复杂度降到 O(1)。

然而,当前的递归实现并不是尾递归的,因为递归调用嵌套在一个幂运算中:

return a ** tetration_recursive(a, n - 1)

这里,幂运算依赖于递归调用的结果,所以在完成当前调用之前必须计算出结果,从而阻止了尾递归优化。

迭代函数计算迭代幂

迭代函数:此函数使用 for 循环遍历高度 n,通过在每次迭代中更新幂运算的结果,来从下至上计算结果。

def tetration_iterative(a, n):
    if n == 0:
        return 1
    result = a
    for _ in range(1, n):
        result = a ** result
    return result

迭代幂算法的时间/空间复杂度

Python函数计算迭代幂的时间和空间复杂度取决于其递归或迭代实现。让我们分析两种实现。

递归函数的复杂度

时间复杂度:

  • 每次递归调用都会与之前的调用结果进行一次幂运算。
  • 总共有n-1次递归调用,所以该函数被调用了O(n)次。
  • 然而,像 tex_ad68cb15ab4e6c9d3aa23d421625d67a 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 的幂运算需要 tex_e6c0128be5c7d7501ff5a45664d688da 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 的时间。
  • 因此,对于较大的 n 值,由于幂次的增长,其时间复杂度会呈指数增长。
  • 这导致总的时间复杂度大约为 tex_61a94ff4b35f50421447e762bcc2b21e 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 ,有n层,这意味着增长速度非常快

空间复杂度:

  • 由于这是一个递归函数,每次调用都需要堆栈空间。
  • 递归的最大深度为 n,所以空间复杂度为 O(n)。
迭代函数的复杂度

时间复杂度:

  • 与递归版本一样,该函数迭代 n – 1 次
  • 每次迭代涉及计算 tex_58c6653dfed174ea991f702adfb3e6f4 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 的幂次,这个结果会呈指数增长。
  • 因此,时间复杂度也成为 tex_61a94ff4b35f50421447e762bcc2b21e 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机 ,有n层,因为每一步都是指数级增长。

空间复杂度:

  • 此迭代版本仅需要少量额外空间用于 result 变量等,因此它的额外空间复杂度为 O(1)。
  • 然而,结果本身可能会变得非常大,如果 a 和 n 很大,可能需要大量内存来存储。

由于反复幂次的快速增长,这两种实现的时间复杂度都非常高,对于较大的值变得不可行。递归版本由于调用堆栈的使用空间复杂度为 O(n),而迭代版本的辅助空间复杂度为 O(1),但仍然需要处理极大数,这可能会间接影响内存使用。

英文:Tetration Operator in Math Simply Explained with Python Algorithms

本文一共 1253 个汉字, 你数一下对不对.
迭代幂运算/重幂的介绍与其Python代码实现. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 迭代幂运算/重幂的介绍与其Python代码实现 Python 学习笔记 数学 数学 程序设计 计算机
The post 迭代幂运算/重幂的介绍与其Python代码实现 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. 英国房子的EPC节能报告(Energe/Efficiency Performance Certificate) EPC (Energe/Efficiency Performance Certificate) 是英国房子的节能报告, 法律上规定, 每个房子都必须要有一个EPC报告, 报告的有效期为十年. 房东在把房子出租或者想卖房的时候, 这个EPC就必须有效, 在一些情况下 比如出租房子的时候, 这个EPC报告还必须符合一些最低标准, 比如房子必须满足 F档(类似及格线)...
  4. 给孩子零花钱培养孩子正确的金钱观价值观 两个娃已经不知不觉7岁8岁了. 媳妇和我商量一下决定给孩子每人每周5英镑的零花钱(Pocket Money). 这样他们慢慢的就有自己的小积蓄备将来不时之需: 比如朋友聚会生日啥的需要准备礼物. 同时, 我们决定不再给孩子买零食(薯片啥的). 孩子一天好几餐, 晚上睡觉前还得吃零食, 我们就多买了很多水果面包, 健康的食物多吃一些总不是啥坏事. 孩子可以用这些零钱买自己想要的东西, 我们也不再过问. 孩子有自己的决定权. 第一周的时候,...
  5. 拔牙后的注意事项(图, 慎入) Care of Mouth after Extraction 昨天又拔了两颗牙, 初步定在5月4号装牙套. 这是牙医诊所给的术后注意事项: 拔完后需要等3-4小时麻醉失效后才能吃喝. 稍微流点血是很正常的. 但是请不要漱口吐出, 因为这会加速流血. 你只要轻轻的含着口水并咽下即可. 如果一直流血, 请拿着纱布(并不是纸巾)放在拔牙处20分钟. 24小时内请不要运动, 术后几小时内回家静静坐着. 12小时内不要吸烟, 喝酒或者喝热饮, 因为这会让伤口流血....
  6. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  7. ChatGPT-4 使用 Math Wolfram 插件解决数学脑筋急转弯问题 这篇文章, 我们看一个简单的数学问题(脑筋急转弯), 并用 Python 解决它. 我们看一下LLM(大型语言模型): ChatGPT3.5和ChatGPT4. 通过 ChatGPT-Plus 订阅(目前每月 20 美元 + VAT增值税), 我们可以启用...
  8. HPZ800服务器主板太老不支持超过2TB的大硬盘 我家里一直用的是HPZ800服务器, 很吵, 很老, 虽然这台服务器已经有十年之久(我在EBAY上买来用了五年多了), 但是即使放到今天, 这服务器速度依旧很快, 很稳定. 由于服务器用的是ECC较验内存, 所以基本上不重启关机. HPZ800主机有两个硬核CPU – 因特志强 X5650 – 每个CPU是12核....

100行代码实现 favicon 小工具

这几天查看统计的时候发现统计页面的小图标不显示了。

图标变成了一个白色方框,这个umami 一直无法加载 favicon,之前换成了:https://favicon.cccyun.cc/h4ck.org.cn

现在这个服务貌似证书过期了,也没人维护,看来也没多少人用啊:

本着能动手尽量别 bb 的理念,既然不能用了那就自建服务吧。

个人觉得最简单的代码还是通过 python 实现,依赖于 flask + favicon 库,只需要一百行代码就 ok 了。实现方式,通过 favicon 库获取图标,将图标数据缓存到 redis,再次请求直接返回 redis 缓存数据。完整代码如下:

import json

from flask import Flask, request, redirect, jsonify
import favicon
import redis
import json
from urllib.parse import urlparse

app = Flask(__name__)

rds = redis.Redis(host='localhost', port=6379, db=1)


def get_domain_from_url(url):
    parsed_uri = urlparse(url)
    return 'https://{uri.netloc}'.format(uri=parsed_uri)


def get_query_count():
    key = 'QUERY_COUNT'
    count = 1
    if rds.exists(key):
        count = int(rds.get(key))
    return count


def set_query_count():
    key = 'QUERY_COUNT'
    count = 1
    if rds.exists(key):
        count = int(rds.get(key))
        count += 1
    rds.set(key, count)
    return count


def get_icon_list_from_rds(key):
    if rds.exists(key):
        # print('cached')
        cashed = rds.get(key)
        js = json.loads(cashed)
        return js
    icons = favicon.get(key)
    # rds.set('url',icons,)
    icon_list = []
    for i in icons:
        data = {
            'url': i.url,
            'width': i.width,
            'height': i.height,
            'format': i.format
        }
        icon_list.append(data)
    js_str = json.dumps(icon_list)
    rds.setex(key, 86400, js_str)
    return icon_list


@app.route('/')
def hello_world():  # put application's code here

    return ('--------------------------- <br> '
            'Query count:' + str(get_query_count()) + '<br>'
                                                      '=========================== <br> '
                                                      'Baby Favicon Tool v1.0  \r\n<br> by:obaby \r\n <br><a href="https://oba.by" target="_blank">https://oba.by</a> <br>\r\r '
                                                      '<a href="https://h4ck.org.cn" target="_blank">https://h4ck.org.cn</a>')


# http://127.0.0.1:5000/api/get_favicon?url=https://h4ck.org.cn
@app.route('/api/get_favicon')
def search():
    query = request.args.get('url')
    if '.' not in query:
        return 'invalid url'
    if not query.startswith('http'):
        query = 'http://' + query

    icons = get_icon_list_from_rds(query)
    set_query_count()
    # icons_str = json.dumps(icons)
    return jsonify(icons)


@app.route('/api/redirect_favicon')
def redirect_icon():
    query = request.args.get('url')
    if '.' not in query:
        return 'invalid url'
    if not query.startswith('http'):
        query = 'http://' + query
    set_query_count()
    icons = get_icon_list_from_rds(query)
    try:
        icon_url = icons[0]['url']
    except:
        icon_url = 'https://h4ck.org.cn/wp-content/uploads/2024/09/favicon.png'
    return redirect(icon_url, code=302)


if __name__ == '__main__':
    app.run()

到这里这个服务就算完成了,后续就是通过 nginx 反代了,经常反代的朋友都回了,我就不写了。

修改 umami 源代码:vim umami/src/components/common/Favicon.tsx

修改划线部分为上述内容,重新编译即可,编译过程中很可能会卡在 build-geo.修改 build 脚本 vim scripts/build-geo.js

这个破玩意儿 bug 之处在于,如果使用 github 代理,下载过程会出错,第二部分的实时解压就挂了,这个逻辑也是 tm 神了,不能下载完再解压吗?

直接下载第一处gz 文件解压,将 GeoLite2-City.mmdb放入geo 目录下,注释掉第二部分执行 yarn build 即可。不得不多,这 dq 真是给程序员创建了无数的便利,就尼玛离谱。重新启动服务一切就 ok 了。

图标又回来了,现有服务地址: https://favicon.h4ck.org.cn (不保证服务可用性,有时候的确是懒不想折腾了,之前的 gravatar 忽然因为 cdn 问题就失效了,结果删除重建也不行就放弃了。这个实属无奈,但是基本都会保证一个可用的服务。)

使用方法:

接口:  
1. 获取 favicon 数据,返回 json 格式  
http://127.0.0.1:5000/api/get_favicon?url=oba.by  
返回数据内容:  
```json
[
  {
    "format": "png",
    "height": 300,
    "url": "https://oba.by/wp-content/uploads/2020/09/icon-500-300x300.png",
    "width": 300
  },
  {
    "format": "png",
    "height": 200,
    "url": "https://oba.by/wp-content/uploads/2020/09/icon-500-200x200.png",
    "width": 200
  },
  {
    "format": "png",
    "height": 192,
    "url": "https://oba.by/wp-content/uploads/2020/09/icon-500-200x200.png",
    "width": 192
  },
  {
    "format": "png",
    "height": 32,
    "url": "https://oba.by/wp-content/uploads/2020/09/icon-500-100x100.png",
    "width": 32
  },
  {
    "format": "ico",
    "height": 0,
    "url": "https://oba.by/favicon.ico",
    "width": 0
  },
  {
    "format": "jpg",
    "height": 0,
    "url": "https://h4ck.org.cn/screenshots/obaby_tuya.jpg",
    "width": 0
  }
]
```
2. 直接返回 favicon 链接  
http://127.0.0.1:5000/api/redirect_favicon?url=oba.by  
返回数据内容为上述接口的第一个结果,例如上面的 域名将会直接 302跳转到 https://oba.by/wp-content/uploads/2020/09/icon-500-300x300.png  
如果没有 favicon 将会返回默认连接:https://h4ck.org.cn/wp-content/uploads/2024/09/favicon.png

代码地址:

https://github.com/obaby/baby-favicon-tool.git

第十五届蓝桥杯完美通关

说起第十五届蓝桥杯比赛,这其实是一个悲伤的故事。1月28日(年前)小王子参加完蓝桥STEMA考试,年后成绩公布,尽管心中有所预期,但当小王子最终斩获全国TOP2%的优异成绩时,我依旧深感意外。

小试自定义GPT

最近不是在折腾LLM嘛,于是就试了两条路子:用openai的api,以及直接在openai的界面里面创建GPT。

前者没啥特别的,chatgpt的api做的很成熟了,from openai import OpenAI 之后直接在python里面调用几个现成的函数就好了。可选的参数其实也不多,主要就是prompt写的好一点就行。我的要求也不高,试了试基本满足。此外我还用到了微软 azure api,也很方便,两者一结合基本一个app就搓出来了,只是暂时还只能在命令行运行,没写前端ui罢了。

后者就麻烦了。我想着自己写前端ui还挺麻烦的,就想偷个懒直接在GPT里面弄弄看看行不。结果呢,现在这个版本实在是太挫了,只支持最最基本的action,虽然可以调用其他api,但还没研究出来怎么实现用户上传的文件扔到action api call里面。搜了搜他们的论坛也没啥结果,然后心累就到此为止了。

最后贴一下如何在openai 的GPT里面调用azure api。主要是api key那里实在是反用户直觉,我找了好久……一定要选 custom 然后把自定义的名字设为 Ocp-Apim-Subscription-Key 才可以。贴个图。

自定义 action -> authentication -> custom header name

当然azure api的文档做的也很差就是了,经常搜出来的是过时的文档,试一试都是404错误。哎,时间都花在这些琐碎的调试bug上了。

最后的结论是,在现在这个阶段,openai GPT的多模态做的还是太封闭,只适用于比较基础的交互需求,得等到后面允许自定义编程更丰富一些才可以。想做的稍稍复杂一点,写ui是逃不掉的了。web版还可以写个python+js凑和一下(flask这么轻量级的web开发框架真的是效率提升利器),app版xcode看了半天发现也是一等一的复杂……说好的ai改变程序开发呢?叹口气……

Chinese-Calendar: 一个帮助你判断今天是不是工作日的 Pypi 包

在开发过程中,你可能会需要实现某些和工作日相关的特性(比如,工作日才发某些通知 /推送),这个时候,你可以借助于 chinese_calendar 这个包,来查看当前是否是工作日,你可以引入 chinese_calendar 这个包,来实现判断今天是否是工作日。

可以参考如下代码,is_workday_today 返回 True 时,就是工作日,就需要执行某些特定的逻辑。

from datetime import datetime
from chinese_calendar import  is_workday

# https://github.com/LKI/chinese-calendar
def is_workday_today():
    today = datetime.now();
    return is_workday(today)

PIP Chill–更精简的依赖包导出工具

Make requirements with only the packages you need

项目导入的 module 越多,导出的依赖库就越多,尤其是很多系统自带的库一并给导出来来了。

pip freeze 导出效果:

asgiref==3.3.4
async-timeout==4.0.3
certifi==2021.5.30
chardet==4.0.0
coreapi==2.3.3
coreschema==0.0.4
Django==3.2.3
django-admin-lightweight-date-hierarchy==1.1.0
django-comment-migrate==0.1.5
django-cors-headers==3.10.1
django-crontab==0.7.1
django-export-xls==0.1.1
django-filter==21.1
django-ranged-response==0.2.0
django-redis==5.2.0
django-restql==0.15.4
django-simple-captcha==0.5.14
django-simpleui==2022.7.29
django-timezone-field==4.2.3
djangorestframework==3.12.4
djangorestframework-simplejwt==5.1.0
drf-yasg==1.20.0
et-xmlfile==1.1.0
idna==2.10
inflection==0.5.1
itypes==1.2.0
Jinja2==3.0.1
MarkupSafe==2.0.1
openpyxl==3.0.9
packaging==20.9
paho-mqtt==1.6.1
Pillow==8.3.1
PyJWT==2.1.0
PyMySQL==1.0.2
pyparsing==2.4.7
pyPEG2==2.15.2
pypinyin==0.46.0
pypng==0.20220715.0
pytz==2021.1
qrcode==7.4.2
redis==5.0.8
requests==2.25.1
ruamel.yaml==0.18.6
ruamel.yaml.clib==0.2.8
simplejson==3.18.4
six==1.16.0
smmap==4.0.0
sqlparse==0.4.1
typing-extensions==3.10.0.0
tzlocal==2.1
ua-parser==0.10.0
uritemplate==3.0.1
urllib3==1.26.6
user-agents==2.2.0
whitenoise==5.3.0
xlwt==1.3.0

pip-chill 导出效果:

django-admin-lightweight-date-hierarchy==1.1.0
django-comment-migrate==0.1.5
django-cors-headers==3.10.1
django-crontab==0.7.1
django-export-xls==0.1.1
django-filter==21.1
django-redis==5.2.0
django-restql==0.15.4
django-simple-captcha==0.5.14
django-simpleui==2022.7.29
django-timezone-field==4.2.3
djangorestframework-simplejwt==5.1.0
drf-yasg==1.20.0
encryptpy==1.0.5
openpyxl==3.0.9
paho-mqtt==1.6.1
pip-chill==1.0.3
pycryptodome==3.20.0
pymysql==1.0.2
pypinyin==0.46.0
qrcode==7.4.2
simplejson==3.18.4
tzlocal==2.1
user-agents==2.2.0
whitenoise==5.3.0

 

整体减掉了差不多一半多,同样在构建环境的时候也少了很多可能出问题的包,尤其是跨平台 install 的时候。

官方用法:

Suppose you have installed in your virtualenv a couple packages. When you run pip freeze, you'll get a list of all packages installed, with all dependencies. If one of the packages you installed ceases to depend on an already installed package, you have to manually remove it from the list. The list also makes no distinction about the packages you actually care about and packages your packages care about, making the requirements file bloated and, ultimately, inaccurate.

On your terminal, run:

$ pip-chill
bandit==1.7.0
bumpversion==0.6.0
click==7.1.2
coverage==5.3.1
flake8==3.8.4
nose==1.3.7
pip-chill==1.0.1
pytest==6.2.1
...
Or, if you want it without version numbers:

$ pip-chill --no-version
bandit
bumpversion
click
coverage
flake8
nose
pip-chill
pytest
...
Or, if you want it without pip-chill:

$ pip-chill --no-chill
bandit==1.7.0
bumpversion==0.6.0
click==7.1.2
coverage==5.3.1
flake8==3.8.4
nose==1.3.7
pytest==6.2.1
...
Or, if you want to list package dependencies too:

$ pip-chill -v
bandit==1.7.0
bumpversion==0.6.0
click==7.1.2
coverage==5.3.1
flake8==3.8.4
nose==1.3.7
pip-chill==1.0.1
pytest==6.2.1
sphinx==3.4.3
tox==3.21.1
twine==3.3.0
watchdog==1.0.2
# alabaster==0.7.12 # Installed as dependency for sphinx
# appdirs==1.4.4 # Installed as dependency for virtualenv
# attrs==20.3.0 # Installed as dependency for pytest
# babel==2.9.0 # Installed as dependency for sphinx

 

 

姐姐,你也不想让别人知道你的秘密吧? — 浅谈 Python 代码加密

像 python 这种非编译型的语言,在代码加密上有这先天性的弱势,虽然java 之类的编译成 jar 依然比较容易反编译回来,但是毕竟也算是提升了那么一点点门槛,再加上混淆神马的,基本就能避免一些入门级的破解了。

但是对于 python 这种,如果发布不想直接让别人看代码,最简单的办法就是打包成二进制。通常的做法就是 py2exe.

官网地址:https://www.py2exe.org

py2exe

py2exe is a Python Distutils extension which converts Python scripts into executable Windows programs, able to run without requiring a Python installation.Spice

Development is hosted on GitHub. You can find the mailing listsvn, and downloads for Python 2 there. Downloads for Python 3 are on PyPI.

py2exe was originally developed by Thomas Heller who still makes contributions. Jimmy Retzlaff, Mark Hammond, and Alberto Sottile have also made contributions. Code contributions are always welcome from the community and many people provide invaluable help on the mailing list and the Wiki.

py2exe is used by BitTorrentSpamBayes, and thousands more – py2exe averages over 5,000 downloads per month.

In an effort to limit Wiki spam, this front page is not editable. Feel free to edit other pages with content relevant to py2exe. You will need an account to edit (but not to read) and your account information will be used only for the purposes of administering this Wiki.

The old py2exe web site is still available until that information has found its way into this wiki.

之前发布的各种美女爬虫基本都是通过 py2exe 打包的,虽然体积比较大,但是整体来说效果还算不错。

但是对于 web 框架,例如 flask django 之类的该怎么打包?这个就稍显麻烦一些了。

搜索一下,也能找到一些工具,例如 https://github.com/amchii/encryptpy 这个东西底层还是通过 cython 来实现的,如果不想使用这个工具,那么直接使用 cython 也是可以的,至于原理,本质上是直接把 py代码编译成了二进制文件。

下面直接用 cython 来实现:

pip install cython

编写编译脚本,叫什么无所谓,这里我的名称是cython_build.py:

from distutils.core import setup
from Cython.Build import cythonize

setup(
    ext_modules=cythonize(["application/settings.py",
                           "PowerManagement/models.py",
                           "PowerManagement/views/meter.py",
                           "PowerManagement/views/meter_remote.py",
                           "PowerManagement/views/substation_picture.py",
                           "PowerManagement/views/circuit.py",
                           ])
)

建议将上面的代码放在项目的根目录下,要处理的 modules 使用相对路径来实现。

通过下面的命令编译 py 文件:

python3 cython_build.py build_ext --inplace

但是上面的代码有个问题,那就是–inplace 并没有吧所有的 so文件放到原来的目录下,编译之后,一些文件放到了项目根目录下:

扩展名为 so 的文件就是编译生成的二进制文件,此时如果直接运行项目会提示各种组件找不到,还需要将处理后的文件复制到原来的目录下:

mv *.so PowerManagement/views/

最后一步就是删除原来的 py 文件:

cd "PowerManagement/views/"
rm  *.py

到这里整个编译流程就算完成了,可以尝试重新启动服务了。

毕竟姐姐,你也不想你的代码被人随便给抄走吧?

Centos 7 安装PyMuPDF

接引前文,昨天把代码写好测试 ok 之后,以为就万事大吉了。然而,今天往服务器上部署的时候,直接给整麻了。问题一个接一个,错误一堆接一堆。直接让人破防了。

对于 linux 的发行版,我并没有神马偏见,主要是用过的版本也不多,但是,不得不说那个 centos 是真烂,也不知道为啥那么多人喜欢用这个破系统。

直接 pip 安装,好嘛,这一堆错误:

[root@iZbp12k4fwg2euy5kkr9u7Z ~]# pip install PyMuPDF
Looking in indexes: http://mirrors.cloud.aliyuncs.com/pypi/simple/
Collecting PyMuPDF
  Using cached http://mirrors.cloud.aliyuncs.com/pypi/packages/9f/1d/032d24e0c774e67742395fda163a172c60e4d0f9875785d5199eb2956d5e/PyMuPDF-1.19.6.tar.gz (2.3 MB)
  Preparing metadata (setup.py) ... done
Using legacy 'setup.py install' for PyMuPDF, since package 'wheel' is not installed.
Installing collected packages: PyMuPDF
    Running setup.py install for PyMuPDF ... error
    ERROR: Command errored out with exit status 1:
     command: /usr/bin/python3 -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-8aqc9a1k/pymupdf_d5ebd12caf9445ab82d6a5af68229d72/setup.py'"'"'; __file__='"'"'/tmp/pip-install-8aqc9a1k/pymupdf_d5ebd12caf9445ab82d6a5af68229d72/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-7fhkepkr/install-record.txt --single-version-externally-managed --compile --install-headers /usr/local/include/python3.6m/PyMuPDF
         cwd: /tmp/pip-install-8aqc9a1k/pymupdf_d5ebd12caf9445ab82d6a5af68229d72/
    Complete output (20 lines):
    running install
    running build
    running build_py
    creating build
    creating build/lib.linux-x86_64-3.6
    creating build/lib.linux-x86_64-3.6/fitz
    copying fitz/__init__.py -> build/lib.linux-x86_64-3.6/fitz
    copying fitz/fitz.py -> build/lib.linux-x86_64-3.6/fitz
    copying fitz/utils.py -> build/lib.linux-x86_64-3.6/fitz
    copying fitz/__main__.py -> build/lib.linux-x86_64-3.6/fitz
    running build_ext
    building 'fitz._fitz' extension
    creating build/temp.linux-x86_64-3.6
    creating build/temp.linux-x86_64-3.6/fitz
    gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -I/usr/include/mupdf -I/usr/local/include/mupdf -Imupdf/thirdparty/freetype/include -I/usr/include/freetype2 -I/usr/include/python3.6m -c fitz/fitz_wrap.c -o build/temp.linux-x86_64-3.6/fitz/fitz_wrap.o
    fitz/fitz_wrap.c:2755:18: fatal error: fitz.h: No such file or directory
     #include <fitz.h>
                      ^
    compilation terminated.
    error: command 'gcc' failed with exit status 1
    ----------------------------------------
ERROR: Command errored out with exit status 1: /usr/bin/python3 -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-8aqc9a1k/pymupdf_d5ebd12caf9445ab82d6a5af68229d72/setup.py'"'"'; __file__='"'"'/tmp/pip-install-8aqc9a1k/pymupdf_d5ebd12caf9445ab82d6a5af68229d72/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-7fhkepkr/install-record.txt --single-version-externally-managed --compile --install-headers /usr/local/include/python3.6m/PyMuPDF Check the logs for full command output.

按照提示看来是 gcc 报错了,错误原因是没有头文件,一通搜索:https://blog.csdn.net/u012140499/article/details/112798704 提供了解决思路,下载源码https://casper.mupdf.com/releases/安装。

直接下载最新版编译,又是一堆报错:

source/fitz/util.c: In function ‘fz_new_xhtml_document_from_document’:
source/fitz/util.c:866:2: warning: ‘new_doc’ may be used uninitialized in this function [-Wmaybe-uninitialized]
  return new_doc;
  ^
    CC build/release/source/fitz/warp.o
    CC build/release/source/fitz/writer.o
source/fitz/writer.c: In function ‘fz_new_document_writer_with_buffer’:
source/fitz/writer.c:305:2: warning: ‘wri’ may be used uninitialized in this function [-Wmaybe-uninitialized]
  return wri;
  ^
    CC build/release/source/fitz/xml.o
    CC build/release/source/fitz/xmltext-device.o
    CC build/release/source/fitz/zip.o
    CXX build/release/source/fitz/tessocr.o
/bin/sh: g++: command not found
make: *** [build/release/source/fitz/tessocr.o] Error 127

提示找不到 g++,嗯,再来解决 g++

yum search "gcc-c++"

就一个结果:

oaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.cloud.aliyuncs.com
 * extras: mirrors.cloud.aliyuncs.com
 * updates: mirrors.cloud.aliyuncs.com
======================================================================================================= N/S matched: gcc-c++ =======================================================================================================
gcc-c++.x86_64 : C++ support for GCC

  Name and summary matches only, use "search all" for everything.

安装 g++:

[root@iZbp12k4fwg2euy5kkr9u7Z mupdf-1.22.0-source]# yum install "gcc-c++.x86_64" -y
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.cloud.aliyuncs.com
 * extras: mirrors.cloud.aliyuncs.com
 * updates: mirrors.cloud.aliyuncs.com
Resolving Dependencies
--> Running transaction check
---> Package gcc-c++.x86_64 0:4.8.5-44.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

====================================================================================================================================================================================================================================
 Package                                                Arch                                                  Version                                                     Repository                                           Size
====================================================================================================================================================================================================================================
Installing:
 gcc-c++                                                x86_64                                                4.8.5-44.el7                                                base                                                7.2 M

Transaction Summary
====================================================================================================================================================================================================================================
Install  1 Package

Total download size: 7.2 M
Installed size: 16 M
Downloading packages:
gcc-c++-4.8.5-44.el7.x86_64.rpm                                                                                                                                                                              | 7.2 MB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : gcc-c++-4.8.5-44.el7.x86_64                                                                                                                                                                                      1/1 
  Verifying  : gcc-c++-4.8.5-44.el7.x86_64                                                                                                                                                                                      1/1 

Installed:
  gcc-c++.x86_64 0:4.8.5-44.el7                                                                                                                                                                                                     

Complete!

再来一遍:

make HAVE_X11=no HAVE_GLUT=no prefix=/usr/local install

编译安装命令参考这个链接:https://mupdf.readthedocs.io/en/latest/quick-start-guide.html#linux

几百行错误出来了:

thirdparty/harfbuzz/src/graph/../hb-meta.hh:76:41: note: in definition of macro ‘HB_AUTO_RETURN’
 #define HB_AUTO_RETURN(E) -> decltype ((E)) { return (E); }
                                         ^
In file included from thirdparty/harfbuzz/src/graph/pairpos-graph.hh:32:0,
                 from thirdparty/harfbuzz/src/graph/gsubgpos-graph.hh:31,
                 from thirdparty/harfbuzz/src/graph/gsubgpos-context.cc:27:
thirdparty/harfbuzz/src/graph/classdef-graph.hh: In constructor ‘graph::class_def_size_estimator_t::class_def_size_estimator_t(It)’:
thirdparty/harfbuzz/src/graph/classdef-graph.hh:155:44: error: ‘struct hb_hashmap_t<unsigned int, hb_set_t>’ has no member named ‘keys’
     for (unsigned klass : glyphs_per_class.keys ())
                                            ^
thirdparty/harfbuzz/src/graph/classdef-graph.hh: In member function ‘bool graph::class_def_size_estimator_t::in_error()’:
thirdparty/harfbuzz/src/graph/classdef-graph.hh:200:47: error: ‘struct hb_hashmap_t<unsigned int, hb_set_t>’ has no member named ‘values’
     for (const hb_set_t& s : glyphs_per_class.values ())
                                               ^
In file included from thirdparty/harfbuzz/src/graph/gsubgpos-context.cc:27:0:
thirdparty/harfbuzz/src/graph/gsubgpos-graph.hh: In member function ‘void graph::Lookup::fix_existing_subtable_links(graph::gsubgpos_graph_context_t&, unsigned int, hb_vector_t<hb_pair_t<unsigned int, hb_vector_t<unsigned int> > >&)’:
thirdparty/harfbuzz/src/graph/gsubgpos-graph.hh:259:28: error: ‘struct hb_serialize_context_t::object_t’ has no member named ‘all_links_writer’
       for (auto& l : v.obj.all_links_writer ())
                            ^
thirdparty/harfbuzz/src/graph/gsubgpos-context.cc: In member function ‘unsigned int graph::gsubgpos_graph_context_t::num_non_ext_subtables()’:
thirdparty/harfbuzz/src/graph/gsubgpos-context.cc:62:25: error: ‘struct hb_hashmap_t<unsigned int, graph::Lookup*>’ has no member named ‘values’
   for (auto l : lookups.values ())
                         ^
In file included from thirdparty/harfbuzz/src/graph/../hb.hh:484:0,
                 from thirdparty/harfbuzz/src/graph/../hb-set.hh:31,
                 from thirdparty/harfbuzz/src/graph/graph.hh:27,
                 from thirdparty/harfbuzz/src/graph/gsubgpos-graph.hh:27,
                 from thirdparty/harfbuzz/src/graph/gsubgpos-context.cc:27:
thirdparty/harfbuzz/src/graph/../hb-vector.hh: In instantiation of ‘Type hb_vector_t<Type, sorted>::pop() [with Type = hb_user_data_array_t::hb_user_data_item_t; bool sorted = false]’:
thirdparty/harfbuzz/src/graph/../hb-object.hh:127:7:   required from ‘void hb_lockable_set_t<item_t, lock_t>::fini(lock_t&) [with item_t = hb_user_data_array_t::hb_user_data_item_t; lock_t = hb_mutex_t]’
thirdparty/harfbuzz/src/graph/../hb-object.hh:185:34:   required from here
thirdparty/harfbuzz/src/graph/../hb-vector.hh:398:43: error: cannot convert ‘std::remove_reference<hb_user_data_array_t::hb_user_data_item_t&>::type {aka hb_user_data_array_t::hb_user_data_item_t}’ to ‘hb_user_data_key_t*’ in initialization
     Type v {std::move (arrayZ[length - 1])};
                                           ^
In file included from thirdparty/harfbuzz/src/graph/../hb.hh:481:0,
                 from thirdparty/harfbuzz/src/graph/../hb-set.hh:31,
                 from thirdparty/harfbuzz/src/graph/graph.hh:27,
                 from thirdparty/harfbuzz/src/graph/gsubgpos-graph.hh:27,
                 from thirdparty/harfbuzz/src/graph/gsubgpos-context.cc:27:
thirdparty/harfbuzz/src/graph/../hb-iter.hh: In instantiation of ‘void hb_copy(S&&, D&&) [with S = const hb_hashmap_t<unsigned int, unsigned int, true>&; D = hb_hashmap_t<unsigned int, unsigned int, true>&]’:
thirdparty/harfbuzz/src/graph/../hb-map.hh:46:100:   required from ‘hb_hashmap_t<K, V, minus_one>::hb_hashmap_t(const hb_hashmap_t<K, V, minus_one>&) [with K = unsigned int; V = unsigned int; bool minus_one = true]’
thirdparty/harfbuzz/src/graph/../hb-map.hh:444:56:   required from here
thirdparty/harfbuzz/src/graph/../hb-iter.hh:1016:14: error: no match for call to ‘(const<anonymous struct>) (const hb_hashmap_t<unsigned int, unsigned int, true>&)’
   hb_iter (is) | hb_sink (id);
              ^
thirdparty/harfbuzz/src/graph/../hb-iter.hh:156:1: note: candidates are:
 {
 ^
thirdparty/harfbuzz/src/graph/../hb-iter.hh:158:3: note: template<class T> hb_iter_type<T><anonymous struct>::operator()(T&&) const
   operator () (T&& c) const
   ^
thirdparty/harfbuzz/src/graph/../hb-iter.hh:158:3: note:   template argument deduction/substitution failed:
thirdparty/harfbuzz/src/graph/../hb-iter.hh:164:3: note: template<class Type> hb_array_t<Type><anonymous struct>::operator()(Type*, unsigned int) const
   operator () (Type *array, unsigned int length) const
   ^
thirdparty/harfbuzz/src/graph/../hb-iter.hh:164:3: note:   template argument deduction/substitution failed:
thirdparty/harfbuzz/src/graph/../hb-iter.hh:1016:14: note:   mismatched types ‘Type*’ and ‘hb_hashmap_t<unsigned int, unsigned int, true>’
   hb_iter (is) | hb_sink (id);
              ^
thirdparty/harfbuzz/src/graph/../hb-iter.hh:168:3: note: template<class Type, unsigned int length> hb_array_t<Type><anonymous struct>::operator()(Type (&)[length]) const
   operator () (Type (&array)[length]) const
   ^
thirdparty/harfbuzz/src/graph/../hb-iter.hh:168:3: note:   template argument deduction/substitution failed:
thirdparty/harfbuzz/src/graph/../hb-iter.hh:1016:14: note:   mismatched types ‘Type [length]’ and ‘const hb_hashmap_t<unsigned int, unsigned int, true>’
   hb_iter (is) | hb_sink (id);
              ^
In file included from thirdparty/harfbuzz/src/graph/../hb-serialize.hh:36:0,
                 from thirdparty/harfbuzz/src/graph/../hb-machinery.hh:37,
                 from thirdparty/harfbuzz/src/graph/../hb-bit-set.hh:33,
                 from thirdparty/harfbuzz/src/graph/../hb-bit-set-invertible.hh:32,
                 from thirdparty/harfbuzz/src/graph/../hb-set.hh:32,
                 from thirdparty/harfbuzz/src/graph/graph.hh:27,
                 from thirdparty/harfbuzz/src/graph/gsubgpos-graph.hh:27,
                 from thirdparty/harfbuzz/src/graph/gsubgpos-context.cc:27:
thirdparty/harfbuzz/src/graph/../hb-map.hh: In instantiation of ‘uint32_t hb_hashmap_t<K, V, minus_one>::hash() const [with K = unsigned int; V = unsigned int; bool minus_one = true; uint32_t = unsigned int]’:
thirdparty/harfbuzz/src/graph/../hb-algs.hh:237:43:   required from ‘constexpr hb_head_t<unsigned int, decltype (hb_deref(v).hash())><anonymous struct>::impl(const T&, hb_priority<1u>) const [with T = hb::shared_ptr<hb_map_t>; hb_head_t<unsigned int, decltype (hb_deref(v).hash())> = unsigned int]’
thirdparty/harfbuzz/src/graph/../hb-algs.hh:245:3:   required by substitution of ‘template<class T> constexpr hb_head_t<unsigned int, decltype (((const<anonymous struct>*)this)-><anonymous struct>::impl(v, hb_priority<16u>()))><anonymous struct>::operator()(const T&) const [with T = hb::shared_ptr<hb_map_t>]’
thirdparty/harfbuzz/src/graph/../hb-map.hh:257:50:   required from ‘bool hb_hashmap_t<K, V, minus_one>::has(K, VV**) const [with VV = unsigned int; K = hb::shared_ptr<hb_map_t>; V = unsigned int; bool minus_one = false]’
thirdparty/harfbuzz/src/graph/../hb-ot-layout-common.hh:3034:36:   required from here
thirdparty/harfbuzz/src/graph/../hb-map.hh:291:19: error: ‘iter_items’ was not declared in this scope
     + iter_items ()
                   ^
thirdparty/harfbuzz/src/graph/../hb-map.hh: In instantiation of ‘bool hb_hashmap_t<K, V, minus_one>::is_equal(const hb_hashmap_t<K, V, minus_one>&) const [with K = unsigned int; V = unsigned int; bool minus_one = true]’:
thirdparty/harfbuzz/src/graph/../hb-map.hh:306:78:   required from ‘bool hb_hashmap_t<K, V, minus_one>::operator==(const hb_hashmap_t<K, V, minus_one>&) const [with K = unsigned int; V = unsigned int; bool minus_one = true]’
thirdparty/harfbuzz/src/graph/../hb-map.hh:96:65:   required from ‘bool hb_hashmap_t<K, V, minus_one>::item_t::operator==(const K&) const [with K = hb::shared_ptr<hb_map_t>; V = unsigned int; bool minus_one = false]’
thirdparty/harfbuzz/src/graph/../hb-map.hh:258:33:   required from ‘bool hb_hashmap_t<K, V, minus_one>::has(K, VV**) const [with VV = unsigned int; K = hb::shared_ptr<hb_map_t>; V = unsigned int; bool minus_one = false]’
thirdparty/harfbuzz/src/graph/../hb-ot-layout-common.hh:3034:36:   required from here
thirdparty/harfbuzz/src/graph/../hb-map.hh:300:28: error: ‘iter’ was not declared in this scope
     for (auto pair : iter ())
                            ^
make: *** [build/release/thirdparty/harfbuzz/src/graph/gsubgpos-context.o] Error 1

尝试多个版本都会出现上面的错误,或者会提示不支持 c++17 标准,直接搜索错误多数解决方案都是升级 gcc 编译器,这尼玛,yum 不支持,源码安装又是一堆依赖,我升级,升级你妹。

尝试降级 mupdf 版本,终于经过多次尝试之后发现1.12 版本是可以安装的。

install -d /usr/local/include/mupdf
install -d /usr/local/include/mupdf/fitz
install -d /usr/local/include/mupdf/pdf
install include/mupdf/*.h /usr/local/include/mupdf
install include/mupdf/fitz/*.h /usr/local/include/mupdf/fitz
install include/mupdf/pdf/*.h /usr/local/include/mupdf/pdf
install -d /usr/local/lib
install build/release/libmupdf.a build/release/libmupdfthird.a /usr/local/lib
install -d /usr/local/bin
install build/release/mutool    build/release/muraster   build/release/mujstest build/release/mjsgen /usr/local/bin
install -d /usr/local/share/man/man1
install docs/man/*.1 /usr/local/share/man/man1
install -d /usr/local/share/doc/mupdf
install -d /usr/local/share/doc/mupdf/examples
install README COPYING CHANGES /usr/local/share/doc/mupdf
install docs/*.html docs/*.css docs/*.png /usr/local/share/doc/mupdf
install docs/examples/* /usr/local/share/doc/mupdf/examples

来继续 pip,来看着几千行的报错,尼玛,你要炸啊:

    fitz/fitz_wrap.c: In function ‘JM_rect_from_py’:
    fitz/fitz_wrap.c:4042:1: warning: control reaches end of non-void function [-Wreturn-type]
     }
     ^
    fitz/fitz_wrap.c: In function ‘util_include_point_in_rect’:
    fitz/fitz_wrap.c:3447:1: warning: control reaches end of non-void function [-Wreturn-type]
     }
     ^
    fitz/fitz_wrap.c: In function ‘util_transform_point’:
    fitz/fitz_wrap.c:3461:1: warning: control reaches end of non-void function [-Wreturn-type]
     }
     ^
    fitz/fitz_wrap.c: In function ‘util_union_rect’:
    fitz/fitz_wrap.c:3468:1: warning: control reaches end of non-void function [-Wreturn-type]
     }
     ^
    fitz/fitz_wrap.c: In function ‘util_concat_matrix’:
    fitz/fitz_wrap.c:3475:1: warning: control reaches end of non-void function [-Wreturn-type]
     }
     ^
    fitz/fitz_wrap.c: In function ‘JM_matrix_from_py’:
    fitz/fitz_wrap.c:4131:1: warning: control reaches end of non-void function [-Wreturn-type]
     }
     ^
    fitz/fitz_wrap.c: In function ‘JM_derotate_page_matrix’:
    fitz/fitz_wrap.c:5193:1: warning: control reaches end of non-void function [-Wreturn-type]
     }
     ^
    fitz/fitz_wrap.c: In function ‘JM_irect_from_py’:
    fitz/fitz_wrap.c:4071:1: warning: control reaches end of non-void function [-Wreturn-type]
     }
     ^
    error: command 'gcc' failed with exit status 1
    ----------------------------------------
ERROR: Command errored out with exit status 1: /usr/bin/python3 -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-kfa4_6i0/pymupdf_d444a7b2e89d4aa38ac652587530e9a2/setup.py'"'"'; __file__='"'"'/tmp/pip-install-kfa4_6i0/pymupdf_d444a7b2e89d4aa38ac652587530e9a2/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-b8m2p6nm/install-record.txt --single-version-externally-managed --compile --install-headers /usr/local/include/python3.6m/PyMuPDF Check the logs for full command output.

尝试降低版本:

[root@iZbp12k4fwg2euy5kkr9u7Z mupdf-1.12.0-source]# pip install PyMuPDF==1.12
Looking in indexes: http://mirrors.cloud.aliyuncs.com/pypi/simple/
ERROR: Could not find a version that satisfies the requirement PyMuPDF==1.12 (from versions: 1.11.2, 1.12.5, 1.13.20, 1.14.19.post2, 1.14.19.2, 1.14.20, 1.14.21, 1.16.0, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5, 1.16.6, 1.16.7, 1.16.8, 1.16.8.1, 1.16.9, 1.16.10, 1.16.11, 1.16.12, 1.16.13, 1.16.14, 1.16.15, 1.16.16, 1.16.17, 1.16.18, 1.17.0, 1.17.1, 1.17.2, 1.17.3, 1.17.4, 1.17.5, 1.17.6, 1.17.7, 1.18.0, 1.18.1, 1.18.2, 1.18.3, 1.18.4, 1.18.5, 1.18.6, 1.18.7, 1.18.8, 1.18.9, 1.18.10, 1.18.11, 1.18.12, 1.18.13, 1.18.14, 1.18.15, 1.18.16, 1.18.17, 1.18.18, 1.18.19, 1.19.0, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.19.5, 1.19.6)
ERROR: No matching distribution found for PyMuPDF==1.12

提示没有 1.12,那就1.12.5:

[root@iZbp12k4fwg2euy5kkr9u7Z mupdf-1.12.0-source]# pip install PyMuPDF==1.12.5
Looking in indexes: http://mirrors.cloud.aliyuncs.com/pypi/simple/
Collecting PyMuPDF==1.12.5
  Downloading http://mirrors.cloud.aliyuncs.com/pypi/packages/c1/4a/f6424f019bbc3ac70b55fd589f6b3eb777e13d1a3600dbdb726575d5f5df/PyMuPDF-1.12.5-cp36-cp36m-manylinux1_x86_64.whl (3.4 MB)
     |████████████████████████████████| 3.4 MB 1.2 MB/s            
Installing collected packages: PyMuPDF
Successfully installed PyMuPDF-1.12.5

nice 终于装上了,启动服务,尝试进行文件拼接,直接报下面的错误:

'Document' object has no attribute 'new_page'

wtf,骇然不让人活了?

尝试升级版本:

[root@iZbp12k4fwg2euy5kkr9u7Z mupdf-1.12.0-source]# pip install PyMuPDF==1.18.19
Looking in indexes: http://mirrors.cloud.aliyuncs.com/pypi/simple/
Collecting PyMuPDF==1.18.19
  Downloading http://mirrors.cloud.aliyuncs.com/pypi/packages/d8/b6/59c001fa851ec4ad216232bc256b9aaff67ff9cf1c4bb542f68f1ad5fcd8/PyMuPDF-1.18.19-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (6.4 MB)
     |████████████████████████████████| 6.4 MB 1.4 MB/s            
Installing collected packages: PyMuPDF
  Attempting uninstall: PyMuPDF
    Found existing installation: PyMuPDF 1.12.5
    Uninstalling PyMuPDF-1.12.5:
      Successfully uninstalled PyMuPDF-1.12.5
Successfully installed PyMuPDF-1.18.19

世界终于清净了:

总结:

1. mupdf 源码安装选择mupdf-1.12.0 https://mupdf.com/downloads/archive/mupdf-1.20.0-source.tar.gz
2. pip 安装选择1.18.19 pip install PyMuPDF==1.18.19

后记:

刚才尝试将 centos 的 python 升级为 3.8.6 之后,pymupdf 貌似能正常安装新版本。这尼玛,系统自带的这一堆低版本垃圾:

Successfully installed Babel-2.14.0 Jinja2-3.1.3 MarkupSafe-2.1.5 PyMuPDF-1.24.9 PyMuPDFb-1.24.9 PyPDF2-3.0.1 Pygments-2.18.0 SecretStorage-3.3.3 SimpleWebSocketServer-0.1.2 aliyun-python-sdk-core-2.14.0 aliyun-python-sdk-imm-1.24.0 aliyun-python-sdk-kms-2.16.2 backports.tarfile-1.2.0 certifi-2024.2.2 cffi-1.17.0 charset-normalizer-3.3.2 ci-info-0.3.0 click-8.1.7 configobj-5.0.8 configparser-7.1.0 contourpy-1.1.1 crcmod-1.7 cryptography-42.0.4 cycler-0.12.1 docutils-0.20.1 docxcompose-1.4.0 docxtpl-0.16.7 etelemetry-0.3.1 filelock-3.15.4 fonttools-4.53.1 fsspec-2024.6.1 httplib2-0.22.0 idna-3.6 importlib-metadata-8.4.0 importlib-resources-6.4.3 isodate-0.6.1 jaraco.classes-3.4.0 jaraco.context-6.0.1 jaraco.functools-4.0.2 jeepney-0.8.0 jmespath-0.10.0 keyring-25.3.0 kiwisolver-1.4.5 looseversion-1.3.0 lxml-5.1.0 markdown-it-py-3.0.0 matplot-0.1.9 matplotlib-3.7.5 mdurl-0.1.2 more-itertools-10.4.0 mpmath-1.3.0 networkx-3.1 nh3-0.2.18 numpy-1.24.4 nvidia-cublas-cu12-12.1.3.1 nvidia-cuda-cupti-cu12-12.1.105 nvidia-cuda-nvrtc-cu12-12.1.105 nvidia-cuda-runtime-cu12-12.1.105 nvidia-cudnn-cu12-9.1.0.70 nvidia-cufft-cu12-11.0.2.54 nvidia-curand-cu12-10.3.2.106 nvidia-cusolver-cu12-11.4.5.107 nvidia-cusparse-cu12-12.1.0.106 nvidia-nccl-cu12-2.20.5 nvidia-nvjitlink-cu12-12.6.20 nvidia-nvtx-cu12-12.1.105 opencv-python-4.10.0.84 oss2-2.18.4 packaging-24.1 pandas-2.0.3 pathlib-1.0.1 pillow-10.4.0 pkginfo-1.11.1 pycparser-2.21 pycryptodome-3.20.0 pydot-3.0.1 pyloco-0.0.139 pyparsing-3.1.2 python-dateutil-2.9.0.post0 python-docx-1.1.0 pytz-2024.1 pyxnat-1.6.2 rdflib-6.3.2 readme-renderer-43.0 requests-2.32.3 requests-toolbelt-1.0.0 rfc3986-2.0.0 rich-13.7.1 scipy-1.10.1 simplejson-3.19.3 six-1.16.0 sympy-1.13.2 torch-2.4.0 traits-6.3.2 triton-3.0.0 twine-5.1.1 typing-3.7.4.3 typing-extensions-4.9.0 tzdata-2024.1 urllib3-2.2.2 ushlex-0.99.1 websocket-client-1.8.0 zipp-3.20.0

 

Python中规范的变量命名会让你的代码更优雅

人生苦短,我学Python!在Python中,每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。变量名应该清晰地表达其用途或含义,避免使用无意义的名称。好的命名习惯能会让你的代码更优雅,读起来更有故事感。

Python极简美学:一行代码完成的26个日常任务

Python以其简洁优雅著称,能够用最少的代码行数实现强大的功能。本文将通过展示Python如何以一行代码来解决常见的编程任务,从而体验Python的极简美学。通过这些实例,不仅能够学习到Python的基础知识,还能掌握一些高效编码的小技巧。
❌