Cloudflare Worker + 域名:零成本搭建私人爬蟲代理池
整理版優先睇
用 Cloudflare Worker 加自己域名,10分鐘免費整到私人代理池,仲可以爬微信公眾號文章
呢篇文章係教人點樣用 Cloudflare Worker 同自定義域名,免費搭建一個私人爬蟲代理池。作者想解決嘅問題係:個人開發者想爬數據,但唔想畀錢買代理服務,或者嫌免費代理唔穩定。整體結論係:Cloudflare Worker 免費額度每日有10萬次請求,全球300+邊緣節點,出口 IP 自然輪換,只要綁個域名(最平每年4.64蚊人民幣),10分鐘就搞得掂,夠曬個人用。
文章詳細講解咗點解 Worker 可以做代理——因為請求經過邊緣節點,目標網站見到嘅係 Cloudflare IP 唔係本機 IP,每次請求行邊個節點由 Cloudflare 路由決定,自然做到 IP 輪換。同時作者提醒,如果目標網站用咗 Cloudflare 自家防護,可能會攔截,而且免費版有併發限制,適合個人採集、繞過簡單頻率限制呢類場景。
跟住就係實戰步驟:註冊 Cloudflare 賬號、創建 Worker、貼上佢提供嘅代碼、綁定自定義域名、可選配置 token 做認證。最後仲分享咗點樣配合 AI 模型整一個「網頁解析工作流」,用平價模型清洗 HTML,再畀頂級模型處理,大幅度降低成本。
- Cloudflare Worker 免費額度每日10萬次請求,全球300+邊緣節點,出口 IP 自然輪換,足以應付個人爬蟲需求。
- 只需要一個自定義域名(最平每年¥4.64)就係唯一成本,部署過程唔使10分鐘。
- Worker 代碼已經提供,支援 GET 同 POST 請求,可以自訂 Headers,仲內置微信公眾號平台 preset。
- 可以透過路由方式綁定多個子域名,進一步分散出口 IP,適合搭代理池。
- 配合平價 AI 模型(如 Qwen、DeepSeek)清洗 HTML,再用頂級模型(如 Claude、GPT)處理,可以大幅降低 API 成本。
Cloudflare Worker 代理爬蟲代碼
完整 Worker 代碼,包含自訂 UA、平台 presets、CORS 頭、token 驗證、請求解析等功能。直接複製到 Worker 編輯器即可使用。
點解要用 Worker 做代理?
Cloudflare Worker 運行喺邊緣節點上,你發一個請求畀 Worker,Worker 再去請求目標網站——目標網站見到嘅 IP 係 Cloudflare 邊緣節點 IP,唔係你本機 IP。每次請求行邊個邊緣節點,由 Cloudflare 路由決定,冇得精確控制,但呢個反而帶嚟自然嘅 IP 輪換效果。
配合多個 Worker 路由或子域名,可以進一步分散請求來源。免費額度每天10萬次請求,全球300+邊緣節點,出口 IP 分佈喺幾十個國家,個人用完全足夠。
自然的 IP 輪換效果
全球300+邊緣節點
創建同設定 Worker
- 1 登錄 Cloudflare Dashboard → Workers & Pages → 創建應用程序,選擇「從 Hello World! 開始」
- 2 點擊部署,等佢完成,再點擊編輯代碼
- 3 將默認代碼替換成文章提供嘅 Proxy 代碼(見下方)
- 4 點擊右側預覽 tab 嘅刷新按鈕,見到「URL not found」即係成功
- 5 最後點擊部署,注意免費版有部署次數限制,最好調試好先部署
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36";
const PRESETS = {
wechat: {
Referer: "https://mp.weixin.qq.com",
},
};
const CORS_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Max-Age": "86400",
};
function error(msg, status = 400) {
return new Response(msg, { status, headers: CORS_HEADERS });
}
function checkToken(req, env) {
if (!env.TOKEN) return;
const authHeader = req.headers.get("Authorization") || "";
const token = authHeader.startsWith("Bearer ")
? authHeader.slice(7)
: new URL(req.url).searchParams.get("token");
if (token !== env.TOKEN) throw new Error("Unauthorized");
}
async function parseRequest(req) {
const origin = req.headers.get("origin") || "*";
let targetURL = "";
let targetMethod = "GET";
let targetBody = "";
let targetHeaders = {};
let preset = "";
const method = req.method.toLowerCase();
if (method === "get") {
const { searchParams } = new URL(req.url);
targetURL = searchParams.get("url") || "";
if (searchParams.has("method")) targetMethod = searchParams.get("method").toUpperCase();
targetBody = searchParams.get("body") || "";
if (searchParams.has("headers")) {
try {
targetHeaders = JSON.parse(searchParams.get("headers"));
} catch (_) {
throw new Error("headers not valid");
}
}
preset = searchParams.get("platform") || searchParams.get("preset") || "";
} else if (method === "post") {
const payload = await req.json();
if (payload.url) targetURL = payload.url;
if (payload.method) targetMethod = payload.method.toUpperCase();
if (payload.body) targetBody = payload.body;
if (payload.headers) targetHeaders = payload.headers;
preset = payload.platform || payload.preset || "";
} else {
throw new Error("Method not implemented");
}
if (!targetURL) throw new Error("URL not found");
if (!/^https?:\/\//.test(targetURL)) throw new Error("URL not valid");
if (targetMethod === "GET" && targetBody) throw new Error("GET method can't have body");
if (Object.prototype.toString.call(targetHeaders) !== "[object Object]") {
throw new Error("Headers not valid");
}
if (!targetHeaders["User-Agent"]) targetHeaders["User-Agent"] = UA;
if (preset in PRESETS) Object.assign(targetHeaders, PRESETS[preset]);
return { origin, targetURL, targetMethod, targetBody, targetHeaders };
}
export default {
async fetch(request, env) {
if (request.method === "OPTIONS") {
return new Response(null, { status: 204, headers: CORS_HEADERS });
}
try {
checkToken(request, env);
const { origin, targetURL, targetMethod, targetBody, targetHeaders } = await parseRequest(request);
const response = await fetch(targetURL, {
method: targetMethod,
body: targetBody || undefined,
headers: targetHeaders,
});
return new Response(response.body, {
status: response.status,
headers: {
...CORS_HEADERS,
"Access-Control-Allow-Origin": origin,
"Content-Type": response.headers.get("Content-Type"),
},
});
} catch (err) {
const status = err.message === "Unauthorized" ? 401 : 400;
return error(err.message, status);
}
},
};
綁定自定義域名同配置 token
Worker 默認會分配一個 *.workers.dev 子域名,但 workers.dev 可能被部分目標網站封鎖,喺國內訪問亦唔穩定。最好綁定自己嘅域名,喺 Worker 設置頁面點擊「域和路由」→「自定義域」,輸入域名就得。
如果你打算搭代理池,可以揀「路由」方式批量綁定子域名,例如 `proxy*.yourdomain.com/*`,咁樣 proxy1、proxy2 全部指向同一個 Worker,出口 IP 更分散。
路由方式批量綁定
想加認證就喺 Worker 設置 → 變量和機密 → 添加變量 `TOKEN`,值自訂。未配置 token 時任何人都可以用你個代理,所以建議加返。
配置 token 做認證
點樣用個代理?
代理上線之後,用法好簡單:
https://proxy.yourdomain.com/?url=https://目標網站.com/路徑
特別係微信公眾號文章,可以用埋內置 preset:
https://proxy.yourdomain.com/?url=https://mp.weixin.qq.com/s/xxxxxx&platform=wechat
如果有 token,可以透過 query param `&token=<密鑰>` 或者 `Authorization: Bearer <密鑰>` header 傳遞。
內置微信公眾號 preset
配合 AI 大幅度降本同侷限
作者提出一個「網頁解析工作流」:先用 Worker 代理拎 HTML,再用 Qwen 或 DeepSeek 等平價模型將 HTML 轉為結構化 Markdown/JSON,最後先將精簡版數據交畀 Claude 或 GPT 處理。咁樣可以大幅降低 API 成本。
- IP 並非高匿名:Cloudflare 嘅 IP 公開可知,反爬極強嘅網站(例如都係用 Cloudflare 防護嘅)可能會攔截。
- 併發限制:免費版每日10萬次請求,但瞬間併發過高有機會觸發頻率限制。
- 適合場景:個人採集、繞過簡單 IP 頻率限制、API 轉發、小規模結構化數據抓取。
配合平價 AI 模型清洗 HTML
適合個人採集、繞過簡單 IP 頻率限制
Cloudflare Worker 嘅免費限額係每日 10 萬次請求,全球有 300+ 邊緣節點,出口 IP 分佈喺幾十個國家。
綁定自己嘅域名,只需 10 分鐘就可以整一個私人代理池,域名係唯一嘅成本。免費限額夠個人用。
最重要嘅係:仲可以用嚟爬取公眾號文章。
Worker 點解可以做代理
Cloudflare Worker 係運行喺邊緣節點上面。Send 一個請求畀 Worker,Worker 再幫你去請求目標網站——目標網站見到嘅 IP,係 Cloudflare 邊緣節點嘅 IP,唔係你本機嘅 IP。
每次請求行邊個邊緣節點,由 Cloudflare 嘅路由決定,冇得精確控制,但咁反而帶嚟自然嘅 IP 輪換效果。配合多個 Worker 路由或者子域名,可以進一步分散請求來源。
準備材料
Cloudflare 帳號(免費) 一個域名(如果冇域名或者域名冇 set 做 Cloudflare 嘅 DNS,可以參考呢個系列嘅上一篇文章)
建立同設定 Worker
登入 Cloudflare Dashboard → Workers & Pages → 建立應用程式,揀「從 Hello World! 開始」:

然後撳部署:

等部署完成,撳編輯程式碼:

將默認程式碼換成:
constUA =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36";
constPRESETS = {
wechat: {
Referer:"https://mp.weixin.qq.com",
},
};
constCORS_HEADERS = {
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Methods":"GET, POST, OPTIONS",
"Access-Control-Allow-Headers":"Content-Type",
"Access-Control-Max-Age":"86400",
};
functionerror(msg, status = 400) {
returnnewResponse(msg, { status, headers:CORS_HEADERS });
}
functioncheckToken(req, env) {
if (!env.TOKEN) return; // 未配置 token 則跳過校驗
const authHeader = req.headers.get("Authorization") || "";
const token = authHeader.startsWith("Bearer ")
? authHeader.slice(7)
: newURL(req.url).searchParams.get("token");
if (token !== env.TOKEN) thrownewError("Unauthorized");
}
asyncfunctionparseRequest(req) {
const origin = req.headers.get("origin") || "*";
let targetURL = "";
let targetMethod = "GET";
let targetBody = "";
let targetHeaders = {};
let preset = "";
const method = req.method.toLowerCase();
if (method === "get") {
const { searchParams } = newURL(req.url);
targetURL = searchParams.get("url") || "";
if (searchParams.has("method")) targetMethod = searchParams.get("method").toUpperCase();
targetBody = searchParams.get("body") || "";
if (searchParams.has("headers")) {
try {
targetHeaders = JSON.parse(searchParams.get("headers"));
} catch (_) {
thrownewError("headers not valid");
}
}
preset = searchParams.get("platform") || searchParams.get("preset") || "";
} elseif (method === "post") {
const payload = await req.json();
if (payload.url) targetURL = payload.url;
if (payload.method) targetMethod = payload.method.toUpperCase();
if (payload.body) targetBody = payload.body;
if (payload.headers) targetHeaders = payload.headers;
preset = payload.platform || payload.preset || "";
} else {
thrownewError("Method not implemented");
}
if (!targetURL) thrownewError("URL not found");
if (!/^https?:\/\//.test(targetURL)) thrownewError("URL not valid");
if (targetMethod === "GET" && targetBody) thrownewError("GET method can't have body");
if (Object.prototype.toString.call(targetHeaders) !== "[object Object]") {
thrownewError("Headers not valid");
}
if (!targetHeaders["User-Agent"]) targetHeaders["User-Agent"] = UA;
if (preset inPRESETS) Object.assign(targetHeaders, PRESETS[preset]);
return { origin, targetURL, targetMethod, targetBody, targetHeaders };
}
exportdefault {
asyncfetch(request, env) {
if (request.method === "OPTIONS") {
returnnewResponse(null, { status:204, headers:CORS_HEADERS });
}
try {
checkToken(request, env);
const { origin, targetURL, targetMethod, targetBody, targetHeaders } =
awaitparseRequest(request);
const response = awaitfetch(targetURL, {
method: targetMethod,
body: targetBody || undefined,
headers: targetHeaders,
});
returnnewResponse(response.body, {
status: response.status,
headers: {
...CORS_HEADERS,
"Access-Control-Allow-Origin": origin,
"Content-Type": response.headers.get("Content-Type"),
},
});
} catch (err) {
const status = err.message === "Unauthorized" ? 401:400;
returnerror(err.message, status);
}
},
};
撳右邊預覽 tab 下面嘅重新整理掣,見到頁面出現 URL not found,證明程式碼用得。然後撳部署。

注意:免費版 Cloudflare 有部署次數限制,建議喺呢度 debug 完先部署。
綁定自訂域名
Worker 默認會分配一個 *.workers.dev 嘅子域名,可以直接用,但有兩個問題:
workers.dev 已經被部分目標網站加入黑名單,喺國內訪問唔穩定,仲好易被封。 唔好記,輪換管理麻煩
喺 Worker 設置頁面,撳域同路由右邊嘅添加掣,會出現兩個選項:自定義域同路由。揀自定義域,輸入要綁定嘅域名:

添加完成之後就可以喺設置度見到:

呢度簡單講下兩個選項嘅分別:自定義域要逐個綁定。路由支援批量,例如填 proxy*.yourdomain.com/*,咁樣 proxy1、proxy2、proxy3 全部命中同一個 Worker。唔同子域名對應唔同範圍嘅 Cloudflare 邊緣節點,出口 IP 更分散,有需要整代理池嘅就按路由方式設定。
設定 token(可選)
喺 Cloudflare Dashboard → Worker → 設定 → 變量同機密 → 添加變量,填變量名 TOKEN,值 set 做自己嘅密鑰。

未配置 TOKEN 時會直接跳過驗證。注意:即係話任何知道呢個 URL 嘅人都可以借用呢個代理。
呼叫方式
代理上線之後,用法係:
https://proxy.yourdomain.com/?url=https://目標網站.com/路徑
微信公眾號文章呼叫示例:
https://proxy.yourdomain.com/?url=https://mp.weixin.qq.com/s/xxxxxx&platform=wechat

如果設定咗 token,呼叫時帶埋:
# query param
?url=https://example.com&token=<密鑰>
# 或 Authorization header
Authorization: Bearer <密鑰>
題外話:配合 AI 大幅降低成本
有咗呢個代理,你可以做一個「網頁解析工作流程」:
- 獲取內容:
經由 Worker 代理爬取網頁 HTML。 - 初步清理:
用 Qwen 或 DeepSeek 呢啲平價模型將 HTML 轉成結構化嘅 Markdown/JSON。 - 深度處理:
將清理後嘅「精簡版」數據交畀 Claude 或 GPT 呢啲頂級模型處理。
限制說明
- IP 唔係高匿名:
Cloudflare 嘅 IP 係公開已知嘅,反爬好勁嘅網站(例如用咗 CF 自家防護嘅)可能會攔截。 - 並發限制:
免費版 Worker 雖然每日有 10 萬次請求,但瞬間並發過高可能會觸發頻率限制。
適合場景:個人採集、繞過簡單嘅 IP 頻率限制、API 轉發、小規模結構化數據爬取。
結尾
有咗 Cloudflare Worker,自己想爬啲簡單數據嘅時候,冇必要俾錢買人哋封裝好嘅代理服務。
Worker 免費,域名最低 ¥4.64/年,10 分鐘就部署好,隨用隨開。如果第日請求量真係大到要買代理服務,嗰時個項目大概都值得俾呢筆錢啦。
程式碼得幾十行,有咩唔明可以喺評論區講,或者 private message 我。
Cloudflare Worker 的免費額度是每天 10 萬次請求,全球 300+ 邊緣節點,出口 IP 分佈在幾十個國家。
綁上自己的域名,只需 10 分鐘就能搭一個私人代理池,域名是唯一的成本。免費額度足夠個人使用。
最主要的是:還能用來抓取公眾號文章。
Worker 為什麼能做代理
Cloudflare Worker 運行在邊緣節點上。發一個請求給 Worker,Worker 再去請求目標網站——目標網站看到的 IP,是 Cloudflare 的邊緣節點 IP,不是本機 IP。
每次請求走哪個邊緣節點,由 Cloudflare 的路由決定,無法精確控制,但這反而帶來了自然的 IP 輪換效果。配合多個 Worker 路由或子域名,可以進一步分散請求來源。
準備材料
Cloudflare 賬號(免費) 一個域名(如果沒有域名或者域名沒有配置為 Cloudflare 的 DNS,可以參考本系列上一篇文章)
創建與配置 Worker
登錄 Cloudflare Dashboard → Workers & Pages → 創建應用程序,選擇「從 Hello World! 開始」:

然後點擊部署:

等待部署完成,點擊編輯代碼:

把默認代碼替換為:
constUA =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36";
constPRESETS = {
wechat: {
Referer:"https://mp.weixin.qq.com",
},
};
constCORS_HEADERS = {
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Methods":"GET, POST, OPTIONS",
"Access-Control-Allow-Headers":"Content-Type",
"Access-Control-Max-Age":"86400",
};
functionerror(msg, status = 400) {
returnnewResponse(msg, { status, headers:CORS_HEADERS });
}
functioncheckToken(req, env) {
if (!env.TOKEN) return; // 未配置 token 則跳過校驗
const authHeader = req.headers.get("Authorization") || "";
const token = authHeader.startsWith("Bearer ")
? authHeader.slice(7)
: newURL(req.url).searchParams.get("token");
if (token !== env.TOKEN) thrownewError("Unauthorized");
}
asyncfunctionparseRequest(req) {
const origin = req.headers.get("origin") || "*";
let targetURL = "";
let targetMethod = "GET";
let targetBody = "";
let targetHeaders = {};
let preset = "";
const method = req.method.toLowerCase();
if (method === "get") {
const { searchParams } = newURL(req.url);
targetURL = searchParams.get("url") || "";
if (searchParams.has("method")) targetMethod = searchParams.get("method").toUpperCase();
targetBody = searchParams.get("body") || "";
if (searchParams.has("headers")) {
try {
targetHeaders = JSON.parse(searchParams.get("headers"));
} catch (_) {
thrownewError("headers not valid");
}
}
preset = searchParams.get("platform") || searchParams.get("preset") || "";
} elseif (method === "post") {
const payload = await req.json();
if (payload.url) targetURL = payload.url;
if (payload.method) targetMethod = payload.method.toUpperCase();
if (payload.body) targetBody = payload.body;
if (payload.headers) targetHeaders = payload.headers;
preset = payload.platform || payload.preset || "";
} else {
thrownewError("Method not implemented");
}
if (!targetURL) thrownewError("URL not found");
if (!/^https?:\/\//.test(targetURL)) thrownewError("URL not valid");
if (targetMethod === "GET" && targetBody) thrownewError("GET method can't have body");
if (Object.prototype.toString.call(targetHeaders) !== "[object Object]") {
thrownewError("Headers not valid");
}
if (!targetHeaders["User-Agent"]) targetHeaders["User-Agent"] = UA;
if (preset inPRESETS) Object.assign(targetHeaders, PRESETS[preset]);
return { origin, targetURL, targetMethod, targetBody, targetHeaders };
}
exportdefault {
asyncfetch(request, env) {
if (request.method === "OPTIONS") {
returnnewResponse(null, { status:204, headers:CORS_HEADERS });
}
try {
checkToken(request, env);
const { origin, targetURL, targetMethod, targetBody, targetHeaders } =
awaitparseRequest(request);
const response = awaitfetch(targetURL, {
method: targetMethod,
body: targetBody || undefined,
headers: targetHeaders,
});
returnnewResponse(response.body, {
status: response.status,
headers: {
...CORS_HEADERS,
"Access-Control-Allow-Origin": origin,
"Content-Type": response.headers.get("Content-Type"),
},
});
} catch (err) {
const status = err.message === "Unauthorized" ? 401:400;
returnerror(err.message, status);
}
},
};
點擊右側預覽 tab 下的刷新按鈕,看到頁面出現 URL not found,說明代碼可用。接着點擊部署。

注意:免費的 Cloudflare 有部署次數限制,建議在此調試完成再部署。
綁定自定義域名
Worker 默認會分配一個 *.workers.dev 的子域名,可以直接用,但有兩個問題:
workers.dev 已經被部分目標網站加入黑名單,在國內訪問不穩定,且容易被封。 不好記,輪換管理麻煩
在 Worker 設置頁面,點擊域和路由右側的添加按鈕,會出現兩個選項:自定義域和路由。選擇自定義域,輸入要綁定的域名:

添加完成後就能在設置中看到:

這裏簡單說一下兩個選項的區別:自定義域需要逐個綁定。路由支持批量,比如填 proxy*.yourdomain.com/*,這樣 proxy1、proxy2、proxy3 全部命中同一個 Worker。不同子域名對應不同範圍的 Cloudflare 邊緣節點,出口 IP 更分散,有搭代理池需求的按路由方式配置。
配置token(可選)
在 Cloudflare Dashboard → Worker → 設置 → 變量和機密 → 添加變量,填寫變量名 TOKEN,值設為自己的密鑰。

未配置 TOKEN 時會直接跳過校驗。注意:這意味着任何知道這個 URL 的人都可以借用這個代理。
調用方式
代理上線後,用法是:
https://proxy.yourdomain.com/?url=https://目標網站.com/路徑
微信公眾號文章調用示例:
https://proxy.yourdomain.com/?url=https://mp.weixin.qq.com/s/xxxxxx&platform=wechat

如果配置了 token,調用時帶上:
# query param
?url=https://example.com&token=<密鑰>
# 或 Authorization header
Authorization: Bearer <密鑰>
題外話:配合 AI 大幅度降本
有了這個代理,你可以做一個「網頁解析工作流」:
- 獲取內容:
通過 Worker 代理爬取網頁 HTML。 - 初步清洗:
用 Qwen 或 DeepSeek 等廉價模型將 HTML 轉為結構化的 Markdown/JSON。 - 深度處理:
將清洗後的「精簡版」數據交由 Claude 或 GPT 等頂級模型處理。
侷限性說明
- IP 並非高匿名:
Cloudflare 的 IP 是公開已知的,反爬極強的網站(如使用了 CF 自家防護的)可能會攔截。 - 併發限制:
免費版 Worker 雖然每天 10 萬次請求,但瞬間併發過高可能會觸發頻率限制。
適合場景:個人採集、繞過簡單的 IP 頻率限制、API 轉發、小規模結構化數據抓取。
結尾
有了 Cloudflare Worker,自己抓一些簡單的數據時候,沒必要花錢買別人封裝好的代理服務。
Worker 免費,域名最低 ¥4.64/年,10 分鐘部署,隨用隨起。如果哪天請求量真的大到要買代理服務,那時候項目大概也值得花這筆錢了。
代碼就這幾十行,有什麼不懂的可以評論區說,或者私信我。