在笔者浅显的认识中,一个简单的分布式爬虫雏形就是爬虫客户端通过RESTAPI和 爬虫服务端通讯,做的事情应该是发布和领取爬取任务,同时能够将爬取下来数据做一个文件存储,现在开始一个简易的爬虫服务端设计
1.框架
理论上用到的api不会超过10个,所以flask 比较合适,当然擅长Django也可以用Django,作为初级使用,也不会有高并发的前提下,Flask比较轻还是比较好用的。数据库自然是MongoDB了,因为爬取下来的数据格式的很难说不会变吧。 然后自然得用Nginx做转发了
那么先搭建一个基于 MongoDB+Flask+uwsgi+Nginx的简单restful 服务端
2.代码
基本流程就那样,先用 virtualenv 切下Python环境,然后在pip install
具体代码没啥看的,都是比较初级的使用主要,用到几个包是有
1.mongoengine 和MongoDB交互用的
2.apscheduler 弄个定时任务用
3.Flask 不解释了
具体贴一下代码好了
mongo_main.py 用来放数据库ORM的
import threading
from mongoengine import *
connect("spiderdbs")
lock = threading.Lock()
class TargetUrl(Document):
url=StringField(required=True,unique=True)
title=StringField(required=True)
# 0 为爬取 1 爬取中 2爬取完毕
state=IntField(required=True,default=0)
#上一次的请求时间
lastworktime = LongField()
#完成时间,暂时没用到
success_time=StringField()
netresult.py 用来放响应数据的,通过 flask的jsonify(),或者json.dumps 返回json格式的响应给客户端
class CommonResult(object):
def __init__(self,isok):
self.isok=isok
def serialize(self):
return self.__dict__
class ErrorResult(CommonResult):
def __init__(self,error_code,error_msg):
super().__init__(False)
self.errorcode=error_code
self.errormsg=error_msg
class SucceessResult(CommonResult):
def __init__(self,data):
super().__init__(True)
self.data=data
然后就是主程序了,flask 确实很轻
myflaskapp.py
import json
from mongoengine import NotUniqueError
import netresult
import time
from mongo_main import TargetUrl,lock
from flask import Flask, abort, Response, request, jsonify
app = Flask(__name__)
@app.route('/spider/')
def hello_worldaa(id):
return 'Hello, World!' + str(id)
#只接受post请求
@app.route('/spider/addwork', methods=["POST"])
def addwork():
jsondata = request.get_json()
title = jsondata["title"]
if not title:
return jsonify(netresult.ErrorResult("300", "参数错误").serialize())
url = jsondata["url"]
if not url:
return jsonify(netresult.ErrorResult("300", "参数错误").serialize())
target = TargetUrl(title=str(title), url=str(url))
try:
target.save()
except NotUniqueError:
return jsonify(netresult.ErrorResult("301", "数据重复").serialize())
return jsonify(netresult.CommonResult(True).serialize())
@app.route('/spider/queryworks')
def queryworks():
findobjs=[]
with lock:
#这里有个要注意的地方,TargetUrl.objects(state=0)[:5]生成的
#列表内容没有立刻加到内存里面去
#(自行去看Python yield)
#在update之前没有全部遍历过一遍的话,update后 findobjs又会变为空集合
findobjs = TargetUrl.objects(state=0)[:5]
if (len(findobjs)>0):
# print([{"url": tar.url, "title": tar.title} for tar in findobjs])
findobjs.update(state=1, lastworktime=int(time.time()))
outlist=[{"url": tar.url, "title": tar.title} for tar in findobjs]
return json.dumps(netresult.SucceessResult({"targets": outlist
}).serialize())
if __name__ == '__main__':
app.run(threaded=True)
在看下定时任务,这个就是每隔一段时间清理掉一些数据,在独立进程中调用就可以了
db_sync.py 使用时 直接 python db_sync.py即可
功能是依赖于apscheduler 实现的,可以去看看相关资料
# coding=utf-8
from apscheduler.schedulers.blocking import BlockingScheduler
import time
import os
from mongo_main import TargetUrl,lock
#避免有任务卡住了,2小时还没有的完成可以当作失败了
timelimt = 7200
def runapp():
num=TargetUrl.objects(state=1,lastworktime__lt=int(time.time())-timelimt ).update(state=0)
print(num)
if __name__ == '__main__':
scheduler = BlockingScheduler()
job = scheduler.add_job(runapp, 'interval', minutes=5)
print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C'))
try:
scheduler.start()
except (KeyboardInterrupt, SystemExit):
scheduler.shutdown()
3.Nginx+ uwsgi
写好的flaskapi还是需要放出来使用的,Nginx+uwsgi安全可靠
配起来也不算麻烦,就搞一下
安装方式略,随便搜一搜一大片
uwsgi安装好后,在flask项目中写一个wsgi.py,用来作为uwsgi控制的入口,py名字可以改,不过这个看起来明白用来干嘛的
wsgi.py
from myflaskapp import app
if __name__ == '__main__':
app.run()
仔细看是不是没啥用,然后在同一目录下,创建uwsgi.ini,这是uwsgi的配置文件,很有用
[uwsgi]
socket = :3031
module = wsgi:app
processes = 4
threads = 2
master = true
home = /root/pythonenvs/python36/env
需关注 socket 就是端口啦,module 就是flask的入口,也就是我们刚才写看着无用的wsgi.py的目的啦,home是Python的环境目录,可以直接用virtualenv 生成的env环境(我就是这样啦),其他的就复制我的,没错的
保存好了以后
在当前目录敲命令
uwsgi uwsgi.ini
uwsgi 就在3031端口起来了,同时flask app也被拉起来了
然后我们在Nginx里面设置下,所有 spider路径下的请求就转发到uwsgi 的3031端口 也就是发到我们的flask app里面
打开Nginx的配置项,安装完了Nginx以后都会有个默认项,笔者的在
/etc/nginx/sites-enabled/default
在里面插入,别的不要动就好了,然后重启下Nginx,细节还是自己搜索吧,网上也是一大片
location /spider/ {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
}
这样就完成了,我们可以用get和post的方式访问和同步数据了,也就是爬虫客户端可以在这台服务器上提交爬取目标和领取爬取任务了