全部產品
Search
文件中心

CDN:使用自動化指令碼重新整理和預熱

更新時間:Aug 26, 2025

阿里雲CDN為您提供重新整理預熱自動化指令碼,可以協助您分批進行重新整理或預熱任務,對檔案或目錄快速進行重新整理和預熱,替代手動分批提交的繁瑣操作。本文介紹Python自動化指令碼的使用說明,並以Windows系統樣本為您說明。

功能簡介

當您指定重新整理或預熱URL列表檔案後,指令碼按照指定的並發重新整理或預熱數量對URL列表檔案進行切割,分批進行重新整理或預熱。任務運行後會自動進行判斷,等待上一個任務完成,指令碼自動進行下一個重新整理或預熱任務的操作。具體的操作邏輯如下:

  1. 分批處理:假設您的URL列表中有100個URL,同時您設定了每批次最多處理10個URL,那麼指令碼會將URL列表切割成10個小批次,每個批次包含10個URL。而如果設定的並發數量更大或更小,批次的大小會相應調整。例如設定的並發數量是20,那麼指令碼會將100個URL分成5個批次,每個批次包含20個URL。

  2. 按批次運行任務:指令碼在啟動時會按照批次依次提交重新整理或預熱請求。每個批次的任務是並發執行的。

  3. 等待完成後進行下一批任務:當一個批次的重新整理或預熱任務完成後,指令碼會繼續執行下一個批次的任務。這個判斷和操作是自動進行的,不需要人工幹預。

適用情境

如果您有以下情況,建議您使用重新整理預熱自動化指令碼:

  • 無開發人員,需手動提交重新整理預熱任務,營運成本高。

  • 重新整理或預熱URL過多,分批提交導致重新整理或預熱效率低。

  • 需要人工或程式判斷重新整理預熱任務是否正常進行,費時費力。

使用限制

請確保作業系統的Python版本為3.x版本。您可以通過在命令列輸入python --versionpython3 --version來檢查Python版本是否符合要求。

前提條件

  1. 由於阿里雲帳號(主帳號)擁有資源的所有許可權,其AccessKey一旦泄露風險巨大,所以建議您使用RAM使用者的AccessKey。擷取方法請參見建立AccessKey

  2. 給RAM使用者授予操作網域名稱資源的許可權。本樣本選擇AliyunDomainFullAccess系統策略。

    1. 使用系統策略。

      • AliyunCDNFullAccess:管理CDN資源的許可權。

    2. 使用自訂許可權。

      關於如何建立自訂許可權,請參見建立自訂權限原則

步驟一:安裝依賴

執行以下命令安裝Python CDN SDK模組包,目前使用版本為v20180510。

pip install alibabacloud_cdn20180510

步驟二:準備URL檔案

建立一個包含需要重新整理或預熱的URL列表的檔案。例如:urllist.txt,每行一個URL。請確保每個URL以http://https://開頭,並且是合法的URL格式。內容樣本如下:

http://example.com/file1.jpg
http://example.com/file2.jpg
http://example.com/file3.jpg
...
http://example.com/fileN.jpg

步驟三:建立指令碼

將如下代碼儲存為自動化指令碼,並命名為Refresh.py。您可以自訂指令碼名稱,此處為舉例說明。

指令碼範例程式碼

#!/usr/bin/env python3
# coding=utf-8
# __author__ = 'aliyun.cdn'
# __date__ = '2025-08-15'

# SDK安裝命令 pip install alibabacloud_cdn20180510

'''Check Package'''
# 匯入所需庫
import re, sys, getopt, time, logging, os

try:
    from alibabacloud_cdn20180510.client import Client as Cdn20180510Client
    from alibabacloud_credentials.models import Config as CreConfig
    from alibabacloud_credentials.client import Client as CredentialClient
    from alibabacloud_tea_openapi.models import Config
    from alibabacloud_cdn20180510 import models as cdn_20180510_models
    from alibabacloud_tea_util import models as util_models

# 捕獲匯入異常
except ImportError as e:
    sys.exit(f"[error] Please pip install alibabacloud_cdn20180510. Details: {e}")

# 初始化日誌記錄
logging.basicConfig(level=logging.DEBUG, filename='./RefreshAndPredload.log')

# 定義全域變數類,儲存AK、SK、FD等資訊
class Envariable(object):
    LISTS = []
    # Endpoint 請參考 https://api.aliyun.com/product/Cdn
    ENDPOINT = 'cdn.aliyuncs.com'
    AK = None
    SK = None
    FD = None
    CLI = None
    TASK_TYPE = None
    TASK_AREA = None
    TASK_OTYPE = None

    # 設定AK
    @staticmethod
    def set_ak():
        Envariable.AK = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID')

    # 擷取AK
    @staticmethod
    def get_ak():
        return Envariable.AK

    # 設定SK
    @staticmethod
    def set_sk():
        Envariable.SK = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET')

    # 擷取SK
    @staticmethod
    def get_sk():
        return Envariable.SK

    # 設定FD
    @staticmethod
    def set_fd(fd):
        Envariable.FD = fd

    # 擷取FD
    @staticmethod
    def get_fd():
        return Envariable.FD

    # 設定任務類型
    @staticmethod
    def set_task_type(task_type):
        Envariable.TASK_TYPE = task_type

    # 擷取任務類型
    @staticmethod
    def get_task_type():
        return Envariable.TASK_TYPE
    
    # 設定工作區域
    @staticmethod
    def set_task_area(task_area):
        Envariable.TASK_AREA = task_area

    # 擷取工作區域
    @staticmethod
    def get_task_area():
        return Envariable.TASK_AREA

    # 設定任務物件類型
    @staticmethod
    def set_task_otype(task_otype):
        Envariable.TASK_OTYPE = task_otype

    # 擷取任務物件類型
    @staticmethod
    def get_task_otype():
        return Envariable.TASK_OTYPE

    # 建立新的Client
    @staticmethod
    def set_acs_client():
        try:
            # 使用AK 初始化Credentials Client。
            credentialsConfig = CreConfig(
                # 憑證類型。
                type='access_key',
                # 設定為AccessKey ID值。
                access_key_id=Envariable.get_ak(),
                # 設定為AccessKey Secret值。
                access_key_secret=Envariable.get_sk(),
            )
            credentialClient = CredentialClient(credentialsConfig)

            cdnConfig = Config(credential=credentialClient)
            # 配置雲產品服務接入地址(endpoint)。
            cdnConfig.endpoint = Envariable.ENDPOINT
            # 初始化cdn Client。
            Envariable.CLI = Cdn20180510Client(cdnConfig)
        except Exception as e:
            logging.error(f"Failed to create client: {e}")
            raise

    # 擷取Client
    @staticmethod
    def get_acs_client():
        return Envariable.CLI


# 模組層級別的初始化函數
def initialize_credentials_and_client():
    """在模組載入時初始化AK、SK和Client"""
    try:
        # 從環境變數初始化AK和SK
        Envariable.set_ak()
        Envariable.set_sk()
        
        # 檢查AK和SK是否成功擷取
        if not Envariable.get_ak() or not Envariable.get_sk():
            logging.warning("AK or SK not found in environment variables")
            return False
            
        # 初始化Client
        Envariable.set_acs_client()
        logging.info("Credentials and client initialized successfully")
        return True
    except Exception as e:
        logging.error(f"Failed to initialize credentials and client: {e}")
        return False


# 在模組載入時執行初始化
_initialization_success = initialize_credentials_and_client()





class BaseCheck(object):
    def __init__(self):
        self.invalidurl = ''
        self.lines = 0
        self.urllist = Envariable.get_fd()

    # 檢查配額
    def printQuota(self):
        try:
            client = Envariable.get_acs_client()
            if not client:
                raise Exception("CDN client not initialized")
            
            # 使用SDK調用方式
            request = cdn_20180510_models.DescribeRefreshQuotaRequest()
            runtime = util_models.RuntimeOptions()
            response = client.describe_refresh_quota_with_options(request, runtime)
            quotaResp = response.body.to_map()
        except Exception as e:
            logging.error(f"\n[error]: initial Cdn20180510Client failed: {e}\n")
            sys.exit(1)

        if Envariable.TASK_TYPE:
            if Envariable.TASK_TYPE == 'push':
                if self.lines > int(quotaResp['PreloadRemain']):
                    sys.exit("\n[error]:PreloadRemain is not enough {0}".format(quotaResp['PreloadRemain']))
                return True
            if Envariable.TASK_TYPE == 'clear':
                if Envariable.get_task_otype() == 'File' and self.lines > int(quotaResp['UrlRemain']):
                    sys.exit("\n[error]:UrlRemain is not enough {0}".format(quotaResp['UrlRemain']))
                elif Envariable.get_task_otype() == 'Directory' and self.lines > int(quotaResp['DirRemain']):
                    sys.exit("\n[error]:DirRemain is not enough {0}".format(quotaResp['DirRemain']))
                else:
                    return True

    # 驗證URL格式
    def urlFormat(self):
        try:
            with open(self.urllist, "r") as f:
                for line in f.readlines():
                    self.lines += 1
                    if not re.match(r'^((https)|(http))', line):
                        self.invalidurl = line + '\n' + self.invalidurl
                if self.invalidurl != '':
                    sys.exit("\n[error]: URL format is illegal \n{0}".format(self.invalidurl))
                return True
        except FileNotFoundError:
            sys.exit(f"\n[error]: File not found: {self.urllist}\n")
        except Exception as e:
            sys.exit(f"\n[error]: Failed to read file {self.urllist}: {e}\n")

# 批量處理類,將URL列表按指定數量分成多個批次
class doTask(object):
    @staticmethod
    def urlencode_pl(inputs_str):
        len_str = len(inputs_str)
        if inputs_str == "" or len_str <= 0:
            return ""
        result_end = ""
        for chs in inputs_str:
            if chs.isalnum() or chs in {":", "/", ".", "-", "_", "*"}:
                result_end += chs
            elif chs == ' ':
                result_end += '+'
            else:
                result_end += f'%{ord(chs):02X}'
        return result_end

    # 分批處理URL
    @staticmethod
    def doProd():
        gop = 20  # 這裡定義了每個批次的最大URL數量
        mins = 1
        maxs = gop
        current_batch = []  # 使用局部變數而不是全域變數
        
        try:
            with open(Envariable.get_fd(), "r") as f:
                for line in f.readlines():
                    line = doTask.urlencode_pl(line.strip()) + "\n"
                    current_batch.append(line)
                    if mins >= maxs:
                        yield current_batch
                        current_batch = []
                        mins = 1
                    else:
                        mins += 1
            if current_batch:
                yield current_batch
        except FileNotFoundError:
            sys.exit(f"\n[error]: File not found: {Envariable.get_fd()}\n")
        except Exception as e:
            sys.exit(f"\n[error]: Failed to read file {Envariable.get_fd()}: {e}\n")

    # 執行重新整理或預熱任務
    @staticmethod
    def doRefresh(lists):
        try:
            client = Envariable.get_acs_client()
            if not client:
                raise Exception("CDN client not initialized")

            runtime = util_models.RuntimeOptions()
            taskID = None
            response_data = None

            if Envariable.get_task_type() == 'clear':
                taskID = 'RefreshTaskId'
                request = cdn_20180510_models.RefreshObjectCachesRequest()
                if Envariable.get_task_otype():
                    request.object_type = Envariable.get_task_otype()
                request.object_path = lists
                response = client.refresh_object_caches_with_options(request, runtime)
                response_data = response.body.to_map()
            elif Envariable.get_task_type() == 'push':
                taskID = 'PushTaskId'
                request = cdn_20180510_models.PushObjectCacheRequest()
                if Envariable.get_task_area():
                    request.area = Envariable.get_task_area()
                request.object_path = lists
                response = client.push_object_cache_with_options(request, runtime)
                response_data = response.body.to_map()

            if response_data and taskID:
                print(response_data)

                timeout = 0
                while True:
                    count = 0
                    # 使用SDK調用方式查詢任務狀態
                    taskreq = cdn_20180510_models.DescribeRefreshTasksRequest()
                    taskreq.task_id = response_data[taskID]
                    taskresp = client.describe_refresh_tasks_with_options(taskreq, runtime)
                    taskresp_data = taskresp.body.to_map()
                    print(f"[{response_data[taskID]}] is doing... ...")
                    
                    for t in taskresp_data['Tasks']['CDNTask']:
                        if t['Status'] != 'Complete':
                            count += 1
                    if count == 0:
                        logging.info(f"[{response_data[taskID]}] is finish")
                        break
                    elif timeout > 5:  # 最多等待50秒 (5 * 10秒)
                        logging.info(f"[{response_data[taskID]}] timeout after 50 seconds")
                        break
                    else:
                        timeout += 1
                        time.sleep(10)  # 每10秒檢查一次狀態
                        continue
        except Exception as e:
            logging.error(f"\n[error]:{e}")
            sys.exit(1)


class Refresh(object):
    def main(self, argv):
        if len(argv) < 1:
            sys.exit(f"\n[usage]: {sys.argv[0]} -h ")
        try:
            opts, args = getopt.getopt(argv, "hr:t:a:o:")
        except getopt.GetoptError as e:
            sys.exit(f"\n[usage]: {sys.argv[0]} -h ")

        for opt, arg in opts:
            if opt == '-h':
                self.help()
                sys.exit()
            elif opt == '-r':
                Envariable.set_fd(arg)
            elif opt == '-t':
                Envariable.set_task_type(arg)
            elif opt == '-a':
                Envariable.set_task_area(arg)
            elif opt == '-o':
                Envariable.set_task_otype(arg)
            else:
                sys.exit(f"\n[usage]: {sys.argv[0]} -h ")
        
        # 只有在非協助命令時才檢查初始化狀態
        if not _initialization_success:
            sys.exit("\n[error]: Failed to initialize credentials and client. Please check environment variables.\n")

        try:
            if not (Envariable.get_ak() and Envariable.get_sk() and Envariable.get_fd() and Envariable.get_task_type()):
                sys.exit("\n[error]: Must set environment variables ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET, and parameters '-r', '-t'\n")
            if Envariable.get_task_type() not in {"push", "clear"}:
                sys.exit("\n[error]: taskType Error, '-t' option in 'push' or 'clear'\n")
            if Envariable.get_task_area() and Envariable.get_task_otype():
                sys.exit("\n[error]: -a and -o cannot exist at same time\n")
            if Envariable.get_task_area():
                if Envariable.get_task_area() not in {"domestic", "overseas"}:
                    sys.exit("\n[error]: Area value Error, '-a' option in 'domestic' or 'overseas'\n")
            if Envariable.get_task_otype():
                if Envariable.get_task_otype() not in {"File", "Directory"}:
                    sys.exit("\n[error]: ObjectType value Error, '-a' options in 'File' or 'Directory'\n")
                if Envariable.get_task_type() == 'push':
                    sys.exit("\n[error]: -t must be clear and 'push' -a use together\n")
        except Exception as e:
            logging.error(f"\n[error]: Parameter {e} error\n")
            sys.exit(1)

        handler = BaseCheck()
        if handler.urlFormat() and handler.printQuota():
            for g in doTask.doProd():
                doTask.doRefresh(''.join(g))
                time.sleep(1)

    def help(self):
        print("\nscript options explain: \
                    \n\t -r <filename>                   filename指“檔案所在的路徑+檔案名稱”,自動化指令碼運行後將會讀取檔案內記錄的URL;檔案內的URL記錄方式為每行一條URL,有特殊字元先做URLencode,以http或https開頭; \
                    \n\t -t <taskType>                   任務類型,clear:重新整理,push:預熱; \
                    \n\t -a [String,<domestic|overseas>] 可選項,預熱範圍,不傳預設是全球;\
                    \n\t    domestic                     僅中國內地; \
                    \n\t    overseas                     全球(不包含中國內地); \
                    \n\t -o [String,<File|Directory>]    可選項,重新整理的類型; \
                    \n\t    File                         檔案重新整理(預設值); \
                    \n\t    Directory                    目錄重新整理")


if __name__ == '__main__':
    fun = Refresh()
    fun.main(sys.argv[1:])

代碼執行流程

  1. gop指定的數量(100個)將檔案拆分成多個批次。

  2. 順序處理每個批次的URL。

  3. 等待當前批次任務完成後,再執行下一個批次。

說明

您可以通過調整gop變數調整每個批次的大小。

查看協助資訊

指令碼建立完成後,您可以在命令列(CMD,PowerShell或終端)中運行python $script -h,用於請求並顯示Python指令碼的命令列協助資訊。

說明

$script通常是指一個變數,這個變數是Python指令碼的檔案名稱。例如,如果您的指令檔名是Refresh.py,您可以運行python Refresh.py -h

在命令列(CMD,PowerShell或終端)運行以下命令,指令碼會顯示協助資訊,告訴您如何正確使用該指令碼及其所有參數。

python Refresh.py -h

運行命令後可能會輸出以下內容:

script options explain:
              -r <filename>                    //filename指“檔案所在的路徑+檔案名稱”,自動化指令碼運行後將會讀取檔案內記錄的URL;檔案內的URL記錄方式為每行一條URL,有特殊字元先做URLencode,以http或https開頭;
              -t <taskType>                    //任務類型,clear:重新整理,push:預熱;
              -a [String,<domestic|overseas>   //可選項,預熱範圍,不傳預設是全球;            
                   domestic                    //僅中國內地;             
                   overseas                    //全球(不包含中國內地);             
              -o [String,<File|Directory>]     //可選項,重新整理的類型;             
                   File                        //檔案重新整理(預設值);             
                   Directory                   //目錄重新整理;

步驟四:在環境變數中設定阿里雲AccessKey

使用AccessKey調用API時,不推薦在代碼中明文傳入阿里雲AccessKey。步驟三的指令碼代碼會從環境變數中來讀取AccessKey ID和AccessKey Secret,請參考在Linux、macOS和Windows系統配置環境變數,在執行代碼前配置阿里雲AccessKey。

重要

在Linux和macOS系統中,使用export命令配置的阿里雲AccessKey僅當前會話有效,當會話退出之後所設定的阿里雲AccessKey將會丟失。

若需長期保留阿里雲AccessKey,可將export命令配置到對應作業系統的啟動設定檔中。

步驟五:運行指令碼

在命令列(CMD,PowerShell或終端)使用以下命令列運行指令碼:

python Refresh.py -r <PathToUrlFile> -t <TaskType>
說明

<PathToUrlFile>:包含URL列表的檔案路徑,如urllist.txt

<TaskType>:任務類型,clear(重新整理)或push(預熱)。

資源重新整理命令

  • 假設URL檔案為urllist.txt,且檔案和Refresh.py指令碼在相同目錄下,任務類型為clear(重新整理),在命令列(CMD,PowerShell或終端)執行以下命令。

    python Refresh.py -r urllist.txt -t clear
  • 如果檔案在不同目錄,例如D:\example\filename\urllist.txt,在命令列(CMD,PowerShell或終端)執行以下命令。

    python Refresh.py -r D:\example\filename\urllist.txt -t clear

運行樣本如下:

python Refresh.py -r urllist.txt -t clear
{'RequestId': 'C1686DCA-F3B5-5575-ADD1-05F96617D770', 'RefreshTaskId': '18392588710'}
[18392588710] is doing... ...

如果出現Failed to initialize credentials and client. Please check environment variables.,請先按步驟四在環境變數中配置阿里雲AccessKey,然後在同一個終端視窗中執行命令。

資源預熱命令

  • 假設URL檔案為urllist.txt,且檔案和Refresh.py指令碼在相同目錄下,任務類型為push(預熱),在命令列(CMD,PowerShell或終端)執行以下命令。

    python Refresh.py -r urllist.txt -t push
  • 如果檔案在不同目錄,例如D:\example\filename\urllist.txt,在命令列(CMD,PowerShell或終端)執行以下命令。

    python Refresh.py -r D:\example\filename\urllist.txt -t push

運行樣本如下:

python Refresh.py -r urllist.txt -t push
{'RequestId': 'C1686DCA-F3B5-5575-ADD1-05F96617D771', 'RefreshTaskId': '18392588711'}
[18392588710] is doing... ...

如果出現Failed to initialize credentials and client. Please check environment variables.,請先按步驟四在環境變數中配置阿里雲AccessKey,然後在同一個終端視窗中執行命令。