CoderCastrov logo
CoderCastrov
Covid-19 - Ковид-19

Парсинг для благих целей: написание превентивного электронного письма о COVID-19

Парсинг для благих целей: написание превентивного электронного письма о COVID-19
просмотров
9 мин чтение
#Covid-19 - Ковид-19

Избегайте страха и разочарования от навязчивой проверки результатов теста на COVID-19. Позвольте компьютеру делать это за вас!

На прошлой неделе я почувствовал себя немного нездоровым. Казалось, что ничего серьезного, но у меня была слабость и я чувствовал себя не в своей тарелке. У меня были проблемы с дыханием? Может быть, может быть и нет, и, возможно (вероятно), все это было только в моей голове. Кажется, в эти дни я просыпаюсь посреди ночи, уверенный, что у меня COVID-19, только чтобы на следующее утро почувствовать себя отлично. Я никогда не был настолько обеспокоен, чтобы сделать тест. В конце концов, я соблюдаю социальное дистанцирование, всегда ношу маску в общественных местах, избегаю закрытых помещений (если это возможно) и регулярно мою руки. Однако в последнее время я часто посещал больницы и другие медицинские учреждения, чтобы помочь своей семье, поэтому существует возможность заражения.

Если вы посмотрите на список симптомов, которые могут указывать на наличие COVID-19, вы начнете чувствовать себя беспомощными. Боль в горле, мышечная боль, усталость - эти симптомы легко могут быть объяснены бесчисленными причинами, отличными от COVID-19. Однако есть истории о людях, у которых был диагностирован COVID-19, но они показывают очень умеренные симптомы, если вообще показывают. Я не врач и не эксперт в самодиагностике, и мы имеем дело с пандемией, поэтому я почувствовал, что это моя ответственность - изолироваться и пройти тестирование.

Тест

К счастью, я живу в Сиэтле, где легко доступны пункты тестирования с возможностью проезда на автомобиле. Если вы живете в Сиэтле и вам нужно пройти бесплатное тестирование, вы можете сделать это здесь:

https://www.seattle.gov/mayor/covid-19/covid-19-testing

Регистрация прошла легко, и я смог назначить встречу на следующий день. Благодаря приложению Solv, планирование стало еще проще. С назначенной встречей я прибыл за 25 минут до указанного места, что было удачей, так как в очереди стояло множество машин.

Пожалуйста, проигнорируйте мои грязные окна

Сам тест не был таким уж плохим. Это было похоже на то, что кто-то вставил сырую спагетти-лапшу в мой нос и протянул ее через заднюю часть горла... и повернул ее в течение 10 секунд... и затем повторил то же самое в другом ноздре. К счастью, у меня не возникло ощущения щекотания в мозгу, о котором рассказывали другие. И все, на этом все закончилось. Мне вручили мои документы с кодом для получения результатов, и я уехал. Всего процесс занял около 30 минут. Я был впечатлен.

Что дальше?

И теперь мне предстоит вернуться домой и ждать, стараясь не думать о том, что может произойти. Проблема в том, что они связываются с вами только в случае положительных результатов теста. Если результаты отрицательные, вам придется самостоятельно проверять портал для получения результатов. Но вы не знаете, когда результаты будут готовы. Это может занять часы или даже дни. Мне сказали, что результаты будут готовы в течение 2-3 дней, но есть сообщения о том, что люди получали результаты уже через 24 часа. Так что, наверное, мне предстоит наблюдать за страницей в надежде, что что-то появится? Я решил, что у меня уже достаточно забот, и я оставлю наблюдение за компьютером.

Это приводит нас к сборке.

Сборка

Мой компьютер глупый. Он не знает, как делать вещи без инструкций. Чтобы помочь ему, я написал простой сервис для запроса результатов тестов от его имени и отправки мне уведомлений об изменениях. Чтобы автоматизировать этот процесс, мне нужно было понять, как это работает. Мой первый шаг был вручную запросить результаты и изучить, что происходит за кулисами.

Для этого я перешел по указанному в моих документах URL-адресу https://securelink.labmed.uw.edu и заполнил свои данные. Перед отправкой запроса я открыл вкладку "Network" в Chrome, чтобы изучить отправку. Отправить.

