権限のある X アカウントのプロフィールを一括更新する方法
1 つの X アカウントだけなら、プロフィールは手作業で変更しても大きな問題になりません。大変なのは、ブランドアカウント、地域別サポートアカウント、製品アカウント、キャンペーン用アカウントをまとめて更新する場合です。name、description、location、画像、banner をそろえる必要があり、1 つでも漏れると古い告知や古い画像が残ってしまいます。
TwexAPI のプロフィール更新 APIは、このような繰り返し作業に向いています。安全に進めるなら、最初に 1 アカウントで検証し、同じリクエストを小さな並列数のバッチ処理に移し、最後にアカウントごとの実行結果を残します。
この手順は、所有している、または管理権限を付与された X アカウントにのみ使用してください。アカウント Cookie はパスワードと同様に扱ってください。
一括更新の進め方
プロフィール更新は、アカウント一覧をそのままループに流す作業ではなく、管理されたメンテナンス作業として扱います。API 呼び出し自体は単純です。TwexAPI Bearer Token で POST https://api.twexapi.io/twitter/profile を呼び出し、対象アカウントの cookie と、この実行で変更を許可する項目だけを送信します。
安全な一括更新では、次の 4 点を決めておきます。
- まず低リスクの 1 アカウントを更新し、実際の X プロフィール表示を確認する。
- アカウントごとにローカル設定を 1 行ずつ持ち、
updatesには今回変更する項目だけを入れる。 - 並列数は 2 から 3 worker 程度に抑え、失敗原因を追いやすくする。
- 各アカウントの成功、失敗理由、リトライ回数、実行時刻を結果ファイルに残す。
プロフィール変更には専用のプレビューエンドポイントがありません。実質的な dry run は、設定を人が確認し、低リスクの 1 アカウントだけを更新し、実際の X 表示を確認することです。
API エンドポイント
POST https://api.twexapi.io/twitter/profile
Authorization: Bearer <your_twexapi_token>
Content-Type: application/jsonリクエストボディでは次のフィールドを使用できます。
| フィールド | 必須 | 用途 |
|---|---|---|
cookie | はい | 更新対象 X アカウントのログイン済み Cookie 文字列 |
name | いいえ | 表示名 |
website | いいえ | プロフィールの Web サイト URL |
description | いいえ | プロフィールの自己紹介 |
location | いいえ | プロフィールの地域 |
profile_image | いいえ | プロフィール画像 URL |
profile_banner | いいえ | Banner 画像 URL |
proxy | 注記参照 | このリクエストで使うプロキシ URL |
エンドポイントの schema では proxy は nullable と表示されていますが、フィールド説明では住宅プロキシが求められています。一括処理では軽く扱わない方が安全です。TwexAPI サポートから省略可能と案内されていない限り、アカウントごとに安定したプロキシを指定してください。失敗したときに、Cookie、プロキシ、画像 URL、リクエスト項目のどこに原因があるかを切り分けやすくなります。
更新に成功すると、次のレスポンスが返ります。
{
"code": 200,
"msg": "success",
"data": true
}まず 1 つの X アカウントを更新する
最初はリスクの低い 1 アカウントで、リクエスト全体を確認します。TwexAPI Token はソースコードに直接書かず、環境変数に設定します。
export TWEXAPI_TOKEN="your_twexapi_bearer_token"次に、変更したい項目だけを送信します。
1curl --request POST \
2 --url https://api.twexapi.io/twitter/profile \
3 --header "Authorization: Bearer $TWEXAPI_TOKEN" \
4 --header "Content-Type: application/json" \
5 --data '{
6 "cookie": "auth_token=...; ct0=...; twid=...",
7 "proxy": "http://username:password@proxy.example.com:8000",
8 "name": "Acme Support",
9 "description": "Product help, release notes, and service updates.",
10 "location": "New York",
11 "website": "https://example.com/support",
12 "profile_image": "https://example.com/assets/support-avatar.png",
13 "profile_banner": "https://example.com/assets/support-banner.png"
14 }'この 1 回目のリクエストで、Cookie がまだ有効か、プロキシが使えるか、画像 URL が公開アクセスできるかを確認できます。成功してから一括更新に進んでください。
変更が必要な値だけを含めます。たとえばテキスト情報のみ更新する場合は、profile_image と profile_banner を省略します。古い値を設定ファイルに残すと、バッチ処理はその値をそのまま書き戻します。
一括更新用の設定ファイルを準備する
ローカルに profiles.json を作成します。各オブジェクトが 1 アカウントを表し、updates には今回変更する項目だけを入れます。account_id はレポート用のローカルラベルであり、API には送信されません。
1[
2 {
3 "account_id": "acme-support-us",
4 "cookie": "auth_token=...; ct0=...; twid=...",
5 "proxy": "http://username:password@us-proxy.example.com:8000",
6 "updates": {
7 "name": "Acme Support US",
8 "description": "Product help and service updates for US customers.",
9 "location": "United States",
10 "website": "https://example.com/us/support",
11 "profile_image": "https://example.com/assets/us-avatar.png",
12 "profile_banner": "https://example.com/assets/us-banner.png"
13 }
14 },
15 {
16 "account_id": "acme-support-jp",
17 "cookie": "auth_token=...; ct0=...; twid=...",
18 "proxy": "http://username:password@jp-proxy.example.com:8000",
19 "updates": {
20 "name": "Acme Support JP",
21 "description": "Product help and service updates for customers in Japan.",
22 "location": "Japan",
23 "website": "https://example.com/jp/support"
24 }
25 }
26]profiles.json にはアカウント認証情報が含まれるため、バージョン管理に追加しないでください。ローカルプロジェクト内に置く必要がある場合は .gitignore に追加し、ファイル権限を制限します。
chmod 600 profiles.json以下のスクリプトは、updates に明示されたキーだけを API に送信します。省略した項目は既存のプロフィール値を変更しません。フィールドを消去したい場合のみ、1 アカウントで動作確認を行った後に明示的な null を使ってください。
Python 一括更新スクリプト
次のスクリプトは、各アカウントの cookie と updates を確認し、未対応のフィールドを拒否し、一時的な失敗を最大 3 回リトライし、結果を profile-update-results.json に書き出します。環境に requests がない場合はインストールします。
python -m pip install requests次のスクリプトを batch-update-profiles.py として保存します。
1import json
2import os
3import time
4from concurrent.futures import ThreadPoolExecutor, as_completed
5
6import requests
7
8API_URL = "https://api.twexapi.io/twitter/profile"
9ALLOWED_FIELDS = {
10 "name",
11 "website",
12 "description",
13 "location",
14 "profile_image",
15 "profile_banner",
16}
17MAX_ATTEMPTS = 3
18MAX_WORKERS = 3
19
20token = os.environ.get("TWEXAPI_TOKEN")
21if not token:
22 raise RuntimeError("Missing TWEXAPI_TOKEN environment variable.")
23
24def build_body(account):
25 account_id = account.get("account_id", "unnamed-account")
26 cookie = account.get("cookie")
27 updates = account.get("updates")
28
29 if not cookie:
30 raise ValueError(f"{account_id}: missing cookie")
31 if not isinstance(updates, dict) or not updates:
32 raise ValueError(f"{account_id}: updates must be a non-empty object")
33
34 unknown_fields = sorted(set(updates) - ALLOWED_FIELDS)
35 if unknown_fields:
36 raise ValueError(f"{account_id}: unsupported fields: {unknown_fields}")
37
38 body = {"cookie": cookie, **updates}
39 if account.get("proxy"):
40 body["proxy"] = account["proxy"]
41 return body
42
43def update_profile(account):
44 account_id = account.get("account_id", "unnamed-account")
45
46 try:
47 body = build_body(account)
48 except ValueError as error:
49 return {"account_id": account_id, "ok": False, "error": str(error)}
50
51 for attempt in range(1, MAX_ATTEMPTS + 1):
52 try:
53 response = requests.post(
54 API_URL,
55 headers={"Authorization": f"Bearer {token}"},
56 json=body,
57 timeout=45,
58 )
59 payload = response.json()
60
61 if response.ok and payload.get("code") == 200 and payload.get("data") is True:
62 return {"account_id": account_id, "ok": True, "attempts": attempt}
63
64 message = str(payload.get("msg", f"HTTP {response.status_code}"))
65 retryable = response.status_code == 429 or response.status_code >= 500
66 if not retryable:
67 return {"account_id": account_id, "ok": False, "error": message}
68 except (requests.RequestException, ValueError) as error:
69 message = f"{type(error).__name__}: request failed"
70
71 if attempt < MAX_ATTEMPTS:
72 time.sleep(attempt * 2)
73
74 return {
75 "account_id": account_id,
76 "ok": False,
77 "error": message,
78 "attempts": MAX_ATTEMPTS,
79 }
80
81with open("profiles.json", encoding="utf-8") as source:
82 accounts = json.load(source)
83
84if not isinstance(accounts, list) or not accounts:
85 raise RuntimeError("profiles.json must contain a non-empty array.")
86
87results = []
88with ThreadPoolExecutor(max_workers=min(MAX_WORKERS, len(accounts))) as executor:
89 futures = [executor.submit(update_profile, account) for account in accounts]
90 for future in as_completed(futures):
91 result = future.result()
92 results.append(result)
93 print(f"{result['account_id']}: {'updated' if result['ok'] else 'failed'}")
94
95with open("profile-update-results.json", "w", encoding="utf-8") as output:
96 json.dump(results, output, ensure_ascii=False, indent=2)
97
98success_count = sum(1 for result in results if result["ok"])
99print(f"Finished: {success_count}/{len(results)} profiles updated.")実行方法:
TWEXAPI_TOKEN="your_twexapi_bearer_token" python batch-update-profiles.py初回は profiles.json に 1 から 2 アカウントだけ残して実行してください。結果ファイル、実際の X プロフィール、画像の読み込みを確認してから、全件に広げます。
Node.js 一括更新スクリプト
Node.js を使うチーム向けの版です。Node.js 18 以降には fetch が組み込まれているため、追加パッケージは不要です。次のスクリプトを batch-update-profiles.mjs として保存します。
1import { readFile, writeFile } from "node:fs/promises";
2
3const API_URL = "https://api.twexapi.io/twitter/profile";
4const ALLOWED_FIELDS = new Set([
5 "name",
6 "website",
7 "description",
8 "location",
9 "profile_image",
10 "profile_banner",
11]);
12const MAX_ATTEMPTS = 3;
13const MAX_WORKERS = 3;
14const token = process.env.TWEXAPI_TOKEN;
15
16if (!token) {
17 throw new Error("Missing TWEXAPI_TOKEN environment variable.");
18}
19
20const sleep = (milliseconds) =>
21 new Promise((resolve) => setTimeout(resolve, milliseconds));
22
23function buildBody(account) {
24 const accountId = account.account_id || "unnamed-account";
25 const updates = account.updates;
26
27 if (!account.cookie) {
28 throw new Error(`${accountId}: missing cookie`);
29 }
30 if (!updates || typeof updates !== "object" || Array.isArray(updates)) {
31 throw new Error(`${accountId}: updates must be a non-empty object`);
32 }
33
34 const updateKeys = Object.keys(updates);
35 if (updateKeys.length === 0) {
36 throw new Error(`${accountId}: updates must be a non-empty object`);
37 }
38
39 const unknownFields = updateKeys.filter((key) => !ALLOWED_FIELDS.has(key));
40 if (unknownFields.length > 0) {
41 throw new Error(`${accountId}: unsupported fields: ${unknownFields.join(", ")}`);
42 }
43
44 return {
45 cookie: account.cookie,
46 ...(account.proxy ? { proxy: account.proxy } : {}),
47 ...updates,
48 };
49}
50
51async function updateProfile(account) {
52 const accountId = account.account_id || "unnamed-account";
53 let body;
54
55 try {
56 body = buildBody(account);
57 } catch (error) {
58 return { account_id: accountId, ok: false, error: error.message };
59 }
60
61 let message = "request failed";
62 for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
63 try {
64 const response = await fetch(API_URL, {
65 method: "POST",
66 headers: {
67 Authorization: `Bearer ${token}`,
68 "Content-Type": "application/json",
69 },
70 body: JSON.stringify(body),
71 signal: AbortSignal.timeout(45_000),
72 });
73 const payload = await response.json().catch(() => ({}));
74
75 if (response.ok && payload.code === 200 && payload.data === true) {
76 return { account_id: accountId, ok: true, attempts: attempt };
77 }
78
79 message = String(payload.msg || `HTTP ${response.status}`);
80 const retryable = response.status === 429 || response.status >= 500;
81 if (!retryable) {
82 return { account_id: accountId, ok: false, error: message };
83 }
84 } catch (error) {
85 message = `${error.name}: request failed`;
86 }
87
88 if (attempt < MAX_ATTEMPTS) {
89 await sleep(attempt * 2_000);
90 }
91 }
92
93 return { account_id: accountId, ok: false, error: message, attempts: MAX_ATTEMPTS };
94}
95
96async function runQueue(accounts, workerCount) {
97 const results = new Array(accounts.length);
98 let cursor = 0;
99
100 async function worker() {
101 while (cursor < accounts.length) {
102 const index = cursor;
103 cursor += 1;
104 results[index] = await updateProfile(accounts[index]);
105 console.log(`${results[index].account_id}: ${results[index].ok ? "updated" : "failed"}`);
106 }
107 }
108
109 await Promise.all(
110 Array.from({ length: Math.min(workerCount, accounts.length) }, () => worker()),
111 );
112 return results;
113}
114
115const accounts = JSON.parse(await readFile("profiles.json", "utf8"));
116if (!Array.isArray(accounts) || accounts.length === 0) {
117 throw new Error("profiles.json must contain a non-empty array.");
118}
119
120const results = await runQueue(accounts, MAX_WORKERS);
121await writeFile("profile-update-results.json", JSON.stringify(results, null, 2));
122
123const successCount = results.filter((result) => result.ok).length;
124console.log(`Finished: ${successCount}/${results.length} profiles updated.`);実行方法:
TWEXAPI_TOKEN="your_twexapi_bearer_token" node batch-update-profiles.mjs本番運用チェックリスト
- 管理権限を持つアカウントにのみ、この手順を使ってください。
TWEXAPI_TOKENは環境変数またはシークレット管理サービスに保存してください。- X Cookie と認証情報を含むプロキシ URL は、ソース管理の外に保存してください。
- Cookie やプロキシ認証情報をログやレポートに出力しないでください。
- 最初に 1 アカウントで確認し、次に小さなバッチでテストしてから規模を拡大してください。
- 並列数は小さく保ってください。worker を急に増やすと、失敗原因の調査が難しくなります。
- リトライするのは、ネットワーク障害、HTTP
429、HTTP5xxだけにしてください。フィールドエラーや認証エラーは、原因を直してから再実行します。 - 各実行後に
profile-update-results.jsonを確認し、監査記録として保管してください。 profile_imageとprofile_bannerには、安定して公開アクセスできる画像 URL を使ってください。