18、构建简单数据 API 与使用 Docker 容器化爬虫服务

构建简单数据 API 与使用 Docker 容器化爬虫服务

1. 创建简单数据 API

在数据处理中,我们常常需要从各种数据源获取数据。这里我们以获取职位列表信息为例,通过简单的 API 来实现数据的获取和存储。

1.1 获取特定技能信息

我们可以使用 curl 命令来获取特定职位的技能信息,示例如下:

curl localhost:5000/joblisting/122517/skills

执行上述命令后,会得到如下结果:

["c#", "sql", "javascript", "asp.net", "angularjs"]
1.2 将抓取数据存储到 Elasticsearch

为了优化数据请求,避免重复抓取已处理过的职位列表,我们将抓取到的数据存储到 Elasticsearch 中。

准备工作
- 确保 Elasticsearch 在本地运行,代码将访问 localhost:9200 。可以参考 Elasticsearch 安装指南 进行安装。安装完成后,可以使用以下 curl 命令检查安装是否正确:

curl 127.0.0.1:9200?pretty

如果安装正确,会得到类似如下的输出:

{
    "name": "KHhxNlz",
    "cluster_name": "elasticsearch",
    "cluster_uuid": "fA1qyp78TB623C8IKXgT4g",
    "version": {
        "number": "6.1.1",
        "build_hash": "bd92e7f",
        "build_date": "2017-12-17T20:23:25.338Z",
        "build_snapshot": false,
        "lucene_version": "7.1.0",
        "minimum_wire_compatibility_version": "5.6.0",
        "minimum_index_compatibility_version": "5.0.0"
    },
    "tagline": "You Know, for Search"
}
  • 安装 elasticsearch-py 库,可以使用以下命令进行安装:
pip install elasticsearch

具体操作步骤
1. 在 API 代码中添加 elasticsearch-py 的导入:

from elasticsearch import Elasticsearch
  1. 修改 JobListing 类的 get 方法:
class JobListing(Resource):
    def get(self, job_listing_id):
        print("Request for job listing with id: " + job_listing_id)
        listing = get_job_listing_info(job_listing_id)
        es = Elasticsearch()
        es.index(index='joblistings', doc_type='job-listing', id=job_listing_id, body=listing)
        print("Got the following listing as a response: " + listing)
        return listing

在调用 API 之前,使用以下 curl 命令检查是否存在 joblistings 索引:

curl localhost:9200/joblistings

由于刚刚安装 Elasticsearch,会得到如下错误:

{"error":{"root_cause":[{"type":"index_not_found_exception","reason":"no such index","resource.type":"index_or_alias","resource.id":"joblistings","index_uuid":"_na_","index":"joblistings"}],"type":"index_not_found_exception","reason":"no such index","resource.type":"index_or_alias","resource.id":"joblistings","index_uuid":"_na_","index":"joblistings"},"status":404}
  1. 启动 API:
python api.py
  1. 发送 curl 请求获取职位列表:
curl localhost:5000/joblisting/122517

此时,该文档将被存储到 Elasticsearch 中。再次使用以下 curl 命令检查 joblistings 索引:

curl localhost:9200/joblistings

会得到类似如下结果(仅显示前几行):

{
    "joblistings": {
        "aliases": {},
        "mappings": {
            "job-listing": {
                "properties": {
                    "CleanedWords": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "ID": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    }
                }
            }
        }
    }
}

这表明已经创建了名为 joblistings 的索引,并且 Elasticsearch 通过检查文档识别出了索引结构。

  1. 检索特定文档:
curl localhost:9200/joblistings/job-listing/122517

会得到类似如下结果(仅显示内容开头部分):

{
    "_index": "joblistings",
    "_type": "job-listing",
    "_id": "122517",
    "_version": 1,
    "found": true,
    "_source": {
        "ID": "122517",
        "JSON": {
            "@context": "http://schema.org",
            "@type": "JobPosting",
            "title": "SpaceX Enterprise Software Engineer, Full Stack",
            "skills": [
                "c#",
                "sql",
                "javascript",
                "asp.net",
                "angularjs"
            ],
            "description": "<h2>About this job</h2>\r\n<p><span>Location options: <strong>Paid relocation</strong></span><br/><span>Job type: <strong>Permanent</strong></span><br/><span>Experience level: <strong>Mid-Level,"
        }
    }
}
1.3 存储文档的参数说明

在将文档存储到 Elasticsearch 时,使用了 es.index() 方法,其参数说明如下:
| 参数 | 说明 |
| ---- | ---- |
| index | 指定要存储文档的 Elasticsearch 索引名称,这里是 joblistings ,也是检索文档时 URL 的第一部分。 |
| doc_type | 每个 Elasticsearch 索引可以有多个文档类型,这里使用 job-listing ,它也是检索特定文档时 URL 的第二部分。 |
| id | 文档的标识符,如果提供该参数,可以直接查找特定文档而无需搜索。这里使用职位列表的 ID。 |
| body | 指定文档的实际内容,这里直接传递了从爬虫获取的结果。 |

