經過了前幾日的奮鬥,要用來儲存網路新聞文章的資料結構(Data Class) 定義好了,運行在 AWS 上的 PostgreSQL 資料庫也準備好了。
那麼,
要開始讓新聞資料源源不斷進來的環節了。
就如同前幾天的挑戰文章中提到的那樣,我希望這次打造的「基於自然語言處理的新聞意見提取應用」有辦法做到「自動更新獲取最新的新聞文章」,所以一定會需要建立一個機制,能夠在設定的時間自動執行以 Python 撰寫的爬蟲程式。
如果在 Google 上查詢讓 Python 在設定好的時間自動執行的方案,有一些像是用 sleep() 搭配迴圈等待特定時間,或是使用一些排程相關套件的解決辦法。(例如使用Timeloop、sched、schedule、APScheduler等,這部分詳細用法我沒有進一步去了解)
不過以上想法有個很重要的缺點,那就是我沒辦保證包含排程或等待的 Python 程式長久在我個人的電腦上執行。因為除了運行 Python 爬蟲程式外,我還需要用電腦完成其他事務。可能會因為要帶筆電外出,沒有保持網路連線,造成在設定好的時間無法獲得新的新聞資料。又或者電腦因為其他原因發生關機情況,致使 Python 爬蟲程式中斷。
要解決上述情形,我想改在雲端服務的虛擬機執行 Python 爬蟲程式會是個可行的方案。但是虛擬機器的租用價格也有些小貴,難道就沒有更好的解決方案了嗎?
有的!
就是今天要分享的解決方案 –「設定 Amazon EventBridge 規則,定時觸發 AWS Lambda 的 Python 爬蟲程式」。
此方案由於 AWS 本身的免費額度,加上只有運行時計費的特點,會比租用執行個體更經濟實惠。
由於這個方案採用 AWS 的服務架設,會有下面列出的問題需要留意:
AWS Lambda 服務不像一般電腦可以安裝普通的瀏覽器程式(本文章以 google chrome 為例),那要如何使用 Python 的 selenium 套件?
要如何讓建立的 AWS Lambda 服務可以連線到昨天同樣在 AWS 上建立的 PostgreSQL 資料庫?
以上兩個要解決的課題會安插在接下來的解說當中,如果你也在設法解決相似問題,可以多留意這個部分。那麼要正式開始今天的分享了喔!
提醒: 這篇文章主要在講述「讓 AWS Lambda 定時執行爬蟲程式」的方法與設定,並沒有包含爬蟲程式的實作。
以下說明參考及部分擷取自 AWS Lambda
AWS Lambda 是一種無伺服器、事件推動的運算服務,可讓您針對幾乎任何類型的應用程式或後端服務執行程式碼,而無需佈建或管理伺服器。您可以從超過 200 個 AWS 服務和軟體即服務 (SaaS) 應用程式觸發 Lambda,且僅需針對所使用的服務付費。
基於可以搭配其他 AWS 服務的特性,Lambda 的用途相當廣泛,可以作為檔案處理、串流處理、Web 應用程式、IoT 後端、行動後端等應用的其中一個環節。除了Python 外還支援 Java、Go、Node.js 等多種程式語言,且可以選擇在 x86_64 或 ARM 架構下執行你的程式。
以 Python 為例,Lambda 服務的核心是使用者定義的 lambda_handler()
這個 function,程式碼結構範例如下所示:
import json
def lambda_handler(event, context):
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
這段程式碼中有幾個重點:
lambda_handler()
需要接受 event
和 context
這兩個參數,分別用來傳入不同的資訊:
event
:觸發 Lambda 應用的訊息context
:AWS Lambda context object in Python
如果你在最後 return
像是上方程式碼中具有 statusCode
和 body
的結構,將會變成此 Lambda 程式的 response 輸出,後續可以用於 API 建立。當然也可以不 retuern
任何東西。
如同上面 import json
一樣,Python 的 Lambda 程式可以 import
事先設定好要安裝在此 Lambda 應用執行環境中的套件。
「設定 Amazon EventBridge 規則,定時觸發 AWS Lambda 的 Python 爬蟲程式」這個任務中,主要功能只會使用到 Amazon EventBridge 以及 AWS Lambda 這兩個服務(還會需要一些儲存、管理、監控的次要服務),所以可以採用 AWS SAM(Serverless Application Model)來一次性建立這兩個服務並完成其他所需設定。
當然你可以使用 AWS 提供的網路使用介面建構 Lambda 以及其他服務,並將他們設定為彼此連動。但是相比於在網頁點選按鈕、操作選單、輸入數值來建立需要的 AWS 服務,在這次的情況下使用 AWS SAM 會讓之後變更 Python 程式碼或者 AWS 服務設定更輕鬆,還有著方便在本地端進行測試、建立 GitHub Action 等優點。
接著就介紹一下 AWS SAM ,以及如何使用這個工具建立這次需要的功能。
下方內容整理自 什麼是AWS Serverless Application Model(AWS SAM)?
AWS Serverless Application Model(AWS SAM)是一種開放原始碼架構,可以用於建置 AWS 無伺服器應用程式上。一個無伺服器應用程式是 Lambda 函數、事件來源及其他資源的組合,以共同執行任務。
AWS SAM 包含下列兩個重要的部分:
AWS SAM模板規範
AWS SAM命令列界面 (AWS SAMCLI)
AWS SAM 優點的詳細說明請見 什麼是AWS Serverless Application Model(AWS SAM)?
注意:下面的安裝過程將以 macOS 為主,使用其他作業系統請查看安裝 AWS SAM CLI
建立 AWS 帳戶
設定AWS Identity and Access Management(IAM) 權限和AWS登入資料
安裝 Docker(視情況安裝,這次要建立的應用需要)
設定AWS證書
Access key ID
和 Secret access key
。us-east-1
以外的地區):
$ aws configure
AWS Access Key ID [None]: Access key ID
AWS Secret Access Key [None]: Secret access key
Default region name [None]: us-east-1
Default output format [None]:
安裝 Homebrew
安裝 AWS SAM CLI
brew tap aws/tap
brew install aws-sam-cli
sam --version
SAM CLI, version 1.35.0
的輸出。此次採用下列設置,以 AWS SAM 建構無伺服器 Python 爬蟲應用程式:
在終端機執行 sam init
,並依照下方所示進行選擇:
❯ sam init
You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Multi-step workflow
3 - Serverless API
4 - Scheduled task
5 - Standalone function
6 - Data processing
7 - Infrastructure event management
8 - Lambda EFS example
9 - Machine Learning
Template: 1
Use the most popular runtime and package type? (Python and zip) [y/N]: N
Which runtime would you like to use?
1 - dotnet6
2 - dotnet5.0
3 - dotnetcore3.1
4 - go1.x
5 - graalvm.java11 (provided.al2)
6 - graalvm.java17 (provided.al2)
7 - java11
8 - java8.al2
9 - java8
10 - nodejs16.x
11 - nodejs14.x
12 - nodejs12.x
13 - python3.9
14 - python3.8
15 - python3.7
16 - python3.6
17 - ruby2.7
18 - rust (provided.al2)
Runtime: 13
What package type would you like to use?
1 - Zip
2 - Image
Package type: 2
Based on your selections, the only dependency manager available is pip.
We will proceed copying the template using pip.
Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]: N
Project name [sam-app]:
注意:
Project name [sam-app]:
可以按 enter 使用預設的 sam-app 或是自行命名。
完成後可以看到類似下方的輸出結果:
Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)
-----------------------
Generating application:
-----------------------
Name: sam-app
Base Image: amazon/python3.9-base
Architectures: x86_64
Dependency Manager: pip
Output Directory: .
Next steps can be found in the README file at ./sam-app/README.md
Commands you can use next
=========================
[*] Create pipeline: cd sam-app && sam pipeline init --bootstrap
[*] Validate SAM template: sam validate
[*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
SAM CLI update available (1.57.0); (1.55.0 installed)
To download: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html
現在有了基本的應用程式檔案架構,長得像這樣:
❯ tree
.
└── sam-app
├── README.md
├── __init__.py
├── events
│ └── event.json
├── hello_world
│ ├── Dockerfile
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── template.yaml
└── tests
├── __init__.py
└── unit
├── __init__.py
└── test_handler.py
5 directories, 11 files
當中有幾個重要的部分(當中 template.yaml
最為重要):
events
資料夾
event.json
,是在執行 sam local invoke HelloWorldFunction --event events/event.json
進行本地端測試時會作為 lambda_handler(event, context)
中的 event 輸入。(注意:HelloWorldFunction 會因為 template.yaml 中的定義而有所不同)hello_world
資料夾
lambda_handler(event, context)
包含在 app.py
當中,requirements.txt
內記錄了需要安裝的 Python 套件資訊,Dockerfile
包含產生 Image 的流程,會在過程中安裝 requirements.txt
所記錄的項目(注意:hello_world
這個資料夾名稱會因為 template.yaml 中的定義而有所不同)tests
資料夾
template.yaml
sam build
、sam deploy
皆會依照 template.yaml
中的設定進行操作。template.yaml
的範本片段,每個區塊的設定內容都可以在 AWS Serverless Application Model(AWS SAM) 規格 中找到相關說明。
Transform: AWS::Serverless-2016-10-31
Globals:
set of globals
Description:
String
Metadata:
template metadata
Parameters:
set of parameters
Mappings:
set of mappings
Conditions:
set of conditions
Resources:
set of resources
Outputs:
set of outputs
events
和 tests
的用途暫時不會碰到,所以先示範如何更動 hello_world
資料夾內的檔案和 template.yaml
。
hello_world
資料夾內的檔案 => 關係到 Lambda 函數的功能template.yaml
=> 關係到 build 流程與 deploy 所需資源及設定template.yaml
中的設定改變 template.yaml
中的設定如下:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
python3.9
Sample SAM Template for sam-app
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 300
Resources:
NewsCollectFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Metadata:
Dockerfile: Dockerfile
DockerContext: ./hello_world
DockerTag: python3.9-v1
Properties:
PackageType: Image
Architectures:
- x86_64
MemorySize: 2048
EphemeralStorage:
Size: 2048
VpcConfig:
SecurityGroupIds:
- sg-XXXXXXX
SubnetIds:
- subnet-XXXXXXX
Events:
ScheduleChinatimes:
Type: Schedule
Properties:
Name: ScheduleChinatimes
Description: Trigger AWS Lambda function to collect Chinatimes news every hour.
Schedule: cron(0 * ? * * *)
Input: "{\"media\":\"chinatimes\"}"
Enabled: True
這裡先針對上方的內容作個說明:
template.yaml
中紀錄的 Lambda 應用最長執行時間 (s)解決問題:要如何讓建立的 AWS Lambda 服務可以連線到昨天同樣在 AWS 上建立的 PostgreSQL 資料庫?
將VpcConfig
的 VPC 設定成跟昨天建立的 PostgreSQL 一樣,就可以解決 VPC 連線權限的問題。
在昨天提到的 AWS RDS > 資料庫中,點選間裡的資料庫 instance,取得「VPC 安全群組」和「子網路」,並且填入下方結構中。
- VpcConfig:
- SecurityGroupIds:
- sg-XXXXXXX
- SubnetIds:
- subnet-XXXXXXX
- subnet-XXXXXXX
"{\"media\":\"chinatimes\"}"
,排程觸發時 lambda_handler
的 event 輸入。補充:
如果想自行更改
template.yaml
中的設定,在 AWS Serverless Application Model(AWS SAM) 規格 可以找到相關說明
Dockerfile
中的設定解決問題:AWS Lambda 服務不像一般電腦可以安裝普通的瀏覽器程式(本文章以 google chrome 為例),那要如何使用 Python 的 selenium 套件?
由於 Python 所撰寫的爬蟲功能會用到 selenium 套件,所以我們需要安裝 Chrome。
我參考 docker-selenium-lambda 改寫
Dockerfile
,內容如下:
FROM public.ecr.aws/lambda/python@sha256:ce32e747dd996dc402c3a68ac5ce3ac116ab470c56b1275907a262137f92a52d as build
RUN yum install -y unzip && \
curl -Lo "/tmp/chromedriver.zip" "https://chromedriver.storage.googleapis.com/104.0.5112.79/chromedriver_linux64.zip" && \
curl -Lo "/tmp/chrome-linux.zip" "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F1012728%2Fchrome-linux.zip?alt=media" && \
unzip /tmp/chromedriver.zip -d /opt/ && \
unzip /tmp/chrome-linux.zip -d /opt/
FROM public.ecr.aws/lambda/python@sha256:ce32e747dd996dc402c3a68ac5ce3ac116ab470c56b1275907a262137f92a52d
RUN yum install atk cups-libs gtk3 libXcomposite alsa-lib \
libXcursor libXdamage libXext libXi libXrandr libXScrnSaver \
libXtst pango at-spi2-atk libXt xorg-x11-server-Xvfb \
xorg-x11-xauth dbus-glib dbus-glib-devel -y
RUN pip install selenium
COPY --from=build /opt/chrome-linux /opt/chrome
COPY --from=build /opt/chromedriver /opt/
COPY requirements.txt ./
RUN pip3 install -r requirements.txt
COPY app.py ./
CMD ["app.lambda_handler"]
app.py
和 requirements.txt
的範例下面是一個用 selenium 獲取網路資料的程式碼範例,之後可以依自己的使用需求做更動
requirements.txt
範例如下(將之後會用到的套件都紀錄在這個檔案裡):
selenium
app.py
範例如下:
import json
from selenium import webdriver
from tempfile import mkdtemp
from selenium.webdriver.common.by import By
def lambda_handler(event, context):
options = webdriver.ChromeOptions()
options.binary_location = '/opt/chrome/chrome'
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1280x1696")
options.add_argument("--single-process")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-dev-tools")
options.add_argument("--no-zygote")
options.add_argument(f"--user-data-dir={mkdtemp()}")
options.add_argument(f"--data-path={mkdtemp()}")
options.add_argument(f"--disk-cache-dir={mkdtemp()}")
options.add_argument("--remote-debugging-port=9222")
chrome = webdriver.Chrome("/opt/chromedriver",
options=options)
chrome.get("https://example.com/")
return {
"statusCode": 200,
"body": json.dumps(
{
"message": f"{chrome.find_element(by=By.XPATH, value="//html").text}",
}
),
}
在有 template.yaml
的路經執行 sam build
。
在有 template.yaml
的路經執行 sam deploy --guided
,除了 HelloWorldFunction may not have authorization defined, Is this okay? [y/N]
要選 Y
以外,其他選項按 enter 使用預設值。
到這裡就完成範例程式的部署了,可以從網頁版的 AWS Lambda 頁面中查看這次範例應用的執行結果。(template.yaml
的範例設定一小時執行一次)
寫的有些匆忙,如果文章有錯誤,歡迎指正~