python办公自动化

scrapy+scrapy_splash + docker爬取J

2019-07-09  本文已影响0人  lizb

最近一段时间做了一个特别恶心的项目,先来吐槽一下,项目需求大致就是给网址分类,鉴别出它是属于什么类型的网站,比如娱乐游戏、音乐影视、新闻咨询等。可能有的公司是用AI来鉴别的,但我们没有那么高大上,用的就是土办法,爬取网页的特定内容,比如title、keywords,description等,中文分词后,和每个类型的核心词对比(当然核心词是有权重的),从而鉴别出网址所属分类。这只是项目的最后一道环节,网址还需要通过前面的几道环节,例如备案等,这里就不再详述,总的来说准确率还是不错的,粗略的算差不多90%左右。其中最恶心的地方就是这个网址源,其来源不方便说,其中有些是与后台的交互Json,有些网址还有乱码,有些还带有中文,总的来说50%左右是打不开的,真的算是最恶劣的数据源,恶心到吐。
言归正传,想到这种大规模的网址爬取,我们采用了scrapy框架,但是这个框架只能爬取静态的网页,有一部分网页是JS动态加载的,所以单用scrapy就不行了,通过查找资料,发现通过scrapy +scrapy_splash+docker可以达到我们的要求,于是撸起袖子就开始干了。
总共有两步:
1. 完成scrapy和scrapy_splash的对接
2. docker环境的搭建

一、scrapy项目中接入scrapy_splash

这一步非常简单,我这里python的版本是3.6.3,我们只需要通过pip安装scrapy_splash库即可:

pip install scrapy_splash

因为我之前是通过scrapy爬取的静态网页,不了解scrapy框架的需要先了解一下,然后在我们请求网址的时候将原来的scrapy.Request替换为SplashRequest:


from scrapy_splash import SplashRequest

    def start_requests(self):
        # 拿取网址源数据
        self.urls = self.db.get_url()

        for key, value in self.urls.items():
            # request = scrapy.Request(value, callback=self.my_parse, dont_filter=True)
            request = SplashRequest(value, self.my_parse, args={'wait': 3}, dont_filter=True)
            # 将id保存在meta中
            request.meta['param'] = key
            yield request

接下来需要在settings.py文件中加入以下内容:


