复用服务器浏览器里的 Google 登录,自动登录 YouTube / X / TikTok
本文记录一种个人自动化做法:在自有服务器(Oracle Cloud)上部署的自托管浏览器中已登录 Google 账号,如何把这份登录态「复用」到 YouTube、X(Twitter)、TikTok,免去重复输密码和验证码。全程基于自己的账号与自己的服务器,不涉及任何第三方凭据。
0. 背景与目标
我在 Oracle Cloud 上用 noVNC 部署了一个常驻浏览器(域名形如 browser.xxx.xyz),里面用我的 Google 账号登录着。某次想在新环境里用「Sign in with Google」登录几个平台,但不想再走一遍输密码 + 2FA 的流程。
既然浏览器里已经有一份有效的 Google 会话,理论上可以直接复用这份登录态,去完成各平台的 OAuth 授权跳转——本质上就是让自动化浏览器「继承」已登录的 profile。
目标平台:YouTube、X(Twitter)、TikTok。
1. 环境与架构探查
SSH 进服务器后,关键发现:
- 常驻 Firefox 进程(snap 版),跑在 xrdp 的 Xorg 上,
DISPLAY=:11.0 - nginx 把
browser.xxx.xyz的 443 → 8443 → noVNC 的 6080 端口 - snap 版 Firefox 的 profile 不在
~/.mozilla,而在:/home/ubuntu/snap/firefox/common/.mozilla/firefox/<profile_id>.default/ - Google 登录态(SID / HSID / SSID / APISID / SAPISID /
__Secure-1PSID/__Host-GAPS等约 40 条 cookie)就存在该 profile 的cookies.sqlite里
2. 验证登录态可复用
把原 profile 复制到隔离副本,用 geckodriver + Selenium 启动一个指向该副本的新 Firefox 实例(必须在同一个 DISPLAY=:11.0 上,否则连不上 X server),访问 https://myaccount.google.com:
- 结果:直接进入 Google 账号主页,没有跳登录页 ✅
- 证明这份 Google 登录态可以被任意新浏览器实例复用,OAuth「Sign in with Google」方案成立
这一步踩的两个坑(很重要):
- geckodriver 不能直接传
/snap/bin/firefox这个 symlink wrapper。geckodriver 会校验 ELF 二进制,wrapper 是 shell 脚本会被拒;但真实二进制直接启动又会因 GLIBC 版本不兼容失败(snap 运行时库是 wrapper 注入的)。
✅ 正确做法:不传binary_location,把/snap/bin加进PATH,让 geckodriver 自动调用 wrapper。 - DISPLAY 必须用原 Firefox 所在的
:11.0,不是常见的:1(这台机器:1上没有 X server)。
3. 各平台实现细节
YouTube —— 最简单
Google 自家服务,复用 Google 会话即直接登录,不需要走 OAuth 授权流程。新实例打开 youtube.com 就是登录态。
TikTok
1. 打开 tiktok.com/login
2. 找到 data-e2e="channel-item" 且文本含 "Google" 的频道 div
3. 用「真实鼠标事件」点击它(见下方坑③)
4. 弹出 accounts.google.com/v3/signin/accountchooser 窗口
5. 在弹窗里点账号 tile:
XPath: //div[@role='option' and @data-email] 或 //*[@data-email]
6. 弹窗自动关闭 → 回到 TikTok,会话 cookie(sid_tt / sessionid 等)已写入
X / Twitter —— 最麻烦
X 用的是 Google Identity Services (GSI)。登录页里有 3 个 accounts.google.com/gsi/iframe/select 的 iframe,真正的「Continue with Google」按钮渲染在 iframe 内部。
外层还有一个 jf-gsi-face 覆盖层,且它被设成 pointer-events: none——所以直接点外层那个看得到的按钮是无效的,必须切进 iframe 点内部的 Continue 按钮。
1. 打开 x.com/i/flow/login(页面很重,加载常超时,需容错)
2. 找到所有 src 含 accounts.google.com 的 GSI iframe
3. 遍历每个 iframe:switch_to.frame → 点内部的 //button[contains(.,'Continue')]
→ 哪个弹出了 Google 账号选择窗就用哪个
4. 弹窗内选账号 → 弹窗关闭 → 回到 x.com/home
5. cookie(auth_token / ct0 / twid)已写入
为什么不能靠「可见性」判断哪个 iframe 是对的? GSI 是异步渲染的,用 is_displayed() 判定经常漏判(时而 3 个、时而 2 个,可见的那个还会变)。直接遍历所有 GSI iframe 逐个尝试最稳。
4. 核心代码(Selenium + Python)
import os, time
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import TimeoutException, NoSuchWindowException
# 关键环境变量
os.environ["DISPLAY"] = ":11.0" # 必须与原 Firefox 同 DISPLAY
os.environ["XAUTHORITY"] = os.path.expanduser("~/.Xauthority")
os.environ["PATH"] = "/snap/bin:" + os.environ.get("PATH", "") # 让 geckodriver 自动调 firefox wrapper
PROFILE = "/home/ubuntu/reuse_profile" # 复制出来的、含 Google cookie 的 profile
GECKO = "/snap/bin/geckodriver"
opts = Options()
opts.add_argument("-profile"); opts.add_argument(PROFILE)
opts.add_argument("-no-remote"); opts.add_argument("-new-instance")
d = webdriver.Firefox(service=Service(executable_path=GECKO), options=opts)
d.set_page_load_timeout(90) # x.com 很重,90s 并容错
def real_click(el):
"""React/Vue 应用需要真实鼠标事件,不能只用 el.click()"""
d.execute_script("arguments[0].scrollIntoView({block:'center'});", el)
ActionChains(d).move_to_element(el).pause(0.3).click().perform()
def on_google(u):
u = u.lower()
return any(k in u for k in ["accountchooser","signin","oauth","consent","/gsi/","google.com"])
# ---- TikTok ----
d.get("https://www.tiktok.com/login"); time.sleep(8)
for el in d.find_elements(By.XPATH, "//div[@data-e2e='channel-item' and contains(., 'Google')]"):
if el.is_displayed():
real_click(el); break
time.sleep(6)
if len(d.window_handles) > 1:
d.switch_to.window(d.window_handles[-1]) # 切到 Google 弹窗
# 在弹窗里选账号
acc = d.find_element(By.XPATH, "//div[@role='option' and @data-email]")
real_click(acc)
# 弹窗会自动关闭,切回主窗口即可
# ---- X / Twitter (GSI) ----
d.get("https://x.com/i/flow/login"); time.sleep(15)
iframes = d.find_elements(By.XPATH, "//iframe[contains(@src,'accounts.google.com')]")
for f in iframes:
try:
d.switch_to.default_content()
if f.rect.get("width", 0) < 5: continue
d.switch_to.frame(f)
btn = d.find_element(By.XPATH, "//button[contains(.,'Continue')]")
real_click(btn)
d.switch_to.default_content()
if len(d.window_handles) > 1 or on_google(d.current_url):
break # 这个 iframe 启动了流程
except Exception:
d.switch_to.default_content()
# 之后同 TikTok:切到弹窗 → 选账号 → 关闭 → 回 x.com/home
5. 踩坑总结
| 坑 | 现象 | 解决 |
|---|---|---|
| geckodriver 启不了 snap Firefox | 二进制 GLIBC 不兼容 / wrapper 被拒 | /snap/bin 进 PATH,不传 binary_location |
| DISPLAY 不对 | 连不上 X server | 用原 Firefox 的 :11.0 |
| TikTok / X 点按钮无反应 | 框架监听真实鼠标事件 | 用 ActionChains 真实点击,而非 el.click() |
| X 外层按钮点不动 | jf-gsi-face 覆盖层 pointer-events:none |
切进 GSI iframe 点内部 Continue |
| GSI iframe 可见性判定失败 | 异步渲染,数量/可见性会变 | 遍历所有 GSI iframe 逐个尝试 |
| x.com 加载超时 | 页面极重 | page_load_timeout=90 且对 TimeoutException 容错继续 |
| 选完账号后脚本报错 | 弹窗自动关闭 | 捕获 NoSuchWindowException,切回主窗口验证 |
6. 安全与注意事项
- 全程基于你自己的 Google 会话,我没有也不会接触你的 Google 密码或验证码。
- profile 里的
cookies.sqlite等同于账号凭证,切勿外泄或提交到公开仓库。 - 复用登录态受 Google 会话有效期限制;若 Google 在浏览器里被登出,需要重新在 noVNC 里登录一次。
- 部分平台对「异地 / 自动化浏览器」可能触发额外验证,属正常风控。
本文方法适用于在自有服务器上复用自有账号的登录态做个人自动化,请勿用于任何未授权的访问。







暂无评论内容