Добрый вечер всем!
Возможно выбрал не лучшее время для охвата аудитории, но тем не менее главное чтоб продукт был хороший, а не статья о нем. Последние несколько недель я пишу приложение в рамках которого надо собирать огромное количество информации из сети(запросы к 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 можно поиграться
Комментарии (9)
Hungryee
00.00.0000 00:00+2Выглядит как воплощение моей мечты в сфере скрейпинга - максимально абстрактная конфигурация для сбора информации. К сожалению в реальности это не будет работать для большинства сайтов, потому что как показывает практика, это большинство сайтов сделаны идиотами на отвали (включая разную структуру html для страниц, которые должны быть по логике одинаковыми и тд)
Именно поэтому много скрейперов - это код на питоне (вставьте другой язык), где множество ветвлений/условий/проверок
PYXRU Автор
00.00.0000 00:00Спасибо большое за идею, я совершенно про такое забыл, я добавлю в поля optional параметр который будет лестинца с приоритетом(первый который не пустой будет выбран)
Понимаю не все это решит в коде все таки легче условия описать, но чуток упростит
nin-jin
00.00.0000 00:00Я немного ужал ваш конфиг в 4 раза:
limits playwright_instance 3 out Server method \GET url \http://www.citymayors.com/gratis/uk_topcities.html out Array in xpath \//table//table//tr[preceding-sibling::*] out Object name String xpath \td[1]//font population String xpath \td[2]/font temperature Chromium in String xpath \td[1]//font url \https://openweathermap.org/find?q={in} attempts 3 timeout 30 wait 30 install false out FireFox in String xpath \//div[@id='forecast_list_ul']//td/b/a/@href url \https://openweathermap.org{in} attempts 4 timeout 30 wait 30 install false type_of_wait \networkidle out String css \//div.current-temp span.heading
PYXRU Автор
00.00.0000 00:00Спасибо большое, это принципе очень похоже на YAML который уже поддерживается(я больше говорил про сложность понимания вариантов, чем про размер)
Ну и у вас опущена, ну и они не совместимы в вашем случае это подошло бы для запуска бинарника Firefox, а в нашем случае Firefox как часть playwright
Но все таки я имел ввиду понимание его неделе размер(согласен чем меньше размер легче понять, попозже перепишу на yaml)
nin-jin
00.00.0000 00:00Это совсем не ямл. Тому, кто будет писать эти конфиги, ему не нужно знать ни про какой playwrite, playleft или какую ещё утилиту для управления браузером вы захотите использовать. Chromium и Firefox - это абстрактные адаптеры. Можно даже добавить адаптер Browser для случая, когда не важно каким браузером загружать данные. Например, он может брать первый освободившийся.
PYXRU Автор
00.00.0000 00:00Ну тоесть таких абстракций много, я больше пока склоняюсь к jsonnet, для более юзабельной конфигурации
MagisterAlexandr
Почему?
PYXRU Автор
По статистикe рабочии блоги читают в середину дня в среду :)