📜  在Python中使用抽象工厂设计模式实现 Web Crawler

📅  最后修改于: 2022-05-13 01:55:09.722000             🧑  作者: Mango

在Python中使用抽象工厂设计模式实现 Web Crawler

在抽象工厂设计模式中,每个产品都有一个抽象的产品接口。这种方法有助于创建独立于工厂类的相关对象系列。因此,您可以在运行时更改工厂以获取不同的对象——简化了产品系列的替换。

在这种设计模式中,客户端使用抽象工厂接口来访问对象。抽象接口将对象的创建与客户端分离,这使得操作更容易并将具体类与客户端隔离。但是,向现有工厂添加新产品很困难,因为您需要扩展工厂接口,包括更改抽象工厂接口类及其所有子类。

让我们看一下Python中的网络爬虫实现,以便更好地理解。如下图所示,您有一个抽象工厂接口类 - AbstractFactory - 和两个具体工厂类 - HTTPConcreteFactoryFTPConcreteFactory 。这两个具体类派生自AbstractFactory 类,并具有创建三个接口实例的方法—— ProtocolAbstractProductPortAbstractProductCrawlerAbstractProduct

由于AbstractFactory类充当HTTPConcreteFactoryFTPConcreteFactory等工厂的接口,因此它具有三个抽象方法 – create_protocol()、create_port()、create_crawler() 。这些方法在工厂类中重新定义。这意味着HTTPConcreteFactory类创建其相关对象系列,例如 HTTPPort、HTTPSecurePort 和 HTTPSecureProtocol,而FTPConcreteFactory类创建 FTPPort、FTPProtocol 和 FTPCrawler。

Python3
import abc
import urllib
import urllib.error
import urllib.request
from bs4 import BeautifulSoup
  
class AbstractFactory(object, metaclass=abc.ABCMeta):
    """ Abstract Factory Interface """
      
    def __init__(self, is_secure):
        self.is_secure = is_secure
  
    @abc.abstractmethod
    def create_protocol(self):
        pass
  
    @abc.abstractmethod
    def create_port(self):
        pass
  
    @abc.abstractmethod
    def create_crawler(self):
        pass
  
class HTTPConcreteFactory(AbstractFactory):
    """ Concrete Factory for building HTTP connection. """
      
    def create_protocol(self):
        if self.is_secure:
            return HTTPSecureProtocol()
        return HTTPProtocol()
  
    def create_port(self):
        if self.is_secure:
            return HTTPSecurePort()
        return HTTPPort()
  
    def create_crawler(self):
        return HTTPCrawler()
  
class FTPConcreteFactory(AbstractFactory):
    """ Concrete Factory for building FTP connection """
      
    def create_protocol(self):
        return FTPProtocol()
  
    def create_port(self):
        return FTPPort()
  
    def create_crawler(self):
        return FTPCrawler()
  
class ProtocolAbstractProduct(object, metaclass=abc.ABCMeta):
    """ An abstract product, represents protocol to connect """
      
    @abc.abstractmethod
    def __str__(self):
        pass
      
class HTTPProtocol(ProtocolAbstractProduct):
    """ An concrete product, represents http protovol """
      
    def __str__(self):
        return 'http'
  
class HTTPSecureProtocol(ProtocolAbstractProduct):
    """ An concrete product, represents https protovol """
      
    def __str__(self):
        return 'https'
  
class FTPProtocol(ProtocolAbstractProduct):
    """ An concrete product, represents ftp protovol """
      
    def __str__(self):
        return 'ftp'
  
class PortAbstractProduct(object, metaclass=abc.ABCMeta):
    """ An abstract product, represents port to connect """
      
    @abc.abstractmethod
    def __str__(self):
        pass
  
class HTTPPort(PortAbstractProduct):
    """ A concrete product which represents http port. """
      
    def __str__(self):
        return '80'
  
class HTTPSecurePort(PortAbstractProduct):
    """ A concrete product which represents https port """
    def __str__(self):
        return '443'
  
class FTPPort(PortAbstractProduct):
    """ A concrete products which represents ftp port. """
      
    def __str__(self):
        return '21'
  
class CrawlerAbstractProduct(object, metaclass=abc.ABCMeta):
    """ An Abstract product, represents parser to parse web content """
      
    @abc.abstractmethod
    def __call__(self, content):
        pass
  
class HTTPCrawler(CrawlerAbstractProduct):
    def __call__(self, content):
        """ Parses web content """
          
        filenames = []
        soup = BeautifulSoup(content, "html.parser")
        links = soup.table.findAll('a')
  
        for link in links:
            filenames.append(link['href'])
              
        return '\n'.join(filenames)
  
class FTPCrawler(CrawlerAbstractProduct):
    def __call__(self, content):
        
        """ Parse Web Content """
        content = str(content, 'utf-8')
        lines = content.split('\n')
        filenames = []
          
        for line in lines:
            splitted_line = line.split(None, 8)
            if len(splitted_line) == 9:
                filenames.append(splitted_line[-1])
  
        return '\n'.join(filenames)
  
class Connector(object):
    """ A client """
      
    def __init__(self, abstractfactory):
        """ calling all attributes
of a connector according to abstractfactory class. """
          
        self.protocol = abstractfactory.create_protocol()
        self.port = abstractfactory.create_port()
        self.crawl = abstractfactory.create_crawler()
  
    def read(self, host, path):
        url = str(self.protocol) + '://' + host + ':' + str(self.port) + path
        print('Connecting to', url)
        return urllib.request.urlopen(url, timeout=10).read()
  
if __name__ == "__main__":
    con_domain = 'ftp.freebsd.org'
    con_path = '/pub/FreeBSD/'
  
    con_protocol = input('Choose the protocol \
                    (0-http, 1-ftp): ')
      
    if con_protocol == '0':
        is_secure = input('Use secure connection? (1-yes, 0-no):')
        if is_secure == '1':
            is_secure = True
        else:
            is_secure = False
        abstractfactory = HTTPConcreteFactory(is_secure)
    else:
        is_secure = False
        abstractfactory = FTPConcreteFactory(is_secure)
  
    connector = Connector(abstractfactory)
  
    try:
        data = connector.read(con_domain, con_path)
    except urllib.error.URLError as e:
        print('Cannot access resource with this method', e)
    else:
        print(connector.crawl(data))


输出

输出

该程序的目标是使用 HTTP 协议或 FTP 协议抓取网站。在这里,我们在实现代码时需要考虑三种场景。

  1. 协议
  2. 港口
  3. 履带式

这三种场景在 HTTP 和 FTP 网络访问模型上有所不同。所以,这里我们需要创建两个工厂,一个用于创建 HTTP 产品,另一个用于创建 FTP 产品—— HTTPConcreteFactory 和 FTPConcreteFactory 。这两个具体工厂派生自一个抽象工厂—— AbstractFactory

使用抽象接口是因为两个工厂类的操作方法相同,只是实现不同,因此客户端代码可以在运行时确定使用哪个工厂。让我们分析一下每个工厂创造的产品。

在协议产品的情况下,HTTP 具体工厂创建 http 或 https 协议,而 FTP 具体工厂创建 ftp 协议。对于端口产品,HTTP 具体工厂生成 80 或 443 作为端口产品,FTP 工厂生成 21 作为端口产品。最后,爬虫实现不同,因为 HTTP 和 FTP 的网站结构不同。

在这里,创建的对象具有相同的接口,而每个工厂创建的具体对象都不同。比如说,HTTP 端口、HTTP 安全端口、FTP 端口等端口产品具有相同的接口,但是两个工厂的具体对象不同。这同样适用于协议和爬虫。

最后,连接器类接受一个工厂,并使用该工厂基于工厂类注入连接器的所有属性。