import logging
import os.path
from logging import StreamHandler, FileHandler
from colorama import Fore, Style
from datetime import datetime

定义一个新的日志级别方法

def notice(self, message, args, *kwargs):

if self.isEnabledFor(NOTICE_LEVEL):
    self._log(NOTICE_LEVEL, message, args, **kwargs)

将该方法绑定到 logger 对象

logging.Logger.notice = notice

NOTICE_LEVEL = 19 # < NOTICE(19) < INFO(20) < WARNING(30)
logging.addLevelName(NOTICE_LEVEL, "NOTICE")

class ColorFormatter(logging.Formatter):

"""日志格式化,支持不同级别的颜色"""
COLOR_MAP = {
    logging.DEBUG: Fore.CYAN,
    logging.INFO: Fore.GREEN,
    logging.WARNING: Fore.YELLOW,
    NOTICE_LEVEL: Fore.BLUE,  # NOTICE 级别使用蓝色
    logging.ERROR: Fore.RED,
    logging.CRITICAL: Fore.MAGENTA + Style.BRIGHT,
}

RESET = Style.RESET_ALL

def format(self, record):
    log_color = self.COLOR_MAP.get(record.levelno, "")
    log_fmt = f"{log_color}[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s{self.RESET}"
    formatter = logging.Formatter(log_fmt, datefmt="%Y-%m-%d %H:%M:%S")
    return formatter.format(record)

def notice(self, message, args, *kwargs):

if self.isEnabledFor(NOTICE_LEVEL):
    self._log(NOTICE_LEVEL, message, args, **kwargs)

logging.Logger.notice = notice # 绑定到 Logger

def log_handler(log_file, level):

today = datetime.today().strftime('%Y-%m-%d')

file_format = logging.Formatter(f"[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s",
                                datefmt="%Y-%m-%d %H:%M:%S")

file_handler = FileHandler(f"{log_file}_{today}_{level}.log", mode="a", encoding="utf-8")
if level == 'info':
    file_handler.setLevel(logging.INFO)
elif level == 'debug':
    file_handler.setLevel(logging.DEBUG)
elif level == 'notice':
    file_handler.setLevel(NOTICE_LEVEL)
elif level == 'warning':
    file_handler.setLevel(logging.WARNING)
else:
    file_handler.setLevel(logging.NOTSET)
file_handler.setFormatter(file_format)

return file_handler

def setup_logger(log_dir = 'logs', log_file='app', task_id=0, uuid=None):

"""
设置全局 logger,支持:
1. 控制台日志(带颜色)
2. 文件日志(info, error)
3. 远程服务器日志上报(仅 NOTICE 级别)
"""
global logger, _task_id, _uuid
if task_id:
    _task_id = task_id
if uuid:
    _uuid = uuid
log_file = os.path.join(log_dir,log_file)
new_logger = logging.getLogger("custom_logger")
new_logger.setLevel(logging.DEBUG)  # 设置最低日志级别

# 清除旧的处理器
if new_logger.handlers:
    for handler in new_logger.handlers[:]:
        new_logger.removeHandler(handler)

# 控制台处理器
console_handler = StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(ColorFormatter())
new_logger.addHandler(console_handler)
if log_file:
    new_logger.addHandler(log_handler(log_file, 'debug'))
    new_logger.addHandler(log_handler(log_file, 'error'))
    new_logger.addHandler(log_handler(log_file, 'info'))
    new_logger.addHandler(log_handler(log_file, 'notice'))

# ✅ 替换 `logger` 的内容,而不改变 `logger` 的引用
logger.__dict__.update(new_logger.__dict__)

初始化全局 logger

logger: logging.Logger
setup_logger()

from playwright.sync_api import sync_playwright
import time
 
 
with sync_playwright() as p:
    '''
    防止被浏览器检测的处理方法
    '''
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()

    #下载地址 https://github.com/kingname/stealth.min.js/blob/main/stealth.min.js
    with open('stealth.min.js','r') as f:
        js=f.read()
    page.add_init_script(js)
    
    page.goto('https://bot.sannysoft.com/')
    
    time.sleep(1000)
 
    browser.close()

server {
    server_name  www.example.com;
    listen 80;
    index index.php;
    root /var/www/blog;

    location / {
    try_files $uri $uri/ /index.php?$query_string;
  }

  location ~ \.php$ {
    #include snippets/fastcgi-php.conf;

    # Nginx php-fpm sock config:
    fastcgi_pass  unix:/run/php/php8.1-fpm.sock;
    #fastcgi_pass  127.0.0.1:9001;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
  }
}

JavaScript 页面加载与卸载相关事件

适用范围事件触发时机适用场景特性/注意点
windowload页面所有资源(HTML、图片、样式等)加载完成需要确保所有资源加载后再执行逻辑DOMContentLoaded 晚,等待资源加载
beforeunload页面即将卸载时(导航到新页面或关闭标签)提示用户保存数据或防止意外离开可显示离开确认对话框,设置 event.returnValue
unload页面完全卸载时(文档和资源从内存中清除)清理资源(如 WebSocket 连接、定时器等)现代浏览器限制较多,无法异步执行逻辑
focus/blur页面获得或失去焦点时触发检测用户切换到其他窗口或标签页可结合 document.hidden 检测可见性变化
documentDOMContentLoadedHTML 完全解析完成,资源(图片、样式表等)未必加载完成操作 DOM 元素,不依赖资源加载load 更早触发,常用于脚本初始化
readystatechangedocument.readyState 改变时触发,包括 loadinginteractivecomplete监控页面加载状态适合实时监控文档解析和加载的不同阶段
visibilitychange页面的可见性状态改变(如切换标签、最小化窗口)检测页面切换状态,节省资源或暂停功能状态通过 document.hiddenvisibilityState 获取
Chrome 插件 Content Scriptrun_at: "document_start"页面开始加载时(HTML 解析前)插入代码以修改页面的初始状态不适合操作 DOM,适合拦截初始 HTML
run_at: "document_end"HTML 完全解析完成(等同于 DOMContentLoaded操作已解析的 DOM,初始化插件逻辑脚本逻辑不会等待资源加载
run_at: "document_idle"HTML 解析完成,大部分资源已加载(页面处于空闲时运行)插件的默认运行时机,适用于大部分场景保证 DOM 和大部分资源已准备好
window 或特定元素error资源加载失败时(如图片、脚本、样式表)捕获资源加载错误或执行失败的脚本可在 window 或特定元素(如图片)上监听