总览

  • 环境
  • 分析
  • 猜想
  • 实施
  • 总结

环境

  • mac or ubuntu 16.04
  • docker

分析

上一章节,==> [可视化抓取]portia2.0尝鲜体验以及自动化畅想-数据输出以及原理分析 <== 我们已经分析了portia 数据 输出的方式,以及从代码的层面来分析它实际的运作原理,群里有小伙伴以及微信私信问我:大鱼,这个怎么和我们的数据库正常的结合呢?一般我们遇到问题,先对比之前的处理经验

回顾

我们在以前正常的scrapy数据入库,都是写好一个pipeline ,然后写好我们的业务代码,加载pipeline就可以写入到数据库里面了,OK,那大方向确定了,我们就正常的从这个方面着手。

准备

还是拿我们上次教程的cnblogs 里面的代码来做例子,目前,我们本地的portia docker 已经起来了,我们看一下状态,使用命令docker ps -a

5ea71b2e3f6d        scrapinghub/portia:portia-2.0.8   "/app/docker/entry"      8 days ago          Up 8 days                0.0.0.0:9001->9001/tcp                         5ea71b2e3f6d_portia_portia_1

当前将宿主机的9001 和docker 内部的 9001 绑定,暴露出http服务,我们在打开浏览器http://127.0.0.1:9001/ 是可以看到我们熟悉的服务界面了,这里就不详细的介绍了,关于使用和环境的搭建以及项目的生成,我们可以好好的参考我前面的两篇文章。

探究

我们首先进入docker 内部 ,使用命令

docker exec -it 5ea71b2e3f6d_portia_portia_1 bash

进入目录/app/data/projects 找到我们熟悉cnblogs项目,我们首先看一下整体的目录结构

    .
    |____extractors.json
    |____items.json
    |____project.json
    |____scrapy.cfg
    |____setup.py
    |____spiders
    | |______init__.py
    | |____settings.py
    | |____www.cnblogs.com
    | | |____dd60-46f2-bbea
    | | | |____original_body.html
    | | | |____rendered_body.html
    | | |____dd60-46f2-bbea.json
    | |____www.cnblogs.com.json

对比一下我们之前的scrapy项目,我们只要写好pipeline ,在settings.py 里面加载好就ok了

settings.py的同级目录下,建立pipelines.py文件,我们这次先正常的输出内容就好,暂时不存入到任何的数据库

    # -*- coding: utf-8 -*-
    class MongoPipeline(object):
    
    
        def process_item(self, item, spider):
    
            print 'i got the spider from the spider'
            return item

Ok ,我们添加到settings.py 里面添加我们的写好的pipeline

    #coding:utf-8
    # Automatically created by: slyd
    import os
    
    SPIDER_MANAGER_CLASS = 'slybot.spidermanager.ZipfileSlybotSpiderManager'
    EXTENSIONS = {'slybot.closespider.SlybotCloseSpider': 1}
    ITEM_PIPELINES = {
        'slybot.dupefilter.DupeFilterPipeline': 1,
        'cnblogs.spiders.pipelines.MongoPipeline': 2, # 这里加入当前的项目路径
    }
    
    SPIDER_MIDDLEWARES = {'slybot.spiderlets.SpiderletsMiddleware': 999}  # as close as possible to spider output
    DOWNLOADER_MIDDLEWARES = {
        'slybot.pageactions.PageActionsMiddleware': 700,
        'slybot.splash.SlybotJsMiddleware': 725
    }
    PLUGINS = [
        'slybot.plugins.scrapely_annotations.Annotations',
        'slybot.plugins.selectors.Selectors'
    ]
    SLYDUPEFILTER_ENABLED = True
    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    
    PROJECT_ZIPFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
    
    try:
        from local_slybot_settings import *
    except ImportError:
        pass

这样,我们就把cnblogs项目里面的pipelines.py 正常的加入到了我们的配置选项里面了,接下来我们来在里面运行我们的项目,切换到/app/data/projects 目录 ,使用命令:

    portiacrawl cnblogs www.cnblogs.com

文档所在:http://portia.readthedocs.io/en/latest/spiders.html#running-a-spider

