feat(v1.8.1): Windows 兼容性修复 + SKILL.md 铁律区块
Windows 兼容性修复(基于真实使用痛点):
- force_utf8_stdout(): 强制 stdout/stderr 为 UTF-8,修复 Windows GBK 崩溃(print('\\xa0') 不再炸)
- resolve_instances/load_config 新增 %APPDATA%/searxng-cli/ 路径,覆盖 Windows 配置约定
- fetch.py 失败诊断增强:输出 status_code=/cause=/url= 字段,AI Agent 可程序化区分 404/403/DNS 失败
SKILL.md 铁律区块(5 条,置顶):
- stdout=数据/stderr=日志 永不混淆
- 禁用 2>/dev/null(丢弃 stderr = 失败时零诊断)
- 排错去 --quiet 加 --verbose
- 配置查找覆盖 WSL + Windows 双路径
- 实例 URL 必填,公共实例发现已移除
测试: 352 -> 362(新增 10 个:force_utf8_stdout 幂等性/GBK 替换/非 ASCII 打印/APPDATA 路径发现/txt 回退/空 APPDATA)
This commit is contained in:
@@ -11,12 +11,14 @@ This module centralizes code that was previously duplicated across
|
||||
* ``FALLBACK_UAS`` — browser-like User-Agents used when blocked
|
||||
* retry constants — ``RETRYABLE_STATUS``, ``RETRY_BACKOFF_BASE``, etc.
|
||||
* ``setup_logging`` — shared logging configuration (--verbose/--quiet)
|
||||
* ``force_utf8_stdout`` — force stdout to UTF-8 (fix Windows GBK crashes)
|
||||
|
||||
Centralizing the retry policy guarantees that both scripts treat 429/5xx
|
||||
as retryable and connection errors as transient, eliminating the previous
|
||||
inconsistency where ``search.py`` ignored 5xx.
|
||||
"""
|
||||
|
||||
import io
|
||||
import logging
|
||||
import sys
|
||||
import urllib.error
|
||||
@@ -56,6 +58,58 @@ def setup_logging(verbose: bool = False, quiet: bool = False) -> None:
|
||||
# Don't let root logger add its own handler — we own the searxng namespace.
|
||||
_LOG.propagate = False
|
||||
|
||||
|
||||
def force_utf8_stdout() -> None:
|
||||
"""Force stdout/stderr to UTF-8 to prevent Windows GBK encoding crashes.
|
||||
|
||||
Windows Python defaults ``sys.stdout`` to the OEM codepage (often GBK on
|
||||
Chinese Windows). ``print()`` of any character outside that codepage
|
||||
(e.g. ``\\xa0`` nbsp, CJK punctuation from foreign pages) raises
|
||||
``UnicodeEncodeError`` and kills the process.
|
||||
|
||||
``PYTHONIOENCODING=utf-8`` is unreliable here because Python 3.7+
|
||||
reconfigures stdout after reading that env var in some scenarios (e.g.
|
||||
when stdout has already been wrapped). The only reliable fix is to
|
||||
reconfigure the stream in-process.
|
||||
|
||||
Uses ``sys.stdout.reconfigure()`` on Python 3.7+, falling back to
|
||||
wrapping ``sys.stdout.buffer`` on older versions. Both paths use
|
||||
``errors='replace'`` so an undecodable byte never crashes the script —
|
||||
better to emit ``?`` than to lose all output.
|
||||
|
||||
Safe to call multiple times; subsequent calls are no-ops once the
|
||||
encoding is already UTF-8 (or close enough — we check the lowercased
|
||||
encoding name to tolerate ``utf-8`` vs ``UTF-8`` vs ``utf8``).
|
||||
"""
|
||||
for stream_name in ("stdout", "stderr"):
|
||||
stream = getattr(sys, stream_name, None)
|
||||
if stream is None:
|
||||
continue
|
||||
# Already UTF-8? Skip (covers Linux/macOS and re-invoked scripts).
|
||||
enc = getattr(stream, "encoding", "") or ""
|
||||
if enc.lower().replace("-", "") in ("utf8", "utf-8-sig"):
|
||||
continue
|
||||
# Python 3.7+ has TextIOWrapper.reconfigure()
|
||||
reconfigure = getattr(stream, "reconfigure", None)
|
||||
if reconfigure is not None:
|
||||
try:
|
||||
reconfigure(encoding="utf-8", errors="replace")
|
||||
continue
|
||||
except (ValueError, OSError):
|
||||
pass # Fall through to the buffer-wrap path
|
||||
# Fallback: wrap the underlying buffer in a new UTF-8 stream.
|
||||
buffer = getattr(stream, "buffer", None)
|
||||
if buffer is not None:
|
||||
try:
|
||||
new_stream = io.TextIOWrapper(
|
||||
buffer, encoding="utf-8", errors="replace", line_buffering=True,
|
||||
)
|
||||
setattr(sys, stream_name, new_stream)
|
||||
except (ValueError, AttributeError):
|
||||
# Last resort: keep the original stream. Better to risk a
|
||||
# GBK crash on exotic characters than to break stdout entirely.
|
||||
pass
|
||||
|
||||
# Retry settings (shared by both scripts)
|
||||
MAX_RETRIES = 3
|
||||
RETRY_BACKOFF_BASE = 1.5 # seconds; exponential backoff + jitter
|
||||
|
||||
Reference in New Issue
Block a user