├── .gitignore ├── requirements.txt ├── config ├── __pycache__ │ └── config.cpython-310.pyc └── config.py ├── notifier ├── __pycache__ │ ├── bark.cpython-310.pyc │ ├── mqtt.cpython-310.pyc │ ├── feishu.cpython-310.pyc │ ├── __init__.cpython-310.pyc │ ├── webhook.cpython-310.pyc │ ├── wechatbot.cpython-310.pyc │ ├── serverchan.cpython-310.pyc │ └── base_notifier.cpython-310.pyc ├── base_notifier.py ├── mqtt.py ├── serverchan.py ├── bark.py ├── wechatbot.py ├── __init__.py ├── webhook.py └── feishu.py ├── scraper ├── __pycache__ │ └── power_scraper.cpython-310.pyc └── power_scraper.py ├── app.py ├── config.ini.example ├── README.md └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | /config.ini 2 | .idea -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | playwright~=1.43.0 2 | requests~=2.31.0 3 | paho-mqtt~=2.1.0 4 | Flask~=3.0.3 -------------------------------------------------------------------------------- /config/__pycache__/config.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okatu-loli/get_power/HEAD/config/__pycache__/config.cpython-310.pyc -------------------------------------------------------------------------------- /notifier/__pycache__/bark.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okatu-loli/get_power/HEAD/notifier/__pycache__/bark.cpython-310.pyc -------------------------------------------------------------------------------- /notifier/__pycache__/mqtt.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okatu-loli/get_power/HEAD/notifier/__pycache__/mqtt.cpython-310.pyc -------------------------------------------------------------------------------- /notifier/__pycache__/feishu.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okatu-loli/get_power/HEAD/notifier/__pycache__/feishu.cpython-310.pyc -------------------------------------------------------------------------------- /notifier/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okatu-loli/get_power/HEAD/notifier/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /notifier/__pycache__/webhook.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okatu-loli/get_power/HEAD/notifier/__pycache__/webhook.cpython-310.pyc -------------------------------------------------------------------------------- /notifier/__pycache__/wechatbot.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okatu-loli/get_power/HEAD/notifier/__pycache__/wechatbot.cpython-310.pyc -------------------------------------------------------------------------------- /notifier/__pycache__/serverchan.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okatu-loli/get_power/HEAD/notifier/__pycache__/serverchan.cpython-310.pyc -------------------------------------------------------------------------------- /notifier/__pycache__/base_notifier.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okatu-loli/get_power/HEAD/notifier/__pycache__/base_notifier.cpython-310.pyc -------------------------------------------------------------------------------- /scraper/__pycache__/power_scraper.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okatu-loli/get_power/HEAD/scraper/__pycache__/power_scraper.cpython-310.pyc -------------------------------------------------------------------------------- /notifier/base_notifier.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | class BaseNotifier(ABC): 4 | 5 | def __init__(self, config): 6 | self.config = config 7 | 8 | @abstractmethod 9 | def send(self, amt, message): 10 | """发送通知的抽象方法,需要在子类中具体实现""" 11 | pass 12 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, send_from_directory 2 | 3 | app = Flask(__name__) 4 | 5 | 6 | @app.route('/qrcode', methods=['GET']) 7 | def get_qrcode(): 8 | return send_from_directory('qrcode', 'qrcode.png', mimetype='image/jpeg') 9 | 10 | if __name__ == '__main__': 11 | app.run("0.0.0.0",debug=True) -------------------------------------------------------------------------------- /notifier/mqtt.py: -------------------------------------------------------------------------------- 1 | import paho.mqtt.client as mqtt 2 | from .base_notifier import BaseNotifier 3 | 4 | 5 | class MQTTNotifier(BaseNotifier): 6 | 7 | def send(self, amt, message): 8 | client = mqtt.Client() 9 | client.connect(self.config.get('MQTT', 'mqtt_host'), 10 | int(self.config.get('MQTT', 'mqtt_port')), 60) 11 | client.publish(self.config.get('MQTT', 'mqtt_topic'), message) 12 | client.disconnect() 13 | -------------------------------------------------------------------------------- /notifier/serverchan.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from .base_notifier import BaseNotifier 3 | 4 | 5 | class ServerChanNotifier(BaseNotifier): 6 | 7 | def send(self, amt, message): 8 | url = f"https://sctapi.ftqq.com/{self.config.get('ServerChan', 'send_key')}.send" 9 | params = { 10 | 'text': '电费提醒', 11 | 'desp': message 12 | } 13 | response = requests.get(url, params=params) 14 | if response.status_code == 200: 15 | print("通知已发送") 16 | else: 17 | print("发送通知失败") 18 | -------------------------------------------------------------------------------- /config.ini.example: -------------------------------------------------------------------------------- 1 | [Notification] 2 | ; 可选平台:feishu, serverchan, mqtt, webhook, bark。多个平台启用时用逗号隔开 3 | platform = 4 | hours = 14 5 | mins = 00 6 | 7 | [ServerChan] 8 | ; Server酱发送密钥 9 | send_key = 10 | 11 | [Feishu] 12 | ; 飞书机器人token 13 | robot_token = 14 | 15 | [Webhook] 16 | ; Webhook URL 17 | webhook_url = 18 | 19 | [MQTT] 20 | ; Mqtt的地址 21 | mqtt_host = your_mqtt_host(example: 127.0.0.1 or mqtt.com) 22 | mqtt_port = your_mqtt_port(example: 1883) 23 | mqtt_topic = your_mqtt_topic(example: /me/electricity) 24 | 25 | [Bark] 26 | url = 27 | token = 28 | 29 | [WeChatBot] 30 | url = 31 | token = 32 | to = 33 | isRoom = -------------------------------------------------------------------------------- /notifier/bark.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import requests 4 | 5 | from .base_notifier import BaseNotifier 6 | 7 | 8 | class BarkNotifier(BaseNotifier): 9 | 10 | def send(self, amt, message): 11 | url = f"{self.config.get('Bark','url')}/{self.config.get('Bark','token')}/{message}" 12 | headers = {'Content-Type': 'application/json'} 13 | payload = { 14 | 'amount': amt, 15 | 'message': message 16 | } 17 | response = requests.post(url, headers=headers, data=json.dumps(payload)) 18 | if response.status_code == 200: 19 | print("通知已发送") 20 | else: 21 | print("发送通知失败") 22 | -------------------------------------------------------------------------------- /notifier/wechatbot.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from .base_notifier import BaseNotifier 4 | 5 | 6 | class WeChatBotNotifier(BaseNotifier): 7 | def send(self, amt, message): 8 | url = f"{self.config.get('WeChatBot', 'url')}/webhook/msg/v2?token={self.config.get('WeChatBot', 'token')}" 9 | print(url) 10 | headers = {'Content-Type': 'application/json'} 11 | payload = { 12 | "to": self.config.get('WeChatBot', 'to'), 13 | "isRoom": self.config.getbool('WeChatBot', 'isRoom'), 14 | "data": {"content": f"{message}"} 15 | } 16 | print("request.data:", json.dumps(payload)) 17 | response = requests.post(url, headers=headers, data=json.dumps(payload)) 18 | if response.status_code == 200: 19 | print('电费通知已发送') 20 | else: 21 | print('发送电费通知失败') 22 | -------------------------------------------------------------------------------- /notifier/__init__.py: -------------------------------------------------------------------------------- 1 | from .webhook import WebhookNotifier 2 | from .mqtt import MQTTNotifier 3 | from .serverchan import ServerChanNotifier 4 | from .feishu import FeishuNotifier 5 | from .bark import BarkNotifier 6 | from .wechatbot import WeChatBotNotifier 7 | 8 | 9 | class NotifierFactory: 10 | notifier_classes = { 11 | 'webhook': WebhookNotifier, 12 | 'mqtt': MQTTNotifier, 13 | 'serverchan': ServerChanNotifier, 14 | 'feishu': FeishuNotifier, 15 | 'bark': BarkNotifier, 16 | 'wechatbot': WeChatBotNotifier 17 | } 18 | 19 | @staticmethod 20 | def create_notifier(platform, config_manager): 21 | notifier_class = NotifierFactory.notifier_classes.get(platform) 22 | if notifier_class: 23 | return notifier_class(config_manager) 24 | else: 25 | raise ValueError(f"通知渠道 '{platform}' 不受支持.") 26 | -------------------------------------------------------------------------------- /config/config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | 3 | 4 | class ConfigManager: 5 | def __init__(self, config_path): 6 | self.config = configparser.ConfigParser() 7 | self.config.read(config_path, encoding='utf-8') 8 | 9 | def get(self, section, key): 10 | return self.config.get(section, key) 11 | 12 | def getbool(self, section, key, default=False): 13 | """ 14 | 获取指定section和key的布尔值。 15 | 16 | :param section: 配置文件中的节(section)名称 17 | :param key: 要获取的键(key)名称 18 | :param default: 如果解析失败时返回的默认值,默认为False 19 | :return: 解析后的布尔值,或在解析失败时返回default值 20 | """ 21 | value = self.config.get(section, key, fallback=None) # 使用fallback参数处理键不存在的情况 22 | if value.lower() in ('true', '1', 'yes'): # 处理大小写及可能的数字表示 23 | return True 24 | elif value.lower() in ('false', '0', 'no'): 25 | return False 26 | else: 27 | # 如果值既不是'true'/'false'等公认布尔表示,也不是数字0/1, 28 | # 则根据default参数决定返回值 29 | return default -------------------------------------------------------------------------------- /notifier/webhook.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from .base_notifier import BaseNotifier 4 | 5 | 6 | class WebhookNotifier(BaseNotifier): 7 | """ 8 | 可以用 Webhook 直接发送到 Home Assistant 9 | Home Assisistant 中的 sensor 参考配置如下 10 | - trigger: 11 | - platform: webhook 12 | webhook_id: {your_webhook_id} 13 | local_only: false 14 | allowed_methods: 15 | - POST 16 | sensor: 17 | - name: "Current Electricity Bill" 18 | unique_id: Tei9S161spDNZDP7 19 | state: "{{ trigger.json.amount }}" 20 | - name: "Current Electricity Message" 21 | unique_id: k0q0GTHrWcczneHw 22 | state: "{{ trigger.json.msg }}" 23 | """ 24 | def send(self, amt, message): 25 | url = self.config.get('Webhook', 'webhook_url') 26 | headers = {'Content-Type': 'application/json'} 27 | payload = { 28 | 'amount': amt, 29 | 'message': message 30 | } 31 | response = requests.post(url, headers=headers, data=json.dumps(payload)) 32 | if response.status_code == 200: 33 | print('通知已发送') 34 | else: 35 | print('发送通知失败') 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GetPower 项目文档 2 | 3 | **有问题可以进QQ群交流:361997678** 4 | 5 | ## 简介 6 | 7 | **GetPower** 是一个实用的小工具,旨在帮助用户实时监控和通知电费情况。该工具支持通过二维码登录,并能将电费信息推送至多个平台,包括飞书、MQTT、[ServerChan](https://sct.ftqq.com/r/9205)、微信机器人Webhook、Webhook以及Bark。 8 | 9 | ## 特性 10 | 11 | - **支持多种通知平台**:飞书、MQTT、[ServerChan](https://sct.ftqq.com/r/9205)、wechatbot-Webhook、Webhook和Bark。 12 | - **二维码登录**:简便快捷的二维码登录方式。 13 | - **定时推送**:可配置的推送时间,按时提醒电费信息。 14 | 15 | ## 快速开始 16 | 17 | ### 1. 配置环境 18 | 19 | 首先,确保你的系统中已安装了Python和所需的依赖。 20 | ``` 21 | git clone https://github.com/okatu-loli/get_power 22 | pip install -r requirements.txt 23 | # python -m playwright install 24 | playwright install 25 | ``` 26 | 27 | ### 2. 配置文件 28 | 29 | 复制 `config.ini.example` 文件并重命名为 `config.ini`,根据需要填写以下配置信息: 30 | 31 | ```plaintext 32 | [Notification] 33 | ; 可选平台:feishu, serverchan, mqtt, webhook, bark。多个平台启用时用逗号隔开 34 | platform = 35 | hours = 14 36 | mins = 00 37 | 38 | [ServerChan] 39 | ; Server酱发送密钥 40 | send_key = 41 | 42 | [Feishu] 43 | ; 飞书机器人token 44 | robot_token = 45 | 46 | [Webhook] 47 | ; Webhook URL 48 | webhook_url = 49 | 50 | [MQTT] 51 | ; Mqtt的地址 52 | mqtt_host = 53 | mqtt_port = 54 | mqtt_topic = 55 | 56 | [Bark] 57 | url = 58 | token = 59 | 60 | [WeChatBot] 61 | url = 62 | token = 63 | to = 64 | isRoom = 65 | ``` 66 | 67 | ### 3. 启动程序 68 | 69 | 启动程序后,访问 `http://你的IP:5000/qrcode`,扫描二维码进行登录。 70 | 71 | ## 注意事项 72 | 73 | - 如果访问二维码页面时提示404,或扫描二维码后提示失效,请检查程序控制台的日志输出。 74 | - 二维码检测可能存在5-10秒的延迟,二维码失效会在日志中有所提示。 75 | 76 | ## TODO 77 | 78 | - [ ] 支持多户号 79 | 80 | ## 贡献 81 | 82 | 如果你在使用过程中遇到任何问题,欢迎提Issue。同时,我们也欢迎各种Pull Requests。 83 | 84 | ## 免责声明 85 | 86 | 本工具仅供学习交流使用,请勿用于商业用途,请在下载后24小时内删除。 87 | -------------------------------------------------------------------------------- /notifier/feishu.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from .base_notifier import BaseNotifier 4 | 5 | 6 | class FeishuNotifier(BaseNotifier): 7 | 8 | def send(self, amt, message): 9 | url = f"https://open.feishu.cn/open-apis/bot/v2/hook/{self.config.get('Feishu', 'robot_token')}" 10 | headers = {'Content-Type': 'application/json'} 11 | 12 | payload = { 13 | "msg_type": "interactive", 14 | "card": { 15 | "config": { 16 | "wide_screen_mode": True 17 | }, 18 | "header": { 19 | "template": "blue", 20 | "title": { 21 | "content": "【电费推送】每日电费推送", 22 | "tag": "plain_text" 23 | } 24 | }, 25 | "elements": [ 26 | { 27 | "tag": "div", 28 | "fields": [ 29 | { 30 | "is_short": True, 31 | "text": { 32 | "content": "**时间**\n2024年5月1日", 33 | "tag": "lark_md" 34 | } 35 | }, 36 | { 37 | "is_short": True, 38 | "text": { 39 | "content": "**地点**\n星修社工作室", 40 | "tag": "lark_md" 41 | } 42 | } 43 | ] 44 | }, 45 | { 46 | "tag": "div", 47 | "text": { 48 | "content": f"今日查询电费:¥{amt}\n如有疑问,请联系千石:", 49 | "tag": "lark_md" 50 | } 51 | } 52 | ] 53 | } 54 | } 55 | 56 | response = requests.post(url, headers=headers, data=json.dumps(payload)) 57 | if response.status_code == 200: 58 | print("电费通知已发送") 59 | else: 60 | print("发送电费通知失败") 61 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from playwright.sync_api import sync_playwright 4 | 5 | from config.config import ConfigManager 6 | from notifier import NotifierFactory 7 | from scraper.power_scraper import fetch_and_save_qrcode, check_login_success, monitor_electricity_bill, \ 8 | LoginFailedException 9 | 10 | 11 | def main(): 12 | config = ConfigManager("config.ini") 13 | platforms = config.get("Notification", "platform").split(',') 14 | notifiers = [NotifierFactory.create_notifier(platform, config) for platform in platforms] if platforms != [ 15 | ''] else [] 16 | 17 | def notify_bill(amount): 18 | """发送电费通知""" 19 | message = f"当前电费:{amount}" 20 | for notifier in notifiers: 21 | notifier.send(amount, message) 22 | 23 | def notify_err(text, err): 24 | """发送错误通知""" 25 | message = f"{text}:{err}" 26 | for notifier in notifiers: 27 | notifier.send(None, message) 28 | 29 | max_retries = -1 30 | retry_delay = 5 31 | retries = 0 32 | hours = int(config.get('Notification', 'hours')) 33 | mins = int(config.get('Notification', 'mins')) 34 | 35 | with sync_playwright() as playwright: 36 | while max_retries == -1 or retries < max_retries: 37 | try: 38 | browser = playwright.chromium.launch(headless=True) 39 | context = browser.new_context() 40 | page = context.new_page() 41 | page.goto("https://95598.cn/osgweb/login") 42 | 43 | fetch_and_save_qrcode(page) 44 | 45 | if check_login_success(page): 46 | print("登录成功.") 47 | time.sleep(10) 48 | 49 | # 设置通知回调,用于在监控过程中发送通知 50 | monitor_electricity_bill(page, hours=hours, mins=mins, notify_callback=notify_bill) 51 | break 52 | else: 53 | raise LoginFailedException("登录失败") 54 | except LoginFailedException as e: 55 | print(f"{e},准备重新尝试...") 56 | retries += 1 57 | if retries <= max_retries: 58 | print(f"等待{retry_delay}秒后重试...") 59 | time.sleep(retry_delay) 60 | notify_err("登录失败", str(e)) 61 | finally: 62 | # 清理浏览器资源 63 | if 'page' in locals(): 64 | page.close() 65 | if 'context' in locals(): 66 | context.close() 67 | if 'browser' in locals(): 68 | browser.close() 69 | 70 | print("所有尝试均告失败或达到最大重试次数。") 71 | 72 | 73 | if __name__ == "__main__": 74 | main() 75 | -------------------------------------------------------------------------------- /scraper/power_scraper.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import os 3 | import time 4 | from datetime import datetime, timedelta 5 | 6 | import playwright 7 | from playwright.sync_api import expect 8 | 9 | QR_CODE_PATH = "qrcode/qrcode.png" 10 | WAIT_TIMEOUT = 5000 11 | 12 | 13 | def ensure_directory_exists(file_path): 14 | """确保文件路径的目录存在""" 15 | os.makedirs(os.path.dirname(file_path), exist_ok=True) 16 | 17 | 18 | def save_qrcode_as_image(base64_data, path): 19 | """保存Base64编码的图片到指定路径""" 20 | image_data = base64.b64decode(base64_data) 21 | with open(path, 'wb') as file: 22 | file.write(image_data) 23 | 24 | 25 | def monitor_qrcode_for_expiry(page): 26 | """持续监控二维码是否失效""" 27 | while True: 28 | # 定位二维码提示元素 29 | failure_prompt_locator = page.locator("#login_box > div.ewm-login > div > div.sweepCodePic > div > p") 30 | 31 | # 获取当前的提示文本内容 32 | failure_texts = failure_prompt_locator.all_inner_texts() 33 | 34 | # 检查是否有二维码失效的提示 35 | if any("二维码失效,点击图片重新获取" in text for text in failure_texts): 36 | print("检测到二维码已失效.") 37 | 38 | page.get_by_text("二维码失效,点击图片重新获取").click() 39 | 40 | # 重新调用获取并保存二维码的函数 41 | fetch_and_save_qrcode(page) 42 | 43 | # 可能需要在此处添加适当的逻辑来中断循环或重置状态,取决于您的具体需求 44 | else: 45 | print("正在检查登录状态...") 46 | if check_login_success(page): 47 | print("登录成功,停止监测二维码。") 48 | break # 登录成功,跳出循环 49 | else: 50 | print("登录未成功,继续监测...") 51 | time.sleep(5) 52 | 53 | 54 | def fetch_and_save_qrcode(page): 55 | """获取并保存二维码到本地""" 56 | qrcode_locator = page.locator(".sweepCodePic .el-tooltip.imgLogin") 57 | expect(qrcode_locator).to_be_visible(timeout=WAIT_TIMEOUT) 58 | page.wait_for_function( 59 | "() => document.querySelector('.sweepCodePic .el-tooltip.imgLogin').src.startsWith('data:image')", 60 | timeout=WAIT_TIMEOUT) 61 | 62 | img_data_url = qrcode_locator.get_attribute('src') 63 | if img_data_url and img_data_url.startswith('data:image'): 64 | img_base64 = img_data_url.split(',', 1)[1] 65 | save_qrcode_as_image(img_base64, QR_CODE_PATH) 66 | print(f'二维码保存到 {QR_CODE_PATH}') 67 | else: 68 | raise LoginFailedException("无法获取有效的二维码图像") 69 | monitor_qrcode_for_expiry(page) 70 | 71 | 72 | def check_login_success(page): 73 | """检查登录是否成功,并使用try-except处理潜在异常""" 74 | login_indicator = "#member_info > div > div > div > div > div.outerLayer > ul > li.content-name" 75 | try: 76 | page.wait_for_selector(login_indicator, timeout=WAIT_TIMEOUT) 77 | is_visible = page.is_visible(login_indicator) 78 | return is_visible 79 | except playwright.sync_api.TimeoutError: 80 | print("等待登录指示器超时,可能是登录未完成或页面结构有变。") 81 | return False 82 | except Exception as e: 83 | print(f"检查登录状态时发生未知错误: {e}") 84 | return False 85 | 86 | 87 | def get_and_print_electricity_bill(page): 88 | """获取并打印电费信息""" 89 | bill_selector = ('#app > div > div > article > div > div > div.content-row.load-after-box > ' 90 | 'div > div > div > div > div > ' 91 | 'div.el-col.el-col-24.el-col-md-18.el-col-lg-19.el-col-xl-19 > div > div > div > ' 92 | 'div.center_menu_t > ul > li > div.content > p:nth-child(2) > b') 93 | try: 94 | time.sleep(10) 95 | page.wait_for_selector(bill_selector, timeout=WAIT_TIMEOUT) 96 | electricity_bill = page.inner_text(bill_selector) 97 | print("当前电费:", electricity_bill) 98 | return electricity_bill 99 | except Exception as e: 100 | print(f"获取电费信息失败: {e}") 101 | 102 | 103 | def should_refresh_at_specific_time(hours, mins): 104 | """判断当前时间是否达到指定的小时和分钟进行刷新""" 105 | now = datetime.now() 106 | target_time = now.replace(hour=hours, minute=mins, second=0, microsecond=0) 107 | if now > target_time: 108 | # 如果当前时间已经超过设定的时间,则安排到明天的同一时间 109 | target_time += timedelta(days=1) 110 | return now >= target_time 111 | 112 | 113 | def monitor_electricity_bill(page, hours, mins, notify_callback=None): 114 | """监控电费信息并在指定时间刷新页面""" 115 | while True: 116 | now = datetime.now() 117 | 118 | # 首次运行或达到刷新间隔,刷新页面并检查登录状态 119 | if hours and mins and should_refresh_at_specific_time(hours, mins): 120 | print(f"到达指定刷新时间({now.strftime('%Y-%m-%d %H:%M:%S')}),刷新页面...") 121 | page.reload() 122 | 123 | while True: 124 | bill_selector = ( 125 | '#app > div > div > article > div > div > div.content-row.load-after-box > div > div > div > ' 126 | 'div > div > div.el-col.el-col-24.el-col-md-18.el-col-lg-19.el-col-xl-19 > div > div > div > ' 127 | 'div.center_menu_t > ul > li:nth-child(2) > div.content > p > b') 128 | element = page.query_selector(bill_selector) 129 | 130 | if element: 131 | # 获取元素的文本内容 132 | text_content = element.text_content() 133 | 134 | # 判断文本内容是否以"--"开头 135 | if not text_content.startswith("--"): 136 | print("元素的文本内容不以'--'开头,停止循环。") 137 | break 138 | else: 139 | print("元素的文本内容仍然以'--'开头,10秒后重试。") 140 | else: 141 | print("未找到指定的元素,10秒后重试。") 142 | 143 | # 等待10秒 144 | time.sleep(10) 145 | 146 | # 刷新页面 147 | page.reload() 148 | 149 | if not check_login_success(page): 150 | print("登录失效,监控结束。") 151 | break 152 | 153 | # 尝试获取并处理电费信息 154 | try: 155 | electricity_bill = get_and_print_electricity_bill(page) 156 | if electricity_bill: 157 | print(f"当前电费:{electricity_bill}元") 158 | if notify_callback: 159 | notify_callback(electricity_bill) # 调用通知回调 160 | except Exception as e: 161 | print(f"获取电费信息失败: {e}") 162 | 163 | # 等待直到下一次检查 164 | wait_until_next_refresh = (now.replace(hour=hours, minute=mins, second=0, microsecond=0) - now).total_seconds() 165 | if wait_until_next_refresh <= 0: # 如果已经过了今天特定时间,等待到明天 166 | wait_until_next_refresh += timedelta(days=1).total_seconds() 167 | time.sleep(wait_until_next_refresh) 168 | 169 | 170 | # 定义自定义异常类 171 | class LoginFailedException(Exception): 172 | """登录失败异常""" 173 | 174 | def __init__(self, message="登录过程中出现问题"): 175 | super().__init__(message) 176 | --------------------------------------------------------------------------------