Implementieren von Web Crawler mithilfe des Abstract Factory Design Pattern in Python
Im Entwurfsmuster von Abstract Factory verfügt jedes Produkt über eine abstrakte Produktschnittstelle. Dieser Ansatz erleichtert die Erstellung von Familien verwandter Objekte, die unabhängig von ihren Fabrikklassen sind. Infolgedessen können Sie die Factory zur Laufzeit ändern, um ein anderes Objekt zu erhalten. Dies vereinfacht das Ersetzen der Produktfamilien.
In diesem Entwurfsmuster verwendet der Client eine abstrakte Factory-Schnittstelle, um auf Objekte zuzugreifen. Die abstrakte Schnittstelle trennt die Erstellung von Objekten vom Client, was die Manipulation erleichtert und die konkreten Klassen vom Client isoliert. Das Hinzufügen neuer Produkte zur vorhandenen Factory ist jedoch schwierig, da Sie die Factory-Schnittstelle erweitern müssen, einschließlich des Änderns der abstrakten Factory-Schnittstellenklasse und aller ihrer Unterklassen.
Schauen wir uns zum besseren Verständnis die Implementierung des Webcrawlers in Python an. Wie im folgenden Diagramm gezeigt, haben Sie eine abstrakte Factory-Schnittstellenklasse - AbstractFactory - und zwei konkrete Factory-Klassen - HTTPConcreteFactory und FTPConcreteFactory . Diese beiden konkreten Klassen sind von der AbstractFactory-Klasse abgeleitet und verfügen über Methoden zum Erstellen von Instanzen von drei Schnittstellen - ProtocolAbstractProduct , PortAbstractProduct und CrawlerAbstractProduct .
Da die AbstractFactory- Klasse als Schnittstelle für Fabriken wie HTTPConcreteFactory und FTPConcreteFactory fungiert , verfügt sie über drei abstrakte Methoden: create_protocol(), create_port(), create_crawler() . Diese Methoden werden in den Factory-Klassen neu definiert. Dies bedeutet, dass die HTTPConcreteFactory- Klasse ihre Familie verwandter Objekte wie HTTPPort, HTTPSecurePort und HTTPSecureProtocol erstellt, während die FTPConcreteFactory- Klasse FTPPort, FTPProtocol und FTPCrawler erstellt.
import
abc
import
urllib
import
urllib.error
import
urllib.request
from
bs4
import
BeautifulSoup
class
AbstractFactory(
object
, metaclass
=
abc.ABCMeta):
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):
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):
def
create_protocol(
self
):
return
FTPProtocol()
def
create_port(
self
):
return
FTPPort()
def
create_crawler(
self
):
return
FTPCrawler()
class
ProtocolAbstractProduct(
object
, metaclass
=
abc.ABCMeta):
@abc
.abstractmethod
def
__str__(
self
):
pass
class
HTTPProtocol(ProtocolAbstractProduct):
def
__str__(
self
):
return
'http'
class
HTTPSecureProtocol(ProtocolAbstractProduct):
def
__str__(
self
):
return
'https'
class
FTPProtocol(ProtocolAbstractProduct):
def
__str__(
self
):
return
'ftp'
class
PortAbstractProduct(
object
, metaclass
=
abc.ABCMeta):
@abc
.abstractmethod
def
__str__(
self
):
pass
class
HTTPPort(PortAbstractProduct):
def
__str__(
self
):
return
'80'
class
HTTPSecurePort(PortAbstractProduct):
def
__str__(
self
):
return
'443'
class
FTPPort(PortAbstractProduct):
def
__str__(
self
):
return
'21'
class
CrawlerAbstractProduct(
object
, metaclass
=
abc.ABCMeta):
@abc
.abstractmethod
def
__call__(
self
, content):
pass
class
HTTPCrawler(CrawlerAbstractProduct):
def
__call__(
self
, 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):
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
):
def
__init__(
self
, abstractfactory):
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
(
'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:
(
'Cannot access resource with this method'
, e)
else
:
(connector.crawl(data))
Ausgabe
Ziel des Programms ist das Crawlen der Website mithilfe des HTTP- oder FTP-Protokolls. Hier müssen wir drei Szenarien bei der Implementierung des Codes berücksichtigen.
- Protokoll
- Hafen
- Crawler
Diese drei Szenarien unterscheiden sich in den HTTP- und FTP-Webzugriffsmodellen. Hier müssen wir also zwei Fabriken erstellen, eine zum Erstellen von HTTP-Produkten und eine zum Erstellen von FTP-Produkten - HTTPConcreteFactory und FTPConcreteFactory . Diese beiden konkreten Fabriken stammen aus einer abstrakten Fabrik - AbstractFactory .
Eine abstrakte Schnittstelle wird verwendet, da die Operationsmethoden für beide Factory-Klassen gleich sind, nur die Implementierung unterschiedlich ist und daher der Client-Code bestimmen kann, welche Factory zur Laufzeit verwendet werden soll. Lassen Sie uns die von jeder Fabrik erstellten Produkte analysieren.
Im Fall eines Protokollprodukts erstellt die HTTP-Betonfabrik entweder das http- oder das https-Protokoll, während die FTP-Betonfabrik das FTP-Protokoll erstellt. Für Hafenprodukte generiert die HTTP-Betonfabrik entweder 80 oder 443 als Hafenprodukt, und die FTP-Fabrik generiert 21 als Hafenprodukt. Und schließlich unterscheidet sich die Crawler-Implementierung, da die Website-Struktur für HTTP und FTP unterschiedlich ist.
Hier hat das erstellte Objekt dieselbe Schnittstelle, während die erstellten konkreten Objekte für jede Fabrik unterschiedlich sind. Angenommen, die Portprodukte wie HTTP-Port, HTTP Secure Port und FTP-Port haben dieselbe Schnittstelle, aber die konkreten Objekte für beide Fabriken sind unterschiedlich. Gleiches gilt auch für Protokoll und Crawler.
Schließlich akzeptiert die Connector-Klasse eine Factory und verwendet diese Factory, um alle Attribute des Connectors basierend auf der Factory-Klasse einzufügen.