小工具      在线工具  汉语词典  css  js  c++  java

scrapy管理多个spider,共用settings问题

python,scrapy,scrapy,策略模式,设计模式,python 额外说明

收录于:40天前

背景资料

一个scrapy project 可以有多个spider,但是它们之前的settings要分开怎么办?redis等配置要各自配置的情况怎么处理?
如果一个任务一个scrapy,感觉对复用方面很不友好

大体的思路:
一般这种抽象,我会采用策略模式来做,同时兼顾单例,根据flag的不同返回不同的策略对象,此对象拥有自己的独有逻辑

问题与解决方案

总体思路是这样
这里写图片描述

策略模式的思想

策略模式自行百度下,有很多例子
结合这边就是 写个spider相关类,
ParentSpiderProc 父类,用于写一些公共方法
AspiderProc, BspiderProc,具体的类,子类然后里面不同的方法,如 getLog,getKfkClient,getRedis等
SpiderContext,对外的方法类,这个对象就是外围实际使用的类,

spiderContext = SpiderContext.getSpiderContext(spider.name)
spiderContext.getLog()....

spiderContext.getKfkClient()....

settings.py,是所有蜘蛛共享的,如何区分这个设置呢?

这个可以用spider自己的custom_settings来处理,把settings_XXSpider.py放在一个地方,解析的时候加载进去,比如

class StItemPage(scrapy.Spider): custom_settings = SpiderContext.getSpiderContext(name).getCustomSettings() 

如何根据redis、kafka等不同的蜘蛛设置不同的值

1.在settings_ASpider.py中

KAFKA_BOOTSTRAP_SERVERS="xxxxx:9092,xxxx:9094"
KAFKA_TOPIC_ID="aaaa_toic"

2.AspiderProc定义一个方法

def getKafka(self):
    if not hasattr(self, "akfk"):
    self.akfk = KfkClient(bootstrap_servers=settings_Aspider.KAFKA_BOOTSTRAP_SERVERS)
    return self.akfk

3、具体用途

spiderContext = SpiderContext.getSpiderContext(spider.name)
topic_id = spider.custom_settings['KAFKA_TOPIC_ID']
result = spiderContext.getKafka().sendMsg(topic_id, seqstr.encode("utf8"), targetjson.encode('utf8'))

如何处理HTTP代理池?

代理池和上面的kfk没什么不同,不过代理池不是直接调用,自有逻辑比较多。自己有一个类GProxy
代理池用redis,怎么把redis传进来?
GProxy初始化时,加上redis对象
def 在里面(self,credisObj):

那么Proc中获取Gproxy时,首先要创建一个新的redis对象

# 父类直接实现
def getGProxy(self):
    # proxy使用comm的redis配置,即settings.py的
    if not hasattr(self, "gProxy"):
        self.gProxy = GProxy(self.getCommRedis())
    return self.gProxy


def getCommRedis(self):
    if not hasattr(self, "r0"):
        self.r0 = redis.Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=settings.REDIS_DB_INDEX,password=settings.REDIS_DB_PWD,decode_responses=True)
    return self.r0

这里解释一下,由于proxy被定义为公共内容,所以这里是getCommRedis,它的链接也是在settings中获取的。

日志可以做什么

除了普通的日志之外,还有一些特殊的日志,一些流程中专门打印的日志,比如请求一个URL,一条日志,返回一条日志,处理所有日志等。这些是不同的日志。

首先我们有一个 logUtil

    def createLogger(loggerName,loggerPath,displayinTerminal=False):  # 日志
        fmt = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        datefmt = "%a %d %b %Y %H:%M:%S"
        formatter = logging.Formatter(fmt, datefmt)

        # 数据日志输出
        logger = logging.getLogger(loggerName)
        if logger.handlers and len(logger.handlers)>0:
            return  logger
        logger.setLevel(logging.DEBUG)
        # create file handler
        fh = logging.handlers.RotatingFileHandler(filename=loggerPath,encoding="utf8")
        fh.setLevel(logging.INFO)
        # create formatter
        fh.setFormatter(formatter)

        if displayinTerminal:
            ch = logging.StreamHandler()
            ch.setLevel(logging.INFO)
            ch.setFormatter(formatter)
            logger.addHandler(ch)

        arr = logger.handlers
        for chan in arr:
            print (chan)
        logger.addHandler(fh)
        return logger

其次,我们在XspiderProc中获取不同的日志并使用它们(实现代码见下一章):

# 普通:
spiderContext = SpiderContext.getSpiderContext(spider.name)
spiderContext.getLog().errorLog().error("请求抛错了<<<<,错误信息为{},代理地址为:{},目标地址为{}".format(exception,proxy,targeturl))

# 具体日志:
spiderContext.getLog().grab_response().info("请求返回<<<<url为:{},返回的状态:{}".format(request.url,response.status))
spiderContext.getLog().grab_request().info(">>>正在请求>>>>{},其meta为:{},其header为{}".format( request.url,request.meta,request.headers))

具体代码

ParentSpiderProc 类:

# -*- coding: utf-8 -*-

from dspider import settings
import redis
from dspider.tools.GetProxy import GetProxy
from dspider.utils.LoggerUtil import LogUtils


# 请不要直接new
class ParentSpiderProc(object):

    def getSpiderName(self):
        return self.spiderName

    def setSpiderName(self, a_spiderName):
        self.spiderName = a_spiderName

    # 子类需要实现
    def getBusiRedis(self):
        raise NotImplementedError()

    # 获取setting
    def getCustomSettings(self):
        raise NotImplementedError()


    def getKafka(self):
        raise NotImplementedError()

    def getLog(self):
        raise NotImplementedError()


    # 父类直接实现
    def getGProxy(self):
        # proxy使用comm的redis配置,即settings.py的
        if not hasattr(self, "gProxy"):
            self.gProxy = GetProxy("http", self.getCommRedis())
        return self.gProxy


    def getCommRedis(self):
        if not hasattr(self, "r0"):
            self.r0 = redis.Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=settings.REDIS_DB_INDEX,
                                  password=settings.REDIS_DB_PWD,
                                  decode_responses=True)
        return self.r0


class BaseBusiLog(object):
    # 根据 spiderName会有不同的目录和log前缀

    def __init__(self,spiderName):
        if not hasattr(self,"lname"):
            self.lname = spiderName
        super().__init__()

    def grab_request(self):
        return BaseBusiLog.__newLog__("grab_request",self.lname)

    def grab_response(self):
        return BaseBusiLog.__newLog__("grab_response",self.lname)

    def errorLog(self):
        return BaseBusiLog.__newLog__("errorLog",self.lname)

    def infoLog(self):
        return BaseBusiLog.__newLog__("infoLog",self.lname)

    def pipelinesLog(self):
        # pipeline到json文件(测试时)
        return BaseBusiLog.__newLog__("pipelinesLog", self.lname)

    def getLogPath(self):
        log_dataInfo_path = settings.LOG_DIR + "/" + self.lname + "/"
        return  log_dataInfo_path



    @staticmethod
    def __newLog__(logname, lname):
        unionLogName = lname+"_"+logname
        log_dataInfo_path = settings.LOG_DIR + "/" + lname +  "/flow_" + unionLogName + ".log"
        logObj = LogUtils.createLogger(logname, log_dataInfo_path, False)
        return logObj

ASPiderProc:

# -*- coding: utf-8 -*-

from dspider import settings_ASpider
from dspider.spider_strategy.ParentSpiderProc import *
from dspider.tools.KfkClient import KfkClient
from dspider.utils.PythonToMap import PythonToMap

# settings_ASpider只允许在这里出现
class ASpiderProc(ParentSpiderProc):

    def getCustomSettings(self):
        return PythonToMap.genMap(settings_ASpider)

    # def getGProxy(self):
    # return super().getGProxy()

    def getKafka(self):
        if not hasattr(self, "akfk"):
            self.akfk = KfkClient(bootstrap_servers=settings_ASpider.KAFKA_BOOTSTRAP_SERVERS)
        return self.akfk

    def getLog(self):
        if not hasattr(self, "alog"):
            self.alog = DetailLog(self.spiderName)
        return self.alog

    def getBusiRedis(self):
        if not hasattr(self, "r1"):
            self.r1 = redis.Redis(host=settings_ASpider.BUSI_REDIS_HOST,
                                  port=settings_ASpider.BUSI_REDIS_PORT,
                                  db=settings_ASpider.BUSI_REDIS_DB_INDEX,
                                  password=settings_ASpider.BUSI_REDIS_DB_PWD,
                                  decode_responses=True)
        return self.r1


class DetailLog(BaseBusiLog):

    def succ_response_but_ignore(self):
        return DetailLog.__newLog__("succ_response_but_ignore", self.lname)

    def succ_response_and_process(self):
        return DetailLog.__newLog__("succ_response_and_process", self.lname)

蜘蛛上下文:

# -*- coding: utf-8 -*-

from dspider.spider_strategy.ASpiderProc import *
from dspider.spider_strategy.PageSpiderProc import *

spiderContextMap = {}

