GitHub Actions 是一个强大的自动化工具:它运行测试、部署应用程序、收集数据并执行其他数十项任务。但是,一旦工作流开始访问外部资源——市场、广告平台、国外 API——它就会立即面临地理封锁和 IP 限制。解决方案只有一个:在管道中直接连接代理。
为什么在 GitHub Actions 中使用代理:真实场景
许多团队不仅使用 GitHub Actions 来部署代码,还用于自动化业务任务:监控竞争对手的价格、从市场收集数据、自动检查广告账户和从不同地区测试网站。所有这些任务都有一个共同的问题——GitHub Actions 的运行器具有来自 Microsoft Azure 的固定 IP,许多服务会阻止或限制它。
以下是一些具体情况,没有代理无法解决:
- 解析 Wildberries、Ozon、Avito——这些平台早已将云服务提供商的 IP 范围列入黑名单。来自 GitHub Actions 运行器的请求将在第 2-3 次尝试时被阻止或收到验证码。
- 地理定位测试——市场营销人员和 QA 工程师检查网站或广告在莫斯科、柏林或纽约的用户眼中的样子。没有代理,运行器总是“看到”来自一个地区的内容。
- 与区域限制的 API 交互——某些 API(例如,特定设置的 Google Ads 区域版本、Facebook 营销 API)根据请求的地理位置返回不同的数据。
- 竞争对手监控——自动收集价格、促销和产品组合需要定期请求,这些请求很容易通过重复的 IP 数据中心被检测到。
- 自动化广告检查——套利者和绩效营销人员通过 CI/CD 中的脚本启动广告状态、余额和指标的自动检查。
- 与外部服务的集成测试——某些服务出于安全原因阻止来自 Azure 范围的请求,测试会毫无解释地失败。
在所有这些情况下,代理从根本上解决了问题:工作流开始看起来像是来自所需城市的普通用户的请求,而不是来自 Microsoft 的云服务器。
GitHub Actions 如何与网络协作
在设置代理之前,重要的是要理解 GitHub Actions 中的网络架构。当您在标准的 ubuntu-latest 运行器上启动工作流时,任务在 Microsoft Azure 的基础设施中的虚拟机上执行。每台这样的机器都有来自 Azure 范围的公共 IP——外部服务看到的正是这个 IP。
GitHub Actions 网络的关键特性:
- 每次启动时 IP 会更改——但仍然在已知的 Azure 范围内,这些范围很容易被检测到。
- 没有内置的代理支持——GitHub 不提供原生的流量代理机制。
- 环境变量全局有效——如果在作业级别设置
HTTP_PROXY,该作业内的所有步骤将使用代理。 - 自托管运行器——一种替代方案,您在自己的服务器上运行运行器。在这种情况下,代理在服务器级别而不是工作流级别进行设置。
对于大多数任务,最佳方法是在工作流文件中通过环境变量设置代理(.github/workflows/your-workflow.yml)。这是一种通用方法,适用于大多数工具:curl、wget、Python requests、Node.js http、Go net/http 等等。
选择哪种类型的代理用于 CI/CD
选择代理类型取决于任务。对于 CI/CD 管道,有三种相关选项,每种都有其特定的用途:
| 代理类型 | 适用于哪些任务 | 速度 | 信任级别 |
|---|---|---|---|
| 住宅代理 | 解析受保护的网站、地理定位、监控市场 | 中等 | 高——真实家庭 IP |
| 移动代理 | 测试移动版本、社交媒体工作、Facebook/TikTok API | 中等 | 最高——运营商 IP |
| 数据中心代理 | 集成测试、请求不受保护的 API、高负载 | 高 | 中等 |
实用规则:如果您的工作流解析 Wildberries、Ozon 或其他具有反机器人保护的市场——请选择住宅代理。如果您正在测试 Facebook Ads 或 TikTok Ads 的广告账户——请选择移动代理。对于简单的集成测试和对开放 API 的请求,数据中心代理就足够了:它们更快且更便宜。
💡 关于协议的重要信息
对于 GitHub Actions,优先使用 HTTP/HTTPS 代理——它们被大多数工具支持,无需额外设置。SOCKS5 也可以工作,但需要在每个工具中明确指定。如果您的提供商支持这两种协议——请从 HTTP 开始。
通过环境变量设置代理
在 GitHub Actions 中连接代理的最通用方法是设置标准环境变量 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY。大多数命令行工具和编程语言会自动识别它们。
使用代理的工作流的基本结构如下:
name: Workflow with Proxy
on:
schedule:
- cron: '0 9 * * *'
workflow_dispatch:
jobs:
scrape-data:
runs-on: ubuntu-latest
env:
HTTP_PROXY: http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }}
HTTPS_PROXY: http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }}
NO_PROXY: localhost,127.0.0.1,github.com
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check current IP (用于检查)
run: curl -s https://api.ipify.org
- name: Run main script
run: python scripts/scraper.py
请注意 NO_PROXY 块——需要在其中添加不需要代理流量的地址。至少包括 localhost 和 127.0.0.1。还建议添加 github.com,以便与存储库的操作(checkout、push)直接进行。
如果代理没有身份验证(只有 IP 和端口),格式会简化:
env:
HTTP_PROXY: http://203.0.113.10:8080
HTTPS_PROXY: http://203.0.113.10:8080
NO_PROXY: localhost,127.0.0.1
对于 SOCKS5 代理,只需更改 URL 中的方案:
env:
HTTP_PROXY: socks5://user:password@proxy-host:1080
HTTPS_PROXY: socks5://user:password@proxy-host:1080
curl、wget 和 shell 中的 HTTP 请求的代理
如果环境变量在作业级别设置(如上所示),curl 和 wget 会自动识别它们。但有时需要明确传递代理——例如,对于特定步骤或调试时。
在 curl 中明确指定代理:
- name: Fetch data with proxy
run: |
curl -x http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }} \
-s \
-o output.json \
https://api.example.com/data
# 通过代理检查
curl -x http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }} \
-s https://api.ipify.org?format=json
对于 wget:
- name: Download with wget via proxy
run: |
wget -e "https_proxy=http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }}" \
-q \
-O data.html \
https://target-site.com/page
一个有用的调试步骤是在工作流开始时检查 IP 地址。如果代理正常工作,您将看到代理服务器的 IP,而不是 Azure 的 IP:
- name: Verify proxy is active
run: |
echo "=== 无代理的 IP ==="
curl -s --noproxy '*' https://api.ipify.org || echo "直接请求失败"
echo ""
echo "=== 通过代理的 IP ==="
curl -s https://api.ipify.org
在工作流中使用 Python 脚本的代理
Python 是 CI/CD 中最流行的脚本语言之一。requests 库会自动读取环境变量 HTTP_PROXY 和 HTTPS_PROXY,如果它们被设置。但为了更灵活的控制,最好明确传递代理。
通过环境变量明确传递代理的 Python 脚本示例:
import os
import requests
# 从环境变量读取代理数据
proxy_host = os.environ.get('PROXY_HOST')
proxy_port = os.environ.get('PROXY_PORT')
proxy_user = os.environ.get('PROXY_USER')
proxy_pass = os.environ.get('PROXY_PASS')
proxies = {
'http': f'http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}',
'https': f'http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}',
}
# 在请求中使用代理
response = requests.get(
'https://www.wildberries.ru/catalog/123456/detail.aspx',
proxies=proxies,
timeout=30,
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
)
print(f"状态: {response.status_code}")
print(f"内容长度: {len(response.content)}")
在工作流文件中,您需要将变量作为单独的秘密传递(而不是作为完整的 URL),以便脚本能够收集它们:
- name: Run Python scraper
env:
PROXY_HOST: ${{ secrets.PROXY_HOST }}
PROXY_PORT: ${{ secrets.PROXY_PORT }}
PROXY_USER: ${{ secrets.PROXY_USER }}
PROXY_PASS: ${{ secrets.PROXY_PASS }}
run: python scripts/scraper.py
对于使用 Playwright 或 Selenium 的 Python,代理配置略有不同:
# Playwright
from playwright.sync_api import sync_playwright
import os
proxy_url = f"http://{os.environ['PROXY_USER']}:{os.environ['PROXY_PASS']}@{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}"
with sync_playwright() as p:
browser = p.chromium.launch(
proxy={
"server": proxy_url
}
)
page = browser.new_page()
page.goto("https://target-site.com")
# ... 后续逻辑
browser.close()
Node.js 和 npm 任务中的代理
Node.js 不会自动读取系统变量 HTTP_PROXY——需要使用特殊库或明确设置代理。最方便的选项是 https-proxy-agent 或 axios,并配置代理。
// 使用 axios
const axios = require('axios');
const proxyConfig = {
host: process.env.PROXY_HOST,
port: parseInt(process.env.PROXY_PORT),
auth: {
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS
}
};
async function fetchData(url) {
try {
const response = await axios.get(url, {
proxy: proxyConfig,
timeout: 30000,
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MyBot/1.0)'
}
});
return response.data;
} catch (error) {
console.error(`请求失败: ${error.message}`);
throw error;
}
}
fetchData('https://api.example.com/prices')
.then(data => console.log(JSON.stringify(data, null, 2)))
.catch(() => process.exit(1));
对于 npm 命令(例如,如果 npm 尝试通过企业代理下载包),配置更简单:
- name: Configure npm proxy
run: |
npm config set proxy http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }}
npm config set https-proxy http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }}
- name: Install dependencies
run: npm install
- name: Reset npm proxy (使用后清除)
run: |
npm config delete proxy
npm config delete https-proxy
在 GitHub Secrets 中安全存储代理数据
永远不要将代理数据(主机、端口、用户名、密码)直接存储在工作流文件中。这是一个严重的安全错误:工作流文件存储在代码库中,可能对项目的所有参与者可见,甚至是公开的。
正确的方法是使用 GitHub Secrets。 这是逐步指南:
- 在 GitHub 上打开代码库
- 转到 Settings → Secrets and variables → Actions
- 点击 New repository secret
- 创建四个秘密:
PROXY_HOST、PROXY_PORT、PROXY_USER、PROXY_PASS - 在工作流中通过语法
${{ secrets.PROXY_HOST }}引用它们
🔒 额外的安全措施
- 如果不同环境(staging/production)使用不同的代理,请使用 Environment secrets 而不是 Repository secrets
- 通过 Environment protection rules 限制对秘密的访问——要求在生产环境中手动确认
- 定期轮换代理凭据——每 30-90 天更改密码
- 不要通过
echo将秘密值输出到日志——GitHub 会自动屏蔽它们,但最好不要冒险
如果您使用轮换代理(每次请求时 IP 更改或按计划更改),通常只需存储一个端点——代理提供商自己管理 IP 池。在这种情况下,秘密中只会有一个轮换网关的主机和端口。
代理轮换和错误处理
即使是高质量的代理有时也会出现故障:IP 可能会被暂时禁止,会话可能会中断,服务器可能没有响应。对于自动运行的 CI/CD 管道,重要的是考虑到这些情况的处理。
策略 1:使用相同的代理重试
import requests
import time
import os
def fetch_with_retry(url, max_retries=3, delay=5):
proxies = {
'http': f"http://{os.environ['PROXY_USER']}:{os.environ['PROXY_PASS']}@{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}",
'https': f"http://{os.environ['PROXY_USER']}:{os.environ['PROXY_PASS']}@{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}",
}
for attempt in range(max_retries):
try:
response = requests.get(url, proxies=proxies, timeout=30)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
print(f"尝试 {attempt + 1} 失败: {e}")
if attempt < max_retries - 1:
print(f"{delay} 秒后重试...")
time.sleep(delay)
delay *= 2 # 指数延迟
raise Exception(f"所有 {max_retries} 次尝试均失败,网址:{url}")
策略 2:代理列表切换
如果您有多个代理服务器,可以将它们的列表存储在一个秘密中(用逗号分隔),并在出错时切换:
import os
import requests
import random
# 秘密 PROXY_LIST 包含: "host1:port1:user1:pass1,host2:port2:user2:pass2"
proxy_list_raw = os.environ.get('PROXY_LIST', '').split(',')
def parse_proxy(proxy_str):
parts = proxy_str.strip().split(':')
if len(parts) == 4:
host, port, user, password = parts
return {
'http': f'http://{user}:{password}@{host}:{port}',
'https': f'http://{user}:{password}@{host}:{port}',
}
return None
proxies = [p for p in [parse_proxy(raw) for raw in proxy_list_raw] if p]
def fetch_with_proxy_rotation(url):
random.shuffle(proxies) # 随机顺序
for proxy in proxies:
try:
response = requests.get(url, proxies=proxy, timeout=20)
if response.status_code == 200:
return response
except Exception as e:
print(f"代理失败: {e}, 尝试下一个...")
raise Exception("所有代理均已耗尽")
策略 3:使用轮换端点
最简单的选项是使用具有单一轮换网关的代理提供商。在这种情况下,您连接到一个地址,而提供商会自动从池中提供不同的 IP。代码中不需要任何轮换逻辑——只需一行连接即可。
真实场景:解析、测试、监控价格
让我们看看三个具体场景,这些场景在使用 GitHub Actions 和代理的团队中最常见。
场景 1:每日监控 Wildberries 的价格
市场卖家经常设置自动收集竞争对手价格的工作流。工作流按计划启动(例如,每天早上 7:00),收集数据并将其保存到 Google Sheets 或发送到 Telegram。
name: Daily Price Monitor
on:
schedule:
- cron: '0 4 * * *' # 07:00 MSK (UTC+3)
jobs:
monitor-prices:
runs-on: ubuntu-latest
env:
HTTP_PROXY: http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }}
HTTPS_PROXY: http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }}
NO_PROXY: github.com,api.github.com
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install requests beautifulsoup4 gspread
- name: Run price scraper
env:
GOOGLE_SHEETS_KEY: ${{ secrets.GOOGLE_SHEETS_KEY }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
run: python scripts/price_monitor.py
- name: Upload results artifact
uses: actions/upload-artifact@v4
with:
name: price-data-${{ github.run_id }}
path: output/prices.json
场景 2:地理定位网站测试
市场营销人员和 QA 团队使用代理检查网站或广告在不同城市的用户眼中的样子。特别适合检查区域价格、内容和重定向。
name: Geo-targeted Site Tests
on:
push:
branches: [main]
pull_request:
jobs:
test-moscow:
runs-on: ubuntu-latest
name: Test from Moscow
steps:
- uses: actions/checkout@v4
- name: Run geo tests (RU/Moscow proxy)
env:
HTTP_PROXY: http://${{ secrets.PROXY_RU_USER }}:${{ secrets.PROXY_RU_PASS }}@${{ secrets.PROXY_RU_HOST }}:${{ secrets.PROXY_RU_PORT }}
HTTPS_PROXY: http://${{ secrets.PROXY_RU_USER }}:${{ secrets.PROXY_RU_PASS }}@${{ secrets.PROXY_RU_HOST }}:${{ secrets.PROXY_RU_PORT }}
run: |
python tests/geo_test.py --region=RU --city=Moscow
test-germany:
runs-on: ubuntu-latest
name: Test from Germany
steps:
- uses: actions/checkout@v4
- name: Run geo tests (DE proxy)
env:
HTTP_PROXY: http://${{ secrets.PROXY_DE_USER }}:${{ secrets.PROXY_DE_PASS }}@${{ secrets.PROXY_DE_HOST }}:${{ secrets.PROXY_DE_PORT }}
HTTPS_PROXY: http://${{ secrets.PROXY_DE_USER }}:${{ secrets.PROXY_DE_PASS }}@${{ secrets.PROXY_DE_HOST }}:${{ secrets.PROXY_DE_PORT }}
run: |
python tests/geo_test.py --region=DE
场景 3:自动检查广告账户
套利者和绩效营销人员经常使用 GitHub Actions 自动检查 Facebook Ads 广告账户的状态、余额和指标。来自 Azure 范围的请求可能会引发额外的安全检查——代理有助于绕过这一点。
name: Ad Account Health Check
on:
schedule:
- cron: '*/30 6-22 * * *' # 每 30 分钟从 6 到 22 MSK
jobs:
check-accounts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install requests
- name: Check Facebook Ads accounts
env:
HTTP_PROXY: http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }}
HTTPS_PROXY: http://${{ secrets.PROXY_USER }}:${{ secrets.PROXY_PASS }}@${{ secrets.PROXY_HOST }}:${{ secrets.PROXY_PORT }}
FB_ACCESS_TOKEN: ${{ secrets.FB_ACCESS_TOKEN }}
ACCOUNT_IDS: ${{ secrets.FB_ACCOUNT_IDS }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
run: python scripts/check_fb_accounts.py
📋 启动代理工作流前的检查清单
- ✅ 代理数据已添加到 GitHub Secrets(而不是工作流文件中)
- ✅ 变量
NO_PROXY包括github.com - ✅ 添加了 IP 检查步骤以进行调试
- ✅ 实现了错误处理和重试逻辑
- ✅ 代理类型与任务相符(受保护网站使用住宅代理)
- ✅ 设置了错误通知(Telegram、Slack 或电子邮件)
- ✅ 在添加调度之前,通过
workflow_dispatch手动测试了工作流
结论
在 GitHub Actions 中设置代理并不是一项复杂的任务,只要知道正确的方法。本指南的关键要点:
- 环境变量
HTTP_PROXY/HTTPS_PROXY——一种通用方法,适用于大多数工具,无需更改代码。 - GitHub Secrets——存储代理凭据的唯一正确位置。
- 代理类型很重要:对于解析受保护的市场,需要住宅 IP,对于广告平台——需要移动代理,对于简单的 API 请求,数据中心代理就足够了。
- 重试逻辑是必需的,适用于按计划无监督运行的管道。
- 工作流开头的 IP 检查步骤将节省调试时间。
如果您的 GitHub Actions 工作流与市场、广告平台或任何具有反机器人保护的服务交互,建议使用 住宅代理——它们具有真实的家庭用户 IP,并且比 GitHub 服务器的云地址更少引发封锁。对于与 Facebook Ads、TikTok 或其他社交平台相关的任务,最佳选择是 移动代理,它们具有运营商 IP——为平台提供了最大的信任级别。
```