结果输出

结果显示,这个文件压根就没有加载进去 ,下面就是本次章节的精彩猜想以及如何去分析一个项目了

猜想

命令

我们来仔细研究一下 portiacrawl 到底是一个怎么样的过程,既然有这个命令,我们直接拿到portia 的官方git里面搜一下 https://github.com/scrapinghub/portia/search?utf8=%E2%9C%93&q=portiacrawl&type=,通过筛查,发现是如下的文件使用

slybot/setup.py

packages=find_packages(exclude=('tests', 'tests.*')),
            platforms=['Any'],
            scripts=['bin/slybot', 'bin/portiacrawl'],
            install_requires=install_requires,

我们进一步查找bin/portiacrawl 这个脚本 https://github.com/scrapinghub/portia/blob/master/slybot/bin/portiacrawl,代码层里面 如下的代码值得查看

        command_spec = ["scrapy", "crawl", args[1]] if len(args) == 2 else ["scrapy", "list"]
        if project_specs.endswith(".zip"):
            command_spec.extend([
                "-s", "PROJECT_ZIPFILE=%s" % project_specs,
                "-s", "SPIDER_MANAGER_CLASS=slybot.spidermanager.ZipfileSlybotSpiderManager",
            ])
        else:
            command_spec.extend([
                "-s", "PROJECT_DIR=%s" % project_specs,
                "-s", "SPIDER_MANAGER_CLASS=slybot.spidermanager.SlybotSpiderManager",
            ])

我们通过代码查看,基本所谓的portiacrawl 其实就是就是套了一层scrapy crawl 的命令 ,那这样,我们之前scrapy crawl spider_name -o result.json -t json 命令是不是也可以正常使用呢?直接将结果输出呢?

实践

还是一样,使用命令

    portiacrawl cnblogs www.cnblogs.com -o test.json -t json

奇迹出现了!我们看看本地文件夹 ,出现了test.json 结果就是我们所爬取的内容,这个时候,我们可以直接将test.json 导入到mongodb,其实也可以算是一种数据库输入的方法了,当然,我们要进一步的去研究pipelines.py的使用

深入

我们注意到 我们每次使用 portiacrawl 这个命令的时候,都会根据当前的 文件类似(是否为zip文件) 来加入一个这个命令

    SPIDER_MANAGER_CLASS=slybot.spidermanager.SlybotSpiderManager

我们进一步去查看scrapy里面的command 关于-s 的用法是什么意思 ,参考文件 https://github.com/scrapy/scrapy/blob/master/scrapy/commands/__init__.py

            group = OptionGroup(parser, "Global Options")
            group.add_option("--logfile", metavar="FILE",
                help="log file. if omitted stderr will be used")
            group.add_option("-L", "--loglevel", metavar="LEVEL", default=None,
                help="log level (default: %s)" % self.settings['LOG_LEVEL'])
            group.add_option("--nolog", action="store_true",
                help="disable logging completely")
            group.add_option("--profile", metavar="FILE", default=None,
                help="write python cProfile stats to FILE")
            group.add_option("--pidfile", metavar="FILE",
                help="write process ID to FILE")
            group.add_option("-s", "--set", action="append", default=[], metavar="NAME=VALUE",
                help="set/override setting (may be repeated)")
            group.add_option("--pdb", action="store_true", help="enable pdb on failure")

参考里面的-s ,简单来看就是替换默认的原来settings.py 里面的配置选项为我们自己的值,OK,我们重新回到docker 内部,我们来找找这个SPIDER_MANAGER_CLASS 到底啥时候用到,因为portia docker内部的 scrapy版本和线上的是不一样的,所以我们直接来看docker 内部的scrapy ,路径如下

    /usr/local/lib/python2.7/dist-packages/scrapy

我们来找找这个SPIDER_MANAGER_CLASS 使用命令

    grep -r 'SPIDER_MANAGER_CLASS'