1.4 Elasticsearch 存储结果分析

通过检索文档的结果,我们可以看到以下信息:
- 索引、文档类型和 ID :在结果的前几行可以看到 _index _type _id ,使用这三个值进行文档检索非常高效。
- 版本信息 :每个文档都有一个版本号,初始版本为 1。如果再次执行相同的查询,文档会被再次存储,版本号会递增。
- 结构化数据 :结果中的 JSON 属性包含了职位描述的结构化数据,如 @context @type title skills 等。这些结构化数据使得我们可以方便地进行各种查询。
- 提取的高价值词汇 :API 返回的 CleanedWords 属性是经过 NLP 处理提取的高价值词汇和术语,我们可以利用这些词汇进行丰富的查询。

1.5 抓取前检查 Elasticsearch

为了进一步优化数据请求,我们可以在抓取职位列表之前先检查 Elasticsearch 中是否已经存在该文档。

代码实现

class JobListing(Resource):
    def get(self, job_listing_id):
        print("Request for job listing with id: " + job_listing_id)
        es = Elasticsearch()
        if (es.exists(index='joblistings', doc_type='job-listing', id=job_listing_id)):
            print('Found the document in ElasticSearch')
            doc = es.get(index='joblistings', doc_type='job-listing', id=job_listing_id)
            return doc['_source']
        listing = get_job_listing_info(job_listing_id)
        es.index(index='joblistings', doc_type='job-listing', id=job_listing_id, body=listing)
        print("Got the following listing as a response: " + listing)
        return listing

上述代码在调用爬虫代码之前,先使用 exists 方法检查文档是否存在于 Elasticsearch 中。如果存在,则直接从 Elasticsearch 中获取文档;否则,抓取文档并存储到 Elasticsearch 中。

JobListingSkills 类的实现

class JobListingSkills(Resource):
    def get(self, job_listing_id):
        print("Request for job listing's skills with id: " + job_listing_id)
        es = Elasticsearch()
        if (es.exists(index='joblistings', doc_type='job-listing', id=job_listing_id)):
            print('Found the document in ElasticSearch')
            doc = es.get(index='joblistings', doc_type='job-listing', id=job_listing_id)
            return doc['_source']['JSON']['skills']
        skills = get_job_listing_skills(job_listing_id)
        print("Got the following skills as a response: " + skills)
        return skills

该实现仅使用 Elasticsearch 检查文档是否存在,不尝试保存新抓取的文档。因为 get_job_listing_skills 方法只返回技能列表,而不是整个文档。一种潜在的解决方案是调用 get_job_listing_info 方法,保存整个文档,然后只返回特定的子集(如技能信息)。

2. 使用 Docker 容器化爬虫服务

为了将爬虫服务部署到实际环境中,我们可以使用 Docker 进行容器化。Docker 提供了一种方便的方式来打包和部署应用程序,使得应用程序可以在不同的环境中一致运行。

2.1 安装 Docker

Docker 支持 Linux、macOS 和 Windows 等主流操作系统,不同操作系统的安装过程有所不同。以下以 macOS 为例,介绍 Docker 的安装步骤。

准备工作
可以参考 Docker 安装指南 进行安装。

具体操作步骤
1. 访问 Docker 下载页面,选择 Docker Community Edition 的稳定版进行下载。
2. 下载完成后,打开 Docker.dmg 文件,将 Moby 鲸鱼图标拖到应用程序文件夹中。
3. 打开 Docker.app ,输入密码进行安装。安装完成后,会在状态栏看到 Moby 图标。
4. 打开终端,输入以下命令验证 Docker 是否正常工作:

docker info
2.2 从 Docker Hub 安装 RabbitMQ 容器

RabbitMQ 是一个消息队列中间件,我们将使用它作为爬虫微服务的消息总线。

准备工作
通常,安装 RabbitMQ 需要安装 Erlang 和 RabbitMQ 本身,还可能需要安装管理工具。使用 Docker 可以方便地获取预配置好的容器。

具体操作步骤
1. 使用 docker pull 命令获取容器,先尝试使用 --help 查看命令帮助:

docker pull --help

输出如下:

Usage: docker pull [OPTIONS] NAME[:TAG|@DIGEST]
Pull an image or a repository from a registry
Options:
  -a, --all-tags Download all tagged images in the repository
      --disable-content-trust Skip image verification (default true)
      --help Print usage
  1. 我们要拉取 rabbitmq:3-management 容器,该容器包含版本 3 的 RabbitMQ 以及管理工具。该容器来自 Docker Hub 的 RabbitMQ 仓库,仓库页面为 https://hub.docker.com/_/rabbitmq/
  2. 在终端中执行以下命令拉取容器:
docker pull rabbitmq:3-management

通过以上步骤,我们完成了简单数据 API 的创建和数据存储,以及 Docker 的安装和 RabbitMQ 容器的拉取。后续可以继续进行容器化爬虫服务的其他操作,如创建 API 容器、运行 Docker 容器等。

构建简单数据 API 与使用 Docker 容器化爬虫服务

