Создание быстрого скрипта с использованием Golang для удаления наклеек LINE

Начнем с того, что согласимся, что у Whatsapp нет отличных стикеров, а у LINE есть. Для тех, кто понятия не имеет, что такое LINE, или недоумевают, чем же так хороши их стикеры, [посмотрите один здесь]. Кроме того, этот проект будет полезен для сбора информации, если вы работаете в этой области.

Вот код в моем [Github repo]

Разборка паутины

Большинство из нас знает, что в Python есть отличные библиотеки, такие как BeautifulSoup/bs4 и Selenium, расширения Chrome, которые могут загружать все медиафайлы на определенной странице, и я бы рекомендовал использовать эти популярные подходы.

Однако сегодня задача этого проекта состоит в том, чтобы:
- сделать это без библиотеки
- попытаться добиться параллелизма/асинхронности (одновременная загрузка нескольких наклеек без ожидания)
- (необязательно) пакет чтобы наши не технические друзья могли его использовать

Что значит загрузить его одновременно/асинхронно? Это означает, что мы не собираемся ждать, пока загрузится и конвертируется одна наклейка, а только приступим к следующей.
Как и в школе, учитель (Мадам Чу) просто дает каждому доступному в классе одно задание для выполнения, в то время как Мадам Чу сидит и ждет, пока все выполнят свое задание, и возвращает его ей. В обычной программе мадам Чу дала бы одному ученику задание, подождала бы, пока этот ученик выполнит, и только тогда она раздаст второе задание. В большинстве случаев это работает нормально, так как этот ученик хорош в том, что он делает, однако мадам Чу понимает, что у нее есть класс из 12 учеников, которые все время ничего не делают. Если бы она могла раздать задание всем 12 ученикам (потокам), ее задание было бы выполнено быстрее.

Почему Голанг

Правда в том, что это можно сделать на любом другом языке программирования [NodeJS, C++, Python…]. Так что на самом деле не должно быть никаких дискуссий по этому поводу, а чисто для развлечения и учебных целей :)

Однако то, что отличало Голанга от остальных, — это способность легко достичь 2-го и 3-го очка. Что еще более важно, Golang создан для того, чтобы мадам Чу легко давала задания всем своим ученикам одновременно.

Учебник по скоростному прохождению

Вот код в моем [Github repo]

Понимание нашей цели

Прежде чем мы начнем, нам нужно понять несколько вещей.
- К счастью, наш магазин наклеек не обрабатывается клиентом с помощью Javascript, поэтому базовый curl получит всю необходимую информацию, не дожидаясь вызовов AJAX.
- Мы можем проанализировать HTML-теги и получить нужные нам стикеры, однако мы заметили, что **необработанная** информация о каждом стикере находится в пользовательском атрибуте HTML с именем `data-preview='{}'`. Это позволяет нам анализировать эту информацию в формате JSON.

data-preview=”{ “type” : “popup_sound”, “id” : “312149456”, “staticUrl” : “https://stickershop.line-scdn.net/stickershop/v1/sticker/312149456/iPhone/[email protected];compress=true", “fallbackStaticUrl” : “https://stickershop.line-scdn.net/stickershop/v1/sticker/312149456/iPhone/[email protected];compress=true", “animationUrl” : “”, “popupUrl” : “https://stickershop.line-scdn.net/stickershop/v1/sticker/312149456/android/sticker_popup.png;compress=true", “soundUrl” : “https://stickershop.line-scdn.net/stickershop/v1/sticker/312149456/android/sticker_sound.m4a" }”

С этим набором начнем

1/ Сначала мы создаем точку входа, которая принимает URL

// Entrypoint
func main(){
 consoleReader := bufio.NewReader(os.Stdin)
 for {
  fmt.Println(“Enter Line Stickershop URL”)
  inputUrl, err := consoleReader.ReadString(‘\n’); if err != nil {
   log.Fatal(err)
  }
  // Check if input has at least a line store format
  if strings.Contains(inputUrl, “https://store.line.me") {
   inputUrl = strings.Replace(inputUrl, “\r\n”, “”, -1)
   err := scrap(inputUrl); if err != nil {
    log.Fatal(err)
   }
  } else {
   fmt.Println(“Invalid format”)
  }
 }
}
// Check if input has at least a line store format
 if strings.Contains(inputUrl, “https://store.line.me") {
 inputUrl = strings.Replace(inputUrl, “\r\n”, “”, -1)
 err := scrap(inputUrl); if err != nil {
 log.Fatal(err)
 }
 } else {
 fmt.Println(“Invalid format”)
 }
 }
}

