使用 Docker 创建爬虫微服务
在当今的软件开发领域,微服务架构因其灵活性和可扩展性而备受青睐。结合 Docker 容器技术,我们可以更高效地部署和管理微服务。本文将详细介绍如何使用 Docker 创建爬虫微服务,包括创建爬虫微服务、构建容器以及创建 API 容器等步骤。
1. 远程过程调用(RPC)基础
远程过程调用(RPC)是一种在分布式系统中常用的通信机制。在 Nameko 框架中,RPC 调用会阻塞,直到从其他系统收到结果。例如,Nameko 调用 hello_microservice 的 hello 方法时,会传递指定的字符串,并等待回复。与发布模型不同,发布模型发送消息后,发送应用程序会继续执行,而 RPC 会等待结果。
Nameko 还有一个很有用的特性,它可以为微服务的多个实例运行监听器,默认数量为 10 个。Nameko 会将微服务客户端的请求发送到 RabbitMQ 队列,有 10 个并发请求处理器监听该队列。如果请求过多,RabbitMQ 会保存消息,直到 Nameko 回收一个现有的微服务实例来处理排队的消息。为了提高微服务的可扩展性,我们可以通过配置增加工作线程的数量,或者在另一个 Docker 容器或计算机系统中运行单独的 Nameko 微服务容器。
2. 创建爬虫微服务
接下来,我们将把爬虫转换为 Nameko 微服务,这样爬虫就可以独立于 API 的实现运行、维护和扩展。具体步骤如下:
- 编写微服务代码 :代码位于
10/02/call_scraper_microservice.py,示例代码如下:
from nameko.rpc import rpc
import sojobs.scraping
class ScrapeStackOverflowJobListingsMicroService:
name = "stack_overflow_job_listings_scraping_microservice"
@rpc
def get_job_listing_info(self, job_listing_id):
listing = sojobs.scraping.get_job_listing_info(job_listing_id)
print(listing)
return listing
if __name__ == "__main__":
print(ScrapeStackOverflowJobListingsMicroService("122517"))
- 运行微服务 :在终端中使用 Nameko 运行服务:
$ nameko run scraper_microservice
starting services:
stack_overflow_job_listings_scraping_microservice
Connected to amqp://guest:**@127.0.0.1:5672//
- 调用微服务 :使用
10/02/call_scraper_microservice.py脚本调用微服务,代码如下:
from nameko.standalone.rpc import ClusterRpcProxy
CONFIG = {'AMQP_URI': "amqp://guest:guest@localhost"}
with ClusterRpcProxy(CONFIG) as rpc:
result = rpc.stack_overflow_job_listings_scraping_microservice.get_job_listing_info("122517")
print(result)
运行上述代码后,会输出类似以下的结果(已截断):
{"ID": "122517", "JSON": {"@context": "http://schema.org", "@type": "JobPosting", "title": "SpaceX Enterprise Software Engineer, Full Stack", "skills": ["c#", "sql", "javascript", "asp.net", "angularjs"]}
此时,我们已经成功创建了一个从 StackOverflow 获取职位列表的微服务。不过,这个微服务只能使用 ClusterRpcProxy 类调用,不能通过 REST 接口被互联网上的任何人甚至本地调用。后续我们会解决这个问题。
3. 创建爬虫容器
为了将爬虫微服务部署到容器中,我们需要创建一个 Docker 容器。以下是具体步骤:
3.1 准备工作
首先,确保 RabbitMQ 在容器中运行,并分配到自定义的 Docker 网络中。可以使用以下命令查看当前安装的网络:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
bc3bed092eff bridge bridge local
26022f784cc1 docker_gwbridge bridge local
448d8ce7f441 dockercompose2942991694582470787_default bridge local
4e549ce87572 dockerelkxpack_elk bridge local
ad399a431801 host host local
rbultxlnlhfb ingress overlay swarm
389586bebcf2 none null local
806ff3ec2421 stackdockermaster_stack bridge local
为了让容器之间能够通信,我们创建一个名为 scraper-net 的新桥接网络:
$ docker network create --driver bridge scraper-net
e4ea1c48395a60f44ec580c2bde7959641c4e1942cea5db7065189a1249cd4f1
然后启动 RabbitMQ 容器并将其连接到 scraper-net 网络:
$ docker run -d --name rabbitmq --network scraper-net -p 15672:15672 -p 5672:5672 rabbitmq:3-management
3.2 创建 Dockerfile
在 10/03 文件夹中有一个 Dockerfile,内容如下:
FROM python:3
WORKDIR /usr/src/app
RUN pip install nameko BeautifulSoup4 nltk lxml
RUN python -m nltk.downloader punkt -d /usr/share/nltk_data all
COPY 10/02/scraper_microservice.py .
COPY modules/sojobs sojobs
CMD ["nameko", "run", "--broker", "amqp://guest:guest@rabbitmq", "scraper_microservice"]
3.3 构建容器镜像
在 10/03 文件夹的终端中运行以下命令构建容器镜像:
$ docker build ../.. -f Dockerfile -t scraping-microservice
构建过程可能需要一些时间,因为需要下载所有的 NLTK 文件到容器中。构建完成后,可以使用以下命令检查镜像是否创建成功:
$ docker images | head -n 2
REPOSITORY TAG IMAGE ID CREATED SIZE
scraping-microservice latest 0e1409911ac9 3 hours ago 4.16GB
3.4 运行容器
使用以下命令运行构建好的镜像:
$ docker run --network scraper-net scraping-microservice
starting services:
stack_overflow_job_listings_scraping_microservice
Connected to amqp://guest:**@rabbitmq:5672//
3.5 测试容器
在另一个终端窗口中运行 Nameko shell:
$ nameko shell
Nameko Python 3.6.1 |Anaconda custom (x86_64)| (default, Mar 22 2017, 19:25:17)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] shell on darwin
Broker: pyamqp://guest:guest@localhost
In [1]:
在提示符中输入以下命令调用微服务:
n.rpc.stack_overflow_job_listings_scraping_microservice.get_job_listing_info("122517")
运行后会输出大量的爬取结果(已截断):
Out[1]: '{"ID": "122517", "JSON": {"@context": "http://schema.org", "@type": "JobPosting", "title": "SpaceX Enterprise Software Engineer, Full Stack", "skills": ["c#", "sql", "javascript", "asp.net"]}
4. Dockerfile 工作原理
接下来分析 Dockerfile 是如何构建 Docker 镜像的:
1. FROM python:3 :告诉 Docker 基于 Docker Hub 上的 Python 3 镜像构建容器镜像,这是一个预构建的安装了 Python 3 的 Linux 镜像。
2. WORKDIR /usr/src/app :指定所有文件操作的相对目录为 /usr/src/app 。
3. RUN pip install nameko BeautifulSoup4 nltk lxml :运行 pip 安装爬虫所需的各种库。
4. RUN python -m nltk.downloader punkt -d /usr/share/nltk_data all :安装 NLTK 数据文件。
5. COPY 10/02/scraper_microservice.py . :将 scraper_microservice.py 文件复制到容器镜像中。
6. COPY modules/sojobs sojobs :复制 sojobs 模块到容器中。
7. CMD ["nameko", "run", "--broker", "amqp://guest:guest@rabbitmq", "scraper_microservice"] :指定容器启动时要运行的命令,即使用 Nameko 运行 scraper_microservice.py 中的微服务,并连接到名为 rabbitmq 的 RabbitMQ 消息代理。
由于我们将爬虫容器和 RabbitMQ 容器都连接到了 scraper-net 网络,Docker 会自动将它们连接起来。Nameko shell 在 Docker 主机系统上运行,启动时会报告与 pyamqp://guest:guest@localhost 的 AMQP 服务器(RabbitMQ)通信。当我们在 shell 中执行命令时,Nameko shell 会将消息发送到本地主机。而 RabbitMQ 容器同时连接到了 scraper-net 网络和主机网络,只要我们在启动时映射了 5672 端口,就可以与 RabbitMQ 代理通信。另一个容器中的微服务会监听 RabbitMQ 容器中的消息,然后响应该容器,最后 Nameko shell 会获取到响应。
5. 创建 API 容器
目前,我们只能使用 AMQP、Nameko shell 或 Nameko ClusterRPCProxy 类与微服务进行通信。接下来,我们将把 Flask-RESTful API 放入另一个容器中,与其他容器一起运行,并进行 REST 调用。由于 API 代码还需要与 Elasticsearch 通信,因此我们还需要运行一个 Elasticsearch 容器。
5.1 启动 Elasticsearch 容器
使用以下命令在 scraper-net 网络中启动 Elasticsearch 容器:
$ docker run -e ELASTIC_PASSWORD=MagicWord --name=elastic --network scraper-net -p 9200:9200 -p 9300:9300 docker.elastic.co/elasticsearch/elasticsearch:6.1.1
5.2 编写 API 代码
在 10/04 文件夹中有一个 api.py 文件,实现了一个修改后的 Flask-RESTful API,代码如下:
from flask import Flask
from flask_restful import Resource, Api
from elasticsearch import Elasticsearch
from nameko.standalone.rpc import ClusterRpcProxy
app = Flask(__name__)
api = Api(app)
CONFIG = {'AMQP_URI': "amqp://guest:guest@rabbitmq"}
class JobListing(Resource):
def get(self, job_listing_id):
print("Request for job listing with id: " + job_listing_id)
es = Elasticsearch(hosts=["elastic"])
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']
print('Not found in Elasticsearch, trying a scrape')
with ClusterRpcProxy(CONFIG) as rpc:
listing = rpc.stack_overflow_job_listings_scraping_microservice.get_job_listing_info(job_listing_id)
print("Microservice returned with a result - storing in Elasticsearch")
es.index(index='joblistings', doc_type='job-listing', id=job_listing_id, body=listing)
return listing
api.add_resource(JobListing, '/', '/joblisting/<string:job_listing_id>')
if __name__ == '__main__':
print("Starting the job listing API ...")
app.run(host='0.0.0.0', port=8080, debug=True)
代码主要有以下几个修改点:
1. Elasticsearch 对象创建 :将 Elasticsearch 对象的创建指向 scraper-net 网络中名为 elastic 的主机:
es = Elasticsearch(hosts=["elastic"])
- 移除
sojobs模块调用 :使用 Nameko ClusterRpcProxy 对象调用爬虫容器中的爬虫微服务:
with ClusterRpcProxy(CONFIG) as rpc:
listing = rpc.stack_overflow_job_listings_scraping_microservice.get_job_listing_info(job_listing_id)
- Flask 应用启动修改 :将 Flask 应用绑定到所有网络接口,并将端口改为 8080:
app.run(host='0.0.0.0', port=8080, debug=True)
5.3 创建 API 容器的 Dockerfile
在 10/04 文件夹中有一个 Dockerfile,内容如下:
FROM python:3
WORKDIR /usr/src/app
RUN pip install Flask-RESTful Elasticsearch Nameko
COPY 10/04/api.py .
CMD ["python", "api.py"]
5.4 构建和运行 API 容器
使用以下命令构建 API 容器:
$ docker build ../.. -f Dockerfile -t scraper-rest-api
然后使用以下命令运行容器:
$ docker run -d -p 8080:8080 --network scraper-net scraper-rest-api
5.5 检查容器运行状态
使用以下命令检查所有容器是否正在运行:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
55e438b4afcd scraper-rest-api "python -u api.py" 46 seconds ago Up 45 seconds 0.0.0.0:8080->8080/tcp vibrant_sammet
bb8aac5b7518 docker.elastic.co/elasticsearch/elasticsearch:6.1.1 "/usr/local/bin/do..." 3 hours ago Up 3 hours 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elastic
ac4f51c1abdc scraping-microservice "nameko run --brok..." 3 hours ago Up 3 hours thirsty_ritchie
18c2f01f58c7 rabbitmq:3-management "docker-entrypoint..." 3 hours ago Up 3 hours 4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp rabbitmq
5.6 测试 API
在主机的终端中使用 curl 命令测试 REST 端点:
$ curl localhost:8080/joblisting/122517
"{\"ID\": \"122517\", \"JSON\": {\"@context\": \"http://schema.org\", \"@type\": \"JobPosting\", \"title\": \"SpaceX Enterprise Software Engineer, Full Stack\", \"skills\": ["c#", "sql", "javas"]}
通过以上步骤,我们成功地将 API 和功能容器化,并在容器中运行了 RabbitMQ 和 Elasticsearch。不过,这种容器化方式需要创建多个 Dockerfile、容器和网络,并独立运行它们。幸运的是,我们可以使用 docker-compose 简化这个过程。
6. 使用 docker-compose 本地组合和运行爬虫
docker-compose 是一个用于定义和运行多容器 Docker 应用程序的工具。使用 docker-compose ,我们可以使用 YAML 文件配置应用程序的服务,然后通过一个简单的配置文件和单个命令创建并启动所有服务。
6.1 准备工作
使用 docker-compose 之前,需要确保它已经安装。在 macOS 上,Docker 会自动安装 docker-compose ,在其他平台上可能需要手动安装,可以参考 官方文档 进行安装。同时,确保之前创建的所有容器都已停止运行,因为我们将创建新的容器。
通过以上步骤,我们完成了使用 Docker 创建爬虫微服务、构建容器、创建 API 容器以及使用 docker-compose 简化部署的过程。这种方式可以提高开发和部署的效率,同时增强系统的可扩展性和可维护性。
使用 Docker 创建爬虫微服务
6.2 创建 docker-compose.yml 文件
在项目根目录下创建一个 docker-compose.yml 文件,用于定义和配置所有的服务。以下是一个示例 docker-compose.yml 文件:
version: '3'
services:
rabbitmq:
image: rabbitmq:3-management
container_name: rabbitmq
ports:
- "15672:15672"
- "5672:5672"
networks:
- scraper-net
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:6.1.1
container_name: elastic
environment:
- ELASTIC_PASSWORD=MagicWord
ports:
- "9200:9200"
- "9300:9300"
networks:
- scraper-net
scraping-microservice:
build:
context: .
dockerfile: 10/03/Dockerfile
container_name: scraping-microservice
networks:
- scraper-net
scraper-rest-api:
build:
context: .
dockerfile: 10/04/Dockerfile
container_name: scraper-rest-api
ports:
- "8080:8080"
networks:
- scraper-net
networks:
scraper-net:
driver: bridge
这个 docker-compose.yml 文件定义了四个服务: rabbitmq 、 elasticsearch 、 scraping-microservice 和 scraper-rest-api ,并将它们连接到 scraper-net 网络。
6.3 使用 docker-compose 启动服务
在终端中,进入 docker-compose.yml 文件所在的目录,然后运行以下命令启动所有服务:
$ docker-compose up -d
-d 参数表示在后台运行容器。运行该命令后, docker-compose 会根据 docker-compose.yml 文件中的配置创建并启动所有服务。
6.4 检查服务运行状态
使用以下命令检查所有服务是否正常运行:
$ docker-compose ps
输出结果应该类似于以下内容:
| Name | Command | State | Ports |
| — | — | — | — |
| rabbitmq | docker-entrypoint.sh rabbitmq-server | Up | 4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp |
| elasticsearch | /usr/local/bin/docker-entrypoint.sh eswrapper | Up | 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp |
| scraping-microservice | nameko run –broker amqp://guest:guest@rabbitmq scraper_microservice | Up | |
| scraper-rest-api | python -u api.py | Up | 0.0.0.0:8080->8080/tcp |
6.5 测试服务
同样,在主机的终端中使用 curl 命令测试 REST 端点:
$ curl localhost:8080/joblisting/122517
如果一切正常,你应该会看到与之前测试相同的结果。
6.6 停止和清理服务
当你不再需要这些服务时,可以使用以下命令停止并删除所有容器:
$ docker-compose down
这个命令会停止并删除所有由 docker-compose 创建的容器、网络和卷。
总结与展望
7.1 总结
通过本文的介绍,我们详细了解了如何使用 Docker 创建爬虫微服务。具体步骤包括:
1. 创建爬虫微服务 :使用 Nameko 框架将爬虫转换为微服务,实现了从 StackOverflow 获取职位列表的功能。
2. 构建容器 :通过 Dockerfile 构建了爬虫微服务容器和 API 容器,使用自定义网络 scraper-net 实现容器之间的通信。
3. 创建 API 容器 :将 Flask-RESTful API 放入容器中,与 Elasticsearch 和爬虫微服务进行交互,实现了 REST 接口的调用。
4. 使用 docker-compose 简化部署 :使用 docker-compose 工具,通过一个 YAML 文件定义和配置所有服务,实现了多容器应用的快速部署和管理。
这种基于 Docker 和微服务架构的开发和部署方式,具有以下优点:
- 可扩展性 :可以轻松地增加或减少微服务的实例数量,以应对不同的负载需求。
- 可维护性 :每个微服务都可以独立开发、测试和部署,降低了系统的耦合度,提高了维护效率。
- 灵活性 :可以根据需要选择不同的技术栈和工具,构建适合业务需求的微服务。
7.2 展望
在未来的开发中,我们可以进一步优化和扩展这个爬虫微服务系统:
- 性能优化 :可以使用缓存技术、异步处理等方式提高系统的性能和响应速度。
- 安全加固 :加强容器和微服务的安全防护,例如使用 SSL/TLS 加密通信、身份验证和授权机制等。
- 自动化部署 :结合 CI/CD 工具,实现自动化的构建、测试和部署流程,提高开发效率和质量。
以下是整个流程的 mermaid 流程图:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(远程过程调用基础):::process --> B(创建爬虫微服务):::process
B --> C(创建爬虫容器):::process
C --> D(Dockerfile 工作原理):::process
D --> E(创建 API 容器):::process
E --> F(使用 docker-compose 本地组合和运行爬虫):::process
F --> G(总结与展望):::process
通过不断地学习和实践,我们可以更好地掌握 Docker 和微服务架构,构建出更加高效、稳定和安全的应用系统。希望本文能对你有所帮助,让你在使用 Docker 创建爬虫微服务的道路上迈出坚实的一步。
超级会员免费看
2524

被折叠的 条评论
为什么被折叠?



