如何将python应用程序的两个组件解耦



我正在努力学习python开发,我一直在阅读有关架构模式和代码设计的主题,因为我想停止黑客攻击,真正进行开发。我正在实现一个网络爬虫,正如你所看到的,我知道它有一个有问题的结构,但我不知道如何修复它

爬网程序将返回一个操作列表,以在mongoDB实例中输入数据。

这是我的应用程序的总体结构:

Spiders

crawlers.py
connections.py
utils.py
__init__.py

crawlers.py实现了一个类型为Crawler的类,每个特定的爬网程序都继承它。每个爬网程序都有一个属性table_name和一个方法:crawl。在connections.py中,我实现了一个pymongo驱动程序来连接数据库。它期望一个爬网程序作为其write方法的参数。下面是技巧部分。。。爬网程序2取决于爬网程序1的结果,所以我最终得到了这样的结果:

from pymongo import InsertOne
class crawler1(Crawler):
def __init__(self):
super().__init__('Crawler 1', 'table_A')
def crawl(self):
return list of InsertOne
class crawler2(Crawler):
def __init__(self):
super().__init__('Crawler 2', 'table_B')
def crawl(self, list_of_codes):
return list of InsertOne # After crawling the list of codes/links

然后,在我的连接中,我创建了一个需要爬网程序的类。

class MongoDriver:
def __init__.py
self.db = MongoClient(...)
def write(crawler, **kwargs):
self.db[crawler.table_name].bulk_write(crawler.crawl(**kwargs))
def get_list_of_codes():
query = {}
return [x['field'] for x in self.db.find(query)]

因此,(最大的(问题来了(因为我认为还有很多其他问题,其中一些我几乎无法理解,另一些我仍然完全视而不见(:我的连接的实现需要爬虫的上下文!!例如:

mongo_driver = MongoDriver()
crawler1 = Crawler1()
crawler2 = Crawler2()
mongo_driver.write(crawler1)
mongo_driver.write(crawler2, list_of_codes=mongo_driver.get_list_of_codes())

如何解决它?在这个结构中还有什么特别令人担忧的呢?感谢您的反馈!

问题1MongoDriver对您的爬网程序了解太多。您应该将驱动程序与crawler1crawler2分开。我不确定您的crawl函数返回了什么,但我认为它是一个类型为A的对象列表。

您可以使用CrawlerService之类的对象来管理MongoDriverCrawler之间的依赖关系。这将把驱动程序的写入责任与爬网程序的爬网责任分开。该服务还将管理操作顺序,在某些情况下,这可能被认为是足够好的。

class Repository:
def write(for_table: str, objects: 'List[A]'):
self.db[for_table].bulk_write(objects)
class CrawlerService:
def __init__(self, repository: Repository, crawlers: List[Crawler]):
...

def crawl(self):
crawler1, crawler2 = crawlers
result = [repository.write(x) for x in crawler1.crawl()]
... # work with crawler2 and result 

问题2Crawler1Crawler2几乎相同;它们只是在我们调用CCD_ 20函数时不同。考虑到DRY原理,您可以将爬网算法分离为策略等对象,并有一个依赖于它的爬网程序(带组成(。

class CrawlStrategy(ABC):

@abstractmethod
def crawl(self) -> List[A]:
pass

class CrawlStrategyA(CrawlStrategy):

def crawl(self) -> List[A]:
...
class CrawlStrategyB(CrawlStrategy):

def __init__(self, codes: List[int]):
self.__codes = codes

def crawl(self) -> List[A]:
...

class Crawler(ABC):

def __init__(self, name: str, strategy: 'CrawlStrategy'):
self.__name = name
self.__strategy = strategy

def crawl(self) -> List[int]:
return self.__strategy.crawl()

通过这样做,Crawler的结构(例如表名等(只存在于一个地方,您可以稍后对其进行扩展

问题3:从这里开始,您可以使用多种方法来改进整体设计。您可以通过创建依赖于数据库连接的新策略来删除CrawlService。为了表示一种策略依赖于另一种策略(例如crawler1crawler2产生结果(,可以将两种策略组合在一起,例如:


class StrategyA(Strategy):

def __init__(self, other: Strategy, database: DB):
self.__other = other
self.__db = database

def crawl(self) -> 'List[A]':
result = self.__other.crawl()
self.__db.write(result)
xs = self.__db.find(...)
# do something with xs
...

当然,这是一个简化的示例,但将消除数据库连接和爬网程序之间对单个中介的需求,并提供更大的灵活性。此外,整体设计更容易测试,因为您所要做的就是对策略对象进行单元测试(并且您可以轻松地模拟数据库连接,从而实现DI(。

从这一点来看,改进整体设计的下一步在很大程度上取决于实现的复杂性以及您通常需要多大的灵活性。

附言:除了策略模式,你还可以尝试其他选择,可能取决于你有多少爬行器和它们的一般结构,你必须使用装饰器模式。

最新更新