2.3 运行 Docker 容器(RabbitMQ)

在成功拉取 RabbitMQ 容器后,我们可以运行该容器。以下是运行 RabbitMQ 容器的具体步骤:
1. 使用以下命令运行容器:

docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management

此命令的参数说明如下:
| 参数 | 说明 |
| ---- | ---- |
| -d | 以守护进程模式运行容器,即容器在后台运行。 |
| --name rabbitmq | 为容器指定名称为 rabbitmq 。 |
| -p 5672:5672 | 将容器内部的 5672 端口映射到主机的 5672 端口,5672 是 RabbitMQ 的 AMQP 协议端口。 |
| -p 15672:15672 | 将容器内部的 15672 端口映射到主机的 15672 端口,15672 是 RabbitMQ 管理界面的端口。 |
| rabbitmq:3-management | 指定要运行的容器镜像。 |
2. 运行成功后,可以使用以下命令查看容器的运行状态:

docker ps

如果看到 rabbitmq 容器处于运行状态,则说明容器已成功启动。

2.4 停止和移除容器及镜像

在某些情况下,我们可能需要停止和移除容器及镜像。以下是具体操作步骤:
1. 停止容器

docker stop rabbitmq

此命令将停止名为 rabbitmq 的容器。
2. 移除容器

docker rm rabbitmq

此命令将移除名为 rabbitmq 的容器。
3. 移除镜像

docker rmi rabbitmq:3-management

此命令将移除 rabbitmq:3-management 镜像。

2.5 创建 API 容器

为了将 API 服务容器化,我们需要创建一个 Dockerfile 来定义容器的构建规则。以下是一个简单的 Dockerfile 示例:

# 使用 Python 基础镜像
FROM python:3.8-slim

# 设置工作目录
WORKDIR /app

# 复制当前目录下的所有文件到工作目录
COPY . /app

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 暴露端口
EXPOSE 5000

# 运行 API
CMD ["python", "api.py"]

创建 Dockerfile 后,使用以下命令构建 API 容器:

docker build -t sojobs-api .

此命令将构建一个名为 sojobs-api 的容器镜像。

2.6 创建通用微服务与 Nameko

Nameko 是一个用于创建微服务的 Python 框架。以下是创建一个简单 Nameko 微服务的示例:

from nameko.rpc import rpc

class MyService:
    name = "my_service"

    @rpc
    def hello(self, name):
        return f"Hello, {name}!"

将上述代码保存为 service.py ,然后使用以下命令启动 Nameko 服务:

nameko run service
2.7 创建爬虫微服务

我们可以将爬虫功能封装成一个微服务。以下是一个简单的爬虫微服务示例:

from nameko.rpc import rpc
import requests

class ScraperService:
    name = "scraper_service"

    @rpc
    def scrape_job_listing(self, job_listing_id):
        url = f"https://example.com/joblisting/{job_listing_id}"
        response = requests.get(url)
        return response.text

将上述代码保存为 scraper_service.py ,然后使用以下命令启动爬虫微服务:

nameko run scraper_service
2.8 创建爬虫容器

为了将爬虫微服务容器化,同样需要创建一个 Dockerfile。以下是一个简单的 Dockerfile 示例:

# 使用 Python 基础镜像
FROM python:3.8-slim

# 设置工作目录
WORKDIR /app

# 复制当前目录下的所有文件到工作目录
COPY . /app

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 暴露端口
EXPOSE 8000

# 运行爬虫微服务
CMD ["nameko", "run", "scraper_service"]

创建 Dockerfile 后,使用以下命令构建爬虫容器:

docker build -t sojobs-scraper .

此命令将构建一个名为 sojobs-scraper 的容器镜像。

2.9 创建后端(ElasticCache)容器

如果需要使用 ElasticCache 作为后端存储,可以使用 Docker 拉取 ElasticCache 容器。以下是具体操作步骤:
1. 拉取 ElasticCache 容器:

docker pull amazon/aws-elasticache-redis
  1. 运行 ElasticCache 容器:
docker run -d --name elasticcache -p 6379:6379 amazon/aws-elasticache-redis
2.10 使用 Docker Compose 组合和运行爬虫容器

Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。以下是一个 docker-compose.yml 文件示例:

version: '3'
services:
  api:
    build: .
    ports:
      - "5000:5000"
    depends_on:
      - rabbitmq
      - elasticcache
  scraper:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      - rabbitmq
  rabbitmq:
    image: rabbitmq:3-management
    ports:
      - "5672:5672"
      - "15672:15672"
  elasticcache:
    image: amazon/aws-elasticache-redis
    ports:
      - "6379:6379"

使用以下命令启动所有容器:

docker-compose up -d

此命令将以守护进程模式启动所有容器。

通过以上步骤,我们完成了使用 Docker 容器化爬虫服务的整个过程,包括安装 Docker、拉取和运行容器、创建 API 容器、微服务以及使用 Docker Compose 组合和运行多个容器。这些操作使得爬虫服务可以方便地部署和管理,并且可以在不同的环境中一致运行。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值