上传文件至 /
This commit is contained in:
205
main.py
Normal file
205
main.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from seleniumwire import webdriver
|
||||||
|
from selenium.webdriver.chrome.options import Options
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
AUTH_FILE = "auth.txt"
|
||||||
|
TARGET_URL = "https://live-liveapi.vzan.com/api/v1/topic/get_topicdatas"
|
||||||
|
RESET_URL = "https://live.vzan.com/NLive/ReSetStatus"
|
||||||
|
USER_DATA_DIR = os.path.join(os.getcwd(), "chrome_user_data")
|
||||||
|
|
||||||
|
# -------------------- 日志 -------------------- #
|
||||||
|
def log(msg, level="INFO"):
|
||||||
|
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] [{level}] {msg}")
|
||||||
|
|
||||||
|
# -------------------- Auth -------------------- #
|
||||||
|
def load_auth():
|
||||||
|
if os.path.exists(AUTH_FILE):
|
||||||
|
with open(AUTH_FILE, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
raise Exception(f"{AUTH_FILE} 不存在,请先生成认证信息")
|
||||||
|
|
||||||
|
# -------------------- 获取话题 -------------------- #
|
||||||
|
def get_topics(headers, cookies):
|
||||||
|
payload = {
|
||||||
|
"keyword": "", "keytype": 1, "state": -2, "psize": 7, "tag": 0, "page": 1,
|
||||||
|
"livescene": -1, "typeid": -1, "types": -1, "isOnShelf": -1,
|
||||||
|
"starttime": "", "endtime": "", "chanid": 0, "isHQOut": 0, "isGHHQOut": 0
|
||||||
|
}
|
||||||
|
request_headers = headers.copy()
|
||||||
|
request_headers["Cookie"] = cookies
|
||||||
|
try:
|
||||||
|
resp = requests.post(TARGET_URL, headers=request_headers, json=payload, timeout=10)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
if data.get("code") != 0:
|
||||||
|
log(f"获取话题API返回错误码: {data.get('code')} 消息: {data.get('msg')}", "ERROR")
|
||||||
|
return []
|
||||||
|
topic_list = data.get("dataObj", {}).get("list", [])
|
||||||
|
log(f"共获取到 {len(topic_list)} 条话题", "INFO")
|
||||||
|
return topic_list
|
||||||
|
except Exception as e:
|
||||||
|
log(f"获取话题列表失败: {e}", "ERROR")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# -------------------- 重置状态0话题 -------------------- #
|
||||||
|
def reset_status_to_minus1(tid, title, headers, cookies):
|
||||||
|
request_headers = headers.copy()
|
||||||
|
request_headers["Cookie"] = cookies
|
||||||
|
request_headers["content-type"] = "application/x-www-form-urlencoded"
|
||||||
|
data = {"tid": tid, "pstate": "-1"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.post(RESET_URL, headers=request_headers, data=data, timeout=10)
|
||||||
|
resp.raise_for_status()
|
||||||
|
result = resp.json()
|
||||||
|
if result.get("isok"):
|
||||||
|
log(f"话题 {tid} ({title}) 状态0已重置为-1", "SUCCESS")
|
||||||
|
else:
|
||||||
|
log(f"话题 {tid} ({title}) 重置失败: {result.get('Msg')} (code: {result.get('code')})", "ERROR")
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
log(f"重置话题 {tid} ({title}) 出错: {e}", "ERROR")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# -------------------- 浏览器操作 -------------------- #
|
||||||
|
def find_element_with_retry(driver, selectors, timeout=10):
|
||||||
|
for selector in selectors:
|
||||||
|
try:
|
||||||
|
el = WebDriverWait(driver, timeout).until(
|
||||||
|
EC.presence_of_element_located((By.XPATH, selector))
|
||||||
|
)
|
||||||
|
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", el)
|
||||||
|
return el
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
def click_element_js(driver, element):
|
||||||
|
try:
|
||||||
|
driver.execute_script("arguments[0].click();", element)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log(f"JS点击失败: {e}", "ERROR")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def wait_status_minus1(tid, headers, cookies, retries=10, interval=1):
|
||||||
|
"""确认话题状态为-1再进行关闭页面"""
|
||||||
|
for _ in range(retries):
|
||||||
|
topics = get_topics(headers, cookies)
|
||||||
|
for t in topics:
|
||||||
|
if t.get("id") == tid and t.get("status") == -1:
|
||||||
|
return True
|
||||||
|
time.sleep(interval)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def automate_browser(topic_id, title, driver, main_window, headers, cookies):
|
||||||
|
try:
|
||||||
|
driver.switch_to.new_window('tab')
|
||||||
|
url = f"https://live.vzan.com/admin/index.html?zbid=951423954&v=638941334981302363#/TopicManage/BaseSetting?topicId={topic_id}"
|
||||||
|
log(f"打开话题ID: {topic_id} ({title})", "INFO")
|
||||||
|
driver.get(url)
|
||||||
|
WebDriverWait(driver, 15).until(lambda d: str(topic_id) in d.current_url)
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# 隐藏tip遮挡
|
||||||
|
try:
|
||||||
|
tip = driver.find_element(By.CSS_SELECTOR, "div.tip-wrap")
|
||||||
|
if tip.is_displayed():
|
||||||
|
driver.execute_script("arguments[0].style.display='none';", tip)
|
||||||
|
log(f"话题 {topic_id} ({title}) tip-wrap遮挡已隐藏", "INFO")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
fake_live_button = find_element_with_retry(driver, [
|
||||||
|
"//button[contains(text(), '伪直播')]",
|
||||||
|
"//span[contains(text(), '伪直播')]/.."
|
||||||
|
], timeout=10)
|
||||||
|
if fake_live_button:
|
||||||
|
click_element_js(driver, fake_live_button)
|
||||||
|
log(f"话题 {topic_id} ({title}) 已点击伪直播按钮", "INFO")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
save_button = find_element_with_retry(driver, [
|
||||||
|
"//button[contains(@class,'create-live-btn')]//span[contains(normalize-space(text()),'保 存')]",
|
||||||
|
"//button[contains(text(),'保 存')]"
|
||||||
|
], timeout=10)
|
||||||
|
if save_button:
|
||||||
|
click_element_js(driver, save_button)
|
||||||
|
log(f"话题 {topic_id} ({title}) 已点击保存按钮", "INFO")
|
||||||
|
time.sleep(2)
|
||||||
|
else:
|
||||||
|
log(f"话题 {topic_id} ({title}) 未找到保存按钮,截图保存", "ERROR")
|
||||||
|
driver.save_screenshot(f"save_not_found_{topic_id}.png")
|
||||||
|
driver.close()
|
||||||
|
driver.switch_to.window(main_window)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 等待状态真正变为-1再关闭页面
|
||||||
|
if wait_status_minus1(topic_id, headers, cookies):
|
||||||
|
log(f"话题 {topic_id} ({title}) 状态已确认为-1,浏览器操作完成", "SUCCESS")
|
||||||
|
else:
|
||||||
|
log(f"话题 {topic_id} ({title}) 状态未生效,可能需要手动检查", "ERROR")
|
||||||
|
|
||||||
|
driver.close()
|
||||||
|
driver.switch_to.window(main_window)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log(f"处理话题 {topic_id} ({title}) 时出错: {e}", "ERROR")
|
||||||
|
driver.close()
|
||||||
|
driver.switch_to.window(main_window)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# -------------------- 主循环 -------------------- #
|
||||||
|
def main_loop():
|
||||||
|
auth = load_auth()
|
||||||
|
headers, cookies = auth["headers"], auth["cookies"]
|
||||||
|
|
||||||
|
chrome_options = Options()
|
||||||
|
chrome_options.add_argument("--start-maximized")
|
||||||
|
chrome_options.add_argument(f"--user-data-dir={USER_DATA_DIR}")
|
||||||
|
chrome_options.add_argument("--headless=new")
|
||||||
|
chrome_options.add_argument("--disable-gpu")
|
||||||
|
chrome_options.add_argument("--window-size=1920,1080")
|
||||||
|
|
||||||
|
driver = webdriver.Chrome(options=chrome_options)
|
||||||
|
driver.get("https://live.vzan.com/admin/index.html")
|
||||||
|
main_window = driver.current_window_handle
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
topics = get_topics(headers, cookies)
|
||||||
|
if not topics:
|
||||||
|
time.sleep(10)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 状态为0的先重置为-1
|
||||||
|
zero_topics = [t for t in topics if t.get("status") == 0]
|
||||||
|
for t in zero_topics:
|
||||||
|
tid = t.get("id")
|
||||||
|
title = t.get("title", "")
|
||||||
|
if tid:
|
||||||
|
reset_status_to_minus1(tid, title, headers, cookies)
|
||||||
|
|
||||||
|
# 状态为-1的处理浏览器操作
|
||||||
|
minus1_topics = [t for t in topics if t.get("status") == -1 or t.get("status") == 0]
|
||||||
|
for t in minus1_topics:
|
||||||
|
tid = t.get("id")
|
||||||
|
title = t.get("title", "")
|
||||||
|
if tid:
|
||||||
|
automate_browser(tid, title, driver, main_window, headers, cookies)
|
||||||
|
|
||||||
|
log("等待10秒再次轮询...", "INFO")
|
||||||
|
time.sleep(10)
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
log("程序启动,动态检测话题状态并处理", "INFO")
|
||||||
|
main_loop()
|
||||||
32
requirements.txt
Normal file
32
requirements.txt
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
attrs==25.3.0
|
||||||
|
blinker==1.5
|
||||||
|
Brotli==1.1.0
|
||||||
|
certifi==2025.8.3
|
||||||
|
cffi==2.0.0
|
||||||
|
charset-normalizer==3.4.3
|
||||||
|
cryptography==46.0.1
|
||||||
|
h11==0.16.0
|
||||||
|
h2==4.3.0
|
||||||
|
hpack==4.1.0
|
||||||
|
hyperframe==6.1.0
|
||||||
|
idna==3.10
|
||||||
|
kaitaistruct==0.11
|
||||||
|
outcome==1.3.0.post0
|
||||||
|
pyasn1==0.6.1
|
||||||
|
pycparser==2.23
|
||||||
|
pydivert==2.1.0
|
||||||
|
pyOpenSSL==25.3.0
|
||||||
|
pyparsing==3.2.5
|
||||||
|
PySocks==1.7.1
|
||||||
|
requests==2.32.5
|
||||||
|
selenium==4.35.0
|
||||||
|
selenium-wire==5.1.0
|
||||||
|
sniffio==1.3.1
|
||||||
|
sortedcontainers==2.4.0
|
||||||
|
trio==0.30.0
|
||||||
|
trio-websocket==0.12.2
|
||||||
|
typing_extensions==4.14.1
|
||||||
|
urllib3==2.5.0
|
||||||
|
websocket-client==1.8.0
|
||||||
|
wsproto==1.2.0
|
||||||
|
zstandard==0.25.0
|
||||||
Reference in New Issue
Block a user