Skip to content

Latest commit

 

History

History
55 lines (41 loc) · 6.04 KB

README.md

File metadata and controls

55 lines (41 loc) · 6.04 KB

handsru-phone-dl

Задача

Компания делает сайт: стандартный справочник организаций. Клиенты на сайте ищут номера телефонов организаций. Номера телефонов устаревают. Из-за этого компания теряет клиентов.

Для каждой организации в базе хранится ссылка на её сайт и путь к странице контактов. Страниц контактов на одном сайте может быть несколько. Есть модуль, который умеет распознавать неактуальный номер. На вход он получает список номеров в формате 8KKKNNNNNNN.

Вам нужно написать модуль, который скачивает web-страницы, находит в тексте и выводит все распознанные номера телефонов в этом формате.

Номера по формату российские. Если для номера не указан код города — номер московский. Чем выше доля распознаваемых реальных номеров на странице и чем быстрее работает модуль — тем он лучше.

Здесь: https://hands.ru/company/about модуль должен найти номер, здесь: https://repetitors.info тоже. Страниц в базе может быть очень много!

В задании не нужно использовать тяжелые фреймворки или сохранять найденные номера в базу. Задание ориентировано буквально на несколько часов.

Решение нужно предоставить в виде отдельного репозитория на github.com.

Пояснения по решению

Пример запуска программы:

cd src;
PYTHONPATH=. downloader/main.py --num_processes 3 --max_downloads_per_process 10 ../data/sample10.txt

В каталоге data два набора данных:

  • sample10.txt - файл с 10 урлами, два из которых упомянуты в задании
  • sample10_repeated.txt - файл с этими же 10 урлами, повторенными несколько раз

Найденные телефоны выводятся в логах.

Теперь собственно по решению.

  • для получения списка урлов используется UrlsProvider и его простая реализация ListUrlsProvider для хранения урлов в памяти (и чтения из файла). Несложно сделать другую реализацию для чтения, например, из базы данных. Кроме того, класс может выдавать урлы батчами, например, с последовательными запросами к базе данных
  • для парсинга телефонов используется класс PhoneParser. Я честно начал писать регулярки на телефоны, но потом подумал, что это тысячу раз делали до меня и нашел прекрасную библиотеку phonenumbers
  • для сохранения результатов использую OutputStorage с простым интерейсом и реализацией на основе мультипроцессной очереди. (поскольку используется несколько процессов, см. ниже). Опять же, несложно заменить на запись в БД
  • такое деление на классы позволяет использовать Downloader достаточно гибко (можно приспособить под парсинг разнообразных данных)
  • сам Downloader - класс для загрузки данных по урлам из UrlsProvider, парсингом их с помощью ParserBase и записью результатов в OutputStorage. Загрузка выполняется с использованием asyncio/aiohttp. Есть поддержка ограничения числа одновременных загрузок на каждый Downloader. Сессия там создается каждый раз новая (но без пула соединений), чтобы избежать общих cookies между запросами. Не уверен, что это актуально для этой задачи (переделать несложно), но как-то в похожей задаче меня банили по кукам из-за этого (но там было сильно больше запросов per site).
  • каждый Downloader реализован как отдельный процесс, у него есть свой идентификатор id, который позволяет распределять урлы между разными инстансами Downloader' (распределяются на основе хэша). Зачем отдельный процесс? Потому что корутины помогают только при скачивании данных, потому что там долгие IO-операции. Если парсинг становится более сложным, то он может стать "узким горлышком" и поэтому нужны честные процессы для использования нескольких ядер процессора. Кроме того, с небольшими доработками это позволяет запустить программу на разных машинах