SPIDER_MIDDLEWARES = {
   'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}

DOWNLOADER_MIDDLEWARES = {
    'scrapy_splash.SplashCookiesMiddleware': 723,
    'scrapy_splash.SplashMiddleware': 725,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,  # 不配置查不到信息
}

HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = 'httpcache'

SPLASH_URL = "http://localhost:8050/"  # 自己安装的docker里的splash位置
DUPEFILTER_CLASS = "scrapy_splash.SplashAwareDupeFilter"
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'

OK,这样就完成了scrapy和scrapy_splash的对接,是不是很简单。

二、docker环境的搭建

对于docker环境的搭建,确实需要费一番力气,如果你的服务器是centOS 7以上的还好,我这里是centOS 6.5,CentOS 6.5 的内核一般都是2.6,在2.6的内核下,Docker运行会比较卡,所以一般会选择升级到更高版本。通过uname -r 命令查看你的内核版本号:

# uname -r
2.6.32-696.el6.x86_64

现在我们先来升级内核版本:

1、导入key(需要root权限)

# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org

问题1:如果报错 curl: (6) Couldn't resolve host 'www.elrepo.org' 则表示DNS解析有问题,需要配置:

# vi /etc/sysconfig/network-scripts/ifcfg-eth0

在末尾添加DNS配置,如下:

DNS1=114.114.114.114
DNS2=8.8.8.8

查看nameserver是否显示正确:

# cat /etc/resolv.conf | grep names
nameserver 114.114.114.114
nameserver 8.8.8.8

然后重新导入key。

问题2:如果报curl: (35) SSL connect error错误则输入

# yum update nss

DNS配置完成,重新运行

# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org

2、安装ELRepo到CentOS

# rpm -Uvh http://www.elrepo.org/elrepo-release-6-8.el6.elrepo.noarch.rpm

3、安装内核

# yum --enablerepo=elrepo-kernel install kernel-lt –y

4、修改引导文件,修改为default=0

# vi /etc/grub.conf

5、重启查看版本

# uname -r
4.4.184-1.el6.elrepo.x86_64

经过上面的步骤,我们升级完了内核版本,接下来,我们就来安装docker。
6、安装docker

# yum install docker-io

如果提示错误:No package docker-io available,则运行

# yum -y install http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

7、启动docker

# service docker start

8、查看docker版本

#docker version
Client:
 Version:      17.09.1-ce
 API version:  1.32
 Go version:   go1.8.3
 Git commit:   19e2cf6
 Built:        Thu Dec  7 22:21:47 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.09.1-ce
 API version:  1.32 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   19e2cf6
 Built:        Thu Dec  7 22:28:28 2017
 OS/Arch:      linux/amd64
 Experimental: false

9、镜像加速

# vim /etc/docker/daemon.json

打开以上路径下的daemon.json文件,如果没有的话就创建一个,添加以下内容:

{
  "registry-mirrors": ["http://hub-mirror.c.163.com"]
}

OK,到现在,docker环境已经搭建完成,可能不同的机器会遇到不同的问题,如果在线安装docker不行,可以离线安装,最终目的都是要把docker服务给启动起来,而启动docker服务也不尽相同,有些是直接dockerd命令,而有些是service docker start命令。
启动docker服务之后,我们的工作还没完,我们得创建并运行一个容器来让我们的爬虫程序得以使用。

10、下载scrapy_splash镜像

# sudo docker pull scrapinghub/splash
Using default tag: latest
latest: Pulling from scrapinghub/splash
7b722c1070cd: Pull complete 
5fbf74db61f1: Pull complete 
ed41cb72e5c9: Pull complete 
7ea47a67709e: Pull complete 
b9ea67282e79: Pull complete 
8d0589f2b410: Pull complete 
11f417145dc7: Pull complete 
14d670a8125e: Pull complete 
81d8bf1e3bdc: Pull complete 
Digest: sha256:ec1198946284ccadf6749ad60b58b2d2fd5574376857255342a913ec7c66cfc5
Status: Downloaded newer image for scrapinghub/splash:latest

11、创建并运行容器

# sudo docker run -p 8050:8050 scrapinghub/splash

如果要让容器在后台运行(大多数情况下需要这样),则在后面加上 & 。这里可以看到,我们的容器占用的端口是8050,所以之前我们在配置scrapy_splash的setting.py中才有这样一句代码:

SPLASH_URL = "http://localhost:8050/"  # 自己安装的docker里的splash位置

我们在浏览器中访问服务器的8050端口,就会出现这样的页面:


好了,到目前为止,所有的工作都已经完成,只要启动我们的爬虫,就会通过这个docker容器来渲染JS动态的页面,然后将页面信息返回给我们。
前面在讲scapy和scrapy_splash对接的时候,都是讲的关键部分,至于数据源从哪里拿,爬取之后的网页内容怎么处理,这个需要按照自己的业务来,我们在做的时候网址源是从数据库拿取的,对返回的网页内容也是直接写到数据库里面,这里就不再详述了。

三、docker的常用命令操作

docker images #列出镜像
docker ps -a #列出容器
docker info #查看docker的信息
docker inspect CONTAINERID | IMAGEID #查看容器或镜像的详细信息
docker stop CONTAINERID #停止正在运行的容器 (默认等待10秒钟再杀死指定容器。可以使用-t参数来设置等待时间。)
docker rename oldname newname #重命名容器
docker restart CONTAINERID #重启容器
docker rm CONTAINERID #删除容器
docker rmi IMAGEID #删除镜像
docker stats #查看容器占用的内存情况

四、使用docker可能遇到的问题

接下来讲一下使用这个docker可能遇到的问题,我们在使用过程中发现容器会中途异常退出,而且退出的状态码为139,网上查找了好一段时间,到现在都还没发现到底是什么原因,有些说是底层C库的原因,有的说是内存的问题,遇到这个问题的也很少,本人也是接触docker不多,希望知晓的大牛可以指教一下。不过也不是解决不了,我们可以“曲线救国”,也就是监控容器的状态,当异常退出的时候重启它就可以了(^ _ ^)。
网上有专门的工具框架来解决这个问题,有兴趣的可以自己去了解一下。
还有一个问题就是当大规模爬取的时候,内存会占用越来越大,当内存过大的时候是会导致容器退出的,对于这个内存问题,可以自己写个监控程序,当达到阈值的时候就重启一下容器,释放内存即可:

import os, time

def get_mem_usage_percent():
    try:
        f = open('/proc/meminfo', 'r')
        for line in f:
            if line.startswith('MemTotal:'):
                mem_total = int(line.split()[1])
            elif line.startswith('MemFree:'):
                mem_free = int(line.split()[1])
            elif line.startswith('Buffers:'):
                mem_buffer = int(line.split()[1])
            elif line.startswith('Cached:'):
                mem_cache = int(line.split()[1])
            elif line.startswith('SwapTotal:'):
                vmem_total = int(line.split()[1])
            elif line.startswith('SwapFree:'):
                vmem_free = int(line.split()[1])
            else:
                continue
        f.close()
    except:
        return None
    physical_percent = usage_percent(mem_total - (mem_free + mem_buffer + mem_cache), mem_total)
    virtual_percent = 0
    if vmem_total > 0:
        virtual_percent = usage_percent((vmem_total - vmem_free), vmem_total)
    return physical_percent, virtual_percent


def usage_percent(use, total):
    try:
        ret = (float(use) / total) * 100
    except ZeroDivisionError:
        raise Exception("ERROR - zero division error")
    return ret

statvfs = os.statvfs('/')

while True:
    mem_usage = get_mem_usage_percent()
    mem_usage = int(mem_usage[0])
    if mem_usage > 75:
        # 将containerID换成自己的容器ID。
        os.system(r"(docker restart containerID)")
    time.sleep(5)

注意将containerID换成自己的容器ID。

上一篇下一篇

猜你喜欢

热点阅读