class SpiderContext(object):
    # 请不要直接使用,请用SpiderContext.getSpiderContext
    def __init__(self,spiderName):
        if spiderName=='ASpider':
            self.spiderProc = ASpiderProc()
        elif spiderName == 'BSpider':
            self.spiderProc = BSpiderProc()
        else:
            raise Exception("不知道的spiderName")
        self.spiderProc.setSpiderName(spiderName)

    @staticmethod
    def getSpiderContext(spiderName):
        targetContext = spiderContextMap.get(spiderName)
        if not targetContext:
            targetContext = SpiderContext(spiderName)  #不同的name不同的context
            spiderContextMap[spiderName] = targetContext
        return  targetContext

    def getSpiderName(self):
        return self.spiderProc.getSpiderName()


    def getCustomSettings(self):
        return self.spiderProc.getCustomSettings()


    def getKafka(self):
        return self.spiderProc.getKafka()

    def getLog(self):
        return self.spiderProc.getLog()

    def getGProxy(self):
        return self.spiderProc.getGProxy()

    def getCommRedis(self):
        return self.spiderProc.getCommRedis()

    def getBusiRedis(self):
        return self.spiderProc.getBusiRedis()

上面提到的工具类PythonToMap:

# -*- coding: utf-8 -*-

import inspect
from dspider import settings
class PythonToMap(object):

    @staticmethod
    def genMap(targetPy):
        targetObj = {}
        for key, obj in inspect.getmembers(targetPy):
            # print("{}:{}".format(key, obj))
            if inspect.isclass(obj):
                continue
            if inspect.ismodule(obj):
                continue
            if '__' in key:
                continue
            if inspect.ismethod(key):
                continue
            targetObj[key] = obj
        # print(targetObj.keys())
        return  targetObj

if __name__ == '__main__':

    print (PythonToMap.genMap(settings))

一些笔记

  • 记得继承ASPiderProc(ParentSpiderProc)
  • 我使用单独的类来处理日志,还有一个BaseXXLog用于写入公共日志。
  • 设置中配置日志LOG_DIR的路径,然后是不同的路径:LOG_DIR/{spidername}/{spidername}_flow_XXX.log
  • .py文件变成Map类型,需要使用PythonToMap
  • redis有一个小矛盾。我不确定它是公共的还是私人的,所以我都提供了。
. . .

相关推荐

额外说明

java jquery json实现二级级联互动菜单(转)

jsp页面的代码: <%@ page contentType="text/html; charset=gbk"%> <%@ taglib prefix="s" uri="/struts-tags"%> <script type="text/javascr

额外说明

leetcode773(滑动拼图:BFS光搜)

在一个 2 x 3 的板上(board)有 5 块砖瓦,用数字 1~5 来表示, 以及一块空缺用 0 来表示. 一次移动定义为选择 0 与一个相邻的数字(上下左右)进行交换. 最终当板 board 的结果是 [[1,2,3],[4,5,0]] 谜板被解开

额外说明

MYSQL_

文章目录 ①. 索引的概述 ②. 二叉树和红黑树 ③. Hash建立索引结构 ④. B树的数据结构 ⑤. MyISAM存储引擎索引实现 ⑥. InnoDB索引实现(聚集) ⑦. 联合索引的设定 ①. 索引的概述 ①. 索引是帮助MySQL高效获取数据的排

额外说明

[bug集] 解决aop多次执行问题的方案之一(代理模式)

目录 1. 测试将aop入口注解 放到方法上面 2.检查项目配置是不是有两个AdvisorAutoProxyCreator 3.详细解释 4. 解决方案  [解决方案之一] 1. 测试将aop入口注解 放到方法上面 发现aop里面逻辑执行了两次 2.检查

额外说明

MyBatis-Plus之注解

MyBatis-Plus之注解 1.0 MyBatis-Plus之注解 @TableName 1.1 扩展配置指定表名 2.0 注解 @TableId 2.1 @TableId 将属性对应的字段指定为主键 2.2 @TableId(value="uid"

额外说明

数据结构——排序算法——计数排序

计数排序就是一种时间复杂度为 O(n) 的排序算法,该算法于 1954 年由 Harold H. Seward 提出。在对一定范围内的整数排序时,它的复杂度为 O(n+k)(其中 k 是整数的范围大小)。 伪计数排序 我们需要对一列数组排序,这个数组中每

额外说明

【GitHub】在Github主页显示你的个人简历

推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 QQ群:1040082875 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 从另一个博主的文章(文章

额外说明

python(matplotlib6)——打印图像(imshow)3D数据(contourf等高线)

文章目录 前言 打印图像 imshow 3D图像 Axes3D 前言 来自 莫烦python的总结。 打印图像 imshow a = np.array([0.313,0.365,0.423, 0.365,0.439,0.525

额外说明

设计安全的多线程应用程序(线程安全)

 以前常听高手告诫MFC对象不要跨线程使用,因为MFC不是线程安全的。比如CWnd对象不要跨线程使用,可以用窗口句柄(HWND)代替。 CSocket/CAsyncSocket对象不要跨线程使用,用SOCKET句柄代替.       那么到底什么是线程安

额外说明

如今的革命:让您的业务更快,更智能,更社交化

As you know that we are attending 博客世博会 East in New York City. We had a pleasure of attending Jay Baer’s (@jaybaer) session in

ads via 小工具