我们得知是在 crawler.py 中使用的,进一步去查找相关的代码,得到

    def _get_spider_loader(settings):
        """ Get SpiderLoader instance from settings """
        if settings.get('SPIDER_MANAGER_CLASS'):
            warnings.warn(
                'SPIDER_MANAGER_CLASS option is deprecated. '
                'Please use SPIDER_LOADER_CLASS.',
                category=ScrapyDeprecationWarning, stacklevel=2
            )
        cls_path = settings.get('SPIDER_MANAGER_CLASS',
                                settings.get('SPIDER_LOADER_CLASS'))
        loader_cls = load_object(cls_path)
        try:
            verifyClass(ISpiderLoader, loader_cls)
        except DoesNotImplement:
            warnings.warn(
                'SPIDER_LOADER_CLASS (previously named SPIDER_MANAGER_CLASS) does '
                'not fully implement scrapy.interfaces.ISpiderLoader interface. '
                'Please add all missing methods to avoid unexpected runtime errors.',
                category=ScrapyDeprecationWarning, stacklevel=2
            )
        return loader_cls.from_settings(settings.frozencopy())

原来本质上就是使用了 SPIDER_LOADER_CLASS,关于这个键是如何使用的,我们可以反查scrapy的官方github分析https://github.com/scrapy/scrapy/search?utf8=%E2%9C%93&q=SPIDER_LOADER_CLASS&type=
简单来说,当我们每次使用命令的时候,加载配置的动作都有一个预加载的过程,这个加载的过程将我们用户自己写好的方法,逻辑,配置加载到写好的模版里面,这样就可以通过命令来执行我们的爬虫了。

结果

既然已经分析到这一步了,我们直接来看看今天的主角slybot.spidermanager.SlybotSpiderManager => https://github.com/scrapinghub/portia/blob/master/slybot/slybot/spidermanager.py

核心代码

    from zope.interface import implementer
    from scrapy.interfaces import ISpiderManager
    from scrapy.utils.misc import load_object
    from scrapy.utils.project import get_project_settings
    
    from slybot.spider import IblSpider
    from slybot.utils import open_project_from_dir, load_plugins
    
    
    @implementer(ISpiderManager)
    class SlybotSpiderManager(object):
    
        def __init__(self, datadir, spider_cls=None, settings=None, **kwargs):
            logging.info('Slybot %s Spider', slybot.__version__)
            if settings is None:
                settings = get_project_settings()
            self.spider_cls = load_object(spider_cls) if spider_cls else IblSpider
            self._specs = open_project_from_dir(datadir)
            settings = settings.copy()
            settings.frozen = False
            settings.set('LOADED_PLUGINS', load_plugins(settings))
            self.settings = settings

在这里,我们看spider_cls ,这里我们直接加载了写好的IblSpider , IblSpider 是直接加载我们的json规则(上篇文章已经分析了相关的规则),然后直接执行,走到这里,我们终于明白原先在项目文件夹下写pipelines.py为什么没有毛用了,原来本质上还是跑的这个/app/slybot/slybot/spider.py 的IblSpider,所以如果要加载我们自己写好的pipeline 呢,直接在这个项目slybot里面加入我们的pipeline ,修改settings.py 就可以了。

实施

修改pipelines

首先,我们将写好的pipelines.py 放在/app/slybot/slybot/ 目录下,然后修改同目录下的settings.py文件

    ITEM_PIPELINES = {'slybot.dupefilter.DupeFilterPipeline': 1,
    'slybot.pipelines.MongoPipeline':2,
    }

在ITEM_PIPELINES 节点,加入我们的slybot.pipelines.MongoPipeline

