αα·αααΆαα αα αααα·ααααΆααΈαα ααααΎαααα»ααααΆααααα αα·ααΈααΆαααααα α½ααααααααααααΎαααΆαααΈαααααααΆαα’αα·ααααααα αααα’αΆα αααααΆαααααΈααΆαααααΆααααααΌααααΌα αααααΆααα»ααα αααααΌαααΆααα αα αααα·ααααΆ α¬α’αααΈααααααααα αα αααα»ααα·αααααααΎαααΆααα·αααααα ααααααα·ααααααααΊαααα αΆαα±ααααΎαα αααΎααααα»ααα αααα»αααααααααα αα·αααΆααΎααα»αα·αα αααααα αΆααααΆααα»ααααΆααααα ααα»αααααα αΌαααααααααααα αα·αααααααα½ααααααααααΌαααΆαααααΎααΆααΆααααααΆαα αα·αα’ααΈααα ααααα·ααα·ααΆαα’αααΈααααααα αΆααααΆααααααα αααα»αααΌαα’ααααΎαα’αααα±ααααααΆα ααααααΎααα½ααααααΆαα Apache Airflow ααααααα αΆαααΈαααααααα’αααα’αΆα αααα·ααααααααΈα’ααΈαααα
αααααα»αααααααααα·ααΆααααα
αα·ααααααααΆα αααΎααα ααααααΌαααΆααααααααΆααααα’ααΈααα ααΈααααΆαααααααααΆααα»αααααα αααααααΆαααα’αααααααααααΆααααα»αα αα»αα ααΆααΆααΆαααα’ααααα·αααΎα’αΆα αααααα ααα»α αααααΆααααΎααααΈααα½αααΆααα·αααααα α¬ααΆααααα»ααααα αααα»αααΆαα·ααΆααααααααΉααααα αΌαααααααΆαααααα αααα»αααααααααααΆααααα½αααΆα ααα»ααααααΆααΏααααΆαααα ααααΆαα·αα’αΆα αα αα½α αααααα ααΆααα·α αα ααΆααααΆααααααααα»αααΆααααααααΊααΆαααααΆαααααααααα CRM ααααααΈααααΆααα ααΉαααααΆαααα·αααααα α αΎααααααΆαααααα αααααααα OLAP α ααΆααΆαααΎαα‘αΎαααΆαααααααα·ααΆαααααααΆαααααΆαααααα»αα αα»αααααααΎαααΆαααααΎααααΆαααααααααααααααΊααΆαααΆαααΆααααα½ααα αααα»ααααααααΆααααΆααααα’αΆααΈαααααα ααΌα ααααα αΎα α’ααααααααααααΆαα·αααΆα ααααααΎααααα·ααααα·ααΆαααΆαα½ααα·ααααααααΈααααααααααΆααΈααΈααΈααααααααα ααΆααααΌα ααΆααΆααα·α ααααααΆαααααΆαααα½αααΆααα·ααααααααΈ API ααΎαα αα ααααΌαααΆααα·ααααΆα ααΆα’αα»αα API αα·αααΆαααααααααααααΎααΆαααα½αααΆααα·ααααααα αΆαααΆα αααΆααα’ααααααα α αΎααα·ααΆαα±ααα ααα ααΆααΊαα αααα»ααα·ααΈααΆα αααΎααααααΆαααααααα ααααΌαα ααααα α αΎααααα½αααααααα αα ααααααα·αα ααααΆα α¬αα·αα’αΆα αααααααΆαααΆαααααααΆαααΎααααΈααααααα»αααΆαααΌααααΌααΆααααααααααα ααα»αααααααααααααααααΆααααααα±ααΆααααα»αααΆαααα½ααα·αααααααααααΆααααΆααααααααΈααααΆαααΆαααααααααα»αααααααααΆαααααααΆαααααααΆααααΆαααα―αααΆααα»αα
αα½αααααααααΆααααΆααααα·ααααααΆααααΈαααα½αααααααα’αΆααΈαααααα αααααααΌααα·ααααααααΈα’ααΈααα α¬α’αααααααΎααΆααααααΆαααααααα ααααααΆαααΆααααα αααα»αααααΈααα ααΎααα·αα’αΆα ααΆαα₯αααα·ααααΎαααα»αα αα»αααΆααΈααΈααΈααααααααααααααααα·ααααααααααΆααα·ααΈαααααα
ααα αΌααααΆαααΈααααααΆαα Apache
ααΎααααΈαααααΎαααααΎαααΆα ETL ααΎαααΆαα αααΎαααααΎ Apache Airflow α ααΎααααΈα±ααα’αααα’αΆαααααα·αααααΆαααα αα αααα·ααααΆααααααααΆααααα αααΆααα’αααΈαααααααααΆααΎααα αααα»αααα·αα α αΎαααΆααΌαα αααα»αααΉααααααΆααα’αααΈααΆαααααΆααα½αα ααα½αα
Apache Airflow ααΊααΆαααα·ααΆα₯ααα·ααααααααααααΌαααΆαααααΎααΎααααΈαααααΎα ααααα·ααααα· αα·ααααα½ααα·αα·αααααααΎαααΆα ETL (Extract-Transform-Loading) αα αααα»α Python α ααααααα·αα αααααα αααα»α Airflow ααΊααΆααααΆα αα acyclic ααΉαααΆα αααα ααα»α ααααΌαααααααΆα ααααΊααΆααααΎαααΆαααΆααααΆαα α αΎααααααααααΆα ααααΊααΆααα αΌαααααΆαααααααααα α¬ααααααΆαα ααααΎαααΆααα½αα’αΆα α α αα»αααΆα Python ααΆαα½αααΆα α¬ααΆα’αΆα ααΆααααααα·ααααΆαααα»αααααααΆαααΈααΆαα α αα»αααΆαααΆα αααΎαααΆαααααααααΆαααα αααα»αααα·ααααααααΆαααα½αα αααααΆααααααα·ααααα·ααΆαααΉαααΆαααααα»α ααΆαααΆαα’αα·αααααααααααααααα½α ααΆααααα ααΆα αααΎααααα’αΆα ααααΎααΆαααΆααααΎαααΆαα ααΆαα’αα·αααααααααααααα½αααΆαα
- ααααα·ααααα·αα - αααααΆααααΆαααααααα·ααααααααΈαααααααα½ααα αααααααα½α α§ααΆα ααα ααΈααΆααΆααα·αααααααα ααΆααααααΆαααα·ααααααα
- α§αααααα αΆαααααααΆ - αααααΆαααααα αΆαααΆαααΎαα‘αΎαααααααΉαααα·ααΆαααααΆααααΆαααα½ααα·αααΉαααΆαααα αΌαααααΆαααααααααααα ααααΌαααΆαααααααααΆααααααααΆα αα;
- ααααα - αααααΆααααααα·ααααα·ααΆαααααα·αααΆα α§ααΆα ααα ααΎααααΈααα½αααΆααα·ααααααααΈααΆααΆαααΌαααααΆααα·αααααα (ααααΎαααα»αααα ααααΈαααααααΆααα);
- α αΎαααΌα αααααα ααΎα
ααΆαα·ααααααααααααα»αααΆααα·αααααΆαααα’α·αα’αααΈ Apache Airflow αα
αααα»αα’ααααααααα ααΆαααααΆαααααααα’αΆα
ααααΌαααΆαααΎα
ααααααααααΆααααΆαααα½αααΆααα·αααααα
ααΆααααΌα ααΎααααΈαααααααΆααααα αΆ ααΎαααααΌααααααααααααααααΎαα’αΆα α
- ααααΆαααα α’ααΈααα
- αααααααα’ααααααααΉαααααΌαα
- ααα½ααα·ααααααααΈαααα»αααα
from airflow.hooks.base_hook import BaseHook
import imaplib
import logging
class IMAPHook(BaseHook):
def __init__(self, imap_conn_id):
"""
IMAP hook Π΄Π»Ρ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ Π΄Π°Π½Π½ΡΡ
Ρ ΡΠ»Π΅ΠΊΡΡΠΎΠ½Π½ΠΎΠΉ ΠΏΠΎΡΡΡ
:param imap_conn_id: ΠΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΡ ΠΊ ΠΏΠΎΡΡΠ΅
:type imap_conn_id: string
"""
self.connection = self.get_connection(imap_conn_id)
self.mail = None
def authenticate(self):
"""
ΠΠΎΠ΄ΠΊΠ»ΡΡΠ°Π΅ΠΌΡΡ ΠΊ ΠΏΠΎΡΡΠ΅
"""
mail = imaplib.IMAP4_SSL(self.connection.host)
response, detail = mail.login(user=self.connection.login, password=self.connection.password)
if response != "OK":
raise AirflowException("Sign in failed")
else:
self.mail = mail
def get_last_mail(self, check_seen=True, box="INBOX", condition="(UNSEEN)"):
"""
ΠΠ΅ΡΠΎΠ΄ Π΄Π»Ρ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡΠ° ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π³ΠΎ ΠΏΠΈΡΡΠΌΠ°,
ΡΠ΄ΠΎΠ²Π»Π΅ΡΠ²ΠΎΡΠ°ΡΡΡΠ΅Π³ΠΎ ΡΡΠ»ΠΎΠ²ΠΈΡΠΌ ΠΏΠΎΠΈΡΠΊΠ°
:param check_seen: ΠΡΠΌΠ΅ΡΠ°ΡΡ ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π΅ ΠΏΠΈΡΡΠΌΠΎ ΠΊΠ°ΠΊ ΠΏΡΠΎΡΠΈΡΠ°Π½Π½ΠΎΠ΅
:type check_seen: bool
:param box: ΠΠ°ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½ΠΈΡ ΡΡΠΈΠΊΠ°
:type box: string
:param condition: Π£ΡΠ»ΠΎΠ²ΠΈΡ ΠΏΠΎΠΈΡΠΊΠ° ΠΏΠΈΡΠ΅ΠΌ
:type condition: string
"""
self.authenticate()
self.mail.select(mailbox=box)
response, data = self.mail.search(None, condition)
mail_ids = data[0].split()
logging.info("Π ΡΡΠΈΠΊΠ΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ ΡΠ»Π΅Π΄ΡΡΡΠΈΠ΅ ΠΏΠΈΡΡΠΌΠ°: " + str(mail_ids))
if not mail_ids:
logging.info("ΠΠ΅ Π½Π°ΠΉΠ΄Π΅Π½ΠΎ Π½ΠΎΠ²ΡΡ
ΠΏΠΈΡΠ΅ΠΌ")
return None
mail_id = mail_ids[0]
# Π΅ΡΠ»ΠΈ ΡΠ°ΠΊΠΈΡ
ΠΏΠΈΡΠ΅ΠΌ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ
if len(mail_ids) > 1:
# ΠΎΡΠΌΠ΅ΡΠ°Π΅ΠΌ ΠΎΡΡΠ°Π»ΡΠ½ΡΠ΅ ΠΏΡΠΎΡΠΈΡΠ°Π½Π½ΡΠΌΠΈ
for id in mail_ids:
self.mail.store(id, "+FLAGS", "\Seen")
# Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌ ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π΅
mail_id = mail_ids[-1]
# Π½ΡΠΆΠ½ΠΎ Π»ΠΈ ΠΎΡΠΌΠ΅ΡΠΈΡΡ ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π΅ ΠΏΡΠΎΡΠΈΡΠ°Π½Π½ΡΠΌ
if not check_seen:
self.mail.store(mail_id, "-FLAGS", "\Seen")
return mail_id
αααααα·ααααΆααΊααα: ααΎαααααΆαα, αααααααα’αααααααααΆααααααααααα»α, ααααα·αααΎααΆαα’ααααααααααα, ααΎααα·αα’αΎααΎαα½αααα αα»αααΆααααααααΌαααΆαααααΎ ααΈαααααα’ααααααααααααΆααα·ααααααααΆααα’ααααα’αααααα»ααα ααααα·αααΎααααα·ααααααΆααααΈααααα α’αααα’αΆα αααα‘ααα’αΆααααα’ααααααΆααα’αα α¬ααααΎαααΆαα’ααααααΈαα½α α αΎααα ααααα ααΎαααα»ααααααααΆααα ααΆααΌαα α’αααΈααααααααΆαααΌα ααααααααα’αΆαααααααΎααΆααα·α αα α
ααΎααααααααα»αααΆααααα½αααΈααα αααααα αααααΆααααΆαααα―αααΆα αα·ααααααΆααααΆαααΆαααα―αααΆααααααααΎαααααααΆααααΈα’ααΈαααα ααααα·ααΈααααα½αααα’αΆα ααααΆααααΈαα ααααα·ααααα·ααααΆα’αΆαααααααΎααΆαααΉαααΆααααααΆαααααΎααααΆαααα»αααΆααααα ααΎααΆαα’αααΈααααααααααΌαααααααααΎααααα αααααααα’αΆαααααααΎαα·α αα ααΆαα ααααα·αααΎα―αααΆαααααΌαααΆαααα½αααααΆαααααα»ααααα»αααααα α’αααα’αΆα ααΆαααα―αααΆαααααΆαααααααα»ααααα»ααα ααααα·αααΎαα·ααααααααααΌαααΆαααα½ααα αααα»ααα·αα·αααα α’αααααααΌαααααα·αα·αααα αα αααα»αααααΈαααααααα»α αααα»αααααααΆααααααΆαα½ααααααααΆαααα½ααα ααΆααααααααΆα ααααααα»αααααΌαααΆαααα ααααααααΆααααΆαααα½α α αΎαα αΆααααααΎαααααΎαααΆαααααα
def download_from_url(self, url, path, chunk_size=128):
"""
ΠΠ΅ΡΠΎΠ΄ Π΄Π»Ρ ΡΠΊΠ°ΡΠΈΠ²Π°Π½ΠΈΡ ΡΠ°ΠΉΠ»Π°
:param url: ΠΠ΄ΡΠ΅Ρ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
:type url: string
:param path: ΠΡΠ΄Π° ΠΏΠΎΠ»ΠΎΠΆΠΈΡΡ ΡΠ°ΠΉΠ»
:type path: string
:param chunk_size: ΠΠΎ ΡΠΊΠΎΠ»ΡΠΊΠΎ Π±Π°ΠΉΡΠΎΠ² ΠΏΠΈΡΠ°ΡΡ
:type chunk_size: int
"""
r = requests.get(url, stream=True)
with open(path, "wb") as fd:
for chunk in r.iter_content(chunk_size=chunk_size):
fd.write(chunk)
def download_mail_href_attachment(self, mail_id, path):
"""
ΠΠ΅ΡΠΎΠ΄ Π΄Π»Ρ ΡΠΊΠ°ΡΠΈΠ²Π°Π½ΠΈΡ ΡΠ°ΠΉΠ»Π° ΠΏΠΎ ΡΡΡΠ»ΠΊΠ΅ ΠΈΠ· ΠΏΠΈΡΡΠΌΠ°
:param mail_id: ΠΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡ ΠΏΠΈΡΡΠΌΠ°
:type mail_id: string
:param path: ΠΡΠ΄Π° ΠΏΠΎΠ»ΠΎΠΆΠΈΡΡ ΡΠ°ΠΉΠ»
:type path: string
"""
response, data = self.mail.fetch(mail_id, "(RFC822)")
raw_email = data[0][1]
raw_soup = raw_email.decode().replace("r", "").replace("n", "")
parse_soup = BeautifulSoup(raw_soup, "html.parser")
link_text = ""
for a in parse_soup.find_all("a", href=True, text=True):
link_text = a["href"]
self.download_from_url(link_text, path)
ααΌαααΊααΆαααα ααΌα ααααααΆααααΎαααααααΌαααΆαααΆαααααααααααααα αααα»αααΉαααααΆααα’αααα’αααΈαααααΆααααααααα imap_conn_id α Apache Airflow αααααΆαα»ααααΆαααΆααααααααααΆααααααΆαα (α αΌα ααΆααααααααΆαα α’αΆααααααΆα αα·ααααΆαααΆαααααααααααααα) αααα’αΆα α αΌαααααΎααΆααααα§ααααααααααααααα’ααααα αααααΎαααΎα ααΆααααααααααααΆααααααΆααααΎααα ααΌα ααα
α§αααααα αΆαααααααΆααΎααααΈαααα αΆααα·αααααα
αααααΆαααΎαααΉαααΈααααααααΆαα αα·αααα½ααα·ααααααααΈαααα»ααααα½α α αΎα α₯α‘αΌααααααΎαα’αΆα αααααα§αααααα αΆαααααααΆααΎααααΈαααα αΆααα½αααα αααα»αααααΈαααααααα»α ααΆαα·αααααΎαααΆααααααα»αααΆααααααααααα·ααααα·ααααααΆαααααααΉαααααΎαααΆααα·ααααααααααα·αααΎααΆα ααΈαααααααααΎαααΆαααααααααααααΎαααΆααααααα’ααααΎαα·αααααααααααα½αααΆαααΈαααα»ααα αα½αααΆαααα·αααααααααααΆαααααααααΈααααααααααααα (API, telephony αααααΆαααααααΆαααα)ααα)α αααα»αααΉααααααα±ααα’αααααΌαα§ααΆα ααααα½αα α’αααααααΎααααΆααααααΈααΆααααα αΆααααα½ααα αααα»ααααααααα CRM α αΎαααΎααα αααα·αααΉαα’αααΈ UUID ααααααΆααα αααααΆαααα αα αααααααΆααΆαααα½ααα·ααααααααΈααΌααααα SIP ααΎαααΉαααα½αααΆαα α ααΌααααααααααααΆααααΆαα½α UUID ααααααΆ ααα»ααααααΎαααΉααα·αα’αΆα αααααΆαα»α αα·αααααΎααααΆααααΆααΆαααααΉαααααΌααααααα αααα»ααααα αΆαααααα ααΆααΆααΆαααααΆαααααααααΌαα αα αΆαα’αααΈααΆαα’αΆααααααααα·αααααα ααΆαα·αααααααα·αααΎαα½αααααααΈαααααααααααααααΆα ααΆααΆααα·αααΆαα ααΆαααααααΊααΆαα·ααΆαααΆααα·ααααααααααΆααααΎααααΈαααααΆααΆαααααΉαααααΌααααα·αααααα ααα»αααααααα»αααααΈααααααΆα αΆαααΆα αα ααΆα/α αΆα α αΎαααΆααα»αα ααααΎααααΈααΆααααΆααααααΆα αααα·αααα ααα»αααααα
ααΌα αααα α§αααααααααααΎαααΉαααΎαααααΎαααΆααααααααΆαααααααααΆααααααααΆα αα ααααα·αααΎααΆαααααααΆαααααΈααα αααα»ααααα»ααα α αΎααααααααΆααααααααΆαααΈαα»αααΆαα·αααΆααααααααααααα
from airflow.sensors.base_sensor_operator import BaseSensorOperator
from airflow.utils.decorators import apply_defaults
from my_plugin.hooks.imap_hook import IMAPHook
class MailSensor(BaseSensorOperator):
@apply_defaults
def __init__(self, conn_id, check_seen=True, box="Inbox", condition="(UNSEEN)", *args, **kwargs):
super().__init__(*args, **kwargs)
self.conn_id = conn_id
self.check_seen = check_seen
self.box = box
self.condition = condition
def poke(self, context):
conn = IMAPHook(self.conn_id)
mail_id = conn.get_last_mail(check_seen=self.check_seen, box=self.box, condition=self.condition)
if mail_id is None:
return False
else:
return True
ααΎαααα½α αα·αααααΎααααΆαααα·αααααα
ααΎααααΈααα½αααΆα αα·αααααΎαααΆααα·αααααα α’αααα’αΆα αααααααααα·ααααα·ααααΆα ααααα‘αα α’αααα’αΆα ααααΎα§ααααααααααααααα½α ααΆααααα α αααααΆααααααα·ααααΆαα ααααΆααΏαααΌα ααΆα - ααΎααααΈαααα·ααααααααΈαααα»ααα ααΆα§ααΆα ααα αααα»αααααΎα±αα PythonOperator αααααααΆα
from airflow.models import DAG
from airflow.operators.python_operator import PythonOperator
from airflow.sensors.my_plugin import MailSensor
from my_plugin.hooks.imap_hook import IMAPHook
start_date = datetime(2020, 4, 4)
# Π‘ΡΠ°Π½Π΄Π°ΡΡΠ½ΠΎΠ΅ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π³ΡΠ°ΡΠ°
args = {
"owner": "example",
"start_date": start_date,
"email": ["[email protected]"],
"email_on_failure": False,
"email_on_retry": False,
"retry_delay": timedelta(minutes=15),
"provide_context": False,
}
dag = DAG(
dag_id="test_etl",
default_args=args,
schedule_interval="@hourly",
)
# ΠΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅ΠΌ ΡΠ΅Π½ΡΠΎΡ
mail_check_sensor = MailSensor(
task_id="check_new_emails",
poke_interval=10,
conn_id="mail_conn_id",
timeout=10,
soft_fail=True,
box="my_box",
dag=dag,
mode="poke",
)
# Π€ΡΠ½ΠΊΡΠΈΡ Π΄Π»Ρ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ Π΄Π°Π½Π½ΡΡ
ΠΈΠ· ΠΏΠΈΡΡΠΌΠ°
def prepare_mail():
imap_hook = IMAPHook("mail_conn_id")
mail_id = imap_hook.get_last_mail(check_seen=True, box="my_box")
if mail_id is None:
raise AirflowException("Empty mailbox")
conn.download_mail_href_attachment(mail_id, "./path.zip")
prepare_mail_data = PythonOperator(task_id="prepare_mail_data", default_args=args, dag=dag, python_callable= prepare_mail)
# ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΎΡΡΠ°Π»ΡΠ½ΡΡ
Π²Π΅ΡΡΠΈΠ½ Π³ΡΠ°ΡΠ°
...
# ΠΠ°Π΄Π°Π΅ΠΌ ΡΠ²ΡΠ·Ρ Π½Π° Π³ΡΠ°ΡΠ΅
mail_check_sensor >> prepare_mail_data
prepare_data >> ...
# ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΎΡΡΠ°Π»ΡΠ½ΡΡ
ΠΏΠΎΡΠΎΠΊΠΎΠ² ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ
ααααα·ααΈααα ααααα·αααΎαααα»αααααΆααΈαααααααααα’αααααααΆααα ααΎ mail.ru αααα’αααααΉααα·αα’αΆα αααααααα’ααααααΆααααααΆααα α’αααααααΎααΆααΎαα αααααααα ααααΆα 2016 αα½αααααΆααααααΆααΆααΉαααααΆαααΆ ααα»ααααααΆαααααααααΆαααααΆααααααΌααααα·ααααααα½αααα αααα»αααΆααααααααΆααααα αΆαααααααααααΎαααααΆα ααααα‘αααα½ααααααΆααα’ααααα αΆαααΆα α αα·ααααα αααααααααααΆααα’ααααα αΆαααΆα ααα αααα»αα ααα»α αααααΆαααααααΆααααα»αααα ααΌα ααααααΆαααα’αααα αα·αααααααααα αΆαααΆα ααααααΆααααΆαααααααα αα αααα»αααααΈαααααααα»α ααΊααααΆαααα (UNSEEN) α αΌααα αααα»αααα―αααΆααααα
ααα»ααα ααΎαααΆαααααΆααααΌα
ααΆααααααα ααΎααα·αα·αααααΎαααΆααΎααΆαα’ααααααααΈααααααααααααααααα¬α’αα ααΎααΆα αααααΎαααΆααααααααααΆααααααααΎαααααααΆααααΈα’ααααα
α»ααααααα
αα
αααααα
ααα»α
α
α»αααααα ααΆααααΌαααΆααα»αα
ααααΆααααααΆααααααΉαααααΌαααΆααααααΆ αα·ααααααααΈααααααΆαααΉαααααΌαααΆααααα’αΆα αα·αααααΎαααΆα α αΎαααΆαααααα α’αααΈαααΉααααααα
αααααααααααααααααααααααΎαααΆα ETL ααα»αααααααααΊα α½ααα
α αΎαα αα·ααΆαααΆαααα’αααααα ααααα·αααΎααΆααααααΆαα½αα±ααα
αΆααα’αΆαααααα αα·αααΆααααααααα ααααααα»αααΉααααααααααΆααα’αααΈαααααααααΆα ETL αα·αααααααααααα½ααααααααΈαααΆααααααΆαα Apache Airflow α
ααααα: www.habr.com