2.3权限管理
This commit is contained in:
@ -1,38 +0,0 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['test1.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('dist', 'dist')],
|
||||
hiddenimports=['flask_sqlalchemy', 'lxml'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='MonitorSystem',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -1,131 +0,0 @@
|
||||
|
||||
This file lists modules PyInstaller was not able to find. This does not
|
||||
necessarily mean this module is required for running your program. Python and
|
||||
Python 3rd-party packages include a lot of conditional or optional modules. For
|
||||
example the module 'ntpath' only exists on Windows, whereas the module
|
||||
'posixpath' only exists on Posix systems.
|
||||
|
||||
Types if import:
|
||||
* top-level: imported at the top-level - look at these first
|
||||
* conditional: imported within an if-statement
|
||||
* delayed: imported within a function
|
||||
* optional: imported within a try-except-statement
|
||||
|
||||
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
|
||||
tracking down the missing module yourself. Thanks!
|
||||
|
||||
missing module named pyimod02_importers - imported by D:\Git\Learn-Web-spider\venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed), D:\Git\Learn-Web-spider\venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgres.py (delayed)
|
||||
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), netrc (delayed, conditional), getpass (delayed), http.server (delayed, optional), webbrowser (delayed), distutils.util (delayed, conditional, optional), setuptools._vendor.backports.tarfile (optional), distutils.archive_util (optional), setuptools._distutils.util (delayed, conditional, optional), setuptools._distutils.archive_util (optional)
|
||||
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), setuptools._vendor.backports.tarfile (optional), distutils.archive_util (optional), setuptools._distutils.archive_util (optional)
|
||||
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
|
||||
missing module named fcntl - imported by subprocess (optional), tornado.platform.posix (top-level)
|
||||
missing module named org - imported by copy (optional)
|
||||
missing module named urllib.urlopen - imported by urllib (delayed, optional), lxml.html (delayed, optional)
|
||||
missing module named urllib.urlencode - imported by urllib (delayed, optional), lxml.html (delayed, optional)
|
||||
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
|
||||
missing module named resource - imported by posix (top-level)
|
||||
missing module named _manylinux - imported by packaging._manylinux (delayed, optional), setuptools._vendor.packaging._manylinux (delayed, optional), wheel.vendored.packaging._manylinux (delayed, optional)
|
||||
missing module named '_typeshed.importlib' - imported by pkg_resources (conditional)
|
||||
missing module named _typeshed - imported by werkzeug._internal (conditional), pkg_resources (conditional), setuptools.glob (conditional), setuptools.compat.py311 (conditional), setuptools._vendor.jaraco.collections (conditional)
|
||||
missing module named jnius - imported by setuptools._vendor.platformdirs.android (delayed, conditional, optional)
|
||||
missing module named android - imported by setuptools._vendor.platformdirs.android (delayed, conditional, optional)
|
||||
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||
missing module named 'distutils._modified' - imported by setuptools._distutils.file_util (delayed)
|
||||
missing module named 'distutils._log' - imported by setuptools._distutils.command.bdist_dumb (top-level), setuptools._distutils.command.bdist_rpm (top-level), setuptools._distutils.command.build_clib (top-level), setuptools._distutils.command.build_ext (top-level), setuptools._distutils.command.build_py (top-level), setuptools._distutils.command.build_scripts (top-level), setuptools._distutils.command.clean (top-level), setuptools._distutils.command.config (top-level), setuptools._distutils.command.install (top-level), setuptools._distutils.command.install_scripts (top-level), setuptools._distutils.command.sdist (top-level)
|
||||
missing module named usercustomize - imported by site (delayed, optional)
|
||||
missing module named sitecustomize - imported by site (delayed, optional)
|
||||
missing module named readline - imported by code (delayed, conditional, optional), flask.cli (delayed, conditional, optional), rlcompleter (optional), cmd (delayed, conditional, optional), pdb (delayed, optional), site (delayed, optional)
|
||||
missing module named _scproxy - imported by urllib.request (conditional)
|
||||
missing module named termios - imported by getpass (optional), tty (top-level), werkzeug._reloader (delayed, optional), click._termui_impl (conditional)
|
||||
missing module named _posixshmem - imported by multiprocessing.resource_tracker (conditional), multiprocessing.shared_memory (conditional)
|
||||
missing module named multiprocessing.set_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||
missing module named multiprocessing.get_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||
missing module named multiprocessing.get_context - imported by multiprocessing (top-level), multiprocessing.pool (top-level), multiprocessing.managers (top-level), multiprocessing.sharedctypes (top-level)
|
||||
missing module named multiprocessing.TimeoutError - imported by multiprocessing (top-level), multiprocessing.pool (top-level)
|
||||
missing module named 'org.python' - imported by pickle (optional), xml.sax (delayed, conditional)
|
||||
missing module named 'java.lang' - imported by platform (delayed, optional), xml.sax._exceptions (conditional)
|
||||
missing module named multiprocessing.BufferTooShort - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||
missing module named multiprocessing.AuthenticationError - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||
missing module named asyncio.DefaultEventLoopPolicy - imported by asyncio (delayed, conditional), asyncio.events (delayed, conditional)
|
||||
missing module named trove_classifiers - imported by setuptools.config._validate_pyproject.formats (optional)
|
||||
missing module named importlib_resources - imported by setuptools._vendor.jaraco.text (optional)
|
||||
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||
missing module named vms_lib - imported by platform (delayed, optional)
|
||||
missing module named java - imported by platform (delayed)
|
||||
missing module named _winreg - imported by platform (delayed, optional), tzlocal.win32 (optional)
|
||||
missing module named collections.MutableMapping - imported by collections (optional), html5lib.treebuilders.dom (optional), html5lib.treebuilders.etree_lxml (optional)
|
||||
missing module named collections.Mapping - imported by collections (optional), html5lib._utils (optional), html5lib._trie._base (optional)
|
||||
missing module named collections.Callable - imported by collections (optional), socks (optional), bs4.element (optional), bs4.builder._lxml (optional)
|
||||
missing module named htmlentitydefs - imported by tornado.escape (conditional), lxml.html.soupparser (optional)
|
||||
missing module named BeautifulSoup - imported by lxml.html.soupparser (optional)
|
||||
missing module named cchardet - imported by bs4.dammit (optional)
|
||||
missing module named bs4.builder.HTMLParserTreeBuilder - imported by bs4.builder (top-level), bs4 (top-level)
|
||||
missing module named StringIO - imported by six (conditional)
|
||||
missing module named html5lib.treebuilders._base - imported by html5lib.treebuilders (optional), bs4.builder._html5lib (optional), lxml.html._html5builder (top-level)
|
||||
missing module named html5lib.XHTMLParser - imported by html5lib (optional), lxml.html.html5parser (optional)
|
||||
runtime module named six.moves - imported by dateutil.tz.tz (top-level), dateutil.tz._factories (top-level), dateutil.tz.win (top-level), dateutil.rrule (top-level), html5lib._inputstream (top-level), six.moves.urllib (top-level), html5lib.filters.sanitizer (top-level)
|
||||
missing module named six.moves.range - imported by six.moves (top-level), dateutil.rrule (top-level)
|
||||
missing module named 'genshi.core' - imported by html5lib.treewalkers.genshi (top-level)
|
||||
missing module named genshi - imported by html5lib.treewalkers.genshi (top-level)
|
||||
missing module named urlparse - imported by tornado.escape (conditional), lxml.ElementInclude (optional), lxml.html.html5parser (optional)
|
||||
missing module named urllib2 - imported by lxml.ElementInclude (optional), lxml.html.html5parser (optional)
|
||||
missing module named lxml_html_clean - imported by lxml.html.clean (optional)
|
||||
missing module named twisted - imported by apscheduler.schedulers.twisted (optional)
|
||||
missing module named trollius - imported by tornado.platform.asyncio (optional)
|
||||
missing module named __builtin__ - imported by tornado.gen (conditional)
|
||||
missing module named backports_abc - imported by tornado.gen (optional)
|
||||
missing module named singledispatch - imported by tornado.gen (optional)
|
||||
missing module named thread - imported by tornado.ioloop (conditional)
|
||||
missing module named 'tornado.speedups' - imported by tornado.util (conditional, optional)
|
||||
missing module named monotonic - imported by tornado.platform.auto (optional)
|
||||
missing module named monotime - imported by tornado.platform.auto (optional)
|
||||
missing module named _curses - imported by curses (top-level), curses.has_key (top-level)
|
||||
missing module named win32evtlog - imported by logging.handlers (delayed, optional)
|
||||
missing module named win32evtlogutil - imported by logging.handlers (delayed, optional)
|
||||
missing module named 'gevent.lock' - imported by apscheduler.schedulers.gevent (optional)
|
||||
missing module named 'gevent.event' - imported by apscheduler.schedulers.gevent (optional)
|
||||
missing module named gevent - imported by apscheduler.executors.gevent (optional), apscheduler.schedulers.gevent (optional)
|
||||
missing module named 'kazoo.client' - imported by apscheduler.jobstores.zookeeper (optional)
|
||||
missing module named kazoo - imported by apscheduler.jobstores.zookeeper (top-level)
|
||||
missing module named annotationlib - imported by sqlalchemy.util.langhelpers (conditional)
|
||||
missing module named pysqlcipher3 - imported by sqlalchemy.dialects.sqlite.pysqlcipher (delayed)
|
||||
missing module named sqlcipher3 - imported by sqlalchemy.dialects.sqlite.pysqlcipher (delayed, optional)
|
||||
missing module named psycopg2 - imported by sqlalchemy.dialects.postgresql.psycopg2 (delayed)
|
||||
missing module named 'psycopg.pq' - imported by sqlalchemy.dialects.postgresql.psycopg (delayed)
|
||||
missing module named 'psycopg.types' - imported by sqlalchemy.dialects.postgresql.psycopg (delayed, conditional)
|
||||
missing module named 'psycopg.adapt' - imported by sqlalchemy.dialects.postgresql.psycopg (delayed, conditional)
|
||||
missing module named psycopg - imported by sqlalchemy.dialects.postgresql.psycopg (delayed, conditional)
|
||||
missing module named asyncpg - imported by sqlalchemy.dialects.postgresql.asyncpg (delayed)
|
||||
missing module named oracledb - imported by sqlalchemy.dialects.oracle.oracledb (delayed, conditional)
|
||||
missing module named cx_Oracle - imported by sqlalchemy.dialects.oracle.cx_oracle (delayed)
|
||||
missing module named mysql - imported by sqlalchemy.dialects.mysql.mysqlconnector (delayed, conditional, optional)
|
||||
missing module named asyncmy - imported by sqlalchemy.dialects.mysql.asyncmy (delayed)
|
||||
missing module named nacl - imported by pymysql._auth (delayed, optional)
|
||||
missing module named 'cryptography.hazmat' - imported by werkzeug.serving (delayed, conditional, optional), redis.ocsp (top-level), pymysql._auth (optional)
|
||||
missing module named rethinkdb - imported by apscheduler.jobstores.rethinkdb (optional)
|
||||
missing module named cryptography - imported by urllib3.contrib.pyopenssl (top-level), requests (conditional, optional), werkzeug.serving (delayed, optional), flask.cli (delayed, conditional, optional), redis.utils (optional), redis.ocsp (top-level)
|
||||
missing module named hiredis - imported by redis.utils (optional), redis.connection (conditional), redis._parsers.hiredis (delayed)
|
||||
missing module named 'cryptography.x509' - imported by urllib3.contrib.pyopenssl (delayed, optional), werkzeug.serving (delayed, conditional, optional), redis.ocsp (top-level)
|
||||
missing module named 'cryptography.exceptions' - imported by redis.ocsp (top-level)
|
||||
missing module named OpenSSL - imported by urllib3.contrib.pyopenssl (top-level), redis.connection (delayed, conditional)
|
||||
missing module named 'pymongo.errors' - imported by apscheduler.jobstores.mongodb (optional)
|
||||
missing module named pymongo - imported by apscheduler.jobstores.mongodb (optional)
|
||||
missing module named bson - imported by apscheduler.jobstores.mongodb (optional)
|
||||
missing module named etcd3 - imported by apscheduler.jobstores.etcd (optional)
|
||||
missing module named 'setuptools._vendor.backports.zoneinfo' - imported by apscheduler.util (conditional)
|
||||
missing module named dateutil.tz.tzfile - imported by dateutil.tz (top-level), dateutil.zoneinfo (top-level)
|
||||
missing module named asgiref - imported by flask.app (delayed, optional)
|
||||
missing module named '_typeshed.wsgi' - imported by werkzeug.exceptions (conditional), werkzeug.http (conditional), werkzeug.wsgi (conditional), werkzeug.utils (conditional), werkzeug.wrappers.response (conditional), werkzeug.test (conditional), werkzeug.formparser (conditional), werkzeug.wrappers.request (conditional), werkzeug.serving (conditional), werkzeug.debug (conditional), werkzeug.middleware.shared_data (conditional), werkzeug.local (conditional), werkzeug.routing.exceptions (conditional), werkzeug.routing.map (conditional), flask.typing (conditional), flask.ctx (conditional), flask.testing (conditional), flask.cli (conditional), flask.app (conditional)
|
||||
missing module named dotenv - imported by flask.cli (delayed, optional)
|
||||
missing module named 'watchdog.observers' - imported by werkzeug._reloader (delayed)
|
||||
missing module named 'watchdog.events' - imported by werkzeug._reloader (delayed)
|
||||
missing module named watchdog - imported by werkzeug._reloader (delayed)
|
||||
missing module named simplejson - imported by requests.compat (conditional, optional)
|
||||
missing module named dummy_threading - imported by requests.cookies (optional)
|
||||
missing module named zstandard - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named brotli - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named brotlicffi - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named win_inet_pton - imported by socks (conditional, optional)
|
||||
missing module named 'OpenSSL.crypto' - imported by urllib3.contrib.pyopenssl (delayed, conditional)
|
||||
missing module named pyodide - imported by urllib3.contrib.emscripten.fetch (top-level)
|
||||
missing module named js - imported by urllib3.contrib.emscripten.fetch (top-level)
|
||||
File diff suppressed because it is too large
Load Diff
BIN
1.1/dabao/dist/MonitorSystem.exe
vendored
BIN
1.1/dabao/dist/MonitorSystem.exe
vendored
Binary file not shown.
155
1.1/dabao/dist/assets/index-0f069df0.js
vendored
155
1.1/dabao/dist/assets/index-0f069df0.js
vendored
File diff suppressed because one or more lines are too long
1
1.1/dabao/dist/assets/index-c85ab497.css
vendored
1
1.1/dabao/dist/assets/index-c85ab497.css
vendored
File diff suppressed because one or more lines are too long
15
1.1/dabao/dist/index.html
vendored
15
1.1/dabao/dist/index.html
vendored
@ -1,15 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>my-vue-app</title>
|
||||
<script type="module" crossorigin src="./assets/index-0f069df0.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index-c85ab497.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1
1.1/dabao/dist/vite.svg
vendored
1
1.1/dabao/dist/vite.svg
vendored
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
@ -1,327 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import threading
|
||||
import requests
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from flask import Flask, jsonify, send_from_directory
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_cors import CORS
|
||||
from flask_apscheduler import APScheduler
|
||||
from lxml import etree
|
||||
|
||||
# --- 配置日志 ---
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
|
||||
# --- 关键路径处理函数 (适配 PyInstaller) ---
|
||||
def get_base_path():
|
||||
"""获取运行时及其所在目录,适配开发环境和打包后的EXE环境"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
# 如果是打包后的 exe,sys.executable 是 exe 的路径
|
||||
return os.path.dirname(sys.executable)
|
||||
# 开发环境下,是当前脚本的路径
|
||||
return os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def get_static_path():
|
||||
"""获取 Vue 静态资源 dist 的路径"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
# PyInstaller 打包时,资源文件会被解压到 sys._MEIPASS 临时目录
|
||||
# 我们需要在打包命令中指定 --add-data "dist;dist"
|
||||
return os.path.join(sys._MEIPASS, 'dist')
|
||||
# 开发环境
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dist')
|
||||
|
||||
|
||||
# --- Flask 初始化 ---
|
||||
# static_folder 指向 Vue 打包后的 dist 目录
|
||||
# static_url_path='' 表示静态文件不需要 /static 前缀
|
||||
dist_folder = get_static_path()
|
||||
app = Flask(__name__, static_folder=dist_folder, static_url_path='')
|
||||
CORS(app)
|
||||
|
||||
# --- 数据库配置 ---
|
||||
# 确保数据库生成在 exe 同级目录下,而不是临时文件夹中
|
||||
db_path = os.path.join(get_base_path(), 'monitor_data.db')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config['SCHEDULER_API_ENABLED'] = True
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
scheduler = APScheduler()
|
||||
|
||||
|
||||
# --- 模型定义 (保持不变) ---
|
||||
class MonitorRecord(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
source = db.Column(db.String(50))
|
||||
name = db.Column(db.String(100))
|
||||
status = db.Column(db.String(50))
|
||||
reason = db.Column(db.String(255))
|
||||
offset = db.Column(db.String(50))
|
||||
latest_time = db.Column(db.String(50))
|
||||
check_time = db.Column(db.String(50))
|
||||
content = db.Column(db.Text, nullable=True)
|
||||
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
# --- 爬虫配置 (保持不变) ---
|
||||
CONFIG = {
|
||||
"106": {
|
||||
"base_url": "http://106.75.72.40:7500/api/proxy/tcp",
|
||||
"primary_auth": "Basic YWRtaW46bGljYWhr",
|
||||
"login_payload": {"username": "admin", "password": "licahk", "recaptcha": ""}
|
||||
},
|
||||
"82": {
|
||||
"base_url": "http://82.156.1.111/weather/php",
|
||||
"login": {'username': 'renlixin', 'password': 'licahk', 'login': '123'}
|
||||
}
|
||||
}
|
||||
|
||||
is_running = False
|
||||
|
||||
|
||||
# --- 核心辅助函数 (保持不变) ---
|
||||
def calculate_offset(latest_time_str):
|
||||
if not latest_time_str or latest_time_str == "N/A":
|
||||
return "从未同步"
|
||||
try:
|
||||
clean_date_str = str(latest_time_str).split()[0].replace('_', '-')
|
||||
target_date = datetime.strptime(clean_date_str, "%Y-%m-%d").date()
|
||||
diff = (datetime.now().date() - target_date).days
|
||||
if diff == 0: return "当天已同步"
|
||||
return f"滞后 {diff} 天"
|
||||
except:
|
||||
return "时间解析失败"
|
||||
|
||||
|
||||
def save_record(source, name, status, reason, latest_time="N/A", content=None):
|
||||
record = MonitorRecord.query.filter_by(source=source, name=name).first()
|
||||
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
current_offset = calculate_offset(latest_time)
|
||||
|
||||
if record:
|
||||
if content is not None: record.content = content
|
||||
if latest_time != "N/A": record.latest_time = latest_time
|
||||
record.status = status
|
||||
record.reason = reason
|
||||
record.check_time = now_str
|
||||
time_base = latest_time if latest_time != "N/A" else record.latest_time
|
||||
record.offset = calculate_offset(time_base)
|
||||
else:
|
||||
new_record = MonitorRecord(
|
||||
source=source, name=name, status=status, reason=reason,
|
||||
offset=current_offset, latest_time=latest_time,
|
||||
check_time=now_str, content=content
|
||||
)
|
||||
db.session.add(new_record)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logging.error(f"DB Error: {e}")
|
||||
return f"{source}_{name}"
|
||||
|
||||
|
||||
# --- 业务逻辑函数 (保持不变) ---
|
||||
def get_106_dynamic_token(port):
|
||||
try:
|
||||
login_url = f"http://106.75.72.40:{port}/api/login"
|
||||
resp = requests.post(login_url, json=CONFIG["106"]["login_payload"], timeout=10)
|
||||
return resp.text.strip().replace('"', '') if resp.status_code == 200 else None
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def find_closest_item(items, is_date_level=True):
|
||||
if not items or not isinstance(items, list): return None
|
||||
today = datetime.now()
|
||||
scored_items = []
|
||||
for item in items:
|
||||
name_val = item.get('name', '')
|
||||
path_val = item.get('path', '')
|
||||
target_str = name_val if name_val else path_val.split('/')[-1]
|
||||
try:
|
||||
if is_date_level:
|
||||
current_date = datetime.strptime(target_str, "%Y_%m_%d")
|
||||
else:
|
||||
mod_str = item.get('modified', '')
|
||||
current_date = datetime.fromisoformat(mod_str.replace('Z', '+00:00'))
|
||||
diff = abs((today - current_date.replace(tzinfo=None)).total_seconds())
|
||||
scored_items.append((diff, item, target_str))
|
||||
except:
|
||||
continue
|
||||
if not scored_items: return None
|
||||
scored_items.sort(key=lambda x: x[0])
|
||||
return scored_items[0]
|
||||
|
||||
|
||||
def run_106_logic(active_set):
|
||||
# (保持原样,省略以节省空间,直接用你原本的逻辑即可)
|
||||
c = CONFIG["106"]
|
||||
today_str = datetime.now().strftime("%Y_%m_%d")
|
||||
main_headers = {"Authorization": c["primary_auth"], "User-Agent": "Mozilla/5.0"}
|
||||
try:
|
||||
resp = requests.get(c["base_url"], headers=main_headers, timeout=20)
|
||||
proxies = resp.json().get('proxies', [])
|
||||
for item in proxies:
|
||||
name = item.get('name', '')
|
||||
if not name.lower().endswith('_data'): continue
|
||||
if "TOWER" not in name.upper(): continue
|
||||
if str(item.get('status')).lower() != 'online':
|
||||
key = save_record("106网站", name, "离线", f"设备状态: {item.get('status')}")
|
||||
active_set.add(key)
|
||||
continue
|
||||
try:
|
||||
port = item.get('conf', {}).get('remote_port')
|
||||
token = get_106_dynamic_token(port)
|
||||
if not token:
|
||||
key = save_record("106网站", name, "异常", "Token获取失败")
|
||||
active_set.add(key)
|
||||
continue
|
||||
headers = {"Authorization": c["primary_auth"], "x-auth": token}
|
||||
api_root = "/api/resources/Data/" if "TOWER_" in name.upper() else "/api/resources/data/"
|
||||
res1 = requests.get(f"http://106.75.72.40:{port}{api_root}", headers=headers, timeout=10)
|
||||
best_date = find_closest_item(res1.json().get('items', []), True)
|
||||
if not best_date or best_date[2] != today_str:
|
||||
key = save_record("106网站", name, "正常", "未找到今日文件夹",
|
||||
latest_time=best_date[2] if best_date else "N/A")
|
||||
active_set.add(key)
|
||||
continue
|
||||
date_path = f"{api_root}{best_date[2]}/"
|
||||
res2 = requests.get(f"http://106.75.72.40:{port}{date_path}", headers=headers, timeout=10)
|
||||
best_file = find_closest_item(res2.json().get('items', []), False)
|
||||
if not best_file:
|
||||
key = save_record("106网站", name, "正常", "今日文件夹为空", latest_time=today_str)
|
||||
active_set.add(key)
|
||||
continue
|
||||
file_item = best_file[1]
|
||||
full_path = file_item.get('path') or f"{date_path}{file_item.get('name')}"
|
||||
is_tower_i = "TOWER" in name.upper() and "TOWER_" not in name.upper()
|
||||
if is_tower_i:
|
||||
download_url = f"http://106.75.72.40:{port}/api/raw{full_path}"
|
||||
res3 = requests.get(download_url, headers=headers, timeout=20)
|
||||
final_content = f"Binary Data Size: {len(res3.content)}"
|
||||
else:
|
||||
file_api_url = f"http://106.75.72.40:{port}/api/resources{full_path}"
|
||||
res3 = requests.get(file_api_url, headers=headers, timeout=20)
|
||||
final_content = res3.json().get('content', '')
|
||||
key = save_record("106网站", name, "正常", "同步成功", latest_time=today_str, content=final_content)
|
||||
active_set.add(key)
|
||||
except Exception as e:
|
||||
key = save_record("106网站", name, "异常", f"采集错误: {str(e)[:50]}")
|
||||
active_set.add(key)
|
||||
except Exception as e:
|
||||
logging.error(f"106 Global Error: {e}")
|
||||
|
||||
|
||||
def run_82_logic(active_set):
|
||||
# (保持原样,直接用你原本的逻辑即可)
|
||||
c = CONFIG["82"]
|
||||
session = requests.Session()
|
||||
try:
|
||||
session.post(f"{c['base_url']}/login.php", data=c["login"], timeout=10)
|
||||
resp = session.post(f"{c['base_url']}/GetStationList.php", timeout=10)
|
||||
stations = etree.HTML(resp.content).xpath('//option/@value')
|
||||
for sid in [s for s in stations if s]:
|
||||
try:
|
||||
r = session.post(f"{c['base_url']}/getLastWeatherData.php", data=str(sid),
|
||||
headers={'Content-Type': 'text/plain'}, timeout=10)
|
||||
data = r.json()
|
||||
if data:
|
||||
d_list = data.get('date', [])
|
||||
latest = str(d_list[-1]) if d_list else "N/A"
|
||||
key = save_record("82网站", sid, "正常", "同步成功", latest_time=latest,
|
||||
content=json.dumps(data, ensure_ascii=False))
|
||||
active_set.add(key)
|
||||
else:
|
||||
key = save_record("82网站", sid, "异常", "返回空数据")
|
||||
active_set.add(key)
|
||||
except:
|
||||
key = save_record("82网站", sid, "异常", "单个采集失败")
|
||||
active_set.add(key)
|
||||
except Exception as e:
|
||||
logging.error(f"82 Global Error: {e}")
|
||||
|
||||
|
||||
def execute_monitor_task():
|
||||
global is_running
|
||||
if is_running: return
|
||||
is_running = True
|
||||
logging.info("Starting monitor task...")
|
||||
with app.app_context():
|
||||
active_set = set()
|
||||
run_106_logic(active_set)
|
||||
run_82_logic(active_set)
|
||||
all_records = MonitorRecord.query.all()
|
||||
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
for record in all_records:
|
||||
if f"{record.source}_{record.name}" not in active_set:
|
||||
record.status = "已离线"
|
||||
record.reason = "设备本次未出现"
|
||||
record.check_time = now_str
|
||||
record.offset = calculate_offset(record.latest_time)
|
||||
try:
|
||||
db.session.commit()
|
||||
except:
|
||||
db.session.rollback()
|
||||
is_running = False
|
||||
logging.info("Monitor task finished.")
|
||||
|
||||
|
||||
# --- API 路由 (保持不变) ---
|
||||
@app.route('/api/run', methods=['POST'])
|
||||
def manual_start():
|
||||
if is_running: return jsonify({"status": "busy"}), 400
|
||||
threading.Thread(target=execute_monitor_task).start()
|
||||
return jsonify({"status": "started"})
|
||||
|
||||
|
||||
@app.route('/api/status')
|
||||
def status(): return jsonify({"is_running": is_running})
|
||||
|
||||
|
||||
@app.route('/api/logs')
|
||||
def logs():
|
||||
data = MonitorRecord.query.all()
|
||||
return jsonify([{
|
||||
"source": l.source, "name": l.name, "status": l.status,
|
||||
"reason": l.reason, "offset": l.offset, "latest_time": l.latest_time,
|
||||
"check_time": l.check_time, "content": l.content
|
||||
} for l in data])
|
||||
|
||||
|
||||
# --- 新增: 前端页面托管路由 ---
|
||||
@app.route('/')
|
||||
def serve_index():
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
|
||||
|
||||
@app.route('/<path:path>')
|
||||
def serve_static_files(path):
|
||||
# 尝试在 dist 目录寻找文件 (css, js, icons)
|
||||
file_path = os.path.join(app.static_folder, path)
|
||||
if os.path.exists(file_path):
|
||||
return send_from_directory(app.static_folder, path)
|
||||
# 如果找不到文件(例如刷新页面时的路由),返回index.html让Vue Router处理
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
|
||||
|
||||
# --- 调度器与启动 ---
|
||||
@scheduler.task('cron', id='daily_job', hour=10, minute=0)
|
||||
def auto_run_task():
|
||||
with app.app_context():
|
||||
threading.Thread(target=execute_monitor_task).start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
scheduler.init_app(app)
|
||||
scheduler.start()
|
||||
# Host='0.0.0.0' 允许外部IP访问
|
||||
# Port=5000 (确保 Windows 防火墙开放了此端口)
|
||||
print("应用正在启动... 请确保 dist 文件夹与脚本/exe 同级或已被打包")
|
||||
app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)
|
||||
Binary file not shown.
135
1.1/frps.py
135
1.1/frps.py
@ -1,135 +0,0 @@
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
# --- 配置保持不变 ---
|
||||
BASE_URL = "http://106.75.72.40:7500/api/proxy/tcp"
|
||||
PRIMARY_AUTH = "Basic YWRtaW46bGljYWhr"
|
||||
X_AUTH = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJsb2NhbGUiOiJ6aC1jbiIsInZpZXdNb2RlIjoibGlzdCIsInNpbmdsZUNsaWNrIjpmYWxzZSwicGVybSI6eyJhZG1pbiI6dHJ1ZSwiZXhlY3V0ZSI6dHJ1ZSwiY3JlYXRlIjp0cnVlLCJyZW5hbWUiOnRydWUsIm1vZGlmeSI6dHJ1ZSwiZGVsZXRlIjp0cnVlLCJzaGFyZSI6dHJ1ZSwiZG93bmxvYWQiOnRydWV9LCJjb21tYW5kcyI6W10sImxvY2tQYXNzd29yZCI6ZmFsc2UsImhpZGVEb3RmaWxlcyI6ZmFsc2V9LCJleHAiOjE3Njc2Njg3NzgsImlhdCI6MTc2NzY2MTU3OCwiaXNzIjoiRmlsZSBCcm93c2VyIn0.z9zycFSf3XpUDRhGjziUJ-PUeHIsRba23AI6itqXM-w"
|
||||
|
||||
headers = {
|
||||
"Authorization": PRIMARY_AUTH,
|
||||
"x-auth": X_AUTH,
|
||||
"User-Agent": "Mozilla/5.0"
|
||||
}
|
||||
|
||||
|
||||
def get_today_str():
|
||||
return datetime.now().strftime("%Y_%m_%d")
|
||||
|
||||
|
||||
def find_closest_item(items, is_date_level=True):
|
||||
if not items or not isinstance(items, list): return None
|
||||
today = datetime.now()
|
||||
scored_items = []
|
||||
for item in items:
|
||||
if not isinstance(item, dict): continue
|
||||
path = item.get('path', '')
|
||||
if not path: continue
|
||||
try:
|
||||
if is_date_level:
|
||||
date_str = path.split('/')[-1]
|
||||
current_date = datetime.strptime(date_str, "%Y_%m_%d")
|
||||
else:
|
||||
mod_str = item.get('modified', '')
|
||||
if mod_str:
|
||||
current_date = datetime.fromisoformat(mod_str.replace('Z', '+00:00'))
|
||||
else:
|
||||
continue
|
||||
diff = abs((today - current_date.replace(tzinfo=None)).total_seconds())
|
||||
scored_items.append((diff, item))
|
||||
except:
|
||||
continue
|
||||
|
||||
if not scored_items: return None
|
||||
scored_items.sort(key=lambda x: x[0])
|
||||
return scored_items[0][1]
|
||||
|
||||
|
||||
def debug_process_106():
|
||||
today_str = get_today_str()
|
||||
print(f"=== 开始 106 网站深度调试 (目标日期: {today_str}) ===")
|
||||
|
||||
try:
|
||||
resp = requests.get(BASE_URL, headers=headers, timeout=15)
|
||||
if resp.status_code != 200:
|
||||
print(f"❌ 错误: 主接口访问失败, 状态码: {resp.status_code}")
|
||||
return
|
||||
|
||||
proxies = resp.json().get('proxies', [])
|
||||
data_proxies = [p for p in proxies if p.get('name', '').endswith('_data')]
|
||||
|
||||
for item in data_proxies:
|
||||
name = item.get('name', 'Unknown')
|
||||
# 每一个站点的处理都包裹在独立的 try 里,防止相互干扰
|
||||
try:
|
||||
status = str(item.get('status', '')).lower().strip()
|
||||
conf = item.get('conf') or {}
|
||||
port = conf.get('remote_port')
|
||||
|
||||
print(f"--- [检查站点: {name}] ---")
|
||||
|
||||
if status != 'online':
|
||||
print(f" ⚠ 判定错误: 站点离线 ({status})")
|
||||
continue
|
||||
|
||||
if not port:
|
||||
print(f" ❌ 判定错误: 缺少端口配置")
|
||||
continue
|
||||
|
||||
# Data 目录请求
|
||||
res2 = requests.get(f"http://106.75.72.40:{port}/api/resources/Data/", headers=headers, timeout=10)
|
||||
if res2.status_code != 200:
|
||||
print(f" ❌ 判定错误: Data目录 HTTP {res2.status_code}")
|
||||
continue
|
||||
|
||||
it2 = res2.json().get('items', [])
|
||||
closest_date = find_closest_item(it2, True)
|
||||
if not closest_date:
|
||||
print(f" ❌ 判定错误: Data目录为空")
|
||||
continue
|
||||
|
||||
path_date = closest_date.get('path', '')
|
||||
date_val = path_date.split('/')[-1]
|
||||
|
||||
if date_val != today_str:
|
||||
print(f" ⚠ 判定错误: 日期不符 (最新: {date_val})")
|
||||
continue
|
||||
|
||||
# 文件列表请求
|
||||
res3 = requests.get(f"http://106.75.72.40:{port}/api/resources{path_date}/", headers=headers,
|
||||
timeout=10)
|
||||
it3 = res3.json().get('items', [])
|
||||
closest_file = find_closest_item(it3, False)
|
||||
if not closest_file:
|
||||
print(f" ❌ 判定错误: 日期文件夹内无文件")
|
||||
continue
|
||||
|
||||
# 文件内容请求 - 关键防御点
|
||||
path_csv = closest_file.get('path', '')
|
||||
res4 = requests.get(f"http://106.75.72.40:{port}/api/resources{path_csv}", headers=headers, timeout=10)
|
||||
|
||||
try:
|
||||
file_data = res4.json()
|
||||
if file_data is None:
|
||||
print(f" ❌ 判定错误: 接口返回了 Null (NoneType)")
|
||||
continue
|
||||
|
||||
content = file_data.get('content', '')
|
||||
if not content:
|
||||
print(f" ❌ 判定错误: content 字段为空")
|
||||
else:
|
||||
print(f" ✅ 检查通过: 数据最新,长度 {len(content)}")
|
||||
except Exception as json_e:
|
||||
print(f" ❌ 判定错误: 解析 JSON 失败 (非标准格式)")
|
||||
|
||||
except Exception as site_e:
|
||||
print(f" ❌ 判定错误: 站点逻辑崩溃: {site_e}")
|
||||
|
||||
except Exception as global_e:
|
||||
print(f"❌ 全局严重错误: {global_e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_process_106()
|
||||
@ -1,169 +0,0 @@
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
# --- 基础配置 ---
|
||||
BASE_URL = "http://106.75.72.40:7500/api/proxy/tcp"
|
||||
PRIMARY_AUTH = "Basic YWRtaW46bGljYWhr"
|
||||
LOGIN_PAYLOAD = {"username": "admin", "password": "licahk", "recaptcha": ""}
|
||||
|
||||
SAVE_DIR = "downloaded_data"
|
||||
if not os.path.exists(SAVE_DIR):
|
||||
os.makedirs(SAVE_DIR)
|
||||
|
||||
|
||||
def get_today_str():
|
||||
return datetime.now().strftime("%Y_%m_%d")
|
||||
|
||||
|
||||
def get_dynamic_token(port):
|
||||
"""
|
||||
为指定端口的站点执行登录,获取最新的 x-auth token
|
||||
"""
|
||||
login_url = f"http://106.75.72.40:{port}/api/login"
|
||||
try:
|
||||
# 登录不需要 x-auth,只需要 Basic Auth 或特定的 Payload
|
||||
resp = requests.post(login_url, json=LOGIN_PAYLOAD, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
# 登录成功后,token 通常直接返回在响应体中(纯字符串或 JSON)
|
||||
token = resp.text.strip().replace('"', '')
|
||||
return token
|
||||
else:
|
||||
print(f" ❌ 登录失败: 端口 {port}, 状态码 {resp.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f" ❌ 登录异常: {port}, {e}")
|
||||
return None
|
||||
|
||||
|
||||
def find_closest_item(items, is_date_level=True):
|
||||
if not items or not isinstance(items, list): return None
|
||||
today = datetime.now()
|
||||
scored_items = []
|
||||
today_str = get_today_str()
|
||||
|
||||
for item in items:
|
||||
name_val = item.get('name', '')
|
||||
path_val = item.get('path', '')
|
||||
target_str = name_val if name_val else path_val.split('/')[-1]
|
||||
|
||||
try:
|
||||
if is_date_level:
|
||||
current_date = datetime.strptime(target_str, "%Y_%m_%d")
|
||||
else:
|
||||
mod_str = item.get('modified', '')
|
||||
current_date = datetime.fromisoformat(mod_str.replace('Z', '+00:00'))
|
||||
|
||||
diff = abs((today - current_date.replace(tzinfo=None)).total_seconds())
|
||||
scored_items.append((diff, item, target_str))
|
||||
except:
|
||||
continue
|
||||
|
||||
if not scored_items: return None
|
||||
scored_items.sort(key=lambda x: x[0])
|
||||
return scored_items[0]
|
||||
|
||||
|
||||
def process_site(proxy_item):
|
||||
name = proxy_item.get('name', '')
|
||||
# 严格过滤:只处理以 _data 结尾的站点
|
||||
if not name.lower().endswith('_data'):
|
||||
return
|
||||
|
||||
name_upper = name.upper()
|
||||
is_tower_underscore = "TOWER_" in name_upper
|
||||
is_tower_i = "TOWER" in name_upper and not is_tower_underscore
|
||||
|
||||
if not (is_tower_underscore or is_tower_i):
|
||||
return
|
||||
|
||||
print(f"\n--- [正在处理站点: {name}] ---")
|
||||
try:
|
||||
port = proxy_item.get('conf', {}).get('remote_port')
|
||||
if not port: return
|
||||
|
||||
# 动态获取当前站点的 Token
|
||||
token = get_dynamic_token(port)
|
||||
if not token:
|
||||
print(f" 跳过: 无法获取有效的 x-auth")
|
||||
return
|
||||
|
||||
headers = {
|
||||
"Authorization": PRIMARY_AUTH,
|
||||
"x-auth": token,
|
||||
"User-Agent": "Mozilla/5.0"
|
||||
}
|
||||
today_str = get_today_str()
|
||||
|
||||
# Step 1: 进入 data 根目录 (路径根据类型区分大小写)
|
||||
api_root = "/api/resources/Data/" if is_tower_underscore else "/api/resources/data/"
|
||||
res1 = requests.get(f"http://106.75.72.40:{port}{api_root}", headers=headers, timeout=10)
|
||||
|
||||
# Step 2: 寻找并进入日期目录
|
||||
items1 = res1.json().get('items', [])
|
||||
best_date = find_closest_item(items1, is_date_level=True)
|
||||
if not best_date or best_date[2] != today_str:
|
||||
print(f" ⚠ 跳过: 未找到今日 ({today_str}) 的文件夹")
|
||||
return
|
||||
|
||||
date_path = f"{api_root}{best_date[2]}/"
|
||||
res2 = requests.get(f"http://106.75.72.40:{port}{date_path}", headers=headers, timeout=10)
|
||||
|
||||
# Step 3: 寻找日期目录下的最新文件
|
||||
items2 = res2.json().get('items', [])
|
||||
best_file = find_closest_item(items2, is_date_level=False)
|
||||
if not best_file:
|
||||
print(f" ❌ 日期文件夹内无文件")
|
||||
return
|
||||
|
||||
# 获取文件的完整 path
|
||||
file_item = best_file[1]
|
||||
full_path = file_item.get('path')
|
||||
if not full_path:
|
||||
full_path = f"{date_path}{file_item.get('name')}"
|
||||
|
||||
# --- 获取内容层级 (根据站点类型采用不同接口) ---
|
||||
if is_tower_i:
|
||||
# TowerI 模式:使用 /api/raw/{path} 获取二进制流
|
||||
download_url = f"http://106.75.72.40:{port}/api/raw{full_path}"
|
||||
res3 = requests.get(download_url, headers=headers, timeout=20, stream=True)
|
||||
if res3.status_code == 200:
|
||||
save_path = os.path.join(SAVE_DIR, f"{name}_{today_str}.bin")
|
||||
with open(save_path, 'wb') as f:
|
||||
f.write(res3.content)
|
||||
print(f" ✅ 二进制保存成功: {save_path} (大小: {len(res3.content)} 字节)")
|
||||
else:
|
||||
# Tower_ 模式:原来的 JSON content 模式
|
||||
file_api_url = f"http://106.75.72.40:{port}/api/resources{full_path}"
|
||||
res3 = requests.get(file_api_url, headers=headers, timeout=20)
|
||||
try:
|
||||
content_str = res3.json().get('content', '')
|
||||
if content_str:
|
||||
save_path = os.path.join(SAVE_DIR, f"{name}_{today_str}.json")
|
||||
with open(save_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content_str)
|
||||
print(f" ✅ JSON数据保存成功: {save_path}")
|
||||
except:
|
||||
print(f" ❌ TOWER_ 站点格式错误或无内容")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 站点处理失败: {str(e)}")
|
||||
|
||||
|
||||
def main():
|
||||
print(f"任务启动: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
# 主接口获取站点列表
|
||||
try:
|
||||
# 注意:主控制台可能也需要 token,这里先用硬编码,如果不行也需要为 7500 端口做登录
|
||||
main_headers = {"Authorization": PRIMARY_AUTH, "User-Agent": "Mozilla/5.0"}
|
||||
resp = requests.get(BASE_URL, headers=main_headers, timeout=15)
|
||||
proxies = resp.json().get('proxies', [])
|
||||
for p in proxies:
|
||||
process_site(p)
|
||||
except Exception as e:
|
||||
print(f"❌ 全局错误: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
327
1.1/test1.py
327
1.1/test1.py
@ -1,327 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import threading
|
||||
import requests
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from flask import Flask, jsonify, send_from_directory
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_cors import CORS
|
||||
from flask_apscheduler import APScheduler
|
||||
from lxml import etree
|
||||
|
||||
# --- 配置日志 ---
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
|
||||
# --- 关键路径处理函数 (适配 PyInstaller) ---
|
||||
def get_base_path():
|
||||
"""获取运行时及其所在目录,适配开发环境和打包后的EXE环境"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
# 如果是打包后的 exe,sys.executable 是 exe 的路径
|
||||
return os.path.dirname(sys.executable)
|
||||
# 开发环境下,是当前脚本的路径
|
||||
return os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def get_static_path():
|
||||
"""获取 Vue 静态资源 dist 的路径"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
# PyInstaller 打包时,资源文件会被解压到 sys._MEIPASS 临时目录
|
||||
# 我们需要在打包命令中指定 --add-data "dist;dist"
|
||||
return os.path.join(sys._MEIPASS, 'dist')
|
||||
# 开发环境
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dist')
|
||||
|
||||
|
||||
# --- Flask 初始化 ---
|
||||
# static_folder 指向 Vue 打包后的 dist 目录
|
||||
# static_url_path='' 表示静态文件不需要 /static 前缀
|
||||
dist_folder = get_static_path()
|
||||
app = Flask(__name__, static_folder=dist_folder, static_url_path='')
|
||||
CORS(app)
|
||||
|
||||
# --- 数据库配置 ---
|
||||
# 确保数据库生成在 exe 同级目录下,而不是临时文件夹中
|
||||
db_path = os.path.join(get_base_path(), 'monitor_data.db')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config['SCHEDULER_API_ENABLED'] = True
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
scheduler = APScheduler()
|
||||
|
||||
|
||||
# --- 模型定义 (保持不变) ---
|
||||
class MonitorRecord(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
source = db.Column(db.String(50))
|
||||
name = db.Column(db.String(100))
|
||||
status = db.Column(db.String(50))
|
||||
reason = db.Column(db.String(255))
|
||||
offset = db.Column(db.String(50))
|
||||
latest_time = db.Column(db.String(50))
|
||||
check_time = db.Column(db.String(50))
|
||||
content = db.Column(db.Text, nullable=True)
|
||||
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
# --- 爬虫配置 (保持不变) ---
|
||||
CONFIG = {
|
||||
"106": {
|
||||
"base_url": "http://106.75.72.40:7500/api/proxy/tcp",
|
||||
"primary_auth": "Basic YWRtaW46bGljYWhr",
|
||||
"login_payload": {"username": "admin", "password": "licahk", "recaptcha": ""}
|
||||
},
|
||||
"82": {
|
||||
"base_url": "http://82.156.1.111/weather/php",
|
||||
"login": {'username': 'renlixin', 'password': 'licahk', 'login': '123'}
|
||||
}
|
||||
}
|
||||
|
||||
is_running = False
|
||||
|
||||
|
||||
# --- 核心辅助函数 (保持不变) ---
|
||||
def calculate_offset(latest_time_str):
|
||||
if not latest_time_str or latest_time_str == "N/A":
|
||||
return "从未同步"
|
||||
try:
|
||||
clean_date_str = str(latest_time_str).split()[0].replace('_', '-')
|
||||
target_date = datetime.strptime(clean_date_str, "%Y-%m-%d").date()
|
||||
diff = (datetime.now().date() - target_date).days
|
||||
if diff == 0: return "当天已同步"
|
||||
return f"滞后 {diff} 天"
|
||||
except:
|
||||
return "时间解析失败"
|
||||
|
||||
|
||||
def save_record(source, name, status, reason, latest_time="N/A", content=None):
|
||||
record = MonitorRecord.query.filter_by(source=source, name=name).first()
|
||||
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
current_offset = calculate_offset(latest_time)
|
||||
|
||||
if record:
|
||||
if content is not None: record.content = content
|
||||
if latest_time != "N/A": record.latest_time = latest_time
|
||||
record.status = status
|
||||
record.reason = reason
|
||||
record.check_time = now_str
|
||||
time_base = latest_time if latest_time != "N/A" else record.latest_time
|
||||
record.offset = calculate_offset(time_base)
|
||||
else:
|
||||
new_record = MonitorRecord(
|
||||
source=source, name=name, status=status, reason=reason,
|
||||
offset=current_offset, latest_time=latest_time,
|
||||
check_time=now_str, content=content
|
||||
)
|
||||
db.session.add(new_record)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logging.error(f"DB Error: {e}")
|
||||
return f"{source}_{name}"
|
||||
|
||||
|
||||
# --- 业务逻辑函数 (保持不变) ---
|
||||
def get_106_dynamic_token(port):
|
||||
try:
|
||||
login_url = f"http://106.75.72.40:{port}/api/login"
|
||||
resp = requests.post(login_url, json=CONFIG["106"]["login_payload"], timeout=10)
|
||||
return resp.text.strip().replace('"', '') if resp.status_code == 200 else None
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def find_closest_item(items, is_date_level=True):
|
||||
if not items or not isinstance(items, list): return None
|
||||
today = datetime.now()
|
||||
scored_items = []
|
||||
for item in items:
|
||||
name_val = item.get('name', '')
|
||||
path_val = item.get('path', '')
|
||||
target_str = name_val if name_val else path_val.split('/')[-1]
|
||||
try:
|
||||
if is_date_level:
|
||||
current_date = datetime.strptime(target_str, "%Y_%m_%d")
|
||||
else:
|
||||
mod_str = item.get('modified', '')
|
||||
current_date = datetime.fromisoformat(mod_str.replace('Z', '+00:00'))
|
||||
diff = abs((today - current_date.replace(tzinfo=None)).total_seconds())
|
||||
scored_items.append((diff, item, target_str))
|
||||
except:
|
||||
continue
|
||||
if not scored_items: return None
|
||||
scored_items.sort(key=lambda x: x[0])
|
||||
return scored_items[0]
|
||||
|
||||
|
||||
def run_106_logic(active_set):
|
||||
# (保持原样,省略以节省空间,直接用你原本的逻辑即可)
|
||||
c = CONFIG["106"]
|
||||
today_str = datetime.now().strftime("%Y_%m_%d")
|
||||
main_headers = {"Authorization": c["primary_auth"], "User-Agent": "Mozilla/5.0"}
|
||||
try:
|
||||
resp = requests.get(c["base_url"], headers=main_headers, timeout=20)
|
||||
proxies = resp.json().get('proxies', [])
|
||||
for item in proxies:
|
||||
name = item.get('name', '')
|
||||
if not name.lower().endswith('_data'): continue
|
||||
if "TOWER" not in name.upper(): continue
|
||||
if str(item.get('status')).lower() != 'online':
|
||||
key = save_record("106网站", name, "离线", f"设备状态: {item.get('status')}")
|
||||
active_set.add(key)
|
||||
continue
|
||||
try:
|
||||
port = item.get('conf', {}).get('remote_port')
|
||||
token = get_106_dynamic_token(port)
|
||||
if not token:
|
||||
key = save_record("106网站", name, "异常", "Token获取失败")
|
||||
active_set.add(key)
|
||||
continue
|
||||
headers = {"Authorization": c["primary_auth"], "x-auth": token}
|
||||
api_root = "/api/resources/Data/" if "TOWER_" in name.upper() else "/api/resources/data/"
|
||||
res1 = requests.get(f"http://106.75.72.40:{port}{api_root}", headers=headers, timeout=10)
|
||||
best_date = find_closest_item(res1.json().get('items', []), True)
|
||||
if not best_date or best_date[2] != today_str:
|
||||
key = save_record("106网站", name, "正常", "未找到今日文件夹",
|
||||
latest_time=best_date[2] if best_date else "N/A")
|
||||
active_set.add(key)
|
||||
continue
|
||||
date_path = f"{api_root}{best_date[2]}/"
|
||||
res2 = requests.get(f"http://106.75.72.40:{port}{date_path}", headers=headers, timeout=10)
|
||||
best_file = find_closest_item(res2.json().get('items', []), False)
|
||||
if not best_file:
|
||||
key = save_record("106网站", name, "正常", "今日文件夹为空", latest_time=today_str)
|
||||
active_set.add(key)
|
||||
continue
|
||||
file_item = best_file[1]
|
||||
full_path = file_item.get('path') or f"{date_path}{file_item.get('name')}"
|
||||
is_tower_i = "TOWER" in name.upper() and "TOWER_" not in name.upper()
|
||||
if is_tower_i:
|
||||
download_url = f"http://106.75.72.40:{port}/api/raw{full_path}"
|
||||
res3 = requests.get(download_url, headers=headers, timeout=20)
|
||||
final_content = f"Binary Data Size: {len(res3.content)}"
|
||||
else:
|
||||
file_api_url = f"http://106.75.72.40:{port}/api/resources{full_path}"
|
||||
res3 = requests.get(file_api_url, headers=headers, timeout=20)
|
||||
final_content = res3.json().get('content', '')
|
||||
key = save_record("106网站", name, "正常", "同步成功", latest_time=today_str, content=final_content)
|
||||
active_set.add(key)
|
||||
except Exception as e:
|
||||
key = save_record("106网站", name, "异常", f"采集错误: {str(e)[:50]}")
|
||||
active_set.add(key)
|
||||
except Exception as e:
|
||||
logging.error(f"106 Global Error: {e}")
|
||||
|
||||
|
||||
def run_82_logic(active_set):
|
||||
# (保持原样,直接用你原本的逻辑即可)
|
||||
c = CONFIG["82"]
|
||||
session = requests.Session()
|
||||
try:
|
||||
session.post(f"{c['base_url']}/login.php", data=c["login"], timeout=10)
|
||||
resp = session.post(f"{c['base_url']}/GetStationList.php", timeout=10)
|
||||
stations = etree.HTML(resp.content).xpath('//option/@value')
|
||||
for sid in [s for s in stations if s]:
|
||||
try:
|
||||
r = session.post(f"{c['base_url']}/getLastWeatherData.php", data=str(sid),
|
||||
headers={'Content-Type': 'text/plain'}, timeout=10)
|
||||
data = r.json()
|
||||
if data:
|
||||
d_list = data.get('date', [])
|
||||
latest = str(d_list[-1]) if d_list else "N/A"
|
||||
key = save_record("82网站", sid, "正常", "同步成功", latest_time=latest,
|
||||
content=json.dumps(data, ensure_ascii=False))
|
||||
active_set.add(key)
|
||||
else:
|
||||
key = save_record("82网站", sid, "异常", "返回空数据")
|
||||
active_set.add(key)
|
||||
except:
|
||||
key = save_record("82网站", sid, "异常", "单个采集失败")
|
||||
active_set.add(key)
|
||||
except Exception as e:
|
||||
logging.error(f"82 Global Error: {e}")
|
||||
|
||||
|
||||
def execute_monitor_task():
|
||||
global is_running
|
||||
if is_running: return
|
||||
is_running = True
|
||||
logging.info("Starting monitor task...")
|
||||
with app.app_context():
|
||||
active_set = set()
|
||||
run_106_logic(active_set)
|
||||
run_82_logic(active_set)
|
||||
all_records = MonitorRecord.query.all()
|
||||
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
for record in all_records:
|
||||
if f"{record.source}_{record.name}" not in active_set:
|
||||
record.status = "已离线"
|
||||
record.reason = "设备本次未出现"
|
||||
record.check_time = now_str
|
||||
record.offset = calculate_offset(record.latest_time)
|
||||
try:
|
||||
db.session.commit()
|
||||
except:
|
||||
db.session.rollback()
|
||||
is_running = False
|
||||
logging.info("Monitor task finished.")
|
||||
|
||||
|
||||
# --- API 路由 (保持不变) ---
|
||||
@app.route('/api/run', methods=['POST'])
|
||||
def manual_start():
|
||||
if is_running: return jsonify({"status": "busy"}), 400
|
||||
threading.Thread(target=execute_monitor_task).start()
|
||||
return jsonify({"status": "started"})
|
||||
|
||||
|
||||
@app.route('/api/status')
|
||||
def status(): return jsonify({"is_running": is_running})
|
||||
|
||||
|
||||
@app.route('/api/logs')
|
||||
def logs():
|
||||
data = MonitorRecord.query.all()
|
||||
return jsonify([{
|
||||
"source": l.source, "name": l.name, "status": l.status,
|
||||
"reason": l.reason, "offset": l.offset, "latest_time": l.latest_time,
|
||||
"check_time": l.check_time, "content": l.content
|
||||
} for l in data])
|
||||
|
||||
|
||||
# --- 新增: 前端页面托管路由 ---
|
||||
@app.route('/')
|
||||
def serve_index():
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
|
||||
|
||||
@app.route('/<path:path>')
|
||||
def serve_static_files(path):
|
||||
# 尝试在 dist 目录寻找文件 (css, js, icons)
|
||||
file_path = os.path.join(app.static_folder, path)
|
||||
if os.path.exists(file_path):
|
||||
return send_from_directory(app.static_folder, path)
|
||||
# 如果找不到文件(例如刷新页面时的路由),返回index.html让Vue Router处理
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
|
||||
|
||||
# --- 调度器与启动 ---
|
||||
@scheduler.task('cron', id='daily_job', hour=10, minute=0)
|
||||
def auto_run_task():
|
||||
with app.app_context():
|
||||
threading.Thread(target=execute_monitor_task).start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
scheduler.init_app(app)
|
||||
scheduler.start()
|
||||
# Host='0.0.0.0' 允许外部IP访问
|
||||
# Port=5000 (确保 Windows 防火墙开放了此端口)
|
||||
print("应用正在启动... 请确保 dist 文件夹与脚本/exe 同级或已被打包")
|
||||
app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)
|
||||
@ -1,121 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
from lxml import etree
|
||||
from datetime import datetime
|
||||
|
||||
# --- 配置区 ---
|
||||
BASE_URL = "http://82.156.1.111/weather/php"
|
||||
LOGIN_DATA = {'username': 'renlixin', 'password': 'licahk', 'login': '123'}
|
||||
OUTPUT_DIR = "weather_data_test"
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
|
||||
def fetch_stations():
|
||||
resp = session.post(f"{BASE_URL}/GetStationList.php")
|
||||
if resp.status_code != 200: return []
|
||||
tree = etree.HTML(resp.content)
|
||||
stations = [s for s in tree.xpath('//option/@value') if s and str(s).strip()]
|
||||
return stations
|
||||
|
||||
|
||||
def get_latest_data_item(station_id, data_list):
|
||||
"""
|
||||
核心改进:从列表中筛选出日期距离今天最近的一条数据
|
||||
"""
|
||||
if not data_list or not isinstance(data_list, list):
|
||||
return None
|
||||
|
||||
today = datetime.now().date()
|
||||
parsed_items = []
|
||||
|
||||
for item in data_list:
|
||||
try:
|
||||
# 兼容处理:如果是字符串列表,直接解析;如果是对象列表,取特定键
|
||||
date_str = item.split()[0] if isinstance(item, str) else item.get('date', '').split()[0]
|
||||
current_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
|
||||
# 计算与今天的差异(天数绝对值)
|
||||
diff = abs((today - current_date).days)
|
||||
parsed_items.append({
|
||||
'diff': diff,
|
||||
'date_obj': current_date,
|
||||
'original_data': item
|
||||
})
|
||||
except:
|
||||
continue
|
||||
|
||||
if not parsed_items:
|
||||
return None
|
||||
|
||||
# --- 逻辑更新:按时间差异升序排序(diff越小说明离今天越近) ---
|
||||
parsed_items.sort(key=lambda x: x['diff'])
|
||||
|
||||
# 返回距离最近的那一条原始数据
|
||||
return parsed_items[0]
|
||||
|
||||
|
||||
def save_json(name, data):
|
||||
path = os.path.join(OUTPUT_DIR, f"{name}.json")
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
def main():
|
||||
print("正在登录...")
|
||||
session.post(f"{BASE_URL}/login.php", data=LOGIN_DATA)
|
||||
|
||||
stations = fetch_stations()
|
||||
print(f"成功获取 {len(stations)} 个有效站点")
|
||||
|
||||
for i, sid in enumerate(stations, 1):
|
||||
print(f"[{i}/{len(stations)}] 处理站点: {sid}")
|
||||
try:
|
||||
resp = session.post(f"{BASE_URL}/getLastWeatherData.php",
|
||||
data=str(sid),
|
||||
headers={'Content-Type': 'text/plain'})
|
||||
|
||||
if resp.status_code == 200:
|
||||
full_data = resp.json()
|
||||
|
||||
# 假设 API 返回的 JSON 中 'date' 键对应的是数据列表
|
||||
# 我们调用 get_latest_data_item 来锁定那唯一的一条最新数据
|
||||
data_key = 'date' if 'date' in full_data else 'items' # 根据实际键名调整
|
||||
target_list = full_data.get(data_key, [])
|
||||
|
||||
latest_result = get_latest_data_item(sid, target_list)
|
||||
|
||||
if latest_result:
|
||||
# 重新构造保存内容:只保留最接近今天的数据
|
||||
final_payload = {
|
||||
"proxy_name": sid,
|
||||
"status": "online",
|
||||
"latest_date": str(latest_result['date_obj']),
|
||||
"days_diff": latest_result['diff'],
|
||||
"data_content": latest_result['original_data']
|
||||
}
|
||||
|
||||
# 如果不是今天,输出警告
|
||||
if latest_result['diff'] != 0:
|
||||
print(f" ⚠️ 非当天数据: {latest_result['date_obj']} (差 {latest_result['diff']} 天)")
|
||||
else:
|
||||
print(f" ✨ 数据已同步至今天")
|
||||
|
||||
save_json(sid, final_payload)
|
||||
else:
|
||||
print(f" ⚪ 站点 {sid} 未找到有效日期数据")
|
||||
|
||||
else:
|
||||
print(f" ❌ 请求失败: {resp.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 错误: {e}")
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
291
1.1/整合.py
291
1.1/整合.py
@ -1,291 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import pandas as pd
|
||||
from lxml import etree
|
||||
from datetime import datetime
|
||||
|
||||
# --- 基础配置 ---
|
||||
DATA_ROOT = "data"
|
||||
FRPS_DIR = os.path.join(DATA_ROOT, "frps_106")
|
||||
WEATHER_DIR = os.path.join(DATA_ROOT, "weather_82")
|
||||
EXCEL_PATH = os.path.join(DATA_ROOT, "error_report.xlsx")
|
||||
|
||||
for d in [FRPS_DIR, WEATHER_DIR]:
|
||||
os.makedirs(d, exist_ok=True)
|
||||
|
||||
CONFIG = {
|
||||
"106": {
|
||||
"base_url": "http://106.75.72.40:7500/api/proxy/tcp",
|
||||
"primary_auth": "Basic YWRtaW46bGljYWhr",
|
||||
"login_payload": {"username": "admin", "password": "licahk", "recaptcha": ""}
|
||||
},
|
||||
"82": {
|
||||
"base_url": "http://82.156.1.111/weather/php",
|
||||
"login": {'username': 'renlixin', 'password': 'licahk', 'login': '123'}
|
||||
}
|
||||
}
|
||||
|
||||
error_logs = []
|
||||
|
||||
|
||||
# --- 通用工具函数 ---
|
||||
|
||||
def add_error(source, name, reason, latest_time="N/A"):
|
||||
"""记录错误并计算日期差"""
|
||||
days_diff = "N/A"
|
||||
if latest_time and latest_time != "N/A":
|
||||
try:
|
||||
clean_date_str = str(latest_time).split()[0].replace('_', '-')
|
||||
target_date = datetime.strptime(clean_date_str, "%Y-%m-%d").date()
|
||||
today_date = datetime.now().date()
|
||||
diff = (today_date - target_date).days
|
||||
days_diff = f"滞后 {diff} 天" if diff > 0 else "当天已同步"
|
||||
except:
|
||||
days_diff = "解析失败"
|
||||
|
||||
error_logs.append({
|
||||
"数据来源": source,
|
||||
"站点/代理名称": name,
|
||||
"错误原因": reason,
|
||||
"日期偏移量": days_diff,
|
||||
"最新数据时间": latest_time,
|
||||
"检查时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
})
|
||||
|
||||
|
||||
def find_closest_item(items, is_date_level=True):
|
||||
"""寻找日期最接近今天的那一项"""
|
||||
if not items or not isinstance(items, list): return None
|
||||
today = datetime.now()
|
||||
scored_items = []
|
||||
for item in items:
|
||||
if not isinstance(item, dict): continue
|
||||
name_val = item.get('name', '')
|
||||
path_val = item.get('path', '')
|
||||
target_str = name_val if name_val else path_val.split('/')[-1]
|
||||
try:
|
||||
if is_date_level:
|
||||
current_date = datetime.strptime(target_str, "%Y_%m_%d")
|
||||
else:
|
||||
mod_str = item.get('modified', '')
|
||||
if mod_str:
|
||||
current_date = datetime.fromisoformat(mod_str.replace('Z', '+00:00'))
|
||||
else:
|
||||
continue
|
||||
diff = abs((today - current_date.replace(tzinfo=None)).total_seconds())
|
||||
scored_items.append((diff, item, target_str))
|
||||
except:
|
||||
continue
|
||||
if not scored_items: return None
|
||||
scored_items.sort(key=lambda x: x[0])
|
||||
return scored_items[0]
|
||||
|
||||
|
||||
def process_text_content(raw_content):
|
||||
if not raw_content: return ""
|
||||
lines = str(raw_content).split('\n')
|
||||
result, current = [], ""
|
||||
for line in lines:
|
||||
if " " in line:
|
||||
current += line.strip()
|
||||
else:
|
||||
if current: result.append(current)
|
||||
current = line.strip()
|
||||
if current: result.append(current)
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def save_json(folder, name, data):
|
||||
path = os.path.join(folder, f"{name}.json")
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
|
||||
# --- 106 网站逻辑 ---
|
||||
|
||||
def get_106_dynamic_token(port):
|
||||
"""为106特定端口站点执行登录获取 Token"""
|
||||
url = f"http://106.75.72.40:{port}/api/login"
|
||||
try:
|
||||
resp = requests.post(url, json=CONFIG["106"]["login_payload"], timeout=10)
|
||||
if resp.status_code == 200:
|
||||
return resp.text.strip().replace('"', '')
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def run_106_logic():
|
||||
print("\n>>> 开始处理 106 网站 (FRPS - 修正逻辑)...")
|
||||
c = CONFIG["106"]
|
||||
today_str = datetime.now().strftime("%Y_%m_%d")
|
||||
|
||||
try:
|
||||
main_headers = {"Authorization": c["primary_auth"], "User-Agent": "Mozilla/5.0"}
|
||||
resp = requests.get(c["base_url"], headers=main_headers, timeout=15)
|
||||
if resp.status_code != 200:
|
||||
add_error("106网站", "主入口API", f"访问失败: HTTP {resp.status_code}")
|
||||
return
|
||||
|
||||
proxies = resp.json().get('proxies', [])
|
||||
for item in proxies:
|
||||
if not isinstance(item, dict): continue
|
||||
name = item.get('name', 'Unknown')
|
||||
if not name.lower().endswith('_data'): continue
|
||||
|
||||
# 1. 状态预检 - 离线拦截逻辑
|
||||
status_raw = item.get('status', '')
|
||||
status = str(status_raw).lower().strip() if status_raw else "unknown"
|
||||
|
||||
# 如果离线,直接保存并记录错误,不再进行后续 API 访问
|
||||
if status != 'online':
|
||||
add_error("106网站", name, f"设备离线 (当前状态: {status})")
|
||||
save_json(FRPS_DIR, name, item)
|
||||
continue
|
||||
|
||||
# 2. 识别站点类型并进行在线处理
|
||||
name_up = name.upper()
|
||||
is_tower_underscore = "TOWER_" in name_up
|
||||
is_tower_i = "TOWER" in name_up and not is_tower_underscore
|
||||
if not (is_tower_underscore or is_tower_i): continue
|
||||
|
||||
try:
|
||||
conf = item.get('conf') or {}
|
||||
port = conf.get('remote_port')
|
||||
if not port:
|
||||
add_error("106网站", name, "配置错误: 缺少 remote_port")
|
||||
continue
|
||||
|
||||
# 只有 Online 才获取子站点 Token
|
||||
token = get_106_dynamic_token(port)
|
||||
if not token:
|
||||
add_error("106网站", name, "Token获取失败(登录异常)")
|
||||
continue
|
||||
|
||||
headers = {"Authorization": c["primary_auth"], "x-auth": token, "User-Agent": "Mozilla/5.0"}
|
||||
|
||||
# 查找 Data 根目录 (根据类型区分大小写)
|
||||
api_root = "/api/resources/Data/" if is_tower_underscore else "/api/resources/data/"
|
||||
res2 = requests.get(f"http://106.75.72.40:{port}{api_root}", headers=headers, timeout=10)
|
||||
if res2.status_code != 200:
|
||||
add_error("106网站", name, f"无法打开Data目录 (HTTP {res2.status_code})")
|
||||
continue
|
||||
|
||||
it2 = res2.json().get('items', [])
|
||||
best_date = find_closest_item(it2, is_date_level=True)
|
||||
if not best_date or best_date[2] != today_str:
|
||||
add_error("106网站", name, "未找到今日文件夹", best_date[2] if best_date else "N/A")
|
||||
if not best_date: continue
|
||||
|
||||
date_path = f"{api_root}{best_date[2]}/"
|
||||
|
||||
# 查找文件夹内最新文件
|
||||
res3 = requests.get(f"http://106.75.72.40:{port}{date_path}", headers=headers, timeout=10)
|
||||
it3 = res3.json().get('items', [])
|
||||
best_file = find_closest_item(it3, is_date_level=False)
|
||||
if not best_file:
|
||||
add_error("106网站", name, "文件夹内无文件", best_date[2])
|
||||
continue
|
||||
|
||||
file_item = best_file[1]
|
||||
full_path = file_item.get('path') or f"{date_path}{file_item.get('name')}"
|
||||
|
||||
# 3. 根据类型下载内容
|
||||
if is_tower_i:
|
||||
# TowerI 模式:使用 raw 接口获取二进制
|
||||
raw_url = f"http://106.75.72.40:{port}/api/raw{full_path}"
|
||||
res4 = requests.get(raw_url, headers=headers, timeout=20)
|
||||
if res4.status_code == 200:
|
||||
save_path = os.path.join(FRPS_DIR, f"{name}_{today_str}.bin")
|
||||
with open(save_path, 'wb') as f:
|
||||
f.write(res4.content)
|
||||
print(f" ✅ {name} 二进制数据保存成功")
|
||||
else:
|
||||
# Tower_ 模式:使用 resources 接口获取 JSON
|
||||
file_api_url = f"http://106.75.72.40:{port}/api/resources{full_path}"
|
||||
res4 = requests.get(file_api_url, headers=headers, timeout=20)
|
||||
file_json = res4.json()
|
||||
raw_content = file_json.get('content', '') if file_json else None
|
||||
if raw_content:
|
||||
save_path = os.path.join(FRPS_DIR, f"{name}_{today_str}.json")
|
||||
with open(save_path, 'w', encoding='utf-8') as f:
|
||||
f.write(process_text_content(raw_content))
|
||||
print(f" ✅ {name} JSON数据保存成功")
|
||||
else:
|
||||
add_error("106网站", name, "文件内容为空", best_date[2])
|
||||
|
||||
except Exception as e:
|
||||
add_error("106网站", name, f"站点处理崩溃: {str(e)}")
|
||||
|
||||
except Exception as e:
|
||||
add_error("106网站", "全局逻辑", f"主进程崩溃: {str(e)}")
|
||||
|
||||
|
||||
# --- 82 网站逻辑 (保持原样) ---
|
||||
|
||||
def run_82_logic():
|
||||
print("\n>>> 开始处理 82 网站 (Weather)...")
|
||||
c = CONFIG["82"]
|
||||
session = requests.Session()
|
||||
today_fmt = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
try:
|
||||
session.post(f"{c['base_url']}/login.php", data=c["login"], timeout=10)
|
||||
resp = session.post(f"{c['base_url']}/GetStationList.php", timeout=10)
|
||||
if resp.status_code != 200:
|
||||
add_error("82网站", "登录模块", f"无法获取列表: HTTP {resp.status_code}")
|
||||
return
|
||||
|
||||
stations = etree.HTML(resp.content).xpath('//option/@value')
|
||||
stations = [s for s in stations if s and str(s).strip()]
|
||||
|
||||
for sid in stations:
|
||||
try:
|
||||
r = session.post(f"{c['base_url']}/getLastWeatherData.php", data=str(sid),
|
||||
headers={'Content-Type': 'text/plain'}, timeout=10)
|
||||
if r.status_code != 200:
|
||||
add_error("82网站", sid, f"请求失败: HTTP {r.status_code}")
|
||||
continue
|
||||
|
||||
data = r.json()
|
||||
if data is None:
|
||||
add_error("82网站", sid, "返回 Null 数据")
|
||||
continue
|
||||
|
||||
d_list = data.get('date', [])
|
||||
if not d_list:
|
||||
add_error("82网站", sid, "返回结果中无日期列表")
|
||||
else:
|
||||
latest = str(d_list[-1])
|
||||
if latest.split()[0] != today_fmt:
|
||||
add_error("82网站", sid, "数据非当天更新", latest)
|
||||
|
||||
save_json(WEATHER_DIR, sid, data)
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
add_error("82网站", sid, f"数据解析异常: {str(e)}")
|
||||
except Exception as e:
|
||||
add_error("82网站", "初始化模块", str(e))
|
||||
|
||||
|
||||
# --- 汇总导出 ---
|
||||
|
||||
def export_to_excel():
|
||||
if not error_logs:
|
||||
print("\n[✔] 未发现错误记录。")
|
||||
return
|
||||
|
||||
df = pd.DataFrame(error_logs)
|
||||
cols = ["数据来源", "站点/代理名称", "错误原因", "日期偏移量", "最新数据时间", "检查时间"]
|
||||
df = df[[c for c in cols if c in df.columns]]
|
||||
df.to_excel(EXCEL_PATH, index=False)
|
||||
print(f"\n[!] 错误报表已生成至: {EXCEL_PATH} (共 {len(error_logs)} 条)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_106_logic()
|
||||
run_82_logic()
|
||||
export_to_excel()
|
||||
print("\n任务全部完成。")
|
||||
8
zhandianxinxi/.idea/.gitignore
generated
vendored
8
zhandianxinxi/.idea/.gitignore
generated
vendored
@ -1,8 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
9
zhandianxinxi/.idea/misc.xml
generated
9
zhandianxinxi/.idea/misc.xml
generated
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (zhandianxinxi)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
zhandianxinxi/.idea/modules.xml
generated
8
zhandianxinxi/.idea/modules.xml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/zhandianxinxi.iml" filepath="$PROJECT_DIR$/zhandianxinxi.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
zhandianxinxi/.idea/vcs.xml
generated
6
zhandianxinxi/.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.12 (zhandianxinxi)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Reference in New Issue
Block a user