
За последние десять лет я выиграл во многих маркетинговых видео-конкурсах, используя проверенные техники. Но конкретно этот случай стал первым, в котором значительную роль сыграло программирование и инструменты ИИ.
В этой статье я опишу, как с помощью ChatGPT и JavaScript выяснил, что реально могу победить в конкурсе, и как в итоге пришёл к победе.
В июне у меня родился второй ребёнок. Я его, безусловно, люблю, но мало на что способен, когда глаза закрываются от усталости, а на груди висит этот младенец шести недель от роду.
Тем не менее…
Даже в таких условиях я вполне могу перемещаться по дому с банкой WD-40, устраняя различные мелкие неисправности.
Собственно, поэтому моё внимание и привлёк видео-конкурс WD-40 Repair Challenge.
В целом я не особо люблю всяческие онлайн-конкурсы. Я предпочитаю, когда правила выстроены так, чтобы в них было реально выиграть.
Поэтому для начала давайте разберём некоторые заявленные критерии и условия, которые сделали этот конкурс очень привлекательным для участия.
Три благоприятствующих момента
▍ 1. Нюанс в судейских критериях
Я участвую только в тех конкурсах, где победителей выбирают судьи, никогда не рассматривая случайный выбор или победу большинством голосов (конкурсы популярности).
В данном же случае во взвешенном списке критериев оценки присутствовала строка, которая в конечном счёте исключила 80% участников.

Присваивание 1/4 очков заявке только за то, что это видео, уже говорит о высокой приоритетности именно видеоматериалов.
Тем не менее на конкурс, помимо видео-заявок, принимались и заявки с фото. И поскольку такие заявки согласно условиям автоматически недополучали ¼ очков, я решил исключить всех соответствующих участников как неконкурентных, так как их максимальный балл мог составить лишь 75/100.
В этом конкурсе мне также понравилось, что общее качество контента имело небольшой вес. Обычно более высокая значимость этого критерия оказалась бы мне на руку, так как я всегда стремлюсь к высокому качеству исполнения, но напомню…что я был обременён грудным малышом, и ожидать от меня максимального качества было нереально.

⚖️
Чем сложнее список критериев оценки, тем больше мне хочется принять участие в конкурсе. Никто не анализирует эти списки так, как я. Уж чего у меня полно, так это времени, детка…
▍ 2. Широкий список призов

Очевидно, что мы нацелены на первое место, но всего конкурс предполагал 16 денежных призов и 13 призов в виде неких материальных ценностей. Итого в общей сложности 29 призов. Неслыханно много.
Тем не менее мне нужна уверенность, что мои шансы выиграть приз перевесят усилия, необходимые для создания достойной конкурентной заявки. Я не рассматриваю этот процесс как дополнительную подработку. Мне хочется добиться максимального результата, затратив минимум рабочего времени.
▍ 3. Возможность выиграть более одного приза
Если вы можете создать несколько заявок
(n), то этот нюанс вас определённо порадует.

Перевод выделенного текста. Количество заявок от одного участника не ограничено. Участники, реализовавшие несколько проектов и подавшие несколько заявок, могут получить несколько выигрышей. Примерная рыночная стоимость всех призов составляет $16 190.
Анализ конкурса
В общей сложности на конкурс было зарегистрировано 538 заявок. Вероятность победы равная
n/(538+n) не особо обнадёживает, не так ли?Но давайте копнём глубже, чтобы понять, почему победа не так маловероятна, как кажется на первый взгляд. Для этого нам нужно проанализировать заявки.
Сайт конкурса был создан с помощью Laravel Livewire. Livewire не возвращает из своего API сырые данные JSON о заявках на конкурс, передавая вместо них HTML. Это означало, что я не могу просто обратиться к конечной точке
/submissions, чтобы получить информацию о каждой заявке — нужно было её скрейпить.Я составил скрипт Playwright для постраничного перебора галереи заявок и сбора данных, необходимых для анализа конкурентов.
const { chromium } = require("playwright");
const startScraping = async () => {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("https://repair.wd40.com/gallery");
// Клик по кнопке с надписью "Reject All"
await page.click('button:text("Reject All")');
await getAllEntries(page);
await browser.close();
}
startScraping();Поначалу всё довольно просто. Мы переходим в галерею заявок, отклоняем баннер с запросом об использовании куки и инициируем процесс сбора заявок.

▍ Сортировка заявок по категориям
Для понимания своих шансов мне нужно было определить категорию каждой заявки. Напомню, что я могу смело исключить из списка реальных конкурентов фото-заявки, поскольку они при оценке будут иметь значительно меньший приоритет.
Определить категорию каждой заявки можно было только по тому, какая SVG-иконка использовалась для её представления.
При дальнейшем рассмотрении выяснилось, что всего в конкурсе участвовало три вида заявок:
video, photo, и step.
Категория
Step подразумевала дополнительное сопровождение заявки письменными пошаговыми инструкциями. Поскольку отчётливое определение шагов, предпринятых для починки чего-либо в рамках вашего проекта, добавляло 10/100 очков, я понял, что наиболее перспективной в плане выигрыша является именно категория Step. Тогда я решил, что все мои заявки будут относиться именно к ней.Ниже показано, как я реализовал функцию постраничного скрейпинга
getAllEntries и присваивание каждой заявке категории:// Определение категорий заявок по их иконкам
const IMG_ICON = `<path d="M14 9l-2.519 4-2.481-1.96-5 6.96h16l-6-9zm8-5v16h-20v-16h20zm2-2h-24v20h24v-20zm-20 6c0-1.104.896-2 2-2s2 .896 2 2c0 1.105-.896 2-2 2s-2-.895-2-2z"></path>`.trim();
const VIDEO_ICON = `<path d="M16 18c0 1.104-.896 2-2 2h-12c-1.105 0-2-.896-2-2v-12c0-1.104.895-2 2-2h12c1.104 0 2 .896 2 2v12zm8-14l-6 6.223v3.554l6 6.223v-16z"></path>`.trim();
const STEP_ICON = `<path d="m22 17.75c0-.414-.336-.75-.75-.75h-13.5c-.414 0-.75.336-.75.75s.336.75.75.75h13.5c.414 0 .75-.336.75-.75zm-18.25-2.75c.966 0 1.75.784 1.75 1.75s-.784 1.75-1.75 1.75-1.75-.784-1.75-1.75.784-1.75 1.75-1.75zm18.25-1.25c0-.414-.336-.75-.75-.75h-13.5c-.414 0-.75.336-.75.75s.336.75.75.75h13.5c.414 0 .75-.336.75-.75zm-18.25-3.75c.966 0 1.75.784 1.75 1.75s-.784 1.75-1.75 1.75-1.75-.784-1.75-1.75.784-1.75 1.75-1.75zm18.25-.25c0-.414-.336-.75-.75-.75h-13.5c-.414 0-.75.336-.75.75s.336.75.75.75h13.5c.414 0 .75-.336.75-.75zm-18.25-4.75c.966 0 1.75.784 1.75 1.75s-.784 1.75-1.75 1.75-1.75-.784-1.75-1.75.784-1.75 1.75-1.75zm18.25.75c0-.414-.336-.75-.75-.75h-13.5c-.414 0-.75.336-.75.75s.336.75.75.75h13.5c.414 0 .75-.336.75-.75z" fill-rule="nonzero"></path>`
let totalEntries = 0; // Инициализация счётчика для подсчёта общего числа заявок
let imageEntries = 0; // инициализация счётчика для подсчёта количества фото-заявок
let stepEntries = 0; // Инициализация счётчика для подсчёта заявок категории Step
let videoEntries = 0; // Инициализация счётчика для подсчёта видео-заявок
const getAllEntries = async (page) => {
const entries = await page.$$("ul.grid li.contents");
totalEntries += entries.length; // Инкрементирование общего числа заявок на их количество, обнаруженное на текущей странице
for (const entry of entries) {
const entryHtml = await entry.innerHTML();
const a = await entry.$("a");
const href = await a.getAttribute("href");
if (entryHtml.includes(IMG_ICON)) {
// Это неконкурентная фото-заявка
imageEntries++; // Инкрементирование счётчика фото-заявок без вывода на экран
} else {
if (entryHtml.includes(VIDEO_ICON)) {
console.log('Competitive video entry found');
videoEntries++; // Инкрементирование счётчика видео-заявок
} else if (entryHtml.includes(STEP_ICON)) {
console.log('Competitive step entry found');
stepEntries++; // Инкрементирование счётчика заявок Step
} else {
console.log('Unknown entry type');
}
console.log(href); // Вывод на экран href не-фото заявок для дальнейшего анализа
}
}
const nextButton = await page.$('a[rel="next"]');
if (nextButton) {
await nextButton.click();
await page.waitForResponse(response => response.status() === 200);
// Ожидание несколько секунд, чтобы осуществить загрузку и исключить ограничение скорости передачи
await page.waitForTimeout(3200);
await getAllEntries(page); // Рекурсивный вызов
} else {
console.log('No next button found, reached the end');
// Всё готово, теперь можно анализировать заявки и делать умозаключения
console.log(`Total entries processed: ${totalEntries}`);
console.log(`Image entries: ${imageEntries}`); // Вывод общего числа фото-заявок
console.log(`Video entries: ${videoEntries}`); // Вывод общего числа видео-заявок
console.log(`Step entries: ${stepEntries}`); // Вывод общего числа заявок Step
}
}В этой функции мы рекурсивно выполняем серию задач:
- Получаем количество потомков в элементе
ul.grid li.contents(каждый потомок — это заявка на конкурс). - Для каждого потомка:
- Получаем
href, чтобы вывести его в консоль, если соответствующая заявка нам интересна. - Определяем, какая SVG-иконка использовалась для представления заявки:
- Если это фото-заявка, игнорируем её, так как конкурентной угрозы она для нас не представляет.
- Получаем
- Инкрементируем счётчик соответствующего типа заявок:
- Ищем кнопку Next page
a[rel="next"] - Если она существует, кликаем, ожидаем загрузки следующей страницы и повторяем процесс с шага 1.
- Если кнопки нет, значит мы достигли конца всех заявок, и можно выводить результаты.
- Ищем кнопку Next page
Я выполнил скрипт командой
node index.js и получил в консоли такие результаты:No next button found, reached the end
Total entries processed: 538
Image entries: 439
Video entries: 17
Step entries: 67Из всех 538 заявок на конкурс 439 были определены как неконкурентные фото-заявки.
Поделиться ссылкой:
Интересные статьи
Интересные статьи
Имея за плечами опыт работы с Kubernetes в различных облачных провайдерах вроде AWS и Yandex Cloud я столкнулся с необходимостью развертывания кластера вне облака на виртуальных машинах. В статье...
Предлагается пошаговое руководство по дообучению Whisper для любого многоязычного набора данных ASR с использованием Hugging Face
В прошлое воскресенье 4 сентября я обнаружил, что GitHub-организация DigitalDesignSchool, в которой я являюсь одним из владельцев, забанена Гитхабом с формулировкой "ваш аккаунт может быть предметом а...
Вашингтон (Рейтер) — Министерство юстиции США считает расследования атак с использованием программ-вымогателей таким же важным, как и терроризм. После взлома Colonial Pipeline и увели...
Фармацевты и программисты из компаний Sumitomo Dainippon Pharma и Exscientia подбросили дров в костер спора «где должна заканчиваться самостоятельность машины и начинаться контроль в ручном режим...