OK ,这一切做好,切回到/app/data/projects 目录,使用命令portiacrawl cnblogs www.cnblogs.com ,我们来看看效果:

    i got the spider from the spider
    DEBUG:scrapy.core.scraper:Scraped from <200 https://www.cnblogs.com/>
    {u'_index': 19,
     u'_template': 'dd60-46f2-bbea',
     u'_type': u'content',
     u'art_url': ['http://www.cnblogs.com/CloudMan6/p/7637361.html'],
     u'author': [u'CloudMan'],
     u'comment_count': [u'\u8bc4\u8bba(0)'],
     u'desc': [u'\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u51e0\u4e2a\u76ee\u524d\u6bd4\u8f83\u5e38\u7528\u7684\u5bb9\u5668\u76d1\u63a7\u5de5\u5177\u548c\u65b9\u6848\uff0c\u4e3a\u5927\u5bb6\u6784\u5efa\u81ea\u5df1\u7684\u76d1\u63a7\u7cfb\u7edf\u63d0\u4f9b\u53c2\u8003\u3002 ...'],
     u'read_count': [u'\u9605\u8bfb(171)'],
     u'url': 'https://www.cnblogs.com/'}
    2017-10-09 03:09:22 [scrapy] DEBUG: Scraped from <200 https://www.cnblogs.com/>
    {u'_index': 19,
     u'_template': 'dd60-46f2-bbea',
     u'_type': u'content',
     u'art_url': ['http://www.cnblogs.com/CloudMan6/p/7637361.html'],
     u'author': [u'CloudMan'],
     u'comment_count': [u'\u8bc4\u8bba(0)'],
     u'desc': [u'\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u51e0\u4e2a\u76ee\u524d\u6bd4\u8f83\u5e38\u7528\u7684\u5bb9\u5668\u76d1\u63a7\u5de5\u5177\u548c\u65b9\u6848\uff0c\u4e3a\u5927\u5bb6\u6784\u5efa\u81ea\u5df1\u7684\u76d1\u63a7\u7cfb\u7edf\u63d0\u4f9b\u53c2\u8003\u3002 ...'],
     u'read_count': [u'\u9605\u8bfb(171)'],
     u'url': 'https://www.cnblogs.com/'}
    i got the spider from the spider
    DEBUG:scrapy.core.scraper:Scraped from <200 https://www.cnblogs.com/>
    {u'_index': 20,
     u'_template': 'dd60-46f2-bbea',
     u'_type': u'content',
     u'art_url': ['http://www.cnblogs.com/morang/p/7639033.html'],
     u'author': [u'\u6613\u58a8'],
     u'comment_count': [u'\u8bc4\u8bba(2)'],

i got the spider from the spider 已经正常的打印出来了

安装pymongo

我们在portia docker 里面使用pip 安装pymongo

    pip install pymongo==3.2

修改pipelines.py文件内容为

    # -*- coding: utf-8 -*-
    from pymongo import MongoClient
    
    
    class MongoPipeline(object):
        def __init__(self):
            self.client = MongoClient('mongodb://192.168.1.52:27017')
            self.db = self.client.get_database('portia')
            self.collection = self.db.get_collection('cnblogs')
    
        def process_item(self, item, spider):
            if item:
                self.collection.save(dict(item))
            return item

注意:我这里使用的地址是192.168.1.52,因为我局域网内的机器(192.168.1.52)已经起了mongo 这样一个实例,所以可以直接从docker 里面直接访问,如果你要绑定在另外一个docker 数据 库,请参考docker link的相关语法以及怎么把容器的网络连接在一起

运行

跳转到 /app/data/projects 使用命令

    portiacrawl cnblogs www.cnblogs.com

运行结束,用mongo链接 192.168.1.52的mongo实例,查看内容

    > show dbs
    local   0.000GB
    portia  0.000GB
    > use portia
    switched to db portia
    > show collections
    cnblogs
    > db.cnblogs.count()
    20
    > db.cnblogs.findOne()
    {
        "_id" : ObjectId("59daec586073800080798b7c"),
        "art_url" : [
            "http://www.cnblogs.com/yitouniu/p/7640079.html"
        ],
        "_type" : "content",
        "_index" : 1,
        "url" : "https://www.cnblogs.com/",
        "author" : [
            "飞·羽"
        ],
        "read_count" : [
            "阅读(13)"
        ],
        "comment_count" : [
            "评论(0)"
        ],
        "_template" : "dd60-46f2-bbea",
        "desc" : [
            "本文简单举例说明如何使用wsimport和JAX-WS调用WebService接口 ..."
        ]
    }
    >

OK ,数据已经存入到里面了

总结

本次主要从

  • 分析问题
  • 猜想问题
  • 原理分析
  • 大胆实践

这几个步骤着手,拿到一个框架不要慌,源代码都摆在那里了,你还怕啃不下来,多分析,多动手,问题都会解决的,OK