如果能用Docker搭建出一套Selenium执行环境,并部署在云端。当我们把脚本上传至云后,可以自动执行脚本(按需执行),而脚本执行结束后又能自动退出(不占用资源)。让我们小小的云能最大化利用起来,想想就很激动呢。
需求分析
首先当然是需要一个具有完整Selenium执行环境的Docker,另外需要一个具有浏览器Driver的Docker环境。为什么不合在一个Docker里面呢?一方面希望Docker尽可能功能独立(低耦合),另一方面如果我们自己做Driver的话挺麻烦的(有现成的),所以还是分为两个Docker吧🤔。
差不多如上图所示一个docker为另一个docker提供服务,完成一次网页访问。接下来我们看看这两个docker如何搭建吧。
Docker 搭建
前文提到Driver有现成的docker,那么就先从这里入手吧🤣。
Driver Docker
在Selenium官方Github上提供了一系列已封装好的Driver Docker,并至今依然在持续更新。相关使用方法可以查看其说明,这里就不啰嗦了。简单来讲使用方法如下:
- Firefox
$ docker run -d -p 4444:4444 -v /dev/shm:/dev/shm selenium/standalone-firefox:4.0.0-beta-4-prerelease-20210517
- Chrome
$ docker run -d -p 4444:4444 -v /dev/shm:/dev/shm selenium/standalone-chrome:4.0.0-beta-4-prerelease-20210517
- Edge
$ docker run -d -p 4444:4444 -v /dev/shm:/dev/shm selenium/standalone-edge:4.0.0-beta-4-prerelease-20210517
对外暴露4444端口提供Driver服务。所以我们只需要pull对应的docker镜像就好。
Selenium Docker
现在来看看Selenium环境又如何搭建。其实就是选择一个合适的基础镜像(精简),在其基础上安装好必须的软件即可。通过在DockerHub上选择合适的官方镜像(Python),然后安装常用爬虫库。Dockerfile如下:
FROM python:3.9.5
WORKDIR /home/code
RUN pip config set global.index-url https://mirrors.
aliyun.com/pypi/simple/ \
&& pip install selenium requests lxml beautifuls
up4 pymysql
至此我们就完成了所需Docker的搭建。再来看看如何让他们配合执行。
执行
需要用到我们的老朋友docker-compose😀。将脚本映射进我们的Selenium容器,并联通Driver容器。来看看docker-compose.yml怎么写的呢。
version: "3"
services:
spider:
image: selenium-py
volumes:
- /home/core/data/python:/home/code/
command: python your_script.py
depends_on:
- chrome
chrome:
image: selenium/standalone-chrome
container_name: chrome
volumes:
- /dev/shm:/dev/shm
是不是超级简单?docker运行时就会去执行python your_script.py
,执行完之后就会退出。看上去好完美!而且没有对外暴露任何端口,好安全!来看看这个your_script.py怎么写呢。
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities
mport DesiredCapabilities
driver = webdriver.Remote(
command_executor="http://chrome:4444/wd/hub",
desired_capabilities=DesiredCapabilities.CHROME
)
driver.get("https://www.ray0728.cn")
print(driver.title)
driver.close()
http://chrome:4444/wd/hub
中的chrome是Driver Docker的容器名,在docker-compose.yml中有特别的指定。而执行结果可以通过docker-compose logs -f spider
进行查看。
问题
- 连接Driver失败
Chrome Docker将Chrome运行起来是需要时间的,注意并不是容器运行起来就行了,毕竟Docker只是容器(可以理解为轻量级的虚拟机),服务是容器中的应用程序,就像我们不会认为系统开机,就是后台服务开始运行了。因此我们最好等到Driver开始提供服务后再执行脚本,那么怎么做呢?答案很简单,通过不停的去探测Driver docker对外提供服务的端口是否可用,来判断服务是否被拉起。这里我们用NetCat工具。可是NC工具不一定是每个Linux发行版本,所以加在Dockerfile里,重新构建一个带有nc的Selenium Docker。
FROM python:3.9.5
WORKDIR /home/code
ADD sources.list /etc/apt/
RUN apt update \
&& apt install -y netcat-openbsd \
&& pip config set global.index-url https://mirrors.
liyun.com/pypi/simple/ \
&& pip install selenium requests lxml beautifuls
up4 pymysql
选取的Python镜像是以Debian为基础,sources.list是提前准备好的国内apt源。
在调用your_script.py之前先用nc判断Driver是否准备好,所以重新调整下docker-compose.yml。
version: "3"
services:
spider:
image: selenium-py
volumes:
- /home/core/data/python:/home/code/
command: run.sh
depends_on:
- chrome
chrome:
image: selenium/standalone-chrome
container_name: chrome
volumes:
- /dev/shm:/dev/shm
与之前的区别就在于用run.sh代替了直接调用script。这样就可以将nc调用放在run.sh当中了。
#!/bin/bash
while ! `nc -z chrome 4444`; do sleep 3; done
python your_script.py
- Driver Docker不退出
Selenium Docker执行完脚本后就会自动退出(无其他执行任务)。但是Driver Docker本身就是提供持续服务,因此并不会因为Selenium Docker退出而退出。怎么才能让Driver Docker知道Selenium退出了呢?方法有两种。
- 参考通过nc判断服务端口是否下线来推断Docker是否停止
但是Driver Docker并不是我们自己构建的,而且暂时也不打算以其为基础做二次封装(懒)。所以我们无法加入nc判断逻辑,该方案先搁置吧。
- 通过sock通知
这里就需要知道一个关于Docker Sock的背景知识了。简单来说通过Unix Domain Socket可以让Docker相互之间通信,而通信需符合API规范。我们需要的STOP规范如下:
按照API所描述的方法,将停止命令加在run.sh最后。
#!/bin/bash
while ! `nc -z chrome 4444`; do sleep 3; done
python test.py
curl -s --unix-socket /var/run/docker.sock -X POST h
tp://localhost/containers/chrome/stop
并将主机/var/run/docker.sock映射给Selenium Docker。在docker-compose.yml中加入
docker-compose.yml
version: "3"
services:
spider:
image: selenium-py
volumes:
- /home/core/data/python:/home/code/
- /var/run/docker.sock:/var/run/docker.sock
command: /home/code/run.sh
depends_on:
- chrome
chrome:
image: selenium/standalone-chrome
container_name: chrome
volumes:
- /dev/shm:/dev/shm
至此Selenium Docker运行完成后就会通知Driver Docker退出。完全满足本文开头所描述的需求。😎
引申
本文最后通过sock向Docker Deamon发送命令,从而实现所需要的功能。但是有两点需要特别注意。
并不能按照API文档中的参数直接向Docker Deamon发送命令,而是需要将部分特殊字符(如 [{:)转换为html字符,然后再发送给sock。
不要将docker.sock暴露给外部,因为有安全隐患,毕竟Docker Deamon只要收到符合API规范的字符串就会触发对应的操作(包括停止、删除容器等)。