Dark Mode for life

Бинго. Это говорит мне все, что мне нужно знать. В частности, мне нужно сделать POST-запрос по адресу https://securelink.labmed.uw.edu/result с заголовком content-type application/x-www-form-urlencoded и полями формы barcode и dob. Интересно, что он также перенаправляет мой браузер в своем ответе. Об этом позже.

Построение запроса

Существует множество способов, которыми я мог написать этот скрипт, но я выбрал создать свой сервис, используя NodeJS. Многие люди любят ругать JavaScript, но я люблю его универсальность и прощающую природу. Поэтому я инициализировал новый проект и установил node-fetch. Мой первый шаг был имитировать запрос:

const fetch = require('node-fetch');

const url = 'https://example.com';
const options = {
  method: 'GET',
  headers: {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
  }
};

fetch(url, options)
  .then(response => response.text())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.log(error);
  });

Вызывая метод text() на объекте ответа, я могу получить фактический HTML, возвращаемый запросом. Это привело к странице с более подробной информацией, чем я видел в браузере. Помните редирект из ранее? Ответ перенаправлял мой браузер на более простую страницу, которая просто сообщала мне, что результаты обрабатываются. Однако фактический веб-запрос возвращал ценную информацию, которую мой скрипт мог использовать.

Парсинг HTML

Следующим шагом было выполнение парсинга HTML-ответа. Чтобы помочь с этим, я обратился к cheerio. Передавая сырой HTML в cheerio, вы можете обходить DOM и извлекать нужную информацию. Чтобы получить лучшее представление о том, с чем я работаю, я предварительно просмотрел HTML в браузере. Я видел, что он возвращает таблицу, содержащую некоторые соответствующие поля. А именно: "Ваш результат", "Дата результата", "Тип образца" и "Информация о методе".

Чтобы получить эту информацию с помощью cheerio, вам нужно предоставить селекторы DOM, аналогично тому, как вы бы использовали jQuery. При ручном анализе HTML, казалось, что я могу получить ссылку на все строки в таблице, используя селектор "#result-card > div > table > tbody > tr".

Теперь немного запутанная часть. Каждая строка состоит из двух ячеек данных <td>. Я начал перебирать найденные cheerio строки. Для каждой строки я использовал индекс, чтобы определить, является ли она интересующей меня строкой. Если это было так, я перебирал каждую ячейку данных (кто считает вложенные циклы?), захватывая текст внутри.

Получение текста

К сожалению, не все ячейки данных созданы одинаково. Некоторые содержали простой текст, некоторые были пустыми, за исключением странных пробелов, некоторые состояли из тегов <p>, а другие содержали теги <span>. Поэтому я создал функцию с названием getText, которая была достаточно универсальной для обработки всех возможных ситуаций. На момент написания этого скрипта результаты еще не были доступны. В конце концов, в чем смысл, если они уже были? Поэтому я не знал, что ожидать от разметки, возвращаемой в этих ячейках данных, за исключением того, что она могла быть потенциально запутанной. Поэтому я постарался написать функцию, способную обрабатывать любые ситуации, с которыми я мог бы столкнуться. Конечно, вероятно, в cheerio есть встроенная утилита, которая выполняет эту задачу, но я не смог быстро разобраться в этом, поэтому это казалось разумным путем вперед.

В результате этой работы я написал рекурсивную функцию для обработки ячеек. Я предполагал, что мне могут встретиться два типа узлов. Это может быть узел text, который cheerio обозначает таким образом, или это может быть узел tag с children. Моя функция работает следующим образом. Если переданный ей узел действительно является узлом text, она удаляет все лишние пробелы и возвращает текст. Если же это tag и у него есть атрибут children, то я перебираю children (это третий вложенный цикл?), рекурсивно вызывая функцию (где находится счетчик цикла?) и добавляя результаты к накопительной строке. После перебора всех children накопительная строка возвращается. Вместо того, чтобы тратить время на то, чтобы полностью понять набор данных, с которым я работал, я решил создать себе "запасной выход". Если узел вопроса не является ни узлом text, ни tag с children, функция просто возвращает пустую строку.

