如何在本機上建立一個可以自動下載X上的影片流程,以tampermonkey及cobalt docker搭配進行
【技術實踐】自建 Cobalt Docker 實例:徹底解決 X (Twitter) 影片下載失效問題
身為軟體開發團隊領導與 CS 專業學生,在 X (Twitter) 上收集技術動態或專案靈感時,最常遇到的挫折就是下載腳本頻繁失效。從 Error 812 (Token 過期) 到 HTTP 403 (IP 封鎖),這些問題本質上都是目標網站與公用伺服器之間的對抗。
這篇筆記將分享如何利用家用住宅 IP 自建私有 Cobalt 實例,搭配我微調多次的 Tampermonkey 腳本,打造最穩定的下載環境。
一、 環境準備:Docker 部署私有實例
自建實例最大的優勢在於:你的家用寬頻 IP 不在 X 的資料中心黑名單中,能有效繞過針對 AWS 或 DigitalOcean 等雲端服務商的爬蟲封鎖。
1. 執行 Docker 指令
請在終端機 (Terminal) 執行以下指令。這會確保舊容器被清理並以正確的環境變數重啟:
# 停止並移除舊有的容器
docker stop cobalt-api && docker rm cobalt-api
# 啟動新容器並對應 9000 端口
docker run -d \
--name cobalt-api \
-p 9000:9000 \
-e API_URL="http://localhost:9000/" \
-e API_DEFAULT_LANG="zh-tw" \
ghcr.io/imputnet/cobalt:latest
2. 服務驗證
啟動後,瀏覽 http://localhost:9000/。若顯示 JSON 錯誤訊息 "api is for post requests only",代表後端運作正常。
二、 Tampermonkey 腳本配置
此腳本採用 V18 經典橘色外觀,並具備「自動故障轉移」邏輯:優先存取本地實例,若本地未開啟則嘗試公用鏡像站點。
完整腳本內容
// ==UserScript==
// @name X Video Downloader (V23 Final Local Fix)
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 優先透過本地 Docker 下載,解決 X 全面封鎖問題,修復語法錯誤
// @author Gemini (CS Major)
// @match https://x.com/*
// @match https://twitter.com/*
// @grant GM_xmlhttpRequest
// @grant GM_download
// @connect localhost
// @connect 127.0.0.1
// @connect api.cobalt.tools
// @connect co.wuk.sh
// ==/UserScript==
(function() {
'use strict';
// 實例優先級:本地 Docker -> 備援公用站點
const API_LIST = [
"http://localhost:9000/",
"https://co.wuk.sh/",
"https://api.cobalt.tools/"
];
const style = document.createElement('style');
style.textContent = `
.gm-dl-container { position: absolute !important; top: 10px; left: 10px; z-index: 2147483647; }
.gm-dl-btn {
background-color: #ffad1f !important;
color: #000 !important;
border: 2px solid #000 !important;
padding: 4px 10px !important;
border-radius: 6px !important;
font-size: 11px !important;
font-weight: 900 !important;
cursor: pointer !important;
}
`;
document.head.appendChild(style);
function inject() {
const players = document.querySelectorAll('[data-testid="videoPlayer"]:not(.gm-v23)');
players.forEach(player => {
player.classList.add('gm-v23');
const wrap = document.createElement('div');
wrap.className = 'gm-dl-container';
const btn = document.createElement('button');
btn.className = 'gm-dl-btn';
btn.textContent = '📥 下載影片';
btn.onclick = (e) => {
e.preventDefault(); e.stopPropagation();
const article = player.closest('article');
const link = article?.querySelector('a[href*="/status/"]');
const rawUrl = link ? link.href : window.location.href;
// 清洗追蹤碼並規範化網域
const cleanUrl = rawUrl.split('?')[0].replace('x.com', 'twitter.com');
startDownloadFlow(cleanUrl, btn, 0);
};
wrap.appendChild(btn);
player.appendChild(wrap);
});
}
function startDownloadFlow(url, btn, idx) {
if (idx >= API_LIST.length) {
alert("❌ 所有實例均無法突破 X 的封鎖。請檢查 Docker 狀態或更換 IP。");
resetBtn(btn);
return;
}
const endpoint = API_LIST[idx];
const serverName = new URL(endpoint).hostname;
btn.textContent = `⌛ 站點: ${serverName}...`;
GM_xmlhttpRequest({
method: "POST",
url: endpoint,
headers: { "Content-Type": "application/json", "Accept": "application/json" },
data: JSON.stringify({ url: url, videoQuality: "1080" }),
timeout: 12000,
onload: function(response) {
try {
const res = JSON.parse(response.responseText);
if (res.status === "stream" || res.status === "redirect") {
GM_download({
url: res.url,
name: `X_Video_${Date.now()}.mp4`,
onload: () => {
btn.textContent = '✅ 完成';
setTimeout(() => resetBtn(btn), 3000);
}
});
} else {
startDownloadFlow(url, btn, idx + 1);
}
} catch (e) {
startDownloadFlow(url, btn, idx + 1);
}
},
onerror: () => startDownloadFlow(url, btn, idx + 1)
});
}
function resetBtn(btn) { btn.textContent = '📥 下載影片'; btn.disabled = false; }
setInterval(inject, 2000);
new MutationObserver(inject).observe(document.body, { childList: true, subtree: true });
})();
三、 常見問題排除 (Troubleshooting)
- 連線失敗: 第一次執行時,請確認是否有彈出視窗詢問「跨網域請求權限」,並點擊「總是允許」。
- 語法錯誤 (SyntaxError): 此版本已改用標準
createElement邏輯,能避免節點注入時的解析衝突。 - API 無法獲取連結: X 的封鎖具有週期性。若私有實例也失效,建議更換手機 5G 熱點試試,家用/手機 IP 是突破封鎖的最強武器。
結語: 在開發與 PM 的日常中,這類工具的維護雖然繁雜,但自建服務帶來的穩定性與掌控感是無價的。希望這篇筆記能讓你在資訊收集的路上更加順遂。
留言
發佈留言