[可视化抓取]portia2.0尝鲜体验以及自动化畅想-数据存储入mongodb
总览
- 环境
- 分析
- 猜想
- 实施
- 总结
环境
- 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
- 原文作者:大鱼
- 原文链接:https://brucedone.com/archives/1096/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。