277 lines
9.6 KiB
Python
277 lines
9.6 KiB
Python
|
|
"""
|
|||
|
|
完整签到流程验证脚本
|
|||
|
|
1. 用缓存 cookie 验证登录
|
|||
|
|
2. 获取超话列表 (GET /ajax/profile/topicContent?tabid=231093_-_chaohua)
|
|||
|
|
3. 逐个签到 (GET /p/aj/general/button?api=...checkin&id={containerid})
|
|||
|
|
4. 汇总结果
|
|||
|
|
|
|||
|
|
Cookie 自动从 debug_cookies.json 加载,失效才重新扫码
|
|||
|
|
"""
|
|||
|
|
import re, json, time, sys, os, requests
|
|||
|
|
|
|||
|
|
WEIBO_HEADERS = {
|
|||
|
|
'User-Agent': (
|
|||
|
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
|
|||
|
|
'(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0'
|
|||
|
|
),
|
|||
|
|
'Referer': 'https://weibo.com/',
|
|||
|
|
'Accept': '*/*',
|
|||
|
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|||
|
|
}
|
|||
|
|
COOKIE_FILE = 'debug_cookies.json'
|
|||
|
|
|
|||
|
|
|
|||
|
|
def parse_jsonp(text):
|
|||
|
|
if not text: return None
|
|||
|
|
m = re.search(r'STK_\d+\s*\((.*)\)\s*;?\s*$', text, re.DOTALL)
|
|||
|
|
if m:
|
|||
|
|
try: return json.loads(m.group(1))
|
|||
|
|
except: pass
|
|||
|
|
m = re.search(r'\w+\s*\((.*)\)\s*;?\s*$', text, re.DOTALL)
|
|||
|
|
if m:
|
|||
|
|
try: return json.loads(m.group(1))
|
|||
|
|
except: pass
|
|||
|
|
try: return json.loads(text)
|
|||
|
|
except: return None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def save_cookies(cookies, uid, nick):
|
|||
|
|
with open(COOKIE_FILE, 'w', encoding='utf-8') as f:
|
|||
|
|
json.dump({'cookies': cookies, 'uid': uid, 'nick': nick, 'time': time.time()},
|
|||
|
|
f, ensure_ascii=False, indent=2)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def load_cookies():
|
|||
|
|
if not os.path.exists(COOKIE_FILE):
|
|||
|
|
return None, None, None
|
|||
|
|
try:
|
|||
|
|
with open(COOKIE_FILE, 'r', encoding='utf-8') as f:
|
|||
|
|
data = json.load(f)
|
|||
|
|
cookies = data.get('cookies', {})
|
|||
|
|
uid = data.get('uid', '')
|
|||
|
|
nick = data.get('nick', '')
|
|||
|
|
age = time.time() - data.get('time', 0)
|
|||
|
|
print(f" 从 {COOKIE_FILE} 加载 (uid={uid}, nick={nick}, {age/3600:.1f}h ago)")
|
|||
|
|
r = requests.get('https://weibo.com/ajax/side/cards', params={'count': 1},
|
|||
|
|
cookies=cookies, headers=WEIBO_HEADERS, timeout=10)
|
|||
|
|
if r.json().get('ok') == 1:
|
|||
|
|
print(f" ✅ Cookie 有效")
|
|||
|
|
return cookies, uid, nick
|
|||
|
|
print(f" ❌ Cookie 已失效")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f" 加载失败: {e}")
|
|||
|
|
return None, None, None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def qrcode_login():
|
|||
|
|
print(" 扫码登录...")
|
|||
|
|
sess = requests.Session()
|
|||
|
|
sess.headers.update(WEIBO_HEADERS)
|
|||
|
|
resp = sess.get('https://login.sina.com.cn/sso/qrcode/image',
|
|||
|
|
params={'entry': 'weibo', 'size': '180',
|
|||
|
|
'callback': f'STK_{int(time.time()*1000)}'}, timeout=10)
|
|||
|
|
data = parse_jsonp(resp.text)
|
|||
|
|
if not data or not isinstance(data.get('data'), dict):
|
|||
|
|
print(f" ❌ 生成二维码失败"); sys.exit(1)
|
|||
|
|
qrid = data['data']['qrid']
|
|||
|
|
image = data['data']['image']
|
|||
|
|
if image.startswith('//'): image = 'https:' + image
|
|||
|
|
with open('qrcode.png', 'wb') as f:
|
|||
|
|
f.write(requests.get(image, timeout=10).content)
|
|||
|
|
print(f" 请扫描 qrcode.png ...")
|
|||
|
|
alt_token = None
|
|||
|
|
last = None
|
|||
|
|
for i in range(120):
|
|||
|
|
time.sleep(2)
|
|||
|
|
resp = sess.get('https://login.sina.com.cn/sso/qrcode/check',
|
|||
|
|
params={'entry': 'weibo', 'qrid': qrid,
|
|||
|
|
'callback': f'STK_{int(time.time()*1000)}'}, timeout=10)
|
|||
|
|
d = parse_jsonp(resp.text)
|
|||
|
|
rc = d.get('retcode') if d else None
|
|||
|
|
if rc != last: print(f" [{i+1}] {last} -> {rc}"); last = rc
|
|||
|
|
if rc == 20000000 and isinstance(d.get('data'), dict) and d['data'].get('alt'):
|
|||
|
|
alt_token = d['data']['alt']; break
|
|||
|
|
if rc in (50114004, 50050002): sys.exit(1)
|
|||
|
|
if not alt_token: sys.exit(1)
|
|||
|
|
sso = requests.Session()
|
|||
|
|
sso.headers.update(WEIBO_HEADERS)
|
|||
|
|
resp = sso.get(
|
|||
|
|
f"https://login.sina.com.cn/sso/login.php?entry=weibo&returntype=TEXT"
|
|||
|
|
f"&crossdomain=1&cdult=3&domain=weibo.com&alt={alt_token}&savestate=30"
|
|||
|
|
f"&callback=STK_{int(time.time()*1000)}",
|
|||
|
|
allow_redirects=True, timeout=15)
|
|||
|
|
sso_data = parse_jsonp(resp.text)
|
|||
|
|
uid = str(sso_data.get('uid', ''))
|
|||
|
|
nick = sso_data.get('nick', '')
|
|||
|
|
for u in sso_data.get('crossDomainUrlList', []):
|
|||
|
|
if isinstance(u, str) and u.startswith('http'):
|
|||
|
|
try: sso.get(u, allow_redirects=True, timeout=10)
|
|||
|
|
except: pass
|
|||
|
|
cookies = {}
|
|||
|
|
for c in sso.cookies:
|
|||
|
|
if c.domain and 'weibo.com' in c.domain:
|
|||
|
|
cookies[c.name] = c.value
|
|||
|
|
print(f" ✅ uid={uid}, nick={nick}")
|
|||
|
|
save_cookies(cookies, uid, nick)
|
|||
|
|
return cookies, uid, nick
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_super_topics(sess, xsrf, uid):
|
|||
|
|
"""获取关注的超话列表"""
|
|||
|
|
topics = []
|
|||
|
|
page = 1
|
|||
|
|
while page <= 10:
|
|||
|
|
r = sess.get(
|
|||
|
|
'https://weibo.com/ajax/profile/topicContent',
|
|||
|
|
params={'tabid': '231093_-_chaohua', 'page': str(page)},
|
|||
|
|
headers={
|
|||
|
|
'Referer': f'https://weibo.com/u/page/follow/{uid}/231093_-_chaohua',
|
|||
|
|
'X-XSRF-TOKEN': xsrf,
|
|||
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|||
|
|
},
|
|||
|
|
timeout=10,
|
|||
|
|
)
|
|||
|
|
d = r.json()
|
|||
|
|
if d.get('ok') != 1:
|
|||
|
|
break
|
|||
|
|
topic_list = d.get('data', {}).get('list', [])
|
|||
|
|
if not topic_list:
|
|||
|
|
break
|
|||
|
|
for item in topic_list:
|
|||
|
|
title = item.get('topic_name', '') or item.get('title', '')
|
|||
|
|
# 从 oid "1022:100808xxx" 提取 containerid
|
|||
|
|
containerid = ''
|
|||
|
|
oid = item.get('oid', '')
|
|||
|
|
m = re.search(r'100808[0-9a-fA-F]+', oid)
|
|||
|
|
if m:
|
|||
|
|
containerid = m.group(0)
|
|||
|
|
if not containerid:
|
|||
|
|
scheme = item.get('scheme', '')
|
|||
|
|
m = re.search(r'100808[0-9a-fA-F]+', scheme)
|
|||
|
|
if m:
|
|||
|
|
containerid = m.group(0)
|
|||
|
|
if title and containerid:
|
|||
|
|
topics.append({'title': title, 'containerid': containerid})
|
|||
|
|
max_page = d.get('data', {}).get('max_page', 1)
|
|||
|
|
if page >= max_page:
|
|||
|
|
break
|
|||
|
|
page += 1
|
|||
|
|
return topics
|
|||
|
|
|
|||
|
|
|
|||
|
|
def do_signin(sess, xsrf, containerid, topic_title):
|
|||
|
|
"""
|
|||
|
|
签到单个超话
|
|||
|
|
完整参数来自浏览器抓包:
|
|||
|
|
GET /p/aj/general/button?ajwvr=6&api=http://i.huati.weibo.com/aj/super/checkin
|
|||
|
|
&texta=签到&textb=已签到&status=0&id={containerid}
|
|||
|
|
&location=page_100808_super_index&...
|
|||
|
|
"""
|
|||
|
|
r = sess.get(
|
|||
|
|
'https://weibo.com/p/aj/general/button',
|
|||
|
|
params={
|
|||
|
|
'ajwvr': '6',
|
|||
|
|
'api': 'http://i.huati.weibo.com/aj/super/checkin',
|
|||
|
|
'texta': '签到',
|
|||
|
|
'textb': '已签到',
|
|||
|
|
'status': '0',
|
|||
|
|
'id': containerid,
|
|||
|
|
'location': 'page_100808_super_index',
|
|||
|
|
'timezone': 'GMT+0800',
|
|||
|
|
'lang': 'zh-cn',
|
|||
|
|
'plat': 'Win32',
|
|||
|
|
'ua': WEIBO_HEADERS['User-Agent'],
|
|||
|
|
'screen': '1920*1080',
|
|||
|
|
'__rnd': str(int(time.time() * 1000)),
|
|||
|
|
},
|
|||
|
|
headers={
|
|||
|
|
'Referer': f'https://weibo.com/p/{containerid}/super_index',
|
|||
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|||
|
|
'X-XSRF-TOKEN': xsrf,
|
|||
|
|
},
|
|||
|
|
timeout=10,
|
|||
|
|
)
|
|||
|
|
try:
|
|||
|
|
d = r.json()
|
|||
|
|
code = str(d.get('code', ''))
|
|||
|
|
msg = d.get('msg', '')
|
|||
|
|
data = d.get('data', {})
|
|||
|
|
if code == '100000':
|
|||
|
|
tip = ''
|
|||
|
|
if isinstance(data, dict):
|
|||
|
|
tip = data.get('alert_title', '') or data.get('tipMessage', '')
|
|||
|
|
return {'status': 'success', 'message': tip or '签到成功', 'data': data}
|
|||
|
|
elif code == '382004':
|
|||
|
|
return {'status': 'already_signed', 'message': msg or '今日已签到'}
|
|||
|
|
elif code == '382003':
|
|||
|
|
return {'status': 'failed', 'message': msg or '非超话成员'}
|
|||
|
|
else:
|
|||
|
|
return {'status': 'failed', 'message': f'code={code}, msg={msg}'}
|
|||
|
|
except Exception as e:
|
|||
|
|
return {'status': 'failed', 'message': f'非 JSON: {r.text[:200]}'}
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ================================================================
|
|||
|
|
# Main
|
|||
|
|
# ================================================================
|
|||
|
|
print("=" * 60)
|
|||
|
|
print("微博超话自动签到 - 完整流程验证")
|
|||
|
|
print("=" * 60)
|
|||
|
|
|
|||
|
|
# Step 1: 获取 cookie
|
|||
|
|
print("\n--- Step 1: 初始化 ---")
|
|||
|
|
cookies, uid, nick = load_cookies()
|
|||
|
|
if not cookies:
|
|||
|
|
cookies, uid, nick = qrcode_login()
|
|||
|
|
|
|||
|
|
# 建立 session
|
|||
|
|
sess = requests.Session()
|
|||
|
|
sess.headers.update(WEIBO_HEADERS)
|
|||
|
|
for k, v in cookies.items():
|
|||
|
|
sess.cookies.set(k, v, domain='.weibo.com')
|
|||
|
|
sess.get('https://weibo.com/', timeout=10)
|
|||
|
|
xsrf = sess.cookies.get('XSRF-TOKEN', '')
|
|||
|
|
print(f" XSRF: {'✅' if xsrf else '❌ MISSING'}")
|
|||
|
|
|
|||
|
|
# Step 2: 获取超话列表
|
|||
|
|
print(f"\n--- Step 2: 获取超话列表 ---")
|
|||
|
|
topics = get_super_topics(sess, xsrf, uid)
|
|||
|
|
print(f" 找到 {len(topics)} 个超话:")
|
|||
|
|
for i, t in enumerate(topics):
|
|||
|
|
print(f" [{i+1}] {t['title']} ({t['containerid'][:20]}...)")
|
|||
|
|
|
|||
|
|
if not topics:
|
|||
|
|
print(" ❌ 没有找到超话,退出")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
# Step 3: 逐个签到
|
|||
|
|
print(f"\n--- Step 3: 签到 ({len(topics)} 个超话) ---")
|
|||
|
|
signed = already = failed = 0
|
|||
|
|
results = []
|
|||
|
|
for i, t in enumerate(topics):
|
|||
|
|
print(f"\n [{i+1}/{len(topics)}] {t['title']}", end=' ... ')
|
|||
|
|
r = do_signin(sess, xsrf, t['containerid'], t['title'])
|
|||
|
|
results.append({**r, 'topic': t['title']})
|
|||
|
|
|
|||
|
|
if r['status'] == 'success':
|
|||
|
|
signed += 1
|
|||
|
|
print(f"✅ {r['message']}")
|
|||
|
|
elif r['status'] == 'already_signed':
|
|||
|
|
already += 1
|
|||
|
|
print(f"ℹ️ {r['message']}")
|
|||
|
|
else:
|
|||
|
|
failed += 1
|
|||
|
|
print(f"❌ {r['message']}")
|
|||
|
|
|
|||
|
|
if i < len(topics) - 1:
|
|||
|
|
time.sleep(1.5) # 防封间隔
|
|||
|
|
|
|||
|
|
# Step 4: 汇总
|
|||
|
|
print(f"\n{'=' * 60}")
|
|||
|
|
print(f"签到完成!")
|
|||
|
|
print(f" ✅ 成功: {signed}")
|
|||
|
|
print(f" ℹ️ 已签: {already}")
|
|||
|
|
print(f" ❌ 失败: {failed}")
|
|||
|
|
print(f" 📊 总计: {len(topics)} 个超话")
|
|||
|
|
print(f"{'=' * 60}")
|