Вебскрепінг, недобросовісні провайдери й неочевидні рішення для синхронізації
Привіт! Я Олег Денисенко, Software Architect ITOMYCH STUDIO. Темою моєї статті буде Web Scraping. Що таке вебскрепінг (від англійського scraping — «зішкрібання, зачищання»)? Це процес автоматизованого збору структурованих вебданих, простіше кажучи, отримання вебданих.
Основні випадки, де використовується вебскрепінг — це, наприклад, моніторинг та аналіз цін, моніторинг новин, генерація потенційних клієнтів та дослідження ринку тощо.
Де використовується вебскрепінг
Загалом, отримання вебданих використовується людьми та підприємствами, які хочуть використовувати величезну кількість загальнодоступних даних з інтернету для прийняття розумніших рішень.
Якщо ви коли-небудь копіювали та вставляли інформацію з вебсайту, ви виконували ту ж функцію, що й будь-який вебграбер, тільки на кшталт «захід сонця вручну». На відміну від виснажливого процесу вилучення даних вручну, вебскрепінг використовує інтелектуальну автоматизацію для отримання сотень, мільйонів або навіть мільярдів точок даних зі, здавалося б, нескінченного простору інтернету.
А от ще був випадок
Тепер трохи повернемось до реальності. Ось реальна ситуація, коли доцільно використати вебскрепінг. Минулого року до нас прийшов клієнт і поскаржився, що постачальник послуг закрив усі дані в базі та розірвав контракт.
Штош © ¯\_(ツ)_/¯. Я взяв час для аналізу даних на сайті, дістав наявні логіни та паролі користувача, та пішов тиснути F12 у Chrome.
Попереднє дослідження показало, що сайт написано на ReactJS, backend — на NodeJS, запити йдуть через REST API. Більша частина (майже вся) API — зовсім не закрита, але для 3 endpoint (це з 20, sic!) тра AWS Cognito authentication.
Збірка коду для скрепінга
Настав час спробувати щось забрати до себе. Спочатку я вирішив, що напишу на Bash або на улюбленому Python купу скриптів, але зрозумів, що девелопери будуть робити новий бекенд на .NET, вирішив відразу намалювати консольний аплікейшн, де модельки респонсу можна буде переюзати в майбутньому WEB API.
Як я вже казав раніше, сайт працює на REST API, тому перше, що я додав до консольного аплікейшина — це RestEase та купа AWS-пакетів.
<ItemGroup> <PackageReference Include="AWSSDK.S3" Version="3.7.2.5" /> <PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.1" /> <PackageReference Include="Amazon.Extensions.CognitoAuthentication" Version="2.2.2" /> <PackageReference Include="AWSSDK.CognitoIdentityProvider" Version="3.7.1.37" /> <PackageReference Include="RestEase" Version="1.5.5" /> </ItemGroup>
Далі зробив собі ApiFactory.
[Header("Authorization", "Bearer")] public interface ICensoredProjectContract { } public class CensoredProjectApiFactory { private readonly HttpMessageHandler _handler; public CensoredProjectApiFactory(CensoredProjectConfig config = null) { _handler = new CensoredProjectHttpClientHandler(config); } public T GetClient<T>(Uri url) where T : ICensoredProjectContract { var httpClient = new HttpClient(_handler) { BaseAddress = url, Timeout = TimeSpan.FromSeconds(180), }; return RestClient.For<T>(httpClient); } public T GetClient<T>(Uri url) where T : ICensoredProjectContract { var httpClient = new HttpClient(_handler) { BaseAddress = url, Timeout = TimeSpan.FromSeconds(180), }; return RestClient.For<T>(httpClient); }
Додав «брудну» авторизацію у AWS Cognito.
private async Task<string> Login(string login, string password) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Amz-Target", "AWSCognitoIdentityProviderService.InitiateAuth"); client.BaseAddress = new Uri("https://cognito-idp.us-west-2.amazonaws.com"); var json = "{\"AuthParameters\" : {\"USERNAME\" : \"" + login + "\", \"PASSWORD\" : \"" + password + "\"}, \"AuthFlow\" : \"USER_PASSWORD_AUTH\",\"ClientId\" : \"xxxxxxxxxxxxxxxxxxxxxxxxx\"}"; var content = new StringContent(json, Encoding.UTF8, "application/x-amz-json-1.1"); var result = await client.PostAsync("/", content); string resultContent = await result.Content.ReadAsStringAsync(); JObject jsonObject = JObject.Parse(resultContent); return jsonObject["AuthenticationResult"]["AccessToken"].ToString(); } }
Після чого додав купу інтерфейсів на REST API сайту.
public interface IServiceUserApi : ICensoredProjectContract { [Get("profiles/{userId}")] Task<Profiles> GetProfiles([Path] string userId); } public interface IServicePhotoApi: ICensoredProjectContract { [Get("videos")] Task<List<Video>> GetVideos([Query] string userId); [Get("videos")] Task<List<Video>> GetVideos(); [Get("photos")] Task<List<Photo>> GetPhotos([Query] string userId); [Get("albums")] Task<List<Album>> GetAlbums([Query] string userId); [Get("albums/{albumId}")] Task<FullAlbum> GetAlbum([Path] string albumId); }
І спробував зробити перший запит до API.
Log.Debug($"Fetching user {userId} data.."); [....] Log.Debug($"Profile"); var profile = await _userApi.GetProfiles(userId); _data.AddProfiles(profile, userId); _context.SaveChanges();
Так! Це працює!
Запуск вебскрепінга
Далі — дуже відповідальна та дуже складна частина. Тут я витратив багато часу, щоби правильно запрограмувати весь флоу отримання даних.
Насамперед я стягнув довідники, тобто ті дані, які використовує система: наприклад, список валют, список штатів тощо.
Наступний етап — отримання даних по кожному юзеру, його профіль та купа інформації, яка має лежати за ним.
Відверто кажучи, на етапі датаскрепінга я робив усі моделі, як є — один одному з API response. Це було зроблено, щоб якнайскоріше дістати дані з сайту, бо замовник казав, що його вимкнуть через тиждень-два. На момент розробки нового проєкту я переробив майже всю архітектуру моделей та entities у базі.
Синхронізація медіафайлів — завдання з зірочкою
Коли всі дані вже були в нашій базі, залишилась остання, проте дуже велика частина. Треба було синхронізувати всі медіафайли для кожного профілю.
Усі оригінальні дані сайт зберігав у AWS S3, закритий через CloudFront. Якби в мене був логін до оригінального AWS, можна було б за п’ять хвилин зробити копію в себе...
aws s3 sync s3://source-old-site-bucket s3://my-new-site-bucket
... але ж ніт. Ми ж тут для того, щоби скрепінгувати дані, а не «оцевотвсе».
var photoApi = new CensoredProjectApiFactory().GetClient<IServicePhotoApi>(new Uri("https://service-photo.censored-sie.com"));
Дістаємо метадані через API. Далі найнеприємніше: ми маємо витягнути файл зі старого сайту, зберегти локально та знову залити його вже до нашого bucket-у. Ще одна проблема — відео зберігалися як потокові. Тобто, треба було спочатку забрати m3u8 файл...
using (WebClient localClient = new WebClient()) { var m3u8 = await localClient.DownloadStringTaskAsync(item.HlsUrl);
... а далі взяти всі.ts файли і злити їх у єдиний. Бо згідно з умовами замовника файли мають бути не потоковими, а звичайними на кшталт mp4, а зверху ще й накладений вотермарк компанії.
var fileName = Path.Combine(tmpVideosFolder, $"{item.Id}.mp4"); await SaveVideo(url, fileName); var s3Media = await UploadToS3(fileName, $"{userid}/Videos/{item.Id}.mp4", "video/mp4");
З огляду на те, що в нас було десь 500 профайлів, — це купа часу.
Щоби запустити «копіювання» відео даних з одного бакету до нашого й накласти вотермарк, ми написали просту AWS Lamda. Вона працювала дуже просто: коли файл потрапляв до s3 temp фолдеру, вмикався тригер і lambda починала опрацьовувати файл. Відверто кажучи, сама лямбда була на 10 рядків коду, один із яких був дуже «всратий» :) — це запуск ffmpeg.
Ви заціните:
args = f'/opt/ffmpeg -y -i {original_path} -i {watermark} -filter_complex [1]colorchannelmixer=aa=1.0,scale=iw*0.042:-1[wm];[0][wm]overlay=x=(main_w-overlay_w-120):y=(main_h-overlay_h-60),split=2[vid][img];[img]scale=min(800\\,iw):-1[img] -map [vid] -map 0:a? -codec:a copy -b:v 8192k -preset ultrafast -async 1 -movflags +faststart {output_video_path} -map [img] -frames:v 1 {output_thumb_path}'
Лямбда працює, скрепінг відео даних запущено. Доки йде процес, можна випити кави.
Чи це взагалі законно
Зверніть увагу, що скрейпінг процес проєкту був легальним, тому що права були саме в клієнта.
Та чи сканування інтернету та даних — легальне явище? Це окреме правове питання. Ставлення до легальності вебскрепінгу в різних країнах відрізняється. Деякі вебсайти навіть забороняють скрепінг у правилах використання, але юридичні наслідки такої заборони не є чіткими. А що ви про це думаєте?
Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті
8 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів