Добрый вечер всем!
Возможно выбрал не лучшее время для охвата аудитории, но тем не менее главное чтоб продукт был хороший, а не статья о нем. Последние несколько недель я пишу приложение в рамках которого надо собирать огромное количество информации из сети(запросы к API/парсинг HTML кода) и под конец 4-ой интеграции я подумал что надо бы это максимально облегчить(не дело это пересобирать приложение под каждый чих интеграции), возможно это не лучшая преамбула, но хотя бы была реальная проблема решение к которой хотелось показать и заопенсорнуть.
Итак Fitter = сшиватель достаточно жаргонный перевод, но мне он кажется что лучше всего подходит. Я делал эту штуку исходя из следующих предположений:
Данные могут меняться => нужен механизм обновления
Данные могут быть в нескольких источниках => данные надо сшить(map/reduce)
Нужна авторизация => API ключ/OAuth/Login+Pass
Есть не только Server-side rendering => Данные могут появиться после загрузки клиентской части(я понимаю, что иногда можно сэмулировать запрос)
Данные могут быть невалидны => поле может отсутствовать или разметка поменялась
Мы не знаем где это будет развернуто => отсутствовать нужные абстракции
Нужна конфигурация которую +- легко менять(не в текущем варианте)
Нужно иметь возможность привести данные к одному виду
И так DEMO:
Немного расскажу что он умеет:
Брать данные по HTTP запросам с авторизацией по Header
Брать данные с бинарника Chromium
Брать данные с Docker браузеров
Брать данные с Playwright
Парсить данные HTML/Json/XPath
Пробрасывать данные/связывать с разных источников
Планы на будущее(Roadmap):
Добавить сценарии: некоторые сайты для парсинга требуют авторизацию/принять cookie и тд тп, будет набор команд которые можно будет прогнать до парсинга и после
Добавить способы отдачи информации: чтобы проект смог отправлять Webhook/Queue сообщения и тд тп
Добавить способы сбора информации: сегодня пришла идея чтоб источником информации может быть любая штука например телеграм канал: и для этого нам нужен допустим бот с доступам к сообщениям
Добавить способы запуска: тот же Webhook/Queue
Валидация - отсеевать невалидные данные
Редактор конфигурации - смотри ниже
Текущие точки боли(pain-points):
Пока что он один: конфигурация - для простых ситуаций это просто, однако если хочется связать несколько источников разобраться тяжело.
{
"limits": {
"playwright_instance": 3
},
"item": {
"connector_config": {
"response_type": "HTML",
"connector_type": "server",
"server_config": {
"method": "GET",
"url": "http://www.citymayors.com/gratis/uk_topcities.html"
}
},
"model": {
"type": "array",
"array_config": {
"root_path": "table table tr:not(:first-child)",
"item_config": {
"fields": {
"name": {
"base_field": {
"path": "td:nth-of-type(1) font",
"type": "string"
}
},
"population": {
"base_field": {
"path": "td:nth-of-type(2) font",
"type": "string"
}
},
"temperature": {
"base_field": {
"path": "td:first-child font",
"type": "string",
"generated": {
"model": {
"type": "string",
"path": "temp.temp",
"model": {
"type": "object",
"object_config": {
"fields": {
"temp": {
"base_field": {
"type": "string",
"path": "//div[@id='forecast_list_ul']//td/b/a/@href",
"generated": {
"model": {
"type": "string",
"model": {
"type": "object",
"object_config": {
"fields": {
"temp": {
"base_field": {
"type": "string",
"path": "div.current-temp span.heading"
}
}
}
}
},
"connector_config": {
"response_type": "HTML",
"connector_type": "browser",
"attempts": 4,
"browser_config": {
"url": "https://openweathermap.org{PL}",
"playwright": {
"timeout": 30,
"wait": 30,
"install": false,
"browser": "FireFox",
"type_of_wait": "networkidle"
}
}
}
}
}
}
}
}
}
},
"connector_config": {
"response_type": "xpath",
"connector_type": "browser",
"attempts": 3,
"browser_config": {
"url": "https://openweathermap.org/find?q={PL}",
"playwright": {
"timeout": 30,
"wait": 30,
"install": false,
"browser": "Chromium"
}
}
}
}
}
}
}
}
}
}
}
}
}
Как мы видим это больно, но давайте расскажу что тут происходит:
Мы настраиваем лимиты параллельных запуска Playwright: 3 штуки
Мы будем читать GET запросом HTML c сайта: http://www.citymayors.com/gratis/uk_topcities.html
На выходе мы ожидаем массив следующего вида:
Array<City>
City = {
name: string;
population: string;
temperature: string
}
Если с полями name/population +- все понятно мы их берем из таблицы с сайта указанного выше, то temperature это сгенерированное поле
Мы берем название города из таблицы и пробрасываем его на сайт: https://openweathermap.org/find?q={PL} - где {PL} - имя города , для этого мы используем Playwright так как там client-side rendering
С результатов поиска мы берем relative ссылку на страницу города, пример: /city/2950159 и подставляем https://openweathermap.org{PL} - где {PL} ссылка, для этого мы используем Playwright так как там client side-rendering
По ссылке выше выдергиваем температуру по селектору: `div.current-temp span.heading`
И разворачиваем поле путем парсинга: `temp.temp` из сгенирированных данных
Пример элемента массива
{
"name": "Exeter",
"population": "107,729",
"temperature": "4°C"
},
Ну и принципе и выводим результат.
PS: я понимаю что для достижения результата можно было связать с API попроще, но мне хотелось показать сложный кейс.
PS2: я не выбрал я пиарюсь, потому как коммерческой ценности 0, проект будет открытыми бесплатным, но было бы хорошо собрать отзывы
Спасибо большое за внимание, очень хочется услышать критику ну и идеи на будущее!
Проект и примеры:
Примеры для поиграться(скачиваете Fitter_CLI из релизов и можете следовать Readme
Там есть разные примеры: Docker/Server side HTTP request/Chromium можно поиграться