From c16f9551d4dd5454d86d089212418c53e2612eee Mon Sep 17 00:00:00 2001 From: fengche <1158629543@qq.com> Date: Fri, 26 Dec 2025 18:02:08 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9F=BF=E6=9C=BA=E7=A7=9F=E8=B5=81=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=9F=BF=E6=B1=A0=E7=BD=91=E7=AB=99=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E7=9F=BF=E5=B7=A5=E7=AE=97=E5=8A=9B=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- v1/2miners_collector.py | 92 +++++++++++++++++++++++++++ v1/herominers_collector.py | 103 ++++++++++++++++++++++++++++++ v1/kryptex_collector.py | 96 ++++++++++++++++++++++++++++ v1/kryptex_halfhour_monitor.py | 113 --------------------------------- v1/miner_2miners_collector.py | 108 ------------------------------- v1/run_collectors.py | 45 +++++++++++++ v1/vipor_collector.py | 105 ++++++++++++++++++++++++++++++ 7 files changed, 441 insertions(+), 221 deletions(-) create mode 100644 v1/2miners_collector.py create mode 100644 v1/herominers_collector.py create mode 100644 v1/kryptex_collector.py delete mode 100644 v1/kryptex_halfhour_monitor.py delete mode 100644 v1/miner_2miners_collector.py create mode 100644 v1/run_collectors.py create mode 100644 v1/vipor_collector.py diff --git a/v1/2miners_collector.py b/v1/2miners_collector.py new file mode 100644 index 0000000..f18579a --- /dev/null +++ b/v1/2miners_collector.py @@ -0,0 +1,92 @@ +import redis +import requests +import pymysql +from datetime import datetime, timedelta + +# ---------------- Redis 配置 ---------------- +r = redis.Redis(host='127.0.0.1', port=6379, db=7, decode_responses=True) + +# ---------------- MySQL 配置 ---------------- +conn = pymysql.connect( + host='127.0.0.1', + user='root', + password='123456', + database='pool', + port=25600, + charset='utf8mb4' +) +cursor = conn.cursor() + +# ---------------- API 配置 ---------------- +API_URLS = { + "NEXA": "https://nexa.2miners.com/api/accounts/{wallet}", + "XNA": "https://xna.2miners.com/api/accounts/{wallet}", + "CLORE": "https://clore.2miners.com/api/accounts/{wallet}", + "RVN": "https://rvn.2miners.com/api/accounts/{wallet}", + "ERG": "https://erg.2miners.com/api/accounts/{wallet}" +} + +# ---------------- 工具函数 ---------------- +def to_mhs(value): + """算力转换为 MH/s""" + return float(value) / 1_000_000 if value else 0.0 + +def get_half_hour_time(now): + """取最近半点整时间""" + minute = 0 if now.minute < 30 else 30 + return now.replace(minute=minute, second=0, microsecond=0) + +# ---------------- 主逻辑(单次执行) ---------------- +def run_once(): + keys = r.keys("*") + half_time = get_half_hour_time(datetime.now()) + + for key in keys: + try: + parts = key.split(":") + if len(parts) != 4: + continue + + pool_name, wallet, coin, algo = parts + if pool_name.lower() != "2miners": + continue # 只处理2miners + + coin_upper = coin.upper() + if coin_upper not in API_URLS: + print(f"[跳过] 不支持币种 {coin_upper}") + continue + + # NEXA 需要补全前缀 + wallet_api = f"nexa:{wallet}" if coin_upper=="NEXA" and not wallet.startswith("nexa:") else wallet + + url = API_URLS[coin_upper].format(wallet=wallet_api) + resp = requests.get(url, timeout=10) + resp.raise_for_status() + data = resp.json() + + workers = data.get("workers", {}) + if not workers: + print(f"[无矿机] {coin_upper} {wallet}") + continue + + for miner, info in workers.items(): + hashrate = to_mhs(info.get("hr")) + print(f"写入 → {coin_upper} | {miner} | {hashrate:.2f} MH/s") + + sql = """ + INSERT INTO `2miners` + (datetime,pool_name,wallet,miner,hashrate,coin,algorithm) + VALUES (%s,%s,%s,%s,%s,%s,%s) + """ + cursor.execute(sql,(half_time,pool_name,wallet,miner,hashrate,coin_upper,algo)) + conn.commit() + + except Exception as e: + print(f"[错误] {key} ->", e) + +# ---------------- 执行一次后退出 ---------------- +if __name__ == "__main__": + run_once() + cursor.close() + conn.close() + print("✔ 本次采集结束") \ No newline at end of file diff --git a/v1/herominers_collector.py b/v1/herominers_collector.py new file mode 100644 index 0000000..2ab7c30 --- /dev/null +++ b/v1/herominers_collector.py @@ -0,0 +1,103 @@ +import redis +import requests +import pymysql +from datetime import datetime, timedelta + +# ---------------- Redis 配置 ---------------- +REDIS_HOST = "127.0.0.1" +REDIS_PORT = 6379 +REDIS_DB = 7 + +# ---------------- MySQL 配置 ---------------- +MYSQL_HOST = "127.0.0.1" +MYSQL_PORT = 25600 +MYSQL_USER = "root" +MYSQL_PASS = "123456" +MYSQL_DB = "pool" +TABLE_NAME = "`herominers`" + +# ---------------- 连接 Redis ---------------- +r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB, decode_responses=True) + +# ---------------- 连接 MySQL ---------------- +conn = pymysql.connect(host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER, + password=MYSQL_PASS, database=MYSQL_DB, charset='utf8mb4') +cursor = conn.cursor() + +# ---------------- 币种 -> 域名映射 ---------------- +domain_map = { + "CFX": "conflux.herominers.com", + "IRON": "ironfish.herominers.com", + "KLS": "karlsen.herominers.com", + "RVN": "ravencoin.herominers.com", + "ERG": "ergo.herominers.com", + "XEL": "xelis.herominers.com", +} + +# ---------------- 从 Redis 获取矿池配置 ---------------- +# 格式: herominers:wallet:COIN:ALGORITHM +keys = r.keys("herominers:*") + +def round_half_hour(dt=None): + """返回最近的半点时间""" + dt = dt or datetime.now() + minute = 30 if dt.minute >= 30 else 0 + return dt.replace(minute=minute, second=0, microsecond=0) + +def get_api_url(coin, wallet): + domain = domain_map.get(coin) + if coin == "CFX" and not wallet.startswith("cfx:"): + wallet = f"cfx:{wallet}" + return f"https://{domain}/api/stats_address?address={wallet}&recentBlocksAmount=20&longpoll=false" + +def fetch_herominers_data(pool_name, coin, wallet, algorithm): + url = get_api_url(coin, wallet) + try: + resp = requests.get(url, timeout=10) + resp.raise_for_status() + data = resp.json() + + workers = data.get("workers", []) + if not workers: + print(f"{coin} | {wallet} | {pool_name} ⚠ 当前没有任何在线矿机") + return + + for worker in workers: + name = worker.get("name", "unknown") + hashrate = worker.get("hashrate", 0) / 1_000_000 # 转 MH/s + + # 写入 MySQL + sql = f"""INSERT INTO {TABLE_NAME} + (datetime, pool_name, wallet, miner, hashrate, coin, algorithm) + VALUES (%s, %s, %s, %s, %s, %s, %s)""" + cursor.execute(sql, ( + round_half_hour(), + pool_name, + wallet, + name, + hashrate, + coin, + algorithm + )) + conn.commit() + print(f"{coin} | {pool_name} | {name}: {hashrate:.2f} MH/s 写入成功") + + except requests.exceptions.HTTPError as e: + print(f"{coin} | {wallet} | {pool_name} 请求失败 HTTP 错误:", e) + except Exception as e: + print(f"{coin} | {wallet} | {pool_name} 请求失败:", e) + + +if __name__ == "__main__": + for key in keys: + parts = key.split(":") + if len(parts) != 4: + continue + _, wallet, coin, algorithm = parts + pool_name = parts[0] # Redis key第一部分作为矿池名 + + fetch_herominers_data(pool_name, coin, wallet, algorithm) + +# 关闭连接 +cursor.close() +conn.close() \ No newline at end of file diff --git a/v1/kryptex_collector.py b/v1/kryptex_collector.py new file mode 100644 index 0000000..6191b2f --- /dev/null +++ b/v1/kryptex_collector.py @@ -0,0 +1,96 @@ +import redis +import requests +from bs4 import BeautifulSoup +import pymysql +from datetime import datetime +import time + +# Redis 配置 +r = redis.Redis(host='127.0.0.1', port=6379, db=7, decode_responses=True) + +# MySQL 配置 +conn = pymysql.connect( + host='127.0.0.1', + user='root', + password='123456', + database='pool', + port=25600, + charset='utf8mb4' +) +cursor = conn.cursor() + +# URL 模板 +BASE_URL = "https://pool.kryptex.com/zh-cn/{coin}/miner/stats/{wallet}" +HEADERS = {"User-Agent": "Mozilla/5.0"} + + +def to_mhs(value: str): + """自动将各种单位算力字符串转换成 MH/s 纯数字值""" + value = value.strip().lower() + if "gh/s" in value: return float(value.replace("gh/s", "")) * 1000 + if "mh/s" in value: return float(value.replace("mh/s", "")) + if "kh/s" in value: return float(value.replace("kh/s", "")) / 1000 + if "h/s" in value: return float(value.replace("h/s", "")) / 1_000_000 + return 0.0 + + +def get_half_hour_time(now): + """取最近半点整时间 如 10:08→10:00,10:40→10:30""" + minute = 0 if now.minute < 30 else 30 + return now.replace(minute=minute, second=0, microsecond=0) + + +def query_and_insert(): + keys = r.keys("*") + half_hour_time = get_half_hour_time(datetime.now()) + + for key in keys: + try: + parts = key.split(":") + if len(parts) != 4: + continue + + pool_name, wallet, coin, algo = parts + if pool_name != "pool.kryptex": + continue + + coin_lower = coin.lower() + coin_for_url = "xtm-c29" if coin_lower == "xtm" else coin_lower + url = BASE_URL.format(coin=coin_for_url, wallet=wallet.lower()) + + resp = requests.get(url, headers=HEADERS, timeout=10) + resp.raise_for_status() + + soup = BeautifulSoup(resp.text, "html.parser") + tbody = soup.find("tbody") + if not tbody: + print(f"❗无矿工数据 {wallet} {coin}") + continue + + for row in tbody.find_all("tr"): + try: + worker = row.find("th").find("a").text.strip() + mhs = to_mhs(row.find_all("td")[5].text.strip()) + + print(f"[OK] {coin}:{wallet} {worker} -> {mhs:.2f} MH/s") + + sql = """ + INSERT INTO `pool.kryptex` + (datetime, pool_name, wallet, miner, hashrate, coin, algorithm) + VALUES (%s,%s,%s,%s,%s,%s,%s) + """ + cursor.execute(sql, (half_hour_time, pool_name, wallet, worker, mhs, coin.upper(), algo)) + conn.commit() + + except Exception as e: + print("解析矿工失败 =>", e) + + except Exception as e: + print("请求失败 =>", e) + + +if __name__ == "__main__": + query_and_insert() + cursor.close() + conn.close() + print("✔ 采集完成,程序已退出") \ No newline at end of file diff --git a/v1/kryptex_halfhour_monitor.py b/v1/kryptex_halfhour_monitor.py deleted file mode 100644 index b9b8208..0000000 --- a/v1/kryptex_halfhour_monitor.py +++ /dev/null @@ -1,113 +0,0 @@ -import redis -import requests -from bs4 import BeautifulSoup -import pymysql -from datetime import datetime, timedelta -import time - -# Redis 配置 -r = redis.Redis(host='127.0.0.1', port=6379, db=7, decode_responses=True) - -# MySQL 配置 -conn = pymysql.connect( - host='127.0.0.1', - user='root', - password='123456', - database='pool', - port=25600, - charset='utf8mb4' -) -cursor = conn.cursor() - -# URL 模板 -BASE_URL = "https://pool.kryptex.com/zh-cn/{coin}/miner/stats/{wallet}" -HEADERS = {"User-Agent": "Mozilla/5.0"} - - -def to_mhs(value: str): - """自动把算力字符串转换成 MH/s 纯数字""" - value = value.strip().lower() - if "gh/s" in value: - return float(value.replace("gh/s", "").strip()) * 1000 - if "mh/s" in value: - return float(value.replace("mh/s", "").strip()) - if "kh/s" in value: - return float(value.replace("kh/s", "").strip()) / 1000 - if "h/s" in value: - return float(value.replace("h/s", "").strip()) / 1_000_000 - return 0.0 - - -def get_half_hour_time(now): - """取最近半点时间""" - minute = 0 if now.minute < 30 else 30 - return now.replace(minute=minute, second=0, microsecond=0) - - -def query_and_insert(): - keys = r.keys("*") # 遍历所有 key - half_hour_time = get_half_hour_time(datetime.now()) - - for key in keys: - try: - parts = key.split(":") - if len(parts) != 4: - continue - pool_name, wallet, coin, algo = parts - coin_lower = coin.lower() - # xtm 特殊处理 - coin_for_url = "xtm-c29" if coin_lower == "xtm" else coin_lower - url = BASE_URL.format(coin=coin_for_url, wallet=wallet.lower()) - - resp = requests.get(url, headers=HEADERS, timeout=10) - resp.raise_for_status() - soup = BeautifulSoup(resp.text, "html.parser") - tbody = soup.find("tbody") - if not tbody: - print(f"池:{pool_name} 币种:{coin} 钱包:{wallet} 没有矿工数据") - continue - rows = tbody.find_all("tr") - if not rows: - print(f"池:{pool_name} 币种:{coin} 钱包:{wallet} 没有矿工数据") - continue - - for row in rows: - try: - worker = row.find("th").find("a").text.strip() - tds = row.find_all("td") - # 10 分钟算力列 - hashrate_raw = tds[5].text.strip() - mhs = to_mhs(hashrate_raw) - - # 输出日志 - print(f"池:{pool_name} 币种:{coin} 钱包:{wallet} 矿工:{worker} -> {mhs:.2f} MH/s") - - # 写入数据库 - sql = """ - INSERT INTO `pool.kryptex` - (datetime, pool_name, wallet, miner, hashrate, coin, algorithm) - VALUES (%s,%s,%s,%s,%s,%s,%s) - """ - cursor.execute(sql, (half_hour_time, pool_name, wallet, worker, mhs, coin.upper(), algo)) - conn.commit() - except Exception as e: - print("解析矿工数据失败:", e) - except Exception as e: - print("请求或解析失败:", e) - - -if __name__ == "__main__": - try: - while True: - now = datetime.now() - next_minute = 30 if now.minute < 30 else 60 - wait_seconds = (next_minute - now.minute) * 60 - now.second - if wait_seconds > 0: - print(f"等待 {wait_seconds} 秒到下一个半点...") - time.sleep(wait_seconds) - query_and_insert() - except KeyboardInterrupt: - print("程序结束") - finally: - cursor.close() - conn.close() \ No newline at end of file diff --git a/v1/miner_2miners_collector.py b/v1/miner_2miners_collector.py deleted file mode 100644 index ef3bed2..0000000 --- a/v1/miner_2miners_collector.py +++ /dev/null @@ -1,108 +0,0 @@ -import redis -import requests -import pymysql -from datetime import datetime, timedelta -import time - -# ---------------- Redis 配置 ---------------- -r = redis.Redis(host='127.0.0.1', port=6379, db=7, decode_responses=True) - -# ---------------- MySQL 配置 ---------------- -conn = pymysql.connect( - host='127.0.0.1', - user='root', - password='123456', - database='pool', - port=25600, - charset='utf8mb4' -) -cursor = conn.cursor() - -# ---------------- API 配置 ---------------- -API_URLS = { - "NEXA": "https://nexa.2miners.com/api/accounts/{wallet}", - "XNA": "https://xna.2miners.com/api/accounts/{wallet}", - "CLORE": "https://clore.2miners.com/api/accounts/{wallet}", - "RVN": "https://rvn.2miners.com/api/accounts/{wallet}", - "ERG": "https://erg.2miners.com/api/accounts/{wallet}" -} - -# ---------------- 工具函数 ---------------- -def to_mhs(value): - """算力转换为 MH/s""" - return float(value) / 1_000_000 if value else 0.0 - -def get_half_hour_time(now): - """取最近半点时间""" - minute = 0 if now.minute < 30 else 30 - return now.replace(minute=minute, second=0, microsecond=0) - -# ---------------- 主逻辑 ---------------- -def query_and_insert(): - keys = r.keys("*") # 遍历所有 key - half_hour_time = get_half_hour_time(datetime.now()) - - for key in keys: - try: - # key 格式: pool_name:wallet:coin:algo - parts = key.split(":") - if len(parts) != 4: - continue - pool_name, wallet, coin, algo = parts - if pool_name.lower() != "2miners": - continue # 不是 2Miners 的数据就跳过 - coin_upper = coin.upper() - - if coin_upper not in API_URLS: - print(f"币种 {coin_upper} 不在支持列表中,跳过") - continue - - # NEXA 特殊处理,其他币种直接用 wallet - if coin_upper == "NEXA" and not wallet.lower().startswith("nexa:"): - wallet_for_api = f"nexa:{wallet}" - else: - wallet_for_api = wallet - - url = API_URLS[coin_upper].format(wallet=wallet_for_api) - resp = requests.get(url, timeout=10) - resp.raise_for_status() - data = resp.json() - - # 遍历矿机 - workers = data.get("workers", {}) - if not workers: - print(f"{coin_upper} 钱包 {wallet} 没有矿机数据") - continue - - for miner_name, miner_data in workers.items(): - hashrate = to_mhs(miner_data.get("hr")) - print(f"池:{pool_name} 币种:{coin_upper} 钱包:{wallet} 矿机:{miner_name} -> {hashrate:.2f} MH/s") - - # 写入数据库 - sql = """ - INSERT INTO `2miners` - (datetime, pool_name, wallet, miner, hashrate, coin, algorithm) - VALUES (%s,%s,%s,%s,%s,%s,%s) - """ - cursor.execute(sql, (half_hour_time, pool_name, wallet, miner_name, hashrate, coin_upper, algo)) - conn.commit() - - except Exception as e: - print("请求或解析失败:", e) - -# ---------------- 定时执行 ---------------- -if __name__ == "__main__": - try: - while True: - now = datetime.now() - next_minute = 30 if now.minute < 30 else 60 - wait_seconds = (next_minute - now.minute) * 60 - now.second - if wait_seconds > 0: - print(f"等待 {wait_seconds} 秒到下一个半点...") - time.sleep(wait_seconds) - query_and_insert() - except KeyboardInterrupt: - print("程序结束") - finally: - cursor.close() - conn.close() \ No newline at end of file diff --git a/v1/run_collectors.py b/v1/run_collectors.py new file mode 100644 index 0000000..bc0d050 --- /dev/null +++ b/v1/run_collectors.py @@ -0,0 +1,45 @@ +import os +import time +from datetime import datetime + +# 你的 Python 路径(如无需特定可直接 python3) +PYTHON = "/usr/bin/python3" + +# 要执行的脚本 +SCRIPTS = [ + "2miners_collector.py", + "kryptex_collector.py", + "herominers_collector.py", + "vipor_collector.py" +] + + +def get_half_round_sleep(): + now = datetime.now() + minute = now.minute + + # 计算距离下个半点的秒数 + if minute < 30: + target = now.replace(minute=30, second=0, microsecond=0) + else: + target = now.replace(hour=now.hour + 1, minute=0, second=0, microsecond=0) + + return int((target - now).total_seconds()) + + +def run_scripts(): + print("\n================= 开始执行采集任务 =================") + for script in SCRIPTS: + print(f"▶ 正在执行 {script} ...") + os.system(f"{PYTHON} {script}") # 如果脚本不在同目录请加绝对路径 + print(f"✔ 执行完成 {script}\n") + + print("================= 当前轮次已完成 =================\n") + + +if __name__ == "__main__": + while True: + run_scripts() + sleep_time = get_half_round_sleep() + print(f"⏳ 等待 {sleep_time} 秒进入下个半点执行...\n") + time.sleep(sleep_time) \ No newline at end of file diff --git a/v1/vipor_collector.py b/v1/vipor_collector.py new file mode 100644 index 0000000..949f877 --- /dev/null +++ b/v1/vipor_collector.py @@ -0,0 +1,105 @@ +import redis +import requests +import pymysql +from datetime import datetime + +# ---------------- Redis 配置 ---------------- +REDIS_HOST = "127.0.0.1" +REDIS_PORT = 6379 +REDIS_DB = 7 + +# ---------------- MySQL 配置 ---------------- +MYSQL_HOST = "127.0.0.1" +MYSQL_PORT = 25600 +MYSQL_USER = "root" +MYSQL_PASS = "123456" +MYSQL_DB = "pool" +TABLE_NAME = "`vipor.net`" + +# ---------------- 连接 Redis ---------------- +r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB, decode_responses=True) + +# ---------------- 连接 MySQL ---------------- +conn = pymysql.connect(host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER, + password=MYSQL_PASS, database=MYSQL_DB, charset='utf8mb4') +cursor = conn.cursor() + +# ---------------- 从 Redis 获取矿池配置 ---------------- +# 格式: pool_name:wallet:COIN:ALGORITHM +keys = r.keys("vipor.net:*") + +def get_half_hour_time(): + """返回最近半点时间""" + now = datetime.now() + minute = 30 if now.minute >= 30 else 0 + return now.replace(minute=minute, second=0, microsecond=0) + +def fetch_vipor_data(pool_name, coin, wallet, algorithm, api_pool): + url = f"https://restapi.vipor.net/api/pools/{api_pool}/miners/{wallet}" + try: + resp = requests.get(url, timeout=10) + resp.raise_for_status() + data = resp.json() + + # 优先 performance.workers + workers = data.get("performance", {}).get("workers") + if not workers: + workers = data.get("workers") or data.get("miners") or {} + + if not workers: + print(f"{coin} | {api_pool} | {wallet} ⚠ 当前没有任何在线矿机") + return + + for miner, info in workers.items(): + hashrate = info.get("hashrate", 0) or 0 + hashrate_mhs = hashrate / 1_000_000 # 转为 MH/s + + # 插入 MySQL + sql = f"""INSERT INTO {TABLE_NAME} + (datetime, pool_name, wallet, miner, hashrate, coin, algorithm) + VALUES (%s, %s, %s, %s, %s, %s, %s)""" + cursor.execute(sql, ( + get_half_hour_time(), + pool_name, # 这里使用 Redis key 的第一部分 + wallet, + miner, + hashrate_mhs, + coin, + algorithm + )) + conn.commit() + + print(f"{coin} | {api_pool} | {miner}: {hashrate_mhs:.2f} MH/s 写入成功") + + except requests.exceptions.HTTPError as e: + print(f"{coin} | {api_pool} | {wallet} 请求失败 HTTP 错误:", e) + except Exception as e: + print(f"{coin} | {api_pool} | {wallet} 请求失败:", e) + + +if __name__ == "__main__": + for key in keys: + parts = key.split(":") + if len(parts) != 4: + continue + pool_name, wallet, coin, algorithm = parts + + # 构造普通和 solo pool 名(用于请求 API,不影响存入数据库的 pool_name) + api_pools = [] + if coin == "NEXA": + api_pools = ["nexa", "nexa_solo"] + elif coin == "XEL": + api_pools = ["xelis", "xelis_solo"] + elif coin == "XNA": + api_pools = ["neurai", "neurai_solo"] + elif coin == "CLORE": + api_pools = ["clore", "clore_solo"] + else: + api_pools = ["default", "default_solo"] + + for api_pool in api_pools: + fetch_vipor_data(pool_name, coin, wallet, algorithm, api_pool) + +# 关闭连接 +cursor.close() +conn.close() \ No newline at end of file