开发函数计算的正确姿式 —— 爬虫

在 《函数计算本地运行与调试 - Fun Local 基本用法》 中,咱们介绍了利用 Fun Local 本地运行、调试函数的方法。但若是仅仅这样简单的介绍,并不能展示 Fun Local 对函数计算开发的巨大效率的提高。php

这一次,咱们拿一个简单的场景来举例子——开发一个简单的爬虫函数(代码参考函数计算控制台模板),介绍如何以正确姿式,从零开始,开发一个自动伸缩、按调用次数收费的 serverless 爬虫应用。html

开发步骤

咱们将这个完整的应用拆分红多步,而且在每一步完成后,咱们都会进行相应的运行验证。java

1. 建立 Fun 项目

首先,咱们建立一个名为 image-crawler 的目录做为项目的根。而后在该目录下建立一个名为 template.yml 的文件,内容为:node

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,能够参考 这里python

操做完成后,咱们的项目目录结构以下:git

.
└── template.yml

2. 编写 helloworld 函数代码

在根目录下建立一个名为 code 的目录,并在该目录下建立一个名为 index.py 的文件,内容为一个简单的 helloworld 函数:github

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

在项目根目录下执行:正则表达式

fun local invoke image-crawler

函数运行成功:sql

操做完成后,咱们的项目目录结构以下:json

.
├── 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 方法参考: 《函数计算本地运行与调试 - Fun Local 基本用法》

首先,咱们在下面这一行下个断点:

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 上了。
image

登录 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 命令,一键部署到云端,达到预期的效果。

本文介绍的方法,并非开发函数计算的惟一方式。本文的目的,是可以向开发者传达一种信号——开发函数计算时,只要身姿正确,就会很是享受,开发流程也会十分顺畅。祝您使用愉快。



本文做者:tanhe123

阅读原文

本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索