天天看點

開發函數計算的正确姿勢 —— 爬蟲

《函數計算本地運作與調試 - Fun Local 基本用法》

中,我們介紹了利用 Fun Local 本地運作、調試函數的方法。但如果僅僅這樣簡單的介紹,并不能展現 Fun Local 對函數計算開發的巨大效率的提升。

這一次,我們拿一個簡單的場景來舉例子——開發一個簡單的爬蟲函數(代碼參考函數計算

控制台模闆 ),介紹如何以正确姿勢,從零開始,開發一個自動伸縮、按調用次數收費的 serverless 爬蟲應用。

開發步驟

我們将這個完整的應用拆分成多步,并且在每一步完成後,我們都會進行相應的運作驗證。

1. 建立 Fun 項目

首先,我們建立一個名為 image-crawler 的目錄作為項目的根。然後在該目錄下建立一個名為 template.yml 的檔案,内容為:

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  localdemo:
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'local invoke demo'
    image-crawler:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: index.handler
        CodeUri: code/
        Description: 'Hello world with python2.7!'
        Runtime: python2.7           

如果不了解 Fun 定義的 Serverless Application Model,可以參考

這裡

操作完成後,我們的項目目錄結構如下:

.
└── template.yml           

2. 編寫 helloworld 函數代碼

在根目錄下建立一個名為 code 的目錄,并在該目錄下建立一個名為 index.py 的檔案,内容為一個簡單的 helloworld 函數:

def handler(event, context):
    return 'hello world!'           

在項目根目錄下執行:

fun local invoke image-crawler           

函數運作成功:

開發函數計算的正确姿勢 —— 爬蟲

操作完成後,我們的項目目錄結構如下:

.
├── code
│   └── index.py
└── template.yml           

3. 事件觸發函數運作

我們簡單修改第 2 步的代碼,将 event 列印到 log 中。

import logging

logger = logging.getLogger()

def handler(event, context):
    logger.info("event: " + event)
    return 'hello world!'           

通過觸發事件的方式運作函數,得到如下結果:

開發函數計算的正确姿勢 —— 爬蟲

可以看到,我們的函數已經能正确接收到觸發事件了。

Fun Local 更多幫助資訊,

參考

4. 擷取網頁源碼内容

接下來,我們添加擷取網頁内容的代碼。

import logging
import json
import urllib

logger = logging.getLogger()

def handler(event, context):
    logger.info("event: " + event)
    evt = json.loads(event)
    url = evt['url']
  
    html = get_html(url)
  
    logger.info("html content length: " + str(len(html)))
    return 'Done!'

def get_html(url):
    page = urllib.urlopen(url)
    html = page.read()
    return html           

代碼邏輯比較簡單,我們這裡直接使用了 urllib 庫,讀取網頁内容。

運作函數,得到以下輸出:

開發函數計算的正确姿勢 —— 爬蟲

5. 解析網頁中的圖檔

我們打算通過正則解析網頁中包含的 jpg 圖檔,是以這一步會比較繁瑣,因為涉及到正規表達式的微調。為了能快速的解決問題,我們決定利用 fun local 提供的 local debugging 解決問題。local debugging 方法參考:

首先,我們在下面這一行下個斷點:

logger.info("html content length: " + str(len(html)))           

然後以 debug 的方式啟動,vscode 調試器連接配接後,函數會繼續運作到我們斷點的這一行:

開發函數計算的正确姿勢 —— 爬蟲

我們可以直接在 Locals 一欄看到本地變量,其中包含了

html

這個變量,也就是我們擷取到的 html 源碼。我們可以将它的值複制出來,分析下,然後設計正規表達式。

我們可以先寫一個簡單的,比如可以是