И с этим настало время для тестирования.

Сравнение результатов

Хорошо, продвигаемся! Теперь нужен механизм для сравнения результатов. Поскольку меня интересует только выявление различий и получение результатов, самым простым решением было сериализовать массив, возвращаемый обработкой HTML, и сравнить его с последним сохраненным значением. Для этого требуется иметь предыдущий результат для сравнения, поэтому я написал простое хранилище, которое позволяет загружать сохраненные результаты, сохранять обновленные результаты и получать последние результаты.

Отправка оповещений

Последний кусочек головоломки - механизм оповещений. Все это было хорошо и хорошо, и приятно отвлекало от ожидания результатов теста, но нет особого смысла в этом скрипте, если он самостоятельно не сообщает мне, когда результаты готовы. Поэтому, чтобы выполнить это, я решил отправлять себе электронное письмо каждый раз, когда результаты меняются. Для этого я использовал пакет Nodemailer с SMTP-транспортом, который упрощает процесс отправки электронной почты через node.

Однако перед настройкой этого мне понадобился фиктивный почтовый аккаунт. Самый простой способ, который я нашел для настройки Nodemailer, - использовать учетную запись Gmail с включенными небезопасными приложениями. Звучит немного подозрительно? Вот почему я использовал фиктивный аккаунт.

Когда учетная запись Gmail готова к работе, дело остается за малым - предоставить несколько ключевых данных. Вы знаете, какие данные вам понадобятся для отправки обычного электронного письма.

Сборка всего вместе

Когда система оповещения готова к работе, пришло время соединить все вместе. Различные части выполняются в следующем порядке, как описано в этой статье. Сначала получаем результаты, затем разбираем HTML-ответ, затем сравниваем с предыдущими результатами. Если результаты отличаются, отправляем электронное оповещение и обновляем последние результаты. Все вместе это выглядит так:

def fetch_results():
    # код для получения результатов

def parse_html(response):
    # код для разбора HTML-ответа

def compare_results(current_results, previous_results):
    # код для сравнения результатов

def send_email_alert():
    # код для отправки электронного оповещения

def update_latest_results(current_results):
    # код для обновления последних результатов

def process_results():
    current_results = fetch_results()
    parsed_results = parse_html(current_results)
    if compare_results(parsed_results, previous_results):
        send_email_alert()
        update_latest_results(parsed_results)

def loop():
    while True:
        process_results()
        time.sleep(3600)  # ждем 1 час

loop()

Последний шаг - запустить этот код в цикле с определенным интервалом. Я выбрал получать результаты раз в час. Конечно, мы могли бы проверять чаще, но я не хочу нагружать серверы медицинских учреждений. Я считаю, что раз в час меньше, чем если бы я проверял вручную, поэтому считаю это справедливым компромиссом. Конечно, это, вероятно, должно было быть настроено для запуска как cron job, но чтобы упростить, я написал функцию цикла, которая рекурсивно вызывает саму себя каждый час.

Вот и все!

Получение результатов

Через два часа после завершения скрипта я получил свое первое электронное письмо с информацией об обновлении.

Успех! Все работает! Я оставил скрипт работать на выделенной машине на ночь, и сегодня утром я проснулся от еще одного письма с информацией об обновлении.

Через 17 часов и 46 минут после того, как я вставил макаронину в нос, результаты моего теста оказались отрицательными, и вы, наверняка, знаете, насколько я благодарен. Этот проект был интересным отвлечением, и, что еще лучше, он действительно сработал! Было такое облегчение не чувствовать необходимости наблюдать за обновлениями на портале.

Не стесняйтесь клонировать и изменять код под свои нужды, но пожалуйста, используйте разумный интервал запросов. Этот скрипт очень зависит от структуры портала UW, поэтому изменение его структуры может повлиять на его работу. Учитывая требуемый срок службы для моих нужд, это было разумное решение. Если он не работает для вас, поиграйте и доведите его до работы - в этом и заключается веселье!

Но самое главное, наденьте маску и будьте в безопасности!