""" 完整签到流程验证脚本 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}")