2/ Затем мы создаем функцию вырезки, которая использует встроенную библиотеку http для загрузки веб-страницы.

resp, err := http.Get(scrapUrl); if err != nil {
 return err
}

3/После завершения загрузки мы проанализируем тело загруженной веб-страницы. Помните, мы сказали, что необработанную информацию можно найти в пользовательском атрибуте под названием «предварительный просмотр данных», не слишком усложняя, вызов регулярного выражения сможет извлечь каждое вхождение этого атрибута.

var rgx = regexp.MustCompile(`(data-preview=’.*?’)`)
tmpExtracted := rgx.FindAllStringSubmatch(inputHtml, -1)
for i := 0; i < len(tmpExtracted); i++ {
 // Parse the JSON here

4/ Прежде чем анализировать JSON, давайте создадим структуру на основе необходимой информации, которую мы получили из атрибута предварительного просмотра данных.

type DataPreview struct {
 Id string `json:”id”`
 StickerType string `json:”type”`
 PopupUrl string `json:”popupUrl”`
 StaticUrl string `json:”staticUrl”`
 AnimationUrl string `json:”animationUrl”`
 SoundUrl string `json:”soundUrl”`
}

5/ Отлично, теперь мы просто распаковываем JSON в структуру DataPreview.

6/ Далее нужно создать функцию для одновременной загрузки наклеек. С Golang это может быть так же просто, как несколько строк

var wg sync.WaitGroup
for i := 0; i < len(result); i++ {
 wg.Add(1)
 go downloadImage(result[i], &wg)
}
```
WaitGroup basically just tells Madam Choo how many students has she assigned the task to.

7/ Мы знаем, что существует 3 URL-адреса, которые мы можем использовать: PopupUrl для [больших анимированных стикеров](https://store.line.me/stickershop/product/17300/en), StaticUrl для [стикеров, которые не move](https://store.line.me/stickershop/product/2803/en) и AnimationUrl для [анимированных стикеров обычного размера](https://store.line.me/stickershop/product/ 19770/ru). Создание простого правила переключения поможет нам определить, какой URL-адрес мы должны получить, а затем снова использовать библиотеку HTTP для загрузки GIF.

8/ После загрузки GIF я использую [APNG2GIF] для преобразования APNG в GIF. Это не самое идеальное решение, но точно самое простое.

9/ Прежде чем мы продолжим запрашивать у пользователя другой URL-адрес, мадам Чу хочет дождаться, пока все студенты завершат свою работу.
Нам нужно добавить это в асинхронную функцию, чтобы сообщить мадам Чу, что ее работа выполнена.

defer wg.Done()

И мадам Чу придется дождаться завершения работы каждого, прежде чем продолжить.

wg.Wait()

10/ И в основном мы закончили! Мы можем быстро упаковать его для Windows и отправить скомпилированный двоичный файл нашим друзьям, просто запустив

go build main.go

Финал

Вот и все, быстрая программа для удаления GIF для вашего удовольствия. Хотя это может показаться тривиальной задачей, это имеет большое значение, когда вы собираете данные для обучения машинному обучению или массового разбора. Разница с асинхронностью и без нее влияет на время, необходимое для достижения нашей цели в масштабе.

Спасибо, что дочитали до сюда, надеюсь, вам понравился этот пост!