http:\/\/[^\s,"]*\.jpg

怎麼快速校驗這段代碼的正确性呢?我們可以利用調試器提供的

Watch(監視)

功能。

建立一個 Watch 變量,将下面的值輸入進去:

re.findall(re.compile(r'http:\/\/[^\s,"]*\.jpg'), html)           

回車後,即可看到代碼的執行效果:

開發函數計算的正确姿勢 —— 爬蟲

這裡一般不太容易一次寫對,可以反複修改正則測試,直到正确為止。

我們得到的正确的圖檔解析的邏輯添加到代碼中:

reg = r'http:\/\/[^\s,"]*\.jpg'
imgre = re.compile(reg)

def get_img(html):
    return re.findall(imgre, html)           

然後在 handler 方法中調用即可:

def handler(event, context):
    logger.info("event: " + event)
    evt = json.loads(event)
    url = evt['url']
  
    html = get_html(url)
  
    img_list = get_img(html)
    logger.info(img_list)
  
    return 'Done!'           

編寫完成後,可以繼續本地執行,驗證下結果:

echo '{"url": "https://image.baidu.com/search/index?tn=baiduimage&word=%E5%A3%81%E7%BA%B8"}' \
    | fun local invoke image-crawler           

可以看到,img_list 已經輸出到控制台了:

開發函數計算的正确姿勢 —— 爬蟲

6. 将圖檔上傳到 oss

解析到的圖檔,我們選擇使用

oss

存儲。

首先,我們需要通過環境變量配置 OSS Endpoint 以及 OSS Bucket。

在 template 中配置環境變量(需提前建立好 oss bucket):

EnvironmentVariables:
    OSSEndpoint: oss-cn-hangzhou.aliyuncs.com
    BucketName: fun-local-test           

然後就可以直接在函數中擷取到這兩個環境變量了:

endpoint = os.environ['OSSEndpoint']
bucket_name = os.environ['BucketName']           

另外,fun local 運作函數時,還會提供一個額外的變量用來辨別這是一個本地運作的函數。通過這個辨別,我們可以用來做一些本地化的操作,比如我們可以線上上運作時連接配接 RDS,在本地運作時連接配接 Mysql。

這裡,我們用該辨別以不同的的方式建立 oss client,原因是線上運作時,通過 credentials 擷取到的是扮演角色的臨時 ak,有有效期限制,而本地運作時,沒有該限制。oss 提供了這兩種方式的構造方法,我們直接使用即可:

creds = context.credentials

if (local):
    auth = oss2.Auth(creds.access_key_id,
                     creds.access_key_secret)
else:
    auth = oss2.StsAuth(creds.access_key_id,
                        creds.access_key_secret,
                        creds.security_token)
                        
bucket = oss2.Bucket(auth, endpoint, bucket_name)           

接着我們周遊所有圖檔,将所有的圖檔上傳到 oss:

count = 0
for item in img_list:
    count += 1
    logging.info(item)
    # Get each picture
    pic = urllib.urlopen(item)
    # Store all the pictures in oss bucket, keyed by timestamp in microsecond unit
    bucket.put_object(str(datetime.datetime.now().microsecond) + '.png', pic)             

再在本地運作一下函數:

echo '{"url": "https://image.baidu.com/search/index?tn=baiduimage&word=%E5%A3%81%E7%BA%B8"}' \
    | fun local invoke image-crawler           

可以從日志看到,圖檔被一張一張的解析出來,并被上傳到 oss 上了。

開發函數計算的正确姿勢 —— 爬蟲

登陸 oss 控制台,可以看到這些圖檔。

開發函數計算的正确姿勢 —— 爬蟲

部署

本地開發完成後,我們還需要将其釋出到線上,讓其成為一個可被調用的服務。以往,你可能覺得比較麻煩,比如要登陸控制台,建立服務、建立函數、配置環境變量,建立角色等,現在有了 fun 後,這一切都不需要了。

不過,本地與線上還是有些差別的,那就是要授權函數計算能夠通路 OSS,怎麼做呢?很簡單,在我們的 template 中加入一行配置即可(Polices 文檔,可以

):

Policies: AliyunOSSFullAccess           

添加後的 template.yml 内容如下:

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  localdemo:
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'local invoke demo'
      Policies: AliyunOSSFullAccess
    image-crawler:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: index.handler
        CodeUri: code/
        Description: 'Hello world with python2.7!'
        Runtime: python2.7
        EnvironmentVariables:
          OSSEndpoint: oss-cn-hangzhou.aliyuncs.com
          BucketName: fun-local-test           

然後,使用

fun deploy

後,可以看到部署成功的日志。

開發函數計算的正确姿勢 —— 爬蟲

驗證

通過控制台驗證

登陸

控制台

,可以看到,我們的服務、函數、代碼、環境變量等都已經就緒了。

開發函數計算的正确姿勢 —— 爬蟲

在觸發事件中,寫入我們用來測試的 json,然後執行:

開發函數計算的正确姿勢 —— 爬蟲

可以發現,會獲得與本地一緻的效果:

開發函數計算的正确姿勢 —— 爬蟲

通過 fcli 驗證

fcli 幫助文檔

在終端執行以下指令,可以擷取函數清單:

fcli function list --service-name localdemo           

可以看到我們的 image-crawler 已經建立成功了。

{
  "Functions": [
    "image-crawler",
    "java8",
    "nodejs6",
    "nodejs8",
    "php72",
    "python27",
    "python3"
  ],
  "NextToken": null
}           

使用以下指令則可以調用函數運作:

fcli function invoke --service-name localdemo \
    --function-name image-crawler \
    --event-str '{"url": "https://image.baidu.com/search/index?tn=baiduimage&word=%E5%A3%81%E7%BA%B8"}'           

運作成功後,會得到與控制台與 fun local 一緻的結果。

小結

至此,我們的開發就算告一段落。

本文利用 fun local 提供的本地運作、調試的能力,做到了在本地開發函數,并且通過反複的執行函數得到回報以便于快速疊代代碼。

在本地開發完成後,不需要對代碼進行任何修改,通過 fun deploy 指令,一鍵部署到雲端,達到預期的效果。

本文介紹的方法,并不是開發函數計算的唯一方式。本文的目的,是能夠向開發者傳達一種信号——開發函數計算時,隻要身姿正确,就會非常享受,開發流程也會十分順暢。祝您使用愉快。