- Регистрация
- 06.06.2023
- Сообщения
- 30
- Благодарностей
- 68
- Баллы
- 18
Преамбула
Сначала расскажу немного о себе. С ZennoPoster знаком больше 5-ти лет, но ничего серьёзного не делал до начала прошлого года, даже лицензии ПРО не было. Лишь иногда подписывался на какие-то халтурки, зная основы C# и автоматизации. Позже получилось сделать свои первые более серьёзные шаги, создавая кастомные решения для знакомых сеошников, спамеров, темщиков и прочих персонажей связанных с вебом. И в какой-то момент доходы начали заметно выделяться среди прочих. Решил попробовать забуриться поглубже, и результат меня порадовал. Даже без репутации на форуме и фриланс площадках периодически обращаются люди(по знакомству), что не может не радовать. А так же, находясь в среде автоматизаторов, так или иначе, начинаешь узнавать разные "темы" и пробовать реализовывать их. Первые большие эксперименты были не очень удачные, но терпимы для того, чтобы продолжать. О них рассказывать не буду, они заюзанные до смерти, и не интересные с точки зрения результатов. А здесь расскажу о наполовину удачном кейсе работы с МегаМаркетом(ММ или мм).Всего будет 3 части:
- Часть 1 "Прошлое". 1 миллион СберСпасибо на рефках и отменах!
В этой части расскажу о том, как я залутал за пару месяцев 1млн СберСпасибо на ММ с помощью ZennoPoster. И в целом поделюсь тем, как пишу свои шаблоны. - Часть 2 "Настоящее". Провал ценою в десятки тысяч рублей в сутки на работягах
Здесь расскажу как хотел помочь работягам разбогатеть ничего не делая, автоматизировав их работу, но сам стал работягой - Часть 3 "Будущее". Что со всем этим делать?
Продемонстрирую болванку шаблона по парсингу кешбека, подведу итоги, расскажу о дальнейших планах
Думаю, что цикл моих статей подойдёт для новичков, которые задаются вопросом о том, как зарабатывать с помощью Зеннопостер. В проектах будет сочетаться простота действий(загрузки, клики, ввод данных и т.д.), и в то же время, попытка построения какой-то более серьёзной сетки шаблонов, связанных друг с другом с помощью сторонних дополнений в виде БД.
Вступление
«Мегамаркет» остаётся лидером по развитию среди маркетплейсов на рынке e-Grocery.
«Мегамаркет» сумел обойти ближайшего конкурента и занял третье место по общему обороту среди мультикатегорийных маркетплейсов по итогу IVквартала 2023 года.
Примерно такие заголовки можно было увидеть в новостных пабликах в конце 2023 начале 2024 года. К чему эти заголовки, спросите вы, ведь вряд ли кто-то из нас гендиректор или один из топ-менеджеров ММа. Премию за кратный рост Греф не выпишет. Так зачем нам стоит обращать на подобные заголовки внимание?«Мегамаркет» отчитался о пятикратном росте продаж на площадке за 2023 год
Google, Facebook, Yandex и т.д. -- это большие онлайн-проекты, целые экосистемы, с множеством продуктов и услуг, которые связанны друг с другом с помощью интернета и дающие людям кучу возможностей. Но кроме инструментов для обычных юзеров, в этих экосистемах есть и "несовершенности". Сложность проектов не позволяет уследить за всем, а потребность в постоянном росте клиентской базе вынуждает идти на некоторые риски, что даёт дорогу для "дополнительных возможностей", которые возможно использовать с помощью ZennoPoster. Но все эти фанк-корпорации давно устоялись, их уровень защиты очень высок, и чтобы пользоваться "дополнительными возможностями" надо изрядно постараться. В чатах часто можно увидеть обсуждения о том, как сейчас регать gmail или аккаунты facebook. Если раньше хватало декстопной web-реги, то сейчас надо чуть ли не свою мобильную ферму, чтобы чувствовать себя комфортно на этих рынках. Так а причём тут ММ? МегаМаркет -- это растущий проект экосистемы Сбера, который в данный момент претерпевает все проблемы роста, а это означает, что пороговых вход, чтобы воспользоваться "дополнительными возможностями", очень низкий. Надо лишь понять, что делать... и понять, откуда такой рост у ничем не отличающегося от других маркетплейсов?(много много много очень много халявы)ZennoPoster - это софт, который позволяет пользоваться такой информацией, даже если у вас в кармане всего пару тысяч рублей, а ваши другие характеристики не самые лучшие
Путь к реализации
Об этой теме я узнал в конце 2023 года(что поздновато). Знал, что кто-то крутит, но не знал как, ведь не выгодно же, да и вообще не ММ, а МММ какой-то... Но как-то же на этом зарабатывают... 1 сбс = 1 руб"Получите бонусы за заказы друзей, для этого отправьте им прокомод. Каждый получит скидку 1 000 руб. на первый заказ, а вы — 1 000 бонусов СберСпасибо за каждого друга. Приведите десять друзей и получите максимальный размер поощрения — 10 000 СберСпасибо. Промокод дает выгоду в 1 000 рублей при заказе от 4 000 рублей..."
Это реальный промокод, который работает прямо сейчас, подгончик на неплохую скидку в 1к, а я получу 1к СберСпасибо: fr393rkv785
- раздать свои корешам, и ждать пока они разбогатеют быстрее меня и накупят всяких приколов с моей скидкой или подкинуть промик в статье о том, как заработать мильён
- сделать новые аккаунты, найти товар, который возможно перепродать в моём городе, оформить заказ с промиком, ждать доставки, получить товар и перепродать, дождаться бонусов на акк рефки, обналичить
Рабочая связка
Итак, связка которая работала до марта месяца и позволила мне заработать 1млн+ бонусов СберСпасибо, в простонародье "Работа по отменам":- Создаётся новый аккаунт.
- Создаётся корзина из 2-х товаров: 1. Дорогой товар должен быть одеждой ценой от 3980 до 3999 2. Любой дешевый, быстро доставляемый товар ценой до 25 рублей. Общая сумма корзины должна быть не меньше 4к.
- Заполняются поля о доставке(указывается реальный адрес, куда надо доставлять), вводится промокод(рефка), получателя оставляем дефолтным от новорега и в способе оплаты выбирается "При получении", другими словами платить будем только после того, как примерим заказ, но это лишь хитрость, примерять ничего не надо.
- После клика по оформлению заказа переходим к нему и делаем 2 действия:
1. Меняем получателя для дешевого товара на нас, настоящих
- После этого получаем следующую картину на заказе
Автоматизация
Думаю, что фронт работы для ZennoPoster видно отчётливо. Было написано 4 шаблона:1. Регер
2. Заказчик
2.1. Обработчик ошибок после оформления заказа
3. Отменятор
Технически, на этом этапе в шаблонах не было ничего сверх крутого, обычная веб-автоматизация со списками и таблицами, позже всё переехало на БД, но это уже другая история. Шаблоны прикладывать не буду, лишь скрины, так как на сегодня они не актуальны в том виде в котором остались, а новые завязаны с моей БД и переделывать всё слишком долго, да и не нужно. А если кого-то всё таки заинтересовало, то можно всегда написать в ЛС, придумаем что-нибудь.
1. Генеральная линия шаблона идёт строго слева-->направо, лишь иногда позволяю отступится для визуальной красоты, но при этом стараюсь сделать так, чтобы интуитивно было понятно, где проходит ожидаемый сценарий
2. Все этапы обозначены заметками над ними
3. Возможные, ожидаемые ошибки обрабатываются снизу основной линии и помечены красными заметками
4. Есть переменная "status_project", которая помогает определять этап на котором находится проект, очень помогает в БедЭнде.
Подобный подход диктует то, как должны писаться сами действия автоматизации
1. Обозначение того, что делается в кубиках с помощью заметок
2. Обозначение нового этапа через переменную "status_project"(п.с. важно отметить, что данная переменная скорее подстраивается под логику обработки ошибок на том или ином этапе, нежели под этапы самих действий)
Лично Я в 90% случаев после каждого действия(загрузка, клик, ввод данных и т.д.) ожидаю несколько реакций. Отсюда выработались некоторое правила, которые проще всего увидеть и понять на шаблонном примере загрузки страницы
Пример загрузки страницы с 4 вариантами:
var hlp = new Demiddima.Web(instance, project);
#region Переменные
string idAcc = project.Variables["id_acc"].Value;
string url = "";
#endregion
try
{
for(int i = 0; i <= 1; i++)
{
instance.ActiveTab.MainDocument.EvaluateScript($"window.location.replace('{url}');");
Thread.Sleep(5000);
for(int j = 0; j <= 10; j++)
{
if(hlp.BoolElement("", 300))
{
return "";
}
else if(hlp.BoolElement("",300))
{
return "";
}
else if(hlp.BoolElement("",300))
{
return "";
}
else if(hlp.BoolElement("",300))
{
return "";
}
}
project.SendToLog($"При попытки загрузить сайт '{url}' не появился не один из ожидаемых вариантов", LogType.Info, false, LogColor.Orange);
}
return "";
}
catch (Exception ex)
{
project.Variables["error"].Value = project.Variables["error"].Value + "/n" + $"Ошибка на аккаунте --{idAcc}-- в экшене ''. " + ex.Message;
return "Ошибка ";
}
4. Так же иногда при ошибках требуется начать весь комплекс действий сначала, тогда появляются переменные типа "try_", которые собственно обозначают кол-во попыток, которые уже были сделаны для конкретного действия. Ну и через обычный счётчик(цифра 7), всё это дело регулируется.
5. Ну и когда за n комплексных попыток ничего не получилось, появляется переменная "sub_status_project", которая локально помогает разрулить то, что это за ошибка и отправить в нужный обработчик возможных, ожидаемых ошибок(цифра 6). Используется редко и даже в этом случае она не нужна, осталась как реликвия.
Ещё в самом кубике с кодом можно увидеть некоторую предподготовку в виде подвязки хелпера из общего кода и региона для объявления переменных, где уже есть 1 переменная "id_acc", которую стараюсь использовать почти всегда, не зависимо от того, будет в ней цифирный id или же логин профиля. А так же заготовленная конструкция
try{}catch(){}
, которая так же кочует практически в неизменном виде из проекта в проект в подобных кубиках, особенно если есть какая-та сторонняя логика, которая может выбивать ошибки.
Общий код:
namespace Demiddima
{
public class Web
{
public Instance instance;
public IZennoPosterProjectModel project;
public Web(Instance newInstance, IZennoPosterProjectModel newProject)
{
instance = newInstance;
project = newProject;
}
/// <summary>
/// Получение элемента
/// </summary>
/// <param name="xpath"></param>
/// <param name="timeout"></param>
/// <param name="index"></param>
/// <param name="wait"></param>
/// <returns></returns>
public HtmlElement GetElement(string xpath, int timeout = 5000, int wait = 100)
{
DateTime timeoutDT = DateTime.Now.AddMilliseconds(timeout);
while(DateTime.Now < timeoutDT)
{
HtmlElement element = instance.ActiveTab.FindElementByXPath(xpath, 0);
if (!element.IsVoid)
{
project.SendToLog($"GetElement confirming: элемент {xpath} найден", LogType.Info, false, LogColor.Gray);
return element;
}
Thread.Sleep(wait);
}
throw new Exception($"GetElement error: элемент '{xpath}' не получен за {timeout} миллисекунд каждая");
}
/// <summary>
/// Проверка элемента на наличие через bool
/// </summary>
/// <param name="xpath"></param>
/// <param name="timeout"></param>
/// <param name="index"></param>
/// <param name="wait"></param>
/// <returns></returns>
public bool BoolElement(string xpath,
int timeout = 5000, int wait = 100)
{
DateTime timeoutDT = DateTime.Now.AddMilliseconds(timeout);
while(DateTime.Now < timeoutDT)
{
HtmlElement element = instance.ActiveTab.FindElementByXPath(xpath, 0);
if (!element.IsVoid)
{
project.SendToLog($"BoolElement confirming: элемент {xpath} существует",LogType.Info, false, LogColor.Gray);
return true;
}
Thread.Sleep(wait);
}
project.SendToLog($"BoolElement error: элемент {xpath} не существует",LogType.Warning, false, LogColor.Orange);
return false;
}
/// <summary>
/// Проверка на исчезновение
/// </summary>
/// <param name="xpath"></param>
/// <param name="timeout"></param>
/// <param name="index"></param>
/// <param name="wait"></param>
/// <returns></returns>
public bool DisappearElement(string xpath, int timeout = 5000, int wait = 500)
{
DateTime timeoutDT = DateTime.Now.AddMilliseconds(timeout);
while (DateTime.Now < timeoutDT)
{
HtmlElement element = instance.ActiveTab.FindElementByXPath(xpath, 0);
if (!element.IsVoid)
{
Thread.Sleep(wait);
continue;
}
project.SendToLog($"DisappearElement confirming: элемент {xpath} исчез", LogType.Info, false, LogColor.Gray);
return true;
}
throw new Exception($"DisappearElement error: элемента '{xpath}' не исчез за {timeout} миллисекунд");
}
/// <summary>
/// Клик по элементу через RiseEnent
/// </summary>
/// <param name="xpathClick"></param>
public void Click(string xpathClick, string emulation = "Middle")
{
HtmlElement elementClick = GetElement(xpathClick, 1000);
elementClick.RiseEvent("click", emulation);
project.SendToLog($"Click confirming: по элементу {xpathClick}", LogType.Info, false, LogColor.Gray);
}
/// <summary>
/// ФулКлик по элементу
/// </summary>
/// <param name="xpath"></param>
/// <param name="timeout"></param>
/// <param name="index"></param>
/// <param name="wait"></param>
public HtmlElement FullClick(string xpathFullClick, int timeout = 1000, int wait = 250)
{
HtmlElement element = GetElement(xpathFullClick, 1000);
instance.ActiveTab.FullEmulationMouseMoveToHtmlElement(element);
instance.ActiveTab.FullEmulationMouseClick("left", "click");
project.SendToLog($"FullClick confirming: по элементу {xpathFullClick}", LogType.Info, false, LogColor.Gray);
return element;
}
/// <summary>
/// Клик по элементу с выбором эмуляции и ожиданием его исчезновения
/// </summary>
/// <param name="xpathClick"></param>
/// <param name="timeout"></param>
/// <param name="indexClick"></param>
/// <param name="wait"></param>
/// <param name="emulation"></param>
public void ClickDisappear(string xpathClick, int timeoutDisappear = 10000, int waitDisappear = 500, string emulation = "Middle")
{
for(int i = 0; i <= 1; i++)
{
try
{
Click(xpathClick, emulation);
DisappearElement(xpathClick, timeoutDisappear, waitDisappear);
project.SendToLog($"Завершение работы метода ClickDisappear", LogType.Info, false, LogColor.Green);
break;
}
catch(Exception ex) when (i == 1)
{
throw new Exception("Ошибка выполнения ClickDisappear: " + ex.Message);
}
catch
{
continue;
}
}
}
/// <summary>
/// Проверка value элемента на указанные данные
/// </summary>
/// <param name="element"></param>
/// <param name="xpath"></param>
/// <param name="text"></param>
public void CheckValueElement(HtmlElement element, string xpath, string text)
{
string valueElement = element.GetValue();
if (valueElement.Contains(text))
{
project.SendToLog($"CheckValueElement confirming: у элемента '{xpath}' совпадает value с назначенным '{text}'", LogType.Info, false, LogColor.Gray);
}
else
{
throw new Exception($"CheckValueElement error: element '{xpath}' c значением '{text}' не найден");
}
}
/// <summary>
/// Проверка value элемента на указанные данные
/// </summary>
/// <param name="element"></param>
/// <param name="xpath"></param>
/// <param name="text"></param>
public void CheckValueElement(string xpathElement, string xpath, string text)
{
HtmlElement element = GetElement(xpathElement, 1000);
if (element.GetValue().Trim().Contains(text))
{
project.SendToLog($"CheckValueElement confirming: у элемента '{xpath}' совпадает value с назначенным '{text}'", LogType.Info, false, LogColor.Gray);
}
else
{
throw new Exception($"CheckValueElement error: element '{xpath}' c значением '{text}' не найден");
}
}
/// <summary>
/// Получение значения атрибута
/// </summary>
/// <param name="xpathElement"></param>
/// <param name="nameAttribute"></param>
/// <returns></returns>
public string GetValueAttribute(string xpathElement, string nameAttribute)
{
HtmlElement element = GetElement(xpathElement, 1000);
string value = element.GetAttribute(nameAttribute).Trim();
project.SendToLog($"GetValueAttribute confirming: значение атрибута '{nameAttribute}' элемента '{xpathElement}' получено", LogType.Info, false, LogColor.Gray);
return value;
}
/// <summary>
/// Получить значение после регулярки
/// </summary>
/// <param name="text"></param>
/// <param name="regex"></param>
/// <returns></returns>
public string GetValueRegex(string text, string regex)
{
Match match = Regex.Match(text, $@"{regex}");
return match.Value;
}
/// <summary>
/// Нажатие по клавише указанное кол-во раз
/// </summary>
/// <param name="button"></param>
/// <param name="endCycle"></param>
public void ButtonInCycle(string button,int endCycle = 1)
{
for (int i = 0; i < endCycle; i++)
{
instance.WaitFieldEmulationDelay();
instance.SendText($"{button}", 15);
}
project.SendToLog($"ButtonInCycle confirming: все кнопки были нажаты", LogType.Info, false, LogColor.Gray);
}
/// <summary>
/// Установка value элемента через эмуляцию клавиатуры без проверки
/// </summary>
/// <param name="xpathFullClick"></param>
/// <param name="text"></param>
/// <param name="latency"></param>
/// <param name="timeoutFullClick"></param>
/// <param name="indexFullClick"></param>
/// <param name="wait"></param>
public void SetValue(string xpathSet, string text, string emulationSetValue = "Middle", string emulationFocus = "Middle", int wait = 100)
{
HtmlElement element = GetElement(xpathSet, 1000);
element.RiseEvent("focus", emulationFocus);
element.SetValue(text, emulationSetValue);
}
/// <summary>
/// Установка value элемента через эмуляцию клавиатуры с проверкой
/// </summary>
/// <param name="xpathFullClick"></param>
/// <param name="text"></param>
/// <param name="latency"></param>
/// <param name="timeoutFullClick"></param>
/// <param name="indexFullClick"></param>
/// <param name="wait"></param>
public void SetValueCheck(string xpathSet, string text, string emulationSetValue = "Middle", string emulationFocus = "Middle", int wait = 100)
{
HtmlElement element = GetElement(xpathSet, 1000);
element.RiseEvent("focus", emulationFocus);
element.SetValue(text, emulationSetValue);
CheckValueElement(element, xpathSet, text);
}
/// <summary>
/// Ввод текста с предварительным удалением значения в поле без проверки
/// </summary>
/// <param name="xpathFullClick"></param>
/// <param name="text"></param>
/// <param name="latency"></param>
/// <param name="timeoutFullClick"></param>
/// <param name="indexFullClick"></param>
/// <param name="wait"></param>
public void SetValueDelete(string xpathSet, string text, string emulationSetValue = "Middle", string emulationFocus = "Middle", int latency = 20)
{
HtmlElement element = GetElement(xpathSet, 1000);
int lengthValue = element.GetValue().Length;
//Удаление
element.RiseEvent("focus",emulationFocus);
ButtonInCycle("{END}");
ButtonInCycle("{BACKSPACE}",lengthValue);
element.SetValue(text, emulationSetValue);
}
/// <summary>
/// Ввод текста с предварительным удалением значения в поле с проверкой
/// </summary>
/// <param name="xpathFullClick"></param>
/// <param name="text"></param>
/// <param name="latency"></param>
/// <param name="timeoutFullClick"></param>
/// <param name="indexFullClick"></param>
/// <param name="wait"></param>
public void SetValueDeleteCheck(string xpathSet, string text, string emulationSetValue = "Middle", string emulationFocus = "Middle", int latency = 20)
{
HtmlElement element = GetElement(xpathSet, 1000);
int lengthValue = element.GetValue().Length;
//Удаление
element.RiseEvent("focus",emulationFocus);
ButtonInCycle("{END}");
ButtonInCycle("{BACKSPACE}",lengthValue);
element.SetValue(text, emulationSetValue);
CheckValueElement(element, xpathSet, text);
}
/// <summary>
/// Получение индекса рандомного элемента из коллекции
/// </summary>
/// <param name="xpathCollection"></param>
/// <returns></returns>
public int GetRandomIndexElement(string xpathCollection)
{
HtmlElementCollection elements = instance.ActiveTab.FindElementsByXPath(xpathCollection);
int index = new Random().Next(0, elements.Count - 1);
return index;
}
}
}
public static class StringExtensions
{
public static int ToInt(this string str)
{
return int.Parse(str);
}
public static double ToDouble(this string str)
{
return double.Parse(str);
}
public static float ToFloat(this string str)
{
return float.Parse(str);
}
public static bool ToBool(this string str)
{
return bool.Parse(str);
}
public static DateTime ToDateTime(this string str)
{
return DateTime.Parse(str);
}
public static long ToLong(this string str)
{
return long.Parse(str);
}
}
Финансы
Вот и самая интересная тема. Считаем мои деньги. Период основной активности автоматизированной работы пришёлся на Январь-Февраль.Математика расходов:
- стоимость 1 аккаунта: на момент работы была 30р (сейчас 108р на смсхабе)
- стоимость выкупа 1 дешевого товара: до 25р
- средняя стоимость 1 промокода: 500р
Всего было куплено 102 промика, для красоты будем считать 100. Каждый промик требует 10 заказов, а значит нам надо 1000 аккаунтов.
Считаем математику расходом для 1000 аккаунтов:
- рега: 30*1000=30000р
- выкуп: 25*1000=25000р
- мобильные прокси: 10к за 2 месяца 5 потоков
- промики: 100*500 = 50000
ВСЕГО РАСХОДОВ ЗА ВСЁ ВРЕМЯ НА 1000 АККАНУТОВ: 30к+25к+10к+50к=115к
Математика доходов:
- средний коэффициент обнала: 0.8
- каждый промокод приносит: 10к СберСпасибо
- заработано со 100 промиков: 100*10000 = 1 000 000 СберСпасибо
ЧИСТЫЙ ДОХОД ЗА ВСЁ ВРЕМЯ ДЛЯ 100 ПРОМОКОДОВ: 1 000 000 * 0,8 - 115к = 685к
Эх, вот так из 1млн получилось чуть больше полумиллиона Кликбейт получается...
Но это ещё не всё. 100к потерял, когда хотел разбогатеть путём объединения баллов на один аккаунт, применяя промокод 20к\100к, купив телефон\технику с большим кешбеком процентов на 20, и перепродажи сего, но.... мне заморозили этот аккаунт и ограничили действия на нём Вот и вышло в итоге +-500к
К сожалению результатов сохранилось не так много, не думал, что буду показывать кому-то кейс. А даже если бы и сохранилось, то пруфануть было бы сложно, потому что обналичка шла разными способами, где-то просто у чела, который обменивал эти баллы на рубли или крипту, где-то закупался на 10к, применял промокод на 2-3к и перепродавал. Да ещё ко всему прочему, были другие доходы\расходы, поэтому отделить мух от котлет сейчас сложно.
P.S. Тема с отменами ещё жива, и будет работать до 30 июня, а потом хз, может быть обнулят и ещё раз начнётся всё. Но сейчас очень тяжело достать рефки и отменять через одежду больше не получится. Рабочая связка требует небольшого капитала и холда, выглядит так: прогретый новорег-->корзина на сумму 4к с дорогим и дешевым товаром-->оплата всего заказа через СберПей-->изменение получателя на дешевом товаре-->отмена дорого товара через чат-->ожидание возврата денег от отмены в течении 3 раб дней-->забираем дешевый товар-->ждём 14 дней для бонусов-->профит
Выводы
Это мои первые, более и менее, нормальные деньги заработанные не на выполнении заказов, а на "теме", которую почти самостоятельно получилось раскрыть. Опыт был интересный. Во второй части, расскажу о нескольких решёных технических задачах при реализации УльтимейтСофта для работы с ММ(подключение MySql через EF, а так же решение зависания инстансов движка Хромиум на сайтах с динамической подгрузкой данных), но как в итоге потерпел фиаско на стороне самой идеи, а не её реализации.Если вижу, что какая-та ранее неизвестная интернетная хрень хайпится, значит сейчас она наиболее уязвима для раскрытия "дополнительных возможностей"
Последнее редактирование: