小野（Hirono）博客的个人网站，主要讲述关于小野的一些陈芝麻烂谷子事。网络、生活、我的主张，也是一个自留地
把多余的VPS小鸡，拿来搭建一个专属图床用于博客配图、日常存图，又不希望将它开放为公共平台推荐EasyImage2.0 (简单图床) + PicGo图床下载地址：{cat_download name="EasyImages2.0" url="https://github.com/icret/EasyImages2.0" password=""}开启个人使用安装成功后，登录后台，按照截图开启相应功能即可。“登录上传” —— 防止游客上传，仅限个人使用，保护仅有的小硬盘“开启 API 上传” —— 搭配 PicGo 使用，无须打开网页上传图片Picgo 自定义上传API下载PicGo：{cat_download name="PicGo开源地址" url="https://github.com/Molunerfinn/PicGo" password=""}安装后在插件设置中搜索：web-uploader 1.1.1并安装。图床设置 - 自定义 Web 图床中按照如下方式填写，然后点击确定并设置为默认图床登录图床后台 --> 图床安全 --> 高级设置 --> 开启 API 上传自定义 Web 图床参数API 地址：https://你的域名/api/index.php # 输入你的网站 api 地址 后台API设置有提示地址 POST 参数名: image JSON 路径: url 自定义 Body: {"token":"1xxxxxx3cb5e131"} # 这里输入你网站生成的 token上传成功截图
萝卜PHP探针 - 轻巧好用的服务器检测工具-开源版
简单介绍单文件，不占资源功能实在，没有花哨截图<?php // index.php - PHP探针 error_reporting(0); @ini_set('display_errors', 0); // 1. 基础信息获取（完全安全） function getBasicInfo() { $info = []; // 使用安全的获取方式 $info['服务器名称'] = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '未知'; $info['服务器软件'] = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '未知'; $info['PHP版本'] = PHP_VERSION; $info['操作系统'] = @php_uname('s') . ' ' . @php_uname('r'); $info['服务器时间'] = date('Y-m-d H:i:s'); $info['运行模式'] = PHP_SAPI; // 内存信息（安全获取） $info['内存使用'] = formatBytes(@memory_get_usage(true)); $info['内存峰值'] = formatBytes(@memory_get_peak_usage(true)); return $info; } // 2. 安全的性能测试（简化版） function getPerformance() { $results = []; // 整数运算测试 $start = microtime(true); $result = 0; $iterations = 100000; // 减少迭代次数 for ($i = 0; $i < $iterations; $i++) { $result += $i * $i; } $time_taken = max(microtime(true) - $start, 0.001); $results['整数运算'] = [ 'score' => intval($iterations / $time_taken), 'time' => round($time_taken * 1000, 2) ]; return $results; } // 3. 组件检测（安全版） function getComponents() { $components = []; // 只检测常用组件 $extensions = [ 'MySQLi' => 'mysqli', 'PDO' => 'pdo', 'PDO MySQL' => 'pdo_mysql', 'Redis' => 'redis', 'GD Library' => 'gd', 'cURL' => 'curl', 'OpenSSL' => 'openssl', 'MBString' => 'mbstring', 'JSON' => 'json', 'XML' => 'xml', 'ZIP' => 'zip', 'OPcache' => 'Zend OPcache' ]; foreach ($extensions as $name => $ext) { $components[$name] = @extension_loaded($ext); } return $components; } // 4. 磁盘信息（安全获取） function getDiskInfo() { $info = []; $path = __DIR__; $total = @disk_total_space($path); $free = @disk_free_space($path); if ($total !== false && $free !== false && $total > 0) { $used = $total - $free; $usage_percent = round(($used / $total) * 100, 1); $info['总容量'] = formatBytes($total); $info['已使用'] = formatBytes($used); $info['可用空间'] = formatBytes($free); $info['使用率'] = $usage_percent; } else { $info['总容量'] = '未知'; $info['已使用'] = '未知'; $info['可用空间'] = '未知'; $info['使用率'] = 0; } return $info; } // 5. PHP配置（安全获取） function getPHPConfig() { $config = []; $settings = [ 'memory_limit', 'max_execution_time', 'post_max_size', 'upload_max_filesize', 'max_file_uploads', 'default_charset', 'display_errors', 'error_reporting' ]; foreach ($settings as $setting) { $value = @ini_get($setting); $config[$setting] = $value !== false ? $value : '未知'; } return $config; } // 6. 安全状态 function getSecurityStatus() { $security = []; $security['错误显示'] = @ini_get('display_errors') ? '开启' : '关闭'; $security['远程文件访问'] = @ini_get('allow_url_fopen') ? '开启' : '关闭'; $security['PHP信息暴露'] = @ini_get('expose_php') ? '开启' : '关闭'; $security['目录限制'] = @ini_get('open_basedir') ? '已设置' : '未设置'; return $security; } // 7. 网络信息 function getNetworkInfo() { $network = []; $network['客户端IP'] = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '未知'; $network['请求方法'] = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : '未知'; // 简单的响应时间测试 $start = microtime(true); $test = 0; for ($i = 0; $i < 1000; $i++) { $test += $i; } $network['响应时间'] = round((microtime(true) - $start) * 1000, 2) . ' ms'; return $network; } // 辅助函数 function formatBytes($bytes) { if (!$bytes || $bytes <= 0) return '0 B'; $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= (1 << (10 * $pow)); return round($bytes, 2) . ' ' . $units[$pow]; } function formatScore($score) { if ($score >= 1000000) { return round($score / 1000000, 1) . 'M'; } elseif ($score >= 1000) { return round($score / 1000, 1) . 'K'; } return $score; } // 获取所有数据 $server_info = getBasicInfo(); $performance = getPerformance(); $components = getComponents(); $disk_info = getDiskInfo(); $php_config = getPHPConfig(); $security = getSecurityStatus(); $network_info = getNetworkInfo(); // 计算健康度 $health_score = 85; // 默认良好 ?> 萝卜-PHP服务器探针 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" /> 萝卜服务器监控探针 开源产品 - 稳定运行 检测时间: 系统健康度 100% <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> 系统状态良好 服务器状态 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" /> 运行环境 性能测试 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> 整数运算 磁盘使用 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" /> 可用 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" /> 服务器信息 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> 性能指标 次/秒 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> 组件支持状态 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /> PHP配置信息 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> 安全状态 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" /> 网络信息 萝卜服务器监控探针 © <?php echo date('Y'); ?> - 开源版 稳定运行 | 全面检测 | 安全可靠
今天逛论坛发现有位坛友贴了一段加密代码。问解密难度多大。先看看源代码：<?php echo(gzinflate(base64_decode('[binary]')));这段代码使用的是一种常见的“混淆”方式;我们来分解一下eval(gzinflate(base64_decode('加密代码')));有 base64 编码再用 gzinflate 压缩最后用 eval 执行那如何解密呢？你只需要把 eval 换成 echo 然后浏览器运行跑一下就能还原出原始代码。解密后源代码：/* 加密时间: 2025-10-22 10:47:39 */ $___d41d8='';$___d41d8.=base64_decode('JF9TRVJWRVJb');$___d41d8.=base64_decode('IlJFUVVFU1Rf');$___d41d8.=base64_decode('VVJJIl0=');if(isset($___d41d8) && strpos($_SERVER["REQUEST_URI"], base64_decode('eGRlYnVn')) !== false) exit(base64_decode('5o6l5Y+35qC35LiO77yB')); $___ebd91=base64_decode('cnVua2l0X2Z1bmN0aW9uX3JlZGVmaW5l');if(function_exists($___ebd91)) exit(base64_decode('5o6l5Y+35qC35LiO77yB')); $__funcs=array ( '__full_file__' => '[binary]:4db0b902cec38d5cf0f3d979f5f5c2f0', ); $__salts=array ( '__full_file__' => 'ce390a4e1dfc67b3', ); $__hmacs=array ( '__full_file__' => 'b1415fc56f594e4469d7a47a960e1ba0aa7c900cb8f73bf701449374f827382e', ); $__signatures=array ( '__full_file__' => 'e6e2be52022af457d6476915e7ebb1fa3e493a531b47738b6be2200ebdae84a7ed2605a60b99c6ee70271400c5dd2d4484f80e29e84ad5f87eeddafa0ff19100', ); $__randData=array ( '278edf6f' => 'b665a43e', 'de80dcf3' => 'f3dbc6d2ad84aa', 'bed840e8' => 'NDExMGIxZTQyZTQ4ZWU3ZmM2YjE=', '4a1fe109' => '0c0b92fc8da7a4', '40d454dd' => '5045e44f273606e5', 'fc139939' => '5fe9a6b5', '5b01761d' => 'qs8o2s9sr132172nn5or', 'f373a44b' => 'f0fbca37cdd4', '6f60a960' => 'fe6659baba', 'a8e430f6' => 'n8p98s34q27n5qq14546667r', ); $__keyMap=array ( 0 => 'bed840e8', 1 => '5b01761d', 2 => 'a8e430f6', ); $__segmentCompressMethods=array ( '__full_file__' => 'gzip', ); // 增强的安全密钥处理 $__mk_part1 = strrev(base64_decode($__randData[$__keyMap[0]])); $__mk_part2 = str_rot13($__randData[$__keyMap[1]]); $__mk_part3 = str_rot13($__randData[$__keyMap[2]]); $__masterKey = $__mk_part1 . $__mk_part2 . $__mk_part3; unset($__mk_part1, $__mk_part2, $__mk_part3); $__cache=[]; function f515f13ccdddd7066($n){ global $__funcs, $__salts, $__hmacs, $__signatures, $__masterKey, $__cache, $__segmentCompressMethods; if(isset($__cache[$n])) return; if(!isset($__funcs[$n])) throw new Exception(base64_decode('RnVuY3Rpb24gbm90IGZvdW5kOiB7JG59')); $key = hash_hmac('sha256', $__salts[$n], $__masterKey); list($cb64, $iv) = explode(':', $__funcs[$n]); $cc = base64_decode($cb64, true); if($cc === false) throw new Exception(base64_decode('QmFzZTY057yW56iL5a2X56ym5Liy')); $ivv = hex2bin($iv); if($ivv === false) throw new Exception(base64_decode('SVbil6jmiKvlpKflkI0=')); $dd = openssl_decrypt($cc, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $ivv); if($dd === false) throw new Exception(base64_decode('QUVT57yW56iL5a2X6YOo5pyJ')); $compressMethod = isset($__segmentCompressMethods[$n]) ? $__segmentCompressMethods[$n] : 'gzip'; if($compressMethod === 'zstd' && function_exists('zstd_uncompress')) { $phpCode = zstd_uncompress($dd); } else { $phpCode = gzinflate($dd); } if($phpCode === false) throw new Exception(base64_decode('5byg5bCP5LmL')); if(hash_hmac('sha512', $phpCode, $__masterKey) !== $__signatures[$n]) { throw new Exception(base64_decode('6Z2e57uE6K6+56iL5L2V5LyB5Lia5L2g5rO9')); // 签名校验失败 } if(hash_hmac('sha256', $phpCode, $__masterKey) !== $__hmacs[$n]) exit(base64_decode('Q29kZSBmb3IgeyRufSBoYXMgYmVlbiB0YW1wZXJlZCE=')); eval("?>" . $phpCode); unset($__masterKey, $phpCode); gc_collect_cycles(); static $exec_cache = []; if(!isset($exec_cache[$n])) $exec_cache[$n] = true; $__cache[$n] = true; register_shutdown_function(function() use ($n) { if ($n !== '__full_file__') { exit(base64_decode('5bCP546L5Y2K5YWl77yB')); } }); } f515f13ccdddd7066('__full_file__');
区域选择录制：支持全屏或自定义区域录制实时预览：选择区域后可立即预览参数自定义：可调节FPS、录制时长友好界面：直观的GUI操作界面安装依赖库pip install pillow pyautogui pyinstaller核心源码import tkinter as tk from tkinter import ttk, filedialog, messagebox import pyautogui from PIL import Image, ImageTk, ImageGrab import threading import time import os class GIFRecorderWithRegion: def __init__(self, root): self.root = root self.root.title("GIF录制工具 - 带区域选择") self.root.geometry("500x400") self.recording = False self.selecting_region = False self.frames = [] self.output_path = "favicon.ico" self.region = None # (x1, y1, x2, y2) self.setup_ui() def setup_ui(self): """设置用户界面""" # 输出路径选择 path_frame = ttk.Frame(self.root) path_frame.pack(pady=10, padx=20, fill="x") ttk.Label(path_frame, text="输出路径:").pack(side="left") self.path_var = tk.StringVar(value=self.output_path) ttk.Entry(path_frame, textvariable=self.path_var, width=30).pack(side="left", padx=5) ttk.Button(path_frame, text="浏览", command=self.browse_path).pack(side="left") # 区域选择 region_frame = ttk.LabelFrame(self.root, text="录制区域/////LB5.NET-小野博客//////", padding=10) region_frame.pack(pady=10, padx=20, fill="x") self.region_var = tk.StringVar(value="全屏") ttk.Radiobutton(region_frame, text="全屏", variable=self.region_var, value="全屏", command=self.update_region_display).pack(anchor="w") ttk.Radiobutton(region_frame, text="自定义区域", variable=self.region_var, value="自定义区域", command=self.update_region_display).pack(anchor="w") # 区域坐标显示和选择 coord_frame = ttk.Frame(region_frame) coord_frame.pack(fill="x", pady=5) ttk.Label(coord_frame, text="区域坐标:").pack(side="left") self.coord_var = tk.StringVar(value="未选择") ttk.Label(coord_frame, textvariable=self.coord_var, foreground="green").pack(side="left", padx=5) ttk.Button(coord_frame, text="选择区域", command=self.start_region_selection).pack(side="left", padx=10) # 参数设置 param_frame = ttk.LabelFrame(self.root, text="录制参数", padding=10) param_frame.pack(pady=10, padx=20, fill="x") ttk.Label(param_frame, text="FPS:").grid(row=0, column=0, sticky="w") self.fps_var = tk.StringVar(value="10") ttk.Spinbox(param_frame, from_=1, to=30, textvariable=self.fps_var, width=10).grid(row=0, column=1, padx=5) ttk.Label(param_frame, text="时长(秒):").grid(row=1, column=0, sticky="w", pady=5) self.duration_var = tk.StringVar(value="10") ttk.Spinbox(param_frame, from_=1, to=60, textvariable=self.duration_var, width=10).grid(row=1, column=1, padx=5) # 控制按钮 btn_frame = ttk.Frame(self.root) btn_frame.pack(pady=20) self.record_btn = ttk.Button(btn_frame, text="开始录制", command=self.toggle_recording, width=15) self.record_btn.pack(pady=5) # 状态显示 self.status_var = tk.StringVar(value="准备就绪") status_label = ttk.Label(self.root, textvariable=self.status_var, foreground="blue") status_label.pack(pady=10) # 预览区域 preview_frame = ttk.LabelFrame(self.root, text="区域预览", padding=5) preview_frame.pack(pady=10, padx=20, fill="both", expand=True) self.preview_label = ttk.Label(preview_frame, text="区域预览将显示在这里") self.preview_label.pack(expand=True) def browse_path(self): """选择输出路径""" path = filedialog.asksaveasfilename( defaultextension=".gif", filetypes=[("GIF文件", "*.gif"), ("所有文件", "*.*")] ) if path: self.path_var.set(path) def update_region_display(self): """更新区域显示""" if self.region_var.get() == "全屏": self.coord_var.set("全屏") self.update_preview() elif self.region and self.region_var.get() == "自定义区域": x1, y1, x2, y2 = self.region self.coord_var.set(f"({x1}, {y1}) - ({x2}, {y2})") self.update_preview() def start_region_selection(self): """开始区域选择""" self.status_var.set("请选择录制区域...") self.root.withdraw() # 隐藏主窗口 # 创建区域选择窗口 self.region_selector = RegionSelector(self.root, self.region_selected) def region_selected(self, region): """区域选择回调""" self.region = region self.region_var.set("自定义区域") self.root.deiconify() # 重新显示主窗口 self.update_region_display() self.status_var.set("区域选择完成") def update_preview(self): """更新区域预览""" try: if self.region_var.get() == "全屏": # 全屏预览 - 缩小显示 screenshot = pyautogui.screenshot() preview_size = (300, 200) screenshot.thumbnail(preview_size, Image.Resampling.LANCZOS) photo = ImageTk.PhotoImage(screenshot) self.preview_label.config(image=photo) self.preview_label.image = photo elif self.region and self.region_var.get() == "自定义区域": # 自定义区域预览 x1, y1, x2, y2 = self.region region_screenshot = pyautogui.screenshot(region=(x1, y1, x2-x1, y2-y1)) preview_size = (300, 200) region_screenshot.thumbnail(preview_size, Image.Resampling.LANCZOS) photo = ImageTk.PhotoImage(region_screenshot) self.preview_label.config(image=photo) self.preview_label.image = photo except Exception as e: self.preview_label.config(text=f"预览更新失败: {str(e)}") def toggle_recording(self): """开始/停止录制""" if not self.recording: self.start_recording() else: self.stop_recording() def start_recording(self): """开始录制""" self.output_path = self.path_var.get() fps = int(self.fps_var.get()) duration = int(self.duration_var.get()) if not self.output_path: messagebox.showerror("错误", "请选择输出路径") return self.recording = True self.frames = [] self.record_btn.config(text="停止录制") self.status_var.set("录制中...") # 在新线程中录制 self.record_thread = threading.Thread( target=self.record_screen, args=(fps, duration) ) self.record_thread.daemon = True self.record_thread.start() def stop_recording(self): """停止录制""" self.recording = False self.record_btn.config(text="开始录制") self.status_var.set("正在保存GIF...") def record_screen(self, fps, duration): """录制屏幕""" start_time = time.time() interval = 1.0 / fps while self.recording and (time.time() - start_time) < duration: frame_start = time.time() try: # 根据选择的区域截屏 if self.region_var.get() == "全屏": screenshot = pyautogui.screenshot() else: if self.region: x1, y1, x2, y2 = self.region screenshot = pyautogui.screenshot(region=(x1, y1, x2-x1, y2-y1)) else: screenshot = pyautogui.screenshot() self.frames.append(screenshot) # 更新状态 elapsed = time.time() - start_time remaining = duration - elapsed self.status_var.set(f"录制中... 剩余时间: {remaining:.1f}秒") except Exception as e: print(f"截图失败: {e}") # 控制帧率 elapsed_frame = time.time() - frame_start sleep_time = interval - elapsed_frame if sleep_time > 0: time.sleep(sleep_time) # 保存GIF if self.frames: self.save_gif() def save_gif(self): """保存GIF文件""" try: if self.frames: self.frames[0].save( self.output_path, save_all=True, append_images=self.frames[1:], duration=1000//int(self.fps_var.get()), loop=0, optimize=True ) self.status_var.set(f"GIF已保存: {self.output_path}") messagebox.showinfo("成功", f"GIF已保存到:\n{self.output_path}\n共{len(self.frames)}帧") else: self.status_var.set("没有录制到帧") messagebox.showwarning("警告", "没有录制到任何帧") except Exception as e: self.status_var.set("保存失败") messagebox.showerror("错误", f"保存失败: {str(e)}") class RegionSelector: """区域选择器""" def __init__(self, parent, callback): self.parent = parent self.callback = callback self.start_x = None self.start_y = None self.current_x = None self.current_y = None self.region = None # 创建全屏透明窗口 self.selector = tk.Toplevel(parent) self.selector.attributes('-fullscreen', True) self.selector.attributes('-alpha', 0.3) self.selector.configure(bg='black') self.selector.attributes('-topmost', True) # 创建画布用于绘制选择框 self.canvas = tk.Canvas(self.selector, highlightthickness=0) self.canvas.pack(fill=tk.BOTH, expand=True) # 绑定事件 self.canvas.bind('<Button-1>', self.on_button_press) self.canvas.bind('<B1-Motion>', self.on_mouse_drag) self.canvas.bind('<ButtonRelease-1>', self.on_button_release) self.selector.bind('<Escape>', self.cancel_selection) self.selector.focus_set() def on_button_press(self, event): """鼠标按下""" self.start_x = event.x self.start_y = event.y self.rect = self.canvas.create_rectangle( self.start_x, self.start_y, self.start_x, self.start_y, outline='red', width=2, fill='blue', stipple='gray50' ) def on_mouse_drag(self, event): """鼠标拖动""" self.current_x = event.x self.current_y = event.y self.canvas.coords(self.rect, self.start_x, self.start_y, self.current_x, self.current_y) def on_button_release(self, event): """鼠标释放""" self.current_x = event.x self.current_y = event.y # 确保坐标正确（左上角到右下角） x1 = min(self.start_x, self.current_x) y1 = min(self.start_y, self.current_y) x2 = max(self.start_x, self.current_x) y2 = max(self.start_y, self.current_y) # 如果区域太小，认为是取消 if abs(x2 - x1) < 10 or abs(y2 - y1) < 10: self.cancel_selection() return self.region = (x1, y1, x2, y2) self.selector.destroy() self.callback(self.region) def cancel_selection(self, event=None): """取消选择""" self.selector.destroy() self.callback(None) class AdvancedGIFRecorder(GIFRecorderWithRegion): def __init__(self, root): super().__init__(root) self.root.title("GIF录制截图") # 添加更多功能按钮 advanced_frame = ttk.Frame(self.root) advanced_frame.pack(pady=5) ttk.Button(advanced_frame, text="测试区域", command=self.test_region).pack(side="left", padx=5) ttk.Button(advanced_frame, text="清除区域", command=self.clear_region).pack(side="left", padx=5) def test_region(self): """测试选择的区域""" if self.region and self.region_var.get() == "自定义区域": try: x1, y1, x2, y2 = self.region # 短暂显示区域边框 test_window = tk.Toplevel(self.root) test_window.attributes('-topmost', True) test_window.geometry(f"{x2-x1}x{y2-y1}+{x1}+{y1}") test_window.overrideredirect(True) test_window.configure(bg='red', highlightthickness=2, highlightbackground='yellow') # 3秒后自动关闭 test_window.after(3000, test_window.destroy) except Exception as e: messagebox.showerror("错误", f"测试失败: {str(e)}") else: messagebox.showinfo("提示", "请先选择自定义区域") def clear_region(self): """清除区域选择""" self.region = None self.region_var.set("全屏") self.update_region_display() self.status_var.set("区域已清除") if __name__ == "__main__": root = tk.Tk() app = AdvancedGIFRecorder(root) root.mainloop()
想一次性把 YYDS 源码网 1-200 页的网盘资源全部扒下来？今天分享 30 行 Python 脚本，无需配置环境，双击即可运行，自动提取标题+真实外链，支持 7 大网盘，完全开源！1. 脚本功能顺序抓取 1-200 页文章列表进入详情页提取「侧边栏按钮 + 正文」网盘链接自动跟随 goto?down=xxx 跳转，拿到真实外链支持蓝奏云 / 百度 / 夸克 / 123 / 阿里 / 迅雷 / 腾讯保存格式：标题 | 网盘链接（txt 可直接复制）2. 运行环境项目版本Python3.7+依赖requests、beautifulsoup4一键安装：pip install requests beautifulsoup4 #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 抓取 www.yydsym.com 保存格式：标题 | 网盘链接 输出文件：桌面 title_pan_links.txt """ import os import time import random import requests from bs4 import BeautifulSoup from urllib.parse import urljoin MAX_PAGE = 100 SLEEP = lambda: random.uniform(1.0, 2.0) TIMEOUT = 10 LIST_URL = "https://www.yydsym.com/page/{}" SAVE_FILE = os.path.join(os.path.expanduser("~"), "Desktop", "title_pan_links.txt") HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" } PAN_KEYS = ("lanzou", "lanzn", "pan.baidu", "quark.cn", "123pan.com", "alipan.com", "xunlei.com", "cloud.189.cn", "tc.qq.com") def get_list(page): url = LIST_URL.format(page) resp = requests.get(url, headers=HEADERS, timeout=TIMEOUT) resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") items = [] for art in soup.select('article.post-grid'): a = art.select_one('h2.entry-title a') if not a: continue title = a.get_text(strip=True) link = urljoin(url, a["href"]) items.append((title, link)) return items def extract_pan(html): soup = BeautifulSoup(html, "html.parser") pans = set() for a in soup.select('a[href*="goto?down="]'): short = urljoin("https://www.yydsym.com", a["href"]) try: r = requests.get(short, headers=HEADERS, allow_redirects=False, timeout=10) real = r.headers.get("Location", "") if r.status_code in (301, 302) else short pans.add(real) except Exception: continue for a in soup.find_all("a", href=True): href = a["href"].strip() if any(k in href for k in PAN_KEYS): pans.add(href) return pans def main(): exist = set() if os.path.isfile(SAVE_FILE): with open(SAVE_FILE, "r", encoding="utf-8") as f: for line in f: if "|" in line: exist.add(line.split("|", 1)[1].strip()) print("🔍 开始抓取列表页...") for p in range(1, MAX_PAGE + 1): try: items = get_list(p) print(f" 第 {p:3d} 页 | 本页 {len(items):2d} 篇") except Exception as e: print(f" 第 {p:3d} 页 | 错误：{e}") continue time.sleep(SLEEP()) print(" 扫描详情页...") for title, url in items: try: html = requests.get(url, headers=HEADERS, timeout=TIMEOUT).text pans = extract_pan(html) new = 0 for u in pans: if u in exist: continue new += 1 with open(SAVE_FILE, "a", encoding="utf-8") as f: f.write(f"{title} | {u}\n") exist.add(u) if new: print(f" +{new} 条 | {title[:30]}...") except Exception as e: print(f" 跳过 | {e}") time.sleep(SLEEP()) print(f"\n✅ 全部完成！文件已保存 → {SAVE_FILE}") if __name__ == "__main__": main()