'commit'
This commit is contained in:
@@ -7,7 +7,7 @@ import json
|
|||||||
import cv2
|
import cv2
|
||||||
from Apps.TeLaiDian.Kit import (
|
from Apps.TeLaiDian.Kit import (
|
||||||
take_screenshot, get_image_content_md5, clean_station_name,
|
take_screenshot, get_image_content_md5, clean_station_name,
|
||||||
setup_logger, read_image, save_image
|
setup_logger, read_image, save_image, detect_warm_popup_xczs_cv
|
||||||
)
|
)
|
||||||
from Apps.TeLaiDian.ReadImageKit import ReadImageKit
|
from Apps.TeLaiDian.ReadImageKit import ReadImageKit
|
||||||
from Apps.TeLaiDian.Service import TeLaiDianService
|
from Apps.TeLaiDian.Service import TeLaiDianService
|
||||||
@@ -103,29 +103,18 @@ class TeLaiDianCrawler(BaseCrawler):
|
|||||||
popup_screen_path = take_screenshot(d, f"tld_detail_popup_{int(time.time())}.jpg")
|
popup_screen_path = take_screenshot(d, f"tld_detail_popup_{int(time.time())}.jpg")
|
||||||
logger.info(f"[详情页] 截图用于检测温馨提示弹窗: {popup_screen_path}")
|
logger.info(f"[详情页] 截图用于检测温馨提示弹窗: {popup_screen_path}")
|
||||||
|
|
||||||
template_xczs = os.path.join(os.path.dirname(__file__), "Template", "xczs.jpg")
|
# 使用 OCR 探测“下次再说”按钮 (替代原来的模板匹配)
|
||||||
template_point = None
|
ocr_res = detect_warm_popup_xczs_cv(popup_screen_path)
|
||||||
if os.path.exists(template_xczs):
|
ocr_point = None
|
||||||
try:
|
if ocr_res:
|
||||||
match_res = d.image.match(template_xczs)
|
w, h = d.window_size()
|
||||||
if match_res:
|
ocr_point = (
|
||||||
if hasattr(match_res, "point") and match_res.point:
|
int((ocr_res[0] + ocr_res[2]) / 2 * w / 1000),
|
||||||
template_point = match_res.point
|
int((ocr_res[1] + ocr_res[3]) / 2 * h / 1000)
|
||||||
elif isinstance(match_res, dict):
|
)
|
||||||
if match_res.get("point"):
|
logger.info(f"[详情页] OCR 检测到“下次再说”按钮位置: {ocr_point}")
|
||||||
template_point = match_res["point"]
|
|
||||||
elif "x" in match_res and "y" in match_res:
|
|
||||||
template_point = (match_res["x"], match_res["y"])
|
|
||||||
elif isinstance(match_res, (list, tuple)) and len(match_res) >= 2:
|
|
||||||
template_point = (match_res[0], match_res[1])
|
|
||||||
if template_point:
|
|
||||||
logger.info(f"[详情页] 模板检测到疑似温馨提示弹窗位置: {template_point}")
|
|
||||||
else:
|
|
||||||
logger.info("[详情页] 模板未能确定温馨提示弹窗位置")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[详情页] 模板检测温馨提示弹窗失败: {e}")
|
|
||||||
else:
|
else:
|
||||||
logger.info(f"[详情页] 温馨提示模板文件不存在: {template_xczs}")
|
logger.info("[详情页] OCR 未能检测到“下次再说”按钮")
|
||||||
|
|
||||||
vlm_popup = {"has_popup": False}
|
vlm_popup = {"has_popup": False}
|
||||||
try:
|
try:
|
||||||
@@ -134,10 +123,9 @@ class TeLaiDianCrawler(BaseCrawler):
|
|||||||
logger.error(f"[详情页] VLM 检测温馨提示弹窗失败: {e}")
|
logger.error(f"[详情页] VLM 检测温馨提示弹窗失败: {e}")
|
||||||
|
|
||||||
has_vlm_popup = isinstance(vlm_popup, dict) and vlm_popup.get("has_popup")
|
has_vlm_popup = isinstance(vlm_popup, dict) and vlm_popup.get("has_popup")
|
||||||
if template_point and has_vlm_popup:
|
if ocr_point and has_vlm_popup:
|
||||||
click_x = int(template_point[0])
|
click_x, click_y = ocr_point
|
||||||
click_y = int(template_point[1])
|
logger.info(f"[详情页] OCR 与 VLM 均确认存在温馨提示弹窗,即将点击“下次再说”按钮: ({click_x}, {click_y})")
|
||||||
logger.info(f"[详情页] 模板与 VLM 均确认存在温馨提示弹窗,即将点击关闭按钮: ({click_x}, {click_y})")
|
|
||||||
debug_popup_path = popup_screen_path.replace(".jpg", f"_xczs_click_{click_x}_{click_y}.jpg")
|
debug_popup_path = popup_screen_path.replace(".jpg", f"_xczs_click_{click_x}_{click_y}.jpg")
|
||||||
try:
|
try:
|
||||||
img_popup = read_image(popup_screen_path)
|
img_popup = read_image(popup_screen_path)
|
||||||
@@ -153,7 +141,7 @@ class TeLaiDianCrawler(BaseCrawler):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[详情页] 点击温馨提示“下次再说”失败: {e}")
|
logger.error(f"[详情页] 点击温馨提示“下次再说”失败: {e}")
|
||||||
else:
|
else:
|
||||||
logger.info(f"[详情页] 温馨提示弹窗未通过双重确认,模板检测: {bool(template_point)} | VLM 检测: {vlm_popup}")
|
logger.info(f"[详情页] 温馨提示弹窗未通过双重确认,OCR检测: {bool(ocr_point)} | VLM 检测: {vlm_popup}")
|
||||||
|
|
||||||
w, h = d.window_size()
|
w, h = d.window_size()
|
||||||
logger.info(f"执行显式下拉刷新操作: (x={w//2}, y1={int(h*0.3)} -> y2={int(h*0.8)})")
|
logger.info(f"执行显式下拉刷新操作: (x={w//2}, y1={int(h*0.3)} -> y2={int(h*0.8)})")
|
||||||
@@ -162,10 +150,11 @@ class TeLaiDianCrawler(BaseCrawler):
|
|||||||
logger.info(f"等待 {WAIT_AFTER_SCROLL} 秒确保位置校准和列表刷新完成...")
|
logger.info(f"等待 {WAIT_AFTER_SCROLL} 秒确保位置校准和列表刷新完成...")
|
||||||
await asyncio.sleep(WAIT_AFTER_SCROLL)
|
await asyncio.sleep(WAIT_AFTER_SCROLL)
|
||||||
|
|
||||||
|
max_to_crawl = 1 if FIRST_RUN_ONLY_ONE_STATION else MAX_STATIONS_COUNT
|
||||||
processed_count = 0
|
processed_count = 0
|
||||||
last_md5 = None
|
last_md5 = None
|
||||||
|
|
||||||
while processed_count < MAX_STATIONS_COUNT:
|
while processed_count < max_to_crawl:
|
||||||
# 1. 截图并分析
|
# 1. 截图并分析
|
||||||
screenshot_path = take_screenshot(d, f"tld_list_{int(time.time())}.jpg")
|
screenshot_path = take_screenshot(d, f"tld_list_{int(time.time())}.jpg")
|
||||||
|
|
||||||
@@ -193,7 +182,7 @@ class TeLaiDianCrawler(BaseCrawler):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for station in stations:
|
for station in stations:
|
||||||
if processed_count >= MAX_STATIONS_COUNT:
|
if processed_count >= max_to_crawl:
|
||||||
break
|
break
|
||||||
|
|
||||||
name = station.get("name")
|
name = station.get("name")
|
||||||
@@ -209,7 +198,9 @@ class TeLaiDianCrawler(BaseCrawler):
|
|||||||
logger.info(f"跳过已处理场站 (Redis): {name}")
|
logger.info(f"跳过已处理场站 (Redis): {name}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.info(f"处理场站: {name} (坐标: {point}, 距离: {station.get('distance')})")
|
current_idx = processed_count + 1
|
||||||
|
remaining = max_to_crawl - current_idx
|
||||||
|
logger.info(f"--- [进度: {current_idx}/{max_to_crawl}, 剩余: {remaining}] 处理场站: {name} (坐标: {point}, 距离: {station.get('distance')}) ---")
|
||||||
|
|
||||||
# 点击进入详情
|
# 点击进入详情
|
||||||
d.click(point[0], point[1])
|
d.click(point[0], point[1])
|
||||||
|
|||||||
@@ -188,6 +188,32 @@ def detect_price_info_container_cv(image_path):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def detect_warm_popup_xczs_cv(image_path):
|
||||||
|
"""
|
||||||
|
使用 OCR 精准定位“温馨提示”弹窗中的“下次再说”按钮。
|
||||||
|
返回: [x1, y1, x2, y2] 归一化坐标,如果未找到则返回 None
|
||||||
|
"""
|
||||||
|
img = read_image(image_path)
|
||||||
|
if img is None:
|
||||||
|
return None
|
||||||
|
h, w = img.shape[:2]
|
||||||
|
|
||||||
|
keyword = '下次再说'
|
||||||
|
|
||||||
|
try:
|
||||||
|
reader = get_ocr_reader()
|
||||||
|
results = reader.read_text(img)
|
||||||
|
|
||||||
|
for (quad, text, prob) in results:
|
||||||
|
if keyword in text and prob >= 0.5:
|
||||||
|
res = reader.get_normalized_rect(quad, w, h)
|
||||||
|
print(f"[OCR识别] 找到“下次再说”: '{text}', 置信度: {prob:.4f}, 归一化坐标: {res}")
|
||||||
|
return res
|
||||||
|
except Exception as e:
|
||||||
|
print(f"OCR 识别发生异常: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def setup_logger(name, log_file=None, clear_old_log=False):
|
def setup_logger(name, log_file=None, clear_old_log=False):
|
||||||
"""
|
"""
|
||||||
配置日志,支持同时输出到控制台和文件。
|
配置日志,支持同时输出到控制台和文件。
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.6 KiB |
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,7 @@ import sys
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from Apps.YeLiTe.Kit import take_screenshot, clean_station_name, get_image_content_md5, find_template
|
from Apps.YeLiTe.Kit import take_screenshot, clean_station_name, get_image_content_md5, detect_price_info_container_cv
|
||||||
from Apps.YeLiTe.ReadImageKit import ReadImageKit
|
from Apps.YeLiTe.ReadImageKit import ReadImageKit
|
||||||
from Apps.YeLiTe.Service import YiLaiTeService
|
from Apps.YeLiTe.Service import YiLaiTeService
|
||||||
from Apps.YeLiTe.Config.Setting import (
|
from Apps.YeLiTe.Config.Setting import (
|
||||||
@@ -107,6 +107,7 @@ class YiLaiTeCrawler(BaseCrawler):
|
|||||||
|
|
||||||
name = station.get('name')
|
name = station.get('name')
|
||||||
address = station.get('address')
|
address = station.get('address')
|
||||||
|
distance = station.get('distance')
|
||||||
point = station.get('point')
|
point = station.get('point')
|
||||||
|
|
||||||
if not name or not point:
|
if not name or not point:
|
||||||
@@ -182,7 +183,9 @@ class YiLaiTeCrawler(BaseCrawler):
|
|||||||
logger.info(f"成功进入详情页: {name}")
|
logger.info(f"成功进入详情页: {name}")
|
||||||
|
|
||||||
# --- 新增:点击“阶段性电价”按钮以获取完整电价列表 ---
|
# --- 新增:点击“阶段性电价”按钮以获取完整电价列表 ---
|
||||||
dqdf_pos = find_template(detail_shot, "dqdf.jpg")
|
# 使用 OCR 探测价格入口
|
||||||
|
dqdf_pos = detect_price_info_container_cv(detail_shot)
|
||||||
|
|
||||||
detail_shots = []
|
detail_shots = []
|
||||||
|
|
||||||
if dqdf_pos:
|
if dqdf_pos:
|
||||||
@@ -219,7 +222,7 @@ class YiLaiTeCrawler(BaseCrawler):
|
|||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
# 启动后台任务处理详情页
|
# 启动后台任务处理详情页
|
||||||
task = asyncio.create_task(self.analyze_detail_background(name, detail_shots, address=address))
|
task = asyncio.create_task(self.analyze_detail_background(name, detail_shots, address=address, distance=distance))
|
||||||
background_tasks.append(task)
|
background_tasks.append(task)
|
||||||
|
|
||||||
processed_count += 1
|
processed_count += 1
|
||||||
@@ -268,7 +271,7 @@ class YiLaiTeCrawler(BaseCrawler):
|
|||||||
|
|
||||||
return processed_count
|
return processed_count
|
||||||
|
|
||||||
async def analyze_detail_background(self, station_name, image_paths, address=None):
|
async def analyze_detail_background(self, station_name, image_paths, address=None, distance=None):
|
||||||
"""
|
"""
|
||||||
后台异步分析详情页 (支持多张截图合并)
|
后台异步分析详情页 (支持多张截图合并)
|
||||||
"""
|
"""
|
||||||
@@ -304,8 +307,8 @@ class YiLaiTeCrawler(BaseCrawler):
|
|||||||
unique_prices.sort(key=lambda x: x.get('start', '00:00'))
|
unique_prices.sort(key=lambda x: x.get('start', '00:00'))
|
||||||
|
|
||||||
if unique_prices:
|
if unique_prices:
|
||||||
await self.service.process_price_detail_data(station_name, unique_prices, address=address)
|
await self.service.process_price_detail_data(station_name, unique_prices, address=address, distance=distance)
|
||||||
logger.info(f"场站 {station_name} 价格分析完成并入库 (记录数: {len(unique_prices)}, 地址: {address})")
|
logger.info(f"场站 {station_name} 价格分析完成并入库 (记录数: {len(unique_prices)}, 地址: {address}, 距离: {distance})")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"场站 {station_name} 未识别到价格信息")
|
logger.warning(f"场站 {station_name} 未识别到价格信息")
|
||||||
|
|
||||||
|
|||||||
@@ -270,6 +270,45 @@ def draw_rectangles(image_path, points, output_path=None):
|
|||||||
logger.error(f"绘制矩形框失败: {e}")
|
logger.error(f"绘制矩形框失败: {e}")
|
||||||
return image_path
|
return image_path
|
||||||
|
|
||||||
|
from Util.EasyOcrKit import get_easyocr_reader
|
||||||
|
|
||||||
|
# 预加载 EasyOCR Reader (单例模式)
|
||||||
|
def get_ocr_reader():
|
||||||
|
return get_easyocr_reader(gpu=True)
|
||||||
|
|
||||||
|
def detect_price_info_container_cv(image_path):
|
||||||
|
"""
|
||||||
|
使用 OCR 精准定位详情页中的价格入口文本(“阶段性电价”)。
|
||||||
|
返回: [x1, y1, x2, y2] 绝对像素坐标,如果未找到则返回 None
|
||||||
|
"""
|
||||||
|
img = read_image(image_path)
|
||||||
|
if img is None:
|
||||||
|
return None
|
||||||
|
h, w = img.shape[:2]
|
||||||
|
|
||||||
|
keywords = ['阶段性电价']
|
||||||
|
|
||||||
|
try:
|
||||||
|
reader = get_ocr_reader()
|
||||||
|
results = reader.read_text(img)
|
||||||
|
|
||||||
|
for (quad, text, prob) in results:
|
||||||
|
if any(kw in text for kw in keywords) and prob >= 0.5:
|
||||||
|
pts = np.array(quad).astype(int)
|
||||||
|
x_min = np.min(pts[:, 0])
|
||||||
|
y_min = np.min(pts[:, 1])
|
||||||
|
x_max = np.max(pts[:, 0])
|
||||||
|
y_max = np.max(pts[:, 1])
|
||||||
|
|
||||||
|
center_x = (x_min + x_max) // 2
|
||||||
|
center_y = (y_min + y_max) // 2
|
||||||
|
logger.info(f"[OCR识别] 找到文本: '{text}', 置信度: {prob:.4f}, 中心坐标: ({center_x}, {center_y})")
|
||||||
|
return (center_x, center_y)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"OCR 识别发生异常: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def setup_logger(name, log_file=None, clear_old_log=False):
|
def setup_logger(name, log_file=None, clear_old_log=False):
|
||||||
"""
|
"""
|
||||||
配置日志,支持同时输出到控制台和文件。
|
配置日志,支持同时输出到控制台和文件。
|
||||||
|
|||||||
@@ -153,9 +153,10 @@ class ReadImageKit:
|
|||||||
每个对象包含:
|
每个对象包含:
|
||||||
- "name": 场站名称 (仅提取名称文字)
|
- "name": 场站名称 (仅提取名称文字)
|
||||||
- "address": 场站地址 (通常在名称下方,请完整提取地址文字)
|
- "address": 场站地址 (通常在名称下方,请完整提取地址文字)
|
||||||
|
- "distance": 距离 (如 "1.2km", "800m" 等,如果存在请提取)
|
||||||
- "is_valid": true/false (是否为真实的场站卡片)
|
- "is_valid": true/false (是否为真实的场站卡片)
|
||||||
|
|
||||||
注意:仅提取名称和地址,不要包含距离或价格。
|
注意:仅提取名称、地址和距离,不要包含价格。
|
||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
# 如果 CV 没检测到,退回到纯 VLM 模式
|
# 如果 CV 没检测到,退回到纯 VLM 模式
|
||||||
@@ -165,6 +166,7 @@ class ReadImageKit:
|
|||||||
输出格式为 JSON 数组,每个对象包含:
|
输出格式为 JSON 数组,每个对象包含:
|
||||||
- "name": 场站名称
|
- "name": 场站名称
|
||||||
- "address": 场站地址 (请仔细识别并提取场站的详细地址信息)
|
- "address": 场站地址 (请仔细识别并提取场站的详细地址信息)
|
||||||
|
- "distance": 距离 (如 "1.2km", "800m" 等,如果存在请提取)
|
||||||
- "point": 场站卡片的中心点击坐标 [x, y]
|
- "point": 场站卡片的中心点击坐标 [x, y]
|
||||||
- "bbox": 场站卡片的边界框 [x1, y1, x2, y2]
|
- "bbox": 场站卡片的边界框 [x1, y1, x2, y2]
|
||||||
"""
|
"""
|
||||||
@@ -187,6 +189,7 @@ class ReadImageKit:
|
|||||||
final_stations.append({
|
final_stations.append({
|
||||||
"name": res.get("name"),
|
"name": res.get("name"),
|
||||||
"address": res.get("address"),
|
"address": res.get("address"),
|
||||||
|
"distance": res.get("distance"),
|
||||||
"point": [(bbox[0] + bbox[2]) // 2, (bbox[1] + bbox[3]) // 2],
|
"point": [(bbox[0] + bbox[2]) // 2, (bbox[1] + bbox[3]) // 2],
|
||||||
"bbox": bbox
|
"bbox": bbox
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -90,16 +90,71 @@ class YiLaiTeService:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"更新任务结束日志失败: {e}")
|
logger.error(f"更新任务结束日志失败: {e}")
|
||||||
|
|
||||||
async def process_price_detail_data(self, station_name, hourly_schedule, address=None) -> bool:
|
async def process_price_detail_data(self, station_name, prices, address=None, distance=None) -> bool:
|
||||||
"""
|
"""
|
||||||
直接保存已处理好的小时段价格数据
|
保存场站数据:名称、地址、24小时价格计划、距离
|
||||||
"""
|
"""
|
||||||
if not station_name or not hourly_schedule:
|
if not station_name or not prices:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
station_hash = self.get_hash(station_name)
|
station_hash = self.get_hash(station_name)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
||||||
|
# 1. 预处理价格:生成 24 小时的价格映射
|
||||||
|
hourly_schedule = [None] * 24
|
||||||
|
|
||||||
|
for p in prices:
|
||||||
|
try:
|
||||||
|
start_str = p.get('start') or '00:00'
|
||||||
|
end_str = p.get('end') or '00:00'
|
||||||
|
|
||||||
|
# 解析小时
|
||||||
|
start_hour = int(start_str.split(':')[0])
|
||||||
|
end_hour = int(end_str.split(':')[0])
|
||||||
|
|
||||||
|
# 处理 00:00 作为结束时间
|
||||||
|
if end_hour == 0 and start_hour != 0:
|
||||||
|
end_hour = 24
|
||||||
|
elif end_hour == 0 and start_hour == 0 and len(prices) == 1:
|
||||||
|
end_hour = 24
|
||||||
|
|
||||||
|
# 如果跨天
|
||||||
|
if end_hour < start_hour:
|
||||||
|
end_hour += 24
|
||||||
|
|
||||||
|
price_val = self._to_float(p.get('price'))
|
||||||
|
|
||||||
|
curr = start_hour
|
||||||
|
while curr < end_hour:
|
||||||
|
hour_idx = curr % 24
|
||||||
|
hourly_schedule[hour_idx] = {
|
||||||
|
"start": f"{hour_idx:02d}:00",
|
||||||
|
"end": f"{(hour_idx + 1):02d}:00",
|
||||||
|
"price": price_val
|
||||||
|
}
|
||||||
|
curr += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"解析价格时段失败: {p}, error: {e}")
|
||||||
|
|
||||||
|
# 2. 兜底填充 (补全 24 小时)
|
||||||
|
last_valid = None
|
||||||
|
for item in hourly_schedule:
|
||||||
|
if item:
|
||||||
|
last_valid = item
|
||||||
|
break
|
||||||
|
|
||||||
|
if last_valid:
|
||||||
|
for i in range(24):
|
||||||
|
if hourly_schedule[i] is None:
|
||||||
|
new_item = last_valid.copy()
|
||||||
|
new_item["start"] = f"{i:02d}:00"
|
||||||
|
new_item["end"] = f"{(i+1):02d}:00"
|
||||||
|
hourly_schedule[i] = new_item
|
||||||
|
else:
|
||||||
|
last_valid = hourly_schedule[i]
|
||||||
|
else:
|
||||||
|
logger.warning(f"场站 {station_name} 未能生成有效的 24 小时价格表")
|
||||||
|
|
||||||
async with await self.db.get_session() as session:
|
async with await self.db.get_session() as session:
|
||||||
# 1. 保存 Profile (如果不存在)
|
# 1. 保存 Profile (如果不存在)
|
||||||
profile_id = self.generate_id()
|
profile_id = self.generate_id()
|
||||||
@@ -122,7 +177,27 @@ class YiLaiTeService:
|
|||||||
schedule_json=hourly_schedule,
|
schedule_json=hourly_schedule,
|
||||||
valid_start_time=now
|
valid_start_time=now
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 3. 保存状态 (记录距离和当前价格)
|
||||||
|
current_hour = now.hour
|
||||||
|
current_price_info = hourly_schedule[current_hour] or {}
|
||||||
|
|
||||||
|
status_id = self.generate_id()
|
||||||
|
await self.station_status_model.save(
|
||||||
|
session=session,
|
||||||
|
id=status_id,
|
||||||
|
station_hash=station_hash,
|
||||||
|
total_piles=None,
|
||||||
|
free_piles=None,
|
||||||
|
piles_detail_json=None,
|
||||||
|
current_price=current_price_info.get('price'),
|
||||||
|
distance=distance,
|
||||||
|
valid_start_time=now
|
||||||
|
)
|
||||||
|
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
logger.info(f"成功保存驿来特场站数据: {station_name} | 距离: {distance} | 价格条数: {len([x for x in hourly_schedule if x])}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def process_station_list_vl(self, image_path, json_metadata, device_info=None, max_count=None) -> list:
|
async def process_station_list_vl(self, image_path, json_metadata, device_info=None, max_count=None) -> list:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
Reference in New Issue
Block a user