11import os
22import random
33import time
4+ import functools
5+ import sys
46
57from loguru import logger
68from playwright .sync_api import sync_playwright
79from tabulate import tabulate
810
11+
12+ def retry_decorator (retries = 3 ):
13+ def decorator (func ):
14+ @functools .wraps (func )
15+ def wrapper (* args , ** kwargs ):
16+ for attempt in range (retries ):
17+ try :
18+ return func (* args , ** kwargs )
19+ except Exception as e :
20+ if attempt == retries - 1 : # 最后一次尝试
21+ logger .error (f"函数 { func .__name__ } 最终执行失败: { str (e )} " )
22+ logger .warning (f"函数 { func .__name__ } 第 { attempt + 1 } /{ retries } 次尝试失败: { str (e )} " )
23+ time .sleep (1 )
24+ return None
25+
26+ return wrapper
27+
28+ return decorator
29+
30+
31+ os .environ .pop ("DISPLAY" , None )
32+ os .environ .pop ("DYLD_LIBRARY_PATH" , None )
33+
934USERNAME = os .environ .get ("USERNAME" )
1035PASSWORD = os .environ .get ("PASSWORD" )
1136
@@ -21,7 +46,7 @@ def __init__(self) -> None:
2146 self .page .goto (HOME_URL )
2247
2348 def login (self ):
24- logger .info ("Login " )
49+ logger .info ("开始登录 " )
2550 self .page .click (".login-button .d-button-label" )
2651 time .sleep (2 )
2752 self .page .fill ("#login-account-name" , USERNAME )
@@ -32,36 +57,78 @@ def login(self):
3257 time .sleep (10 )
3358 user_ele = self .page .query_selector ("#current-user" )
3459 if not user_ele :
35- logger .error ("Login failed " )
60+ logger .error ("登录失败 " )
3661 return False
3762 else :
38- logger .info ("Login success " )
63+ logger .info ("登录成功 " )
3964 return True
4065
4166 def click_topic (self ):
42- for topic in self .page .query_selector_all ("#list-area .title" ):
43- logger .info ("Click topic: " + topic .get_attribute ("href" ))
44- page = self .context .new_page ()
45- page .goto (HOME_URL + topic .get_attribute ("href" ))
46- time .sleep (3 )
47- if random .random () < 0.02 : # 100 * 0.02 * 30 = 60
48- self .click_like (page )
49- time .sleep (3 )
50- page .close ()
67+ topic_list = self .page .query_selector_all ("#list-area .title" )
68+ logger .info (f"发现 { len (topic_list )} 个主题帖" )
69+ for topic in topic_list :
70+ self .click_one_topic (topic .get_attribute ("href" ))
71+
72+ @retry_decorator ()
73+ def click_one_topic (self , topic_url ):
74+ page = self .context .new_page ()
75+ page .goto (HOME_URL + topic_url )
76+ if random .random () < 0.3 : # 0.3 * 30 = 9
77+ self .click_like (page )
78+ self .browse_post (page )
79+ page .close ()
80+
81+ def browse_post (self , page ):
82+ prev_url = None
83+ # 开始自动滚动,最多滚动10次
84+ for _ in range (10 ):
85+ # 随机滚动一段距离
86+ scroll_distance = random .randint (550 , 650 ) # 随机滚动 550-650 像素
87+ logger .info (f"向下滚动 { scroll_distance } 像素..." )
88+ page .evaluate (f"window.scrollBy(0, { scroll_distance } )" )
89+ logger .info (f"已加载页面: { page .url } " )
90+
91+ if random .random () < 0.03 : # 33 * 4 = 132
92+ logger .success ("随机退出浏览" )
93+ break
94+
95+ # 检查是否到达页面底部
96+ at_bottom = page .evaluate ("window.scrollY + window.innerHeight >= document.body.scrollHeight" )
97+ current_url = page .url
98+ if current_url != prev_url :
99+ prev_url = current_url
100+ elif at_bottom and prev_url == current_url :
101+ logger .success ("已到达页面底部,退出浏览" )
102+ break
103+
104+ # 动态随机等待
105+ wait_time = random .uniform (2 , 4 ) # 随机等待 2-4 秒
106+ logger .info (f"等待 { wait_time :.2f} 秒..." )
107+ time .sleep (wait_time )
51108
52109 def run (self ):
53110 if not self .login ():
54- return
111+ logger .error ("登录失败,程序终止" )
112+ sys .exit (1 ) # 使用非零退出码终止整个程序
55113 self .click_topic ()
56114 self .print_connect_info ()
57115
58116 def click_like (self , page ):
59- logger .info ("Click like" )
60- page .locator (".discourse-reactions-reaction-button" ).first .click ()
61- logger .info ("Like success" )
117+ try :
118+ # 专门查找未点赞的按钮
119+ like_button = page .locator ('.discourse-reactions-reaction-button[title="点赞此帖子"]' ).first
120+ if like_button :
121+ logger .info ("找到未点赞的帖子,准备点赞" )
122+ like_button .click ()
123+ logger .info ("点赞成功" )
124+ time .sleep (random .uniform (1 , 2 ))
125+ else :
126+ logger .info ("帖子可能已经点过赞了" )
127+ except Exception as e :
128+ logger .error (f"点赞失败: { str (e )} " )
62129
63130 def print_connect_info (self ):
64- logger .info ("Print connect info " )
131+ logger .info ("获取连接信息 " )
65132 page = self .context .new_page ()
66133 page .goto ("https://connect.linux.do/" )
67134 rows = page .query_selector_all ("table tr" )
0 commit comments