Files
ZDXX/2_1banben/services/crawler_82.py
DXC 51714a7883 fix: 82 爬虫上游时间为 UTC,+8 小时转北京时间
上游 82 网站返回的 date 数组是 UTC,直接入库会让这批数字命名的设备时间比
实际早 8 小时。

实证(本地库):
  这批设备的 check_time 是 2026-02-06 17:11:10(那轮采集完成时刻),
  而上游原值落在 09:07:02 ~ 09:10:30,+8h 后正好是 17:07 ~ 17:10 —— 即采集
  完成前一分钟。若原值已是北京时间,则意味着这批站点在采集前 8 小时就集体
  停止上报、且整齐划一,不合常理。

- 解析后 +timedelta(hours=8)。中国无夏令时,固定偏移与 pytz 转换等价。
- 清洗 T / Z / 毫秒后缀,兼容 ISO 格式。
- 解析失败原样兜底 —— 有值但格式不认识,原样透传给入库层的 staleness_of
  再解析,与之前“拿不到时间必须为 None”的约定不冲突。
- 跨日正确:16:00 UTC 顺延至次日 00:00。

注意:本次只改后端源码,需重新打包 exe 才会在服务器生效。
2026-09-16 09:08:05 +08:00

86 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# services/crawler_82.py
import requests
import json
import logging
from lxml import etree
from config import Config
from datetime import datetime, timedelta
CONFIG = Config.CRAWLER_CONFIG["82"]
def run_82_logic():
"""返回 result_list"""
results = []
print(">>> [82爬虫] 启动...")
session = requests.Session()
try:
session.post(f"{CONFIG['base_url']}/login.php", data=CONFIG["login"], timeout=10)
resp = session.post(f"{CONFIG['base_url']}/GetStationList.php", timeout=10)
html = etree.HTML(resp.content)
if html is None: return []
stations = html.xpath('//option/@value')
for sid in [s for s in stations if s]:
data_packet = {
'source': '82网站',
'name': str(sid),
'status': '正常',
'value': '',
# 同 106:默认留空,只有真正拿到数据时间才覆盖,
# 不用 datetime.now() 冒充数据时间
'target_time': None,
'raw_json': {},
'temp_file': None
}
try:
r = session.post(f"{CONFIG['base_url']}/getLastWeatherData.php", data=str(sid),
headers={'Content-Type': 'text/plain'}, timeout=10)
try:
data = r.json()
except:
data = None
if data:
d_list = data.get('date', [])
# 上游返回的是 UTC,需 +8 小时转北京时间。
# 实证:库里 check_time 为 2026-02-06 17:11:10(那轮采集完成时刻),
# 而同一批设备的上游原值落在 09:07~09:10,+8h 后正好是 17:07~17:10,
# 即采集完成前一分钟 —— 若原值已是北京时间,则意味着这批站点在采集
# 前 8 小时就集体停止上报,不合常理。
# 中国无夏令时,固定 +8h 与 pytz 转换等价。
# 拿不到时间时必须是 None,不能是 "N/A" —— 字符串在入库层是真值,
# 会把设备主表已经冻结的有效 latest_time 覆盖掉,令 offset 倒退成"从未同步"
latest = None
if d_list:
raw_time_str = str(d_list[-1])
try:
# 兼容可能的 T 或 Z 等 ISO 格式,先做一下基础清洗
clean_str = raw_time_str.replace('T', ' ').replace('Z', '').split('.')[0]
dt = datetime.strptime(clean_str, "%Y-%m-%d %H:%M:%S")
# 加上 8 小时转换为北京时间
dt = dt + timedelta(hours=8)
latest = dt.strftime("%Y-%m-%d %H:%M:%S")
except Exception:
# 如果解析失败,原样返回兜底
latest = raw_time_str
data_packet['target_time'] = latest
data_packet['value'] = f"Data Points: {len(d_list)}"
data_packet['raw_json'] = data # 🔥 存完整JSON
else:
data_packet['status'] = '异常'
data_packet['value'] = "返回空数据"
except Exception as e:
data_packet['status'] = '异常'
data_packet['value'] = "单个采集失败"
results.append(data_packet)
except Exception as e:
logging.error(f"82 Crawler Error: {e}")
return results