Авторът на статията, чийто превод публикуваме днес, споделя, че целта му е да разкаже за разработването на уеб скрапер на Python с използване на Selenium, който извършва търсене на цени за самолетни билети. При търсенето на билети се използват гъвкави дати (+- 3 дни от посочените дати). Скраперът запазва резултатите от търсенето в Excel файл и изпраща имейл на този, който го е стартирал, с обща информация за това, което е успял да намери. Задачата на този проект е да помага на пътешествениците в търсенето на най-изгодните предложения.
Ако в хода на материала се почувствате изгубени — погледнете на статия.
Какво ще търсим?
Свободни сте да използвате описваната тук система така, както желаете. Аз, например, я използвах за търсене на уикенд турове и билети за родния ми град. Ако сте сериозно настроени да търсите изгодни билети — можете да стартирате скрипта на сървър (обикновен , за 130 лева на месец, напълно подходящ е за това) и да настроите да се стартира веднъж или два пъти на ден. Резултатите от търсенето ще пристигат при вас по имейл. Освен това, препоръчвам да настроите всичко така, че скриптът да запазва Excel файл с резултатите от търсенето в папка в Dropbox, което ще ви позволи да преглеждате подобни файлове откъдето и да сте и по всяко време.

Все още не съм намерил тарифи с грешки, но вярвам, че това е възможно.
При търсенето, както вече беше споменато, се използва "гъвкава дата", скриптът намира предложения, които са в рамките на три дни от зададените дати. Въпреки че при стартиране на скрипта се изпълнява търсене само за едно направление, не е трудно да се доработи, за да може да събира данни за няколко направления на полети. С негова помощ можете дори да търсите грешни тарифи, подобни находки могат да се окажат доста интересни.
Защо е нужен поредният уеб скрапер?
Когато за първи път започнах да се занимавам с уеб-скрапинг, честно казано, не ми беше особено интересно. Исках да работя по повече проекти в сферата на прогнозиране, финансов анализ и, може би, в областта на анализа на емоционалната окраска на текстове. Но се оказа, че е много интересно – да разбера как да създам програма, която събира данни от уебсайтове. Докато се задълбочавах в темата, осъзнах, че именно уеб-скрапингът е "двигателят" на интернет.
Може би ще решите, че това е твърде смело твърдение. Но помислете за факта, че компанията Google започна с уеб-скрапера, който Лари Пейдж създаде с помощта на Java и Python. Роботите на Google проучват и продължават да проучват интернет, опитвайки се да предоставят на потребителите си най-добрите отговори на техните въпроси. Уеб-скрапингът има безброй приложения, и дори ако в сферата на Data Science ви интересува нещо друго, ще ви трябват някои умения за скрапинг, за да си набавите данни за анализ.
Някои от прилаганите тук техники намерих в удивителната за уеб-скрапинг, която наскоро придобих. В нея можете да намерите много прости примери и идеи за практическо приложение на наученото. Освен това, там има много интересна глава относно заобикалянето на проверките reCaptcha. За мен това беше новина, тъй като не знаех, че съществуват специални инструменти и дори цели услуги за решаване на подобни задачи.
Обичате ли да пътувате?!
На простия и доста безобиден въпрос, поставен в заглавието на този раздел, често може да се чуе положителен отговор, снабден с две-три истории от пътувания на този, на когото е зададен. Повечето от нас биха се съгласили, че пътуванията са прекрасен начин да се потопите в нови културни среди и да разширите собствените си хоризонти. Въпреки това, ако зададете на някого въпроса дали му харесва да търси самолетни билети, съм сигурен, че отговорът му вече няма да бъде толкова положителен. В този контекст Python идва на помощ.
Първата задача, която трябва да решим, за да създадем система за търсене на информация за авиабилети, е да изберем подходяща платформа, от която да вземем информация. Решението на тази задача не беше лесно, но в крайна сметка избрах услугата Kayak. Изпробвах услуги като Momondo, Skyscanner, Expedia и някои други, но механизмите за защита от ботове на тези ресурси бяха непробиваеми. След няколко опита, в които, опитвайки се да убедя системите, че съм човек, трябваше да се справям със светофари, пешеходни пътеки и велосипеди, реших, че Kayak е най-подходящ за мен, дори и тук, ако натоваря страниците твърде бързо, също започват проверки. Успях да настроя бота да изпраща заявки към сайта на интервали от 4 до 6 часа, и всичко работеше нормално. Периодично имам затруднения и при работата с Kayak, но ако започнат да ви досаждат с проверки, трябва или да ги разрешите ръчно, а след това да пуснете бота, или да изчакате няколко часа, след които проверките трябва да спрат. Ако е нужно, можете да адаптирате кода за друга платформа, а ако го направите, не се колебайте да споделите в коментарите.
Ако току-що започвате да се запознавате с уеб-скрапинга и не знаете защо някои уебсайтове се борят с него, преди да започнете с първия си проект в тази област — оказвайте си услуга и потърсете в Google материали по отношение на „етиката на уеб-скрапинга“. Вашите експерименти може да приключат по-бързо, отколкото си мислите, ако се занимавате с уеб-скрапинг безразсъдно.
Започване на работа
Ето общ преглед на това, което ще се случи в кода на нашия уеб-скрапер:
- Импорт на необходимите библиотеки.
- Отваряне на таб в Google Chrome.
- Викане на функция, която стартира бота, предавайки му градовете и датите, които ще се използват при търсенето на билети.
- Тази функция извлича първите резултати от търсенето, сортирани по критерия най-атрактивни (best), и натиска бутона за зареждане на допълнителни резултати.
- Още една функция събира данни от цялата страница и връща рамка с данни.
- Двата предишни етапа се извършват, използвайки типове сортиране по цена на билетите (евтино) и по бързина на полета (най-бързо).
- На потребителя на скрипта се изпраща имейл, съдържащ кратко резюме на цените на билетите (най-евтините билети и средната цена), а рамката с данни, сортирани по трите споменати критерия, се запазва в Excel файл.
- Всички описани по-горе действия се извършват в цикъл през зададен интервал от време.
Трябва да се отбележи, че всеки проект Selenium започва с уеб драйвера. Използвам , работя с Google Chrome, но съществуват и други опции. Популярни са също PhantomJS и Firefox. След като заредите драйвера, трябва да го поставите в съответната папка, след което подготовката за използването му приключва. В първите редове на нашия скрипт се отваря нов таб в Chrome.
Имайте предвид, че в разказа си не се опитвам да открия нови хоризонти в търсенето на изгодни оферти за самолетни билети. Съществуват много по-усъвършенствани техники за търсене на подобни предложения. Целта ми е да предложа на читателите на този материал прост, но приложим на практика начин за решаване на тази задача.
Ето кодът, за който говорехме по-горе.
from time import sleep, strftime
from random import randint
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import smtplib
from email.mime.multipart import MIMEMultipart
# Използвайте тук вашия път към chromedriver!
chromedriver_path = 'C:/{YOUR PATH HERE}/chromedriver_win32/chromedriver.exe'
driver = webdriver.Chrome(executable_path=chromedriver_path) # С тази команда се отваря прозорец на Chrome
sleep(2) В началото на кода можете да видите командите за импорт на пакетите, които се използват в целия наш проект. Така, randint се използва, за да накара бота да "заспива" за случайно число секунди, преди да започне нова операция по търсене. Обикновено без това не минава нито един бот. Ако стартирате горепосочения код, ще се отвори прозорец на Chrome, който ботът ще използва за работа с сайтове.
Ще проведем малък експеримент и ще отворим сайта kayak.com в нов прозорец. Ще изберем града, от който планираме да летим, и града, в който искаме да отидем, както и датите на полетите. При избора на дати ще проверим да се използва диапазон от +-3 дни. Написах код, като взех предвид какво предоставя сайтът в отговор на подобни запитвания. Ако например имате нужда да търсите билети само за зададени дати, е вероятно да трябва да направите допълнителна работа по кода на бота. Когато говоря за кода, давам съответните обяснения, но ако почувствате, че сте се объркали — дайте ми знак.
Сега натискаме бутона за стартиране на търсенето и гледаме връзката в адресната лента. Тя трябва да прилича на връзката, която използвам в примера по-долу, там, където е обявена променливата kayak, която съхранява URL адреса, и се използва методът get на уеб-драйвера. След натискане на бутона за търсене, на страницата трябва да се появят резултати.
![]()
Когато използвах командата get повече от две-три пъти в рамките на няколко минути, ми предложиха да премина проверката с reCaptcha. Тази проверка може да се премине ръчно и да продължите експериментите, докато системата реши да организира нова проверка. Когато тествах скрипта, получих впечатление, че първата сесия на търсене винаги минава без проблеми, затова ако искате да експериментирате с кода, ще трябва периодично ръчно да преминавате проверката и да оставяте кода да работи, използвайки дълги интервали между сесиите на търсене. И наистина, ако помислите, на човек едва ли му трябват данни за цените на билетите, получени с 10-минутни интервали между операциите на търсене.
Работа със страницата с помощта на XPath
И така, отворихме прозорец и заредихме сайта. За да получим информация за цените и друга информация, трябва да използваме технологията XPath или CSS селектори. Реших да се спра на XPath и не почувствах нужда от използването на CSS селектори, но е напълно възможно да работите и по този начин. Навигацията по страницата с помощта на XPath може да се окаже трудна задача, и дори ако използвате методите, които описах в в статията, където копирах съответните идентификатори от кода на страницата, осъзнах, че всъщност това не е най-оптималният начин за достъп до необходимите елементи. Между другото, в книгата можете да намерите отлично описание на основите на работата с уеб страници с помощта на XPath и CSS селектори. Ето как изглежда съответният метод на уеб драйвера.
![]()
И така, продължаваме работата по бота. Ще се възползваме от възможностите на програмата за избор на най-евтиния билет. На следващото изображение кодът на XPath селектора е обозначен с червено. За да видите кода, трябва да щракнете с десния бутон на мишката върху интересуващия ви елемент на страницата и в появилото се меню да изберете командата Преглед на кода (Inspect). Тази команда може да бъде извикана за различни елементи на страницата, кодът на които ще бъде изведен и подчертан в прозореца за преглед на кода.

Преглед на кода на страницата
За да намерите потвърждение на моите разсъждения относно недостатъците на копирането на селектори от кода, обърнете внимание на следните особености.
Ето какво се получава при копиране на кода:
//*[@id="wtKI-price_aTab"]/div[1]/div/div/div[1]/div/span/spanЗа да копирате нещо подобно, трябва да щракнете с десния бутон на мишката върху интересуващата ви част от кода и да изберете в появилото се меню командата Copy > Copy XPath.
Ето какво използвах за определяне на бутона Cheapest:
cheap_results = '//*[@data-code = "price"]' 
Команда Copy > Copy XPath
Съвсем очевидно е, че вторият вариант изглежда много по-прост. При използването му се извършва търсене на елемента a, който има атрибут data-code, равен на price. При използването на първия вариант се извършва търсене на елемента ид който е равен на wtKI-price_aTab, като XPath пътят до елемента изглежда така. /div[1]/div/div/div[1]/div/span/spanТази XPath заявка към страницата ще свърши работа, но само веднъж. В момента мога да кажа, че ид ще се промени при следващо зареждане на страницата. Последователността от символи wtKI се променя динамично при всяко зареждане на страницата, в резултат на което кодът, в който тя се използва, след следващото презареждане на страницата ще бъде безполезен. Затова отделете малко време да разберете XPath. Тези знания ще ви бъдат полезни.
Но трябва да се отбележи, че копирането на XPath селектори може да бъде полезно при работа с достатъчно прости сайтове, и ако това ви удовлетворява, няма нищо лошо в това.
Сега да помислим как да постигнем всичките резултати от търсенето в няколко реда, в рамките на списък. Много е просто. Всеки резултат се намира в обект с клас resultWrapper. Зареждането на всички резултати може да се извърши в цикъл, подобен на този, който ще бъде показан по-долу.
Трябва да отбележа, че ако разбирате това, което току-що казах, то вие лесно трябва да разберете повечето от кода, който ще разгледаме. В хода на работата на този код, към това, от което се нуждаем (по същество - елемент, в който е обвързан резултатът), ние се обръщаме с помощта на определен механизъм за указване на пътя (XPath). Това се прави, за да се получи текстът на елемента и да се постави в обект, от който може да се извлекат данни (първо се използва flight_containers, след това - flights_list).

Извеждат се първите три реда и можем ясно да видим всичко, от което се нуждаем. Въпреки това, имаме и по-интересни начини за получаване на информация. Ние трябва да извличаме данни от всеки елемент поотделно.
Нека да започнем!
Най-лесно е да напишем функция за зареждане на допълнителни резултати, така че да започнем с нея. Искам да максимизирам броя на полетите, информация за които получава програмата, без да предизвиквам съмнения у услугата, които водят до проверка, затова кликвам на бутона 'Load more results' всеки път, когато се зарежда страницата. В този код внимание заслужава блокът try, който добавих, защото понякога бутонът не се зарежда нормално. Ако и вие се сблъскате с това, просто закоментирайте повикванията на тази функция в кода на функцията start_kayak, която ще разгледаме по-долу.
# Загрузка большего количества результатов для того, чтобы максимизировать объём собираемых данных
def load_more():
try:
more_results = '//a[@class = "moreButton"]'
driver.find_element_by_xpath(more_results).click()
# Вывод этих заметок в ходе работы программы помогает мне быстро выяснить то, чем она занята
print('sleeping.....')
sleep(randint(45,60))
except:
passСега, след дългия анализ на тази функция (понякога мога да се увлека), сме готови да обявим функция, която ще се занимава с извличането на данни от страницата.
Вече събрах голяма част от необходимото в следната функция, наречена page_scrape. Понякога данните, върнати за етапите на пътуването, се оказват комбинирани, за да ги разделя използвам прост метод. Например, когато за първи път използвам променливите section_a_list и section_b_list. Нашата функция връща рамка с данни flights_df, което ни позволява да разделим резултатите, получени чрез различни методи за сортиране на данни, а по-късно - да ги комбинираме.
def page_scrape():
"""This function takes care of the scraping part"""
xp_sections = '//*[@class="section duration"]'
sections = driver.find_elements_by_xpath(xp_sections)
sections_list = [value.text for value in sections]
section_a_list = sections_list[::2] # так мы разделяем информацию о двух полётах
section_b_list = sections_list[1::2]
# Если вы наткнулись на reCaptcha, вам может понадобиться что-то предпринять.
# О том, что что-то пошло не так, вы узнаете исходя из того, что вышеприведённые списки пусты
# это выражение if позволяет завершить работу программы или сделать ещё что-нибудь
# тут можно приостановить работу, что позволит вам пройти проверку и продолжить скрапинг
# я использую тут SystemExit так как хочу протестировать всё с самого начала
if section_a_list == []:
raise SystemExit
# Я буду использовать букву A для уходящих рейсов и B для прибывающих
a_duration = []
a_section_names = []
for n in section_a_list:
# Получаем время
a_section_names.append(''.join(n.split()[2:5]))
a_duration.append(''.join(n.split()[0:2]))
b_duration = []
b_section_names = []
for n in section_b_list:
# Получаем время
b_section_names.append(''.join(n.split()[2:5]))
b_duration.append(''.join(n.split()[0:2]))
xp_dates = '//div[@class="section date"]'
dates = driver.find_elements_by_xpath(xp_dates)
dates_list = [value.text for value in dates]
a_date_list = dates_list[::2]
b_date_list = dates_list[1::2]
# Получаем день недели
a_day = [value.split()[0] for value in a_date_list]
a_weekday = [value.split()[1] for value in a_date_list]
b_day = [value.split()[0] for value in b_date_list]
b_weekday = [value.split()[1] for value in b_date_list]
# Получаем цены
xp_prices = '//a[@class="booking-link"]/span[@class="price option-text"]'
prices = driver.find_elements_by_xpath(xp_prices)
prices_list = [price.text.replace('$','') for price in prices if price.text != '']
prices_list = list(map(int, prices_list))
# stops - это большой список, в котором первый фрагмент пути находится по чётному индексу, а второй - по нечётному
xp_stops = '//div[@class="section stops"]/div[1]'
stops = driver.find_elements_by_xpath(xp_stops)
stops_list = [stop.text[0].replace('n','0') for stop in stops]
a_stop_list = stops_list[::2]
b_stop_list = stops_list[1::2]
xp_stops_cities = '//div[@class="section stops"]/div[2]'
stops_cities = driver.find_elements_by_xpath(xp_stops_cities)
stops_cities_list = [stop.text for stop in stops_cities]
a_stop_name_list = stops_cities_list[::2]
b_stop_name_list = stops_cities_list[1::2]
# сведения о компании-перевозчике, время отправления и прибытия для обоих рейсов
xp_schedule = '//div[@class="section times"]'
schedules = driver.find_elements_by_xpath(xp_schedule)
hours_list = []
carrier_list = []
for schedule in schedules:
hours_list.append(schedule.text.split('n')[0])
carrier_list.append(schedule.text.split('n')[1])
# разделяем сведения о времени и о перевозчиках между рейсами a и b
a_hours = hours_list[::2]
a_carrier = carrier_list[1::2]
b_hours = hours_list[::2]
b_carrier = carrier_list[1::2]
cols = (['Out Day', 'Out Time', 'Out Weekday', 'Out Airline', 'Out Cities', 'Out Duration', 'Out Stops', 'Out Stop Cities',
'Return Day', 'Return Time', 'Return Weekday', 'Return Airline', 'Return Cities', 'Return Duration', 'Return Stops', 'Return Stop Cities',
'Price'])
flights_df = pd.DataFrame({'Out Day': a_day,
'Out Weekday': a_weekday,
'Out Duration': a_duration,
'Out Cities': a_section_names,
'Return Day': b_day,
'Return Weekday': b_weekday,
'Return Duration': b_duration,
'Return Cities': b_section_names,
'Out Stops': a_stop_list,
'Out Stop Cities': a_stop_name_list,
'Return Stops': b_stop_list,
'Return Stop Cities': b_stop_name_list,
'Out Time': a_hours,
'Out Airline': a_carrier,
'Return Time': b_hours,
'Return Airline': b_carrier,
'Price': prices_list})[cols]
flights_df['timestamp'] = strftime("%Y%m%d-%H%M") # время сбора данных
return flights_df Опитах се да именувам променливите по такъв начин, че кодът да бъде разбираем. Помнете, че променливите, започващи с a се отнасят до първия етап от процеса, а b — до втория. Преминаваме към следващата функция.
Помощни механизми
Сега разполагаме с функция, която позволява да се зареждат допълнителни резултати от търсенето и функция за обработка на тези резултати. Тази статия можеше да завърши тук, тъй като тези две функции предоставят всичко необходимо за скрейпинг на страници, които могат да се отварят самостоятелно. Но все още не разгледахме някои помощни механизми, за които споменахме по-горе. Например — това е код за изпращане на електронни писма и още някои неща. Всичко това може да бъде намерено във функцията start_kayak, която сега ще разгледаме.
За работата на тази функция са необходими данни за градовете и датите. Тя, като използва тези данни, формира линк в променливата kayak, която се използва за преход към страницата, на която ще бъдат резултатите от търсенето, сортирани по най-доброто им съответствие на запитването. След първата сесия на скрейпинг, ще се заемем с цените, намиращи се в таблицата в горната част на страницата. По-точно, ще намерим минималната цена на билета и средната цена. Всичко това, заедно с предсказанието, предоставяно от сайта, ще бъде изпратено по електронна поща. На страницата съответната таблица трябва да бъде в горния ляв ъгъл. Работата с тази таблица, между другото, може да предизвика грешка при търсене с точни дати, тъй като в такъв случай таблицата на страницата не се показва.
def start_kayak(city_from, city_to, date_start, date_end):
"""Кодове на градове - това са IATA кодовете!
Формат на датата - YYYY-MM-DD"""
kayak = ('https://www.kayak.com/flights/' + city_from + '-' + city_to +
'/' + date_start + '-flexible/' + date_end + '-flexible?sort=bestflight_a')
driver.get(kayak)
sleep(randint(8,10))
# понякога се появява изскачащ прозорец, за проверка на това и неговото затваряне може да се използва блок try
try:
xp_popup_close = '//*[contains(@id,"dialog-close") and contains(@class,"Button-No-Standard-Style close ")]'
driver.find_elements_by_xpath(xp_popup_close)[5].click()
except Exception as e:
pass
sleep(randint(60,95))
print('зареждане на повече.....')
# load_more()
print('започване на първото извличане.....')
df_flights_best = page_scrape()
df_flights_best['sort'] = 'best'
sleep(randint(60,80))
# Да вземем най-ниската цена от таблицата, разположена в горната част на страницата
matrix = driver.find_elements_by_xpath('//*[contains(@id,"FlexMatrixCell")]')
matrix_prices = [price.text.replace('$','') for price in matrix]
matrix_prices = list(map(int, matrix_prices))
matrix_min = min(matrix_prices)
matrix_avg = sum(matrix_prices)/len(matrix_prices)
print('превключване към най-евтините резултати.....')
cheap_results = '//*[contains(@data-code = "price")]'
driver.find_element_by_xpath(cheap_results).click()
sleep(randint(60,90))
print('зареждане на повече.....')
# load_more()
print('започване на второто извличане.....')
df_flights_cheap = page_scrape()
df_flights_cheap['sort'] = 'cheap'
sleep(randint(60,80))
print('превключване към най-бързите резултати.....')
quick_results = '//*[contains(@data-code = "duration")]'
driver.find_element_by_xpath(quick_results).click()
sleep(randint(60,90))
print('зареждане на повече.....')
# load_more()
print('започване на третото извличане.....')
df_flights_fast = page_scrape()
df_flights_fast['sort'] = 'fast'
sleep(randint(60,80))
# Запазване на новия фрейм в Excel файл, чието име отразява градовете и датите
final_df = df_flights_cheap.append(df_flights_best).append(df_flights_fast)
final_df.to_excel('search_backups/{}_flights_{}-{}_from_{}_to_{}.xlsx'.format(strftime("%Y%m%d-%H%M"),
city_from, city_to,
date_start, date_end), index=False)
print('запазен df.....')
# Може да се следи как прогнозата, предоставена от сайта, съответства на реалността
xp_loading = '//*[contains(@id,"advice")]'
loading = driver.find_element_by_xpath(xp_loading).text
xp_prediction = '//*[contains(@class="info-text")]'
prediction = driver.find_element_by_xpath(xp_prediction).text
print(loading+'n'+prediction)
# понякога в променливата loading се оказва този ред, който по-късно води до проблеми с изпращането на имейл
# ако това се случи - заменяме я с "Не съм сигурен"
weird = '¯_(ツ)_/¯'
if loading == weird:
loading = 'Не съм сигурен'
username = 'YOUREMAIL@hotmail.com'
password = 'YOUR PASSWORD'
server = smtplib.SMTP('smtp.outlook.com', 587)
server.ehlo()
server.starttls()
server.login(username, password)
msg = ('Subject: Flight Scrapernn
Най-евтин полет: {}nСредна цена: {}nnПрепоръка: {}nnКрай на съобщението'.format(matrix_min, matrix_avg, (loading+'n'+prediction)))
message = MIMEMultipart()
message['From'] = 'YOUREMAIL@hotmail.com'
message['to'] = 'YOUROTHEREMAIL@domain.com'
server.sendmail('YOUREMAIL@hotmail.com', 'YOUROTHEREMAIL@domain.com', msg)
print('изпратен имейл.....')Тествах този скрипт, използвайки акаунт в Outlook (hotmail.com). Не съм проверявал как работи с акаунт в Gmail, който е доста популярен, но има много възможности. Ако използвате акаунт в Hotmail, просто въведете вашите данни в кода, за да заработи.
Ако искате да разберете какво точно се изпълнява в отделните участъци от кода на тази функция, можете да ги копирате и да експериментирате с тях. Експериментацията с кода е единственият начин да го разберете напълно.
Готова система
Сега, след като направихме всичко, за което говорихме, можем да създадем прост цикъл, в който да извикваме нашите функции. Скриптът пита потребителя за данни за градовете и датите. Когато тествате с постоянно рестартиране на скрипта, вероятно няма да искате да въвеждате тези данни всеки път ръчно, затова можете временно да коментирате съответните редове и да разкоментирате тези, които имат нужните на скрипта данни.
city_from = input('От кой град? ')
city_to = input('Къде? ')
date_start = input('Търсите около коя дата на заминаване? Моля, използвайте формат YYYY-MM-DD ')
date_end = input('Кога да върнете? Моля, използвайте формат YYYY-MM-DD ')
# city_from = 'LIS'
# city_to = 'SIN'
# date_start = '2019-08-21'
# date_end = '2019-09-07'
for n in range(0,5):
start_kayak(city_from, city_to, date_start, date_end)
print('Итерация {} завърши @ {}'.format(n, strftime("%Y%m%d-%H%M")))
# Чакаме 4 часа
sleep(60*60*4)
print('Почивката свърши.....') Ето как изглежда тестовото стартиране на скрипта.

Тестово стартиране на скрипта
Резюме
Ако сте стигнали до този момент — поздравления! Сега имате работещ уеб скрапер, въпреки че вече виждам много възможности за подобрение. Например, може да го интегрирате с Twilio, за да изпраща текстови съобщения вместо имейли. Можете да използвате VPN или нещо друго, за да получавате резултати от множество сървъри едновременно. Има и периодично възникваща проблема с потвърждението на потребителя на сайта, че е човек, но и този проблем може да се реши. Във всеки случай, сега имате основа, която можете, ако желаете, да разширявате. Например, да изпращате Excel файл на потребителя като прикачен файл в имейл.
Само регистрирани потребители могат да участват в анкетата. , моля.
Използвате ли технологии за уеб скрапинг?
Да
Не
Гласували 8 потребители. Въздържал се 1 потребител.
Източник: habr.com
