利用 Azure Function 搭配 Python 使用 Selenium 來進行爬蟲處理
臨時有朋友詢問在 Azure 上怎麼來使用 Python,原本想說這個有甚麼好困難的,直接佈署到 Azure Web App 上面,不就解決了嗎 ? 這個題目也太簡單了吧 ?

但沒有想到原來他是要用 Python 搭配 Selenium 來做使用,但因為 Web App 下並沒有辦法在那個環境內預先安裝 Selenium 相關套件,因此雖然是可以執行 Python,但是卻是沒有辦法來運行的。因此在網路上爬文找到一篇「How to create a Selenium web scraper in Azure Functions」,透過該作者的文章,總算解決這個難題了。
但因為自己按照這篇文章在處理的時候,也遇到一些版本上的衝突,因此就順手整理一下,免得之後忘記該如何來處理。
首先是因為在既有的 Azure Web App 或者是 Azure Function,都是採用沙箱乾淨的環境,因此如果要使用 Selenium 這類的套件的時候,因為我們沒有辦法預先在那個沙箱環境內進行安裝,因此會採用 Docker Image 的佈署方式,先在 Docker Image 內佈署相關套件,然後搭配 Azure Function 的架構來撰寫,這樣佈署到 Azure 的時候,就可以利用 Http 來觸發我們所要的功能了。
在開始之前,我們要先預先安裝一些相關的程式,才有辦法順利的進行
- Docker Desktop : 因為要採用 Docker 安裝 , 因此要先在電腦上安裝 Docker
- WSL2 Linux 核心更新套件 : 因為我的電腦是Windows 11 , 安裝這個來支援在 Windows 上啟用 Linux 的 Docker Image
- Azure CLI : 使用相關 CLI 來建立 Azure 的資源
- Azure Core Tools v4.x : 原作者本來是使用 v2.x 的版本,但因為目前抓到的 Base Image 已經是 Python 3.9 的版本,因此這裡我就改用 v4.x 的版本來安裝使用。
基本上該文章作者 René Bremer 已經很佛心的建立好一個 git repository 來放相關的 sample code 和 dockerfile,因此如果懶得動手,那就直接抓他的 git repository 來使用,可以節省不少的時間
git clone https://github.com/rebremer/azure-function-selenium.git
當我們抓取 git 上面的資源,最主要會有幾個部分
- DockerFile : Docker 環境的設定檔案,裡面已經設定好我們的 Image 的基底,並且也已經設定好安裝 Chrome 和 Selenium,要是沒有這個設定檔,光這一塊要設定好可要花上不少的時間。
- azure-pipelines.Yml : Azure Devops 的 Pipeline 都已經寫好,因此可以直接程式進版控就可以完成 CI 的處理了。
- requirements.txt : 在 DockerFile 內進行套件安裝的時候,最後會去透過這個設定檔去安裝 azure-functions , azure-storage-blob 和 azure-identity 的相關套件
- HTTP Trigger : 提供 Http 呼叫的啟動器程式 __init__.py 和設定檔 function.json
- Time Trigger : 提供定時執行的啟動器程式 __init__.py 和設定檔 function.json
有了上述這些作者已經完成的檔案,我們要使用 Python 和 Selenium 就顯得容易多了。
首先我先建立一個 Azure Container Registry,用來放我們開發好的 Image

建立好之後,要到 Access Key 去選擇開啟 [Admin user] 的模式,這樣才可以有帳號和密碼可以透過 CLI 來上傳 Image,否則就只能到 Portal 上手動處理,那這樣要做 CI/CD 就沒有辦法一氣呵成了。

接下來在打包 Docker Image 之前,我們要先來修改一個範例的程式,因為在新版本的 Selenium 的套件有些 Method 已經有做修改了,因此原本的範例程式是沒有辦法執行的,這裡我會做一點修改
這個是原本的程式,因為新版本已經拿掉 find_elements_by_tag_name 的方法

因此我修改成為以下的程式碼,將原本的 find_elements_by_tag_name 換成 find_elements,並且加入 selenium.webdriver.common.bypython 模組

完成上述的修改,接下來就可以按照原本作者所提供的腳本去打包 docker image 了,下面這一段就換上我們前面所建立的 Azure Container Registry 的名稱,以及所使用的帳號和密碼,這樣就可以打包好我們所需要使用的 Docker Image,並且將它推到 Azure Container Registry
# Variables
$acr_id = "<<your acr>>.azurecr.io"
# Create docker image using docker desktop
docker login $acr_id -u <<your username>> -p <<your password>>
docker build --tag $acr_id/selenium .
# Push docker image to Azure Container Registry
docker push $acr_id/selenium:latest
此時我們回到 Azure 上,就可以看到在 Repositories 裡面多了一個 selunium 的 Image 已經放上來了

接下來我們要來建立所使用的 Azure Function , 因為一些成本的原因,因此這裡我調整一下原本作者的腳本,多增加了登入 Azure , 並且將原本的等級從 P1v2 改成 B1
# 變數設定
$rg = "<<資源群組名稱>>"
$loc = "<<地區名稱>>"
$plan = "<<Web Plan 名稱>>"
$stor = "<<存放 Azure Function 紀錄的儲存體名稱>>"
$fun = "<<Azure Function 名稱>>"
$acr_id = "<<Azure Container Registry名稱>>.azurecr.io"
$acr_user = "<<Azure Container Registry帳號>>"
$acr_password = "<<Azure Container Registry密碼>>"
# 登入 Azure
az login
az account set --subscription "<<訂閱名稱>>"
# Create resource group, storage account and app service planaz group create -n $rg -l $loc
az storage account create -n $stor -g $rg --sku Standard_LRS
az appservice plan create --name $plan --resource-group $rg --sku B1 --is-linux
# Create Azure Function using docker image
az functionapp create --resource-group $rg --os-type Linux --plan $plan --deployment-container-image-name $acr_id/selenium:latest --name $fun --storage-account $stor --docker-registry-server-user $acr_user --docker-registry-server-password $acr_password
上述建立好 Azure Function 之後,接下來的語法是要建立一個 Azure Data Lake 的 Storage ,用來存放爬蟲所抓下來的資料,但這裡我也修改部分原作者的腳本,我將最後面的設定權限的部分抽出,改成透過 UI 介面設定,最主要的原因是最後面的指令會牽涉到 AAD 和一些權限,但如果使用者沒有相關權限會造成失敗,因此我拿掉那個部份的設定。
# Variables
$rg = "<<資源群組名稱>>"
$fun = "<<Azure Function 名稱>>"
$adls = "<<Azure Data Lake 的儲存體名稱>>"
$container_name = "scraperesults"
# Create adlsgen2
az storage account create --name $adls --resource-group $rg --location $loc --sku Standard_RAGRS --kind StorageV2 --enable-hierarchical-namespace true
az storage container create --account-name $adls -n $container_name
# Assign identity to function and set params
az webapp identity assign --name $fun --resource-group $rg
az functionapp config appsettings set --name $fun --resource-group $rg --settings par_storage_account_name=$adls par_storage_container_name=$container_name
等上述腳本執行完畢之後,接著到 Azure Function 所在的資源群組,選擇 Access Control(IAM) → Add → Add role assigment 來設定 Azure Function 有權限來存取 Azure Data Lake

選擇設定「Storage Blob Data Contributor」,街個選擇權限要指派的對象是「Managed identity」,接著選擇我們要指派的成員

將我們前面步驟透過腳本所建立的 Azure Function 給加入,這樣就可以讓 Azure Function 有權限可以來存取 Azure Data Lake 的資料

當完成上述相關設定之後,我們就可以來進行測試了,我們可以利用 HttpTrigger 的這個方法來測試,可以選擇「Get Function Url」之後,您就可以取得到一段呼叫的 URL

當我們將該段網址放到瀏覽器上,執行之後就可以有類似以下的結果,那就表示您的 Python + Selenium 可以正常運作了。
