- Регистрация
- 07.09.2015
- Сообщения
- 557
- Благодарностей
- 550
- Баллы
- 93
Приветствую всех!
В этой статье хочу описать свой путь в разработке проектов. Статья будет опираться на шаблон в котором собраны решения помогающие ускорить разработку и сократить количество кода. Таким образом вы можете писать ваши проекты взяв его логику за основу, дополнив необходимым функционалом, либо воспользоваться, как готовым решением.
- В статье я хочу обсудить базовые тезисы. Привести примеры методов расширения.
- Шаблон, это готовое решение для ваших потенциальных проектов. А также, демонстрация, как можно строить шаблоны, на примере парсера интернет магазина.
- Видео, призвано объяснить и помочь разобраться в структуре шаблона.
В каждом шаблоне, могут быть базовые наборы действий:
- Работа с тестовым файлом, получение строки, запись строки, чтение файла;
- Отладка шаблона, сохранение репортов, отлов ошибок;
- Работа с браузером;
Если для вас это актуально, то, как вариант, можно использовать [C Sharp] в своих проектах. В статье я раскажу свой путь и ошибки, которые совершил.
МИФ: КОД БЫСТРЕЕ КУБИКОВ
В 2016, когда я купил ZennoPoster, я писал шаблоны на кубиках, они работали и выполняли возложенные на них задачи. На тот момент, факт того, что всё работает, уже был удовлетворительным. Но именно в этот период времени начал зарождаться тренд на создание шаблонов в коде. Это было ново, интересно, но мало понятно. Возможно именно тогда родилась версия, что код работает быстрее кубиков!?
Можно обратить внимание, что какие-то вещи сделаны в коде, тогда я только начинал его внедрять. И у меня, как у начинающего пользователя, кубик C# мог содержать только одно действие:
C#:
instance.ClearCookie();
C#:
Tab Tab1 = instance.ActiveTab;
try
{
string mystring = Tab1.FindElementByXpath("//a[@class='comp']", 0);
System.Threading.Thread.Sleep(3 * 1000);
}
catch (Exception e)
{
project.SendErrorToLog(e.Message);
}
return mystring;
ВЫВОД: Если у вас есть шаблон, который работает на кубиках без ошибок. Отлично! Перенос проекта в код может никак не сказаться на производительности! Код не работает быстрее кубиков!
МИФ: КОД КОМПАКТНЕЕ КУБИКОВ
С одной стороны может показаться, что код будет гораздо компактнее набора кубиков, и это утверждение отчасти является верным, но это не всегда так. Я привел пример, реализации однотипного функционала на кубиках и в коде, как видно, по площади занимаемой на экране они сопоставимы.
ВЫВОД: Не стоит гнаться за переносом всего и вся в код, практическую пользу можно получить только в отдельно взятых случаях. К примеру, в применении настроек для инстанса.
Этот код, действительно будет компактнее, 5 отдельно взятых кубиков.
C#:
instance.LoadPictures = false;
instance.UseCSS = false;
instance.UsePlugins = false;
instance.DownloadFrame = false;
instance.AllowPopUp = false;
ЗАДАЧИ ПЕРЕД КОДОМ
Когда я научился делать проекты на кубиках, мне это быстро наскучило, на тот момент набирали популярность курсы по C# для ZennoPoster. Желание научиться чему-то новому и делать другим образом, отправили меня в путь оптимизации кода.
Плюсы, которые я для себя выделил в переходе от кубиков к коду:
- Надежность работы шаблона при обновлениях.
- Возможность отладки и проверки всех действий;
- Отсутствие однотипного повторяющегося кода;
- Быстрый доступ к коду, читаемость, блочная структура;
- Внесение правок и доработка проекта не должны ломать структуру;
- Быстрый старт - шаблон должен выступать основой новых проектов.
ЧТО БЫЛО ДАЛЬШЕ? ПОВОРОТ НЕ ТУДА!
В желании учиться делать всё в коде, я сначала начал переносить каждый отдельно взятый кубик в его аналог в коде. Затем я начал всё это объединять в логические блоки сокращая количество кубиков с кодом.
В конечном итоге, это привело к рождению шаблона работающего из одно кубика и содержащего в себе +1100 строк. Всё было последовательно и логично, можно было проскролить быстро к нужному участку, но это не то, что я хотел получить, когда ставил цели перейти на код. Меня не удовлетворяло качество отладки, код содержал много однотипных кусков кода.
Что по итогу я получил, строя свой шаблон полностью в коде и одном кубике:
- Сложность в изменении логики работы шаблона, перестроении циклов.
- Много однотипных кусков кода.
- Функционал шаблона сложен в отладке, т.к. приходиться копировать большие однотипные куски проверок для каждого элемента.
- В больших проектах, количество строк при таком подходе может превышать несколько тысяч, включая комментарии. Что делает работу с такими проектами еще сложнее, чем работу с кубиками.
ВЫВОД: Писать в коде можно, нужно, но не так, как это делал я! Итого, я смог справиться только с первым пунктом "Надежность работы шаблона при обновлениях."
РЕШЕНИЕ == ОБЩИЙ КОД
Я задумался, что я делал не так, вроде всё в коде, но это никак не решало поставленных задач. Поиски решения заставили обратить внимание на общий код. Многие статьи на форуме позволили реализовать этот потенциал. Это было новое и сложное решение, логика которого расходилась с логикой написания шаблонов до этого момента, пришлось перестраиваться и экспериментировать.
Способ работы в общем коде № 1:
C#:
public class z
{
public Instance instance;
public IZennoPosterProjectModel project;
public z (Instance instance, IZennoPosterProjectModel project)
{
this.instance = instance;
this.project = project;
}
}
- Необходимо заранее объявлять переменные в кубике, чтобы их можно было передать в метод общего кода;
- Необходимо прописывать out, в случае возврата значений переменных из метода;
- Необходимо объявлять передаваемые переменные внутри метода;
- Необходимо в каждом классе объявлять проект и инстанс, если это необходимо.
Способ работы в общем коде № 2:
C#:
public class z
{
public Instance instance;
public IZennoPosterProjectModel project;
public _is InputSettings;
public z (Instance instance, IZennoPosterProjectModel project)
{
this.instance = instance;
this.project = project;
InputSettings = new _is(this);
}
}
public class _is
{
public _is (z t)
{
}
}
ОТЛАДКА И ПРОВЕРКА ВСЕХ ДЕЙСТВИЙ
- Обернуть в методы все однотипные проверки:
Что можно проверять на странице:
- Элемент;
- Колекцию;
- Элемент в колекции;
- Атрибут элемента.
C#:
/// <tep.IsVoid_e>
/// Проверка существования элемента, возвращает true/false
/// tep.IsVoid_e(t.tab, ".//*");
/// </tep.IsVoid_e>
/// <param name="Tab">Текущая вкладка</param>
/// <param name="xpath">Путь к элементу, xPath</param>
/// <param name="num">Порядковый номер совпадения (int), по умолчанию - 0</param>
/// <param name="b">Логический тип (bool), по умолчанию - false</param>
public static bool IsVoid_e (this Tab tab, string xpath, int num = 0, bool b = false)
{
if (!tab.FindElementByXPath(xpath,0).IsVoid) b = true;
return b;
}
C#:
/// <hap.IsNull_n>
/// Проверка существования HtmlNode (элемента в коллекции), с помощью HtmlAgilityPack, возвращает true/false
/// hap.IsNull_c(t, ".//*");
/// </hap.IsNull_n>
/// <param name="t">Текущий поток</param>
/// <param name="Node">Объект HtmlNode в котором осуществляется проверка</param>
/// <param name="xpath">Путь к элементу, xPath</param>
/// <param name="b">Логический тип, по умолчанию - false</param>
public static bool IsNull_n (z t, HtmlNode Node, string xPath, bool b = false)
{
if (Node.SelectSingleNode(xPath) != null) b = true;
return b;
}
C#:
/// <hap.IsNull_a>
/// Проверка существования атрибута у элемента (в HtmlNode), с помощью HtmlAgilityPack, возвращает true/false
/// hap.IsNull_a(t, Node[i],".//*", "href");
/// </hap.IsNull_a>
/// <param name="t">Текущий поток</param>
/// <param name="Node">Объект HtmlNode в котором осуществляется проверка</param>
/// <param name="xpath">Путь к элементу, xPath</param>
/// <param name="Attribute">Проверяемый атрибут у элемента</param>
/// <param name="b">Логический тип, по умолчанию - false</param>
public static bool IsNull_a (z t, HtmlNode Node, string xPath, string Attribute, bool b = false)
{
if (Node.SelectSingleNode(xPath).Attributes[Attribute] != null) b = true;
return b;
}
- Сделать возможность дебага проекта:
Если мы не смогли найти элемент по xPath, то через else, мы можем присвоить переменной какое-то значение, к примеру "-", "-1", "null". В этом сценарии мы будем выводить Warning.
В случае если мы нашли путь, но переменная IsNullOrEmpty, то мы вернем Error.
Таким образом мы сможем быстро сориентироваться при отладке проекта, т.к. на разных сайтах разметка может изменяться исходя из категории, и для одного элемента необходимо подбирать несколько разных путей xPath.
Метод отладки работы проекта.
C#:
/// <log.DebugInfo>
/// > Метод отладки работы проекта.
/// > Объявляется после необходимого действия в проекте и передает тип оповещения исходя из получаемого значения действия.
/// log.DebugInfo(t, "Stage", "Variable");
/// </log.DebugInfo>
/// <param name="t">Текущий поток</param>
/// <param name="text">Сообщение отправляемое в лог</param>
/// <param name="showInZP">Логический тип (bool), показывать сообщение в ZennoPoster. По умолчанию - true</param>
/// <param name="stage">Указание точки в проекте, за которую отвечает DebugInfo</param>
/// <param name="variable">Переменная значение которой будет передавать в DebugInfo. Исходя из которого будет выбран тип оповещения.</param>
public static void DebugInfo (z t, string stage, string variable)
{
if (t._is.b_DebugMode)
{
if (string.IsNullOrEmpty(variable)) log.SendLogError(t, String.Format("{0} {1}", stage, variable), t._is.b_DebugMode);
else if (variable == "-") log.SendLogWarning(t, String.Format("{0} {1}", stage, variable), t._is.b_DebugMode);
else log.SendLogInfo(t, String.Format("{0} {1}", stage, variable), t._is.b_DebugMode);
}
}
- Создать генератор отчета об ошибках:
- Дата и время ошибки;
- Проки потока;
- Сообщение об ошибке;
- DOM модель страницы;
- Скриншот инстанса.
C#:
/// <e.BugReport>
/// Метод сохраняет отчет об ошибке.
/// > В папке Bug Reports (директория проекта), создается папка "1589207820", где 1589207820 - это время старта потока в UnixTime формате.
/// > В этой папке создается текстовый документ "1589207820.txt", в который сохраняются данные: Прокси потока, DateTime, Сообщение об ошибке, DOM инстанса.
/// > InstanceScreenshot = true, сохранит скриншот инстанса "1589207820.jpg".
/// e.BugReport(t, "Message");
/// </e.BugReport>
/// <param name="t">Текущий поток</param>
/// <param name="excMessage">Сообщение об ошибке</param>
/// <param name="InstanceScreenshot">Логический тип (bool), по умолчанию - false. Отвечает за сохранение скриншота инстанса.</param>
public static void BugReport (z t, string excMessage, bool InstanceScreenshot = false)
{
lock(Locker.FileExceptionReport)
{
string DOM = string.Empty;
if (!Directory.Exists(Path.Combine(t.project.Directory, "Bug Reports"))) Directory.CreateDirectory(Path.Combine(t.project.Directory, "Bug Reports"));
if (!Directory.Exists(Path.Combine(t.project.Directory, "Bug Reports", t._ts.unixtime.ToString()))) Directory.CreateDirectory(Path.Combine(t.project.Directory, "Bug Reports", t._ts.unixtime.ToString()));
if (!t.tab.IsVoid || !t.tab.IsNull) DOM = t.tab.DomText.ToString();
string TxtFilePath = Path.Combine(t.project.Directory, "Bug Reports", t._ts.unixtime.ToString(), String.Format("{0}.txt", t._ts.unixtime.ToString()));
string ImgFilePath = Path.Combine(t.project.Directory, "Bug Reports", t._ts.unixtime.ToString(), String.Format("{0}.jpg", t._ts.unixtime.ToString()));
string myLine = String.Format("Proxy :: {1};{0}DateTime :: {2};{0}Exception Message :: {3};{0}{0}DomText ::{0}{4}", "\r\n", t._ts.proxy, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), excMessage, DOM);
Files.Writer(t, Locker.FileExceptionReport, TxtFilePath, myLine);
if (InstanceScreenshot && !t.tab.IsVoid || InstanceScreenshot && !t.tab.IsNull)
{
using (var ms = new MemoryStream(Convert.FromBase64String(t.tab.GetPagePreview())))
using (var bm = new Bitmap(ms))
{
bm.Save(ImgFilePath, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
}
}
- Создать свой расширенный Exception метод:
Метод заменяет стандартный throw new Exception(e)
C#:
/// <e.ThrowExc>
/// Метод заменяет стандартный throw new Exception(e)
/// > Метод вернет строки Proxy и Task в текстовые файлы.
/// > Вызовет метод e.BugReport, чтобы сохранить отчет об ошибке в папку Bug Reports (директория проекта).
/// > Вызовет throw new Exception(e), чтобы завершить работу с ошибкой.
/// e.ThrowExc(t, "Message");
/// </e.ThrowExc>
/// <param name="t">Текущий поток</param>
/// <param name="excMessage">Сообщение об ошибке</param>
public static void ThrowExc (z t, string excMessage)
{
try
{
Files.Writer(t, Locker.FileProxy, t._is.FilePath_Proxy, t._ts.proxy);
Files.Writer(t, Locker.FileTask, t._is.FilePath_Task, t._ts.task);
if (!excMessage.Contains("Manual Stopped")) e.BugReport(t, excMessage, true);
}
finally
{
throw new Exception(excMessage);
}
}
УНИВЕРСАЛИЗАЦИЯ ОДНОТИПНОГО КОДА
Работая с файлами и списками, я часто сталкивался с проблемой, что один и тот же код приходилось копировать для работы с разными файлами, хотя могли изменяться всего несколько составляютщих: lock, путь к файлу, переменная.
Чтобы не делать отдельно взятый метод для каждого файла в проекте, необходимо сделать эти методы универсальными. Таким образом, мы уберем повторы однотипного кода в проекте.
Метод чистит список от дубликатов:
C#:
/// <e.DoubleDel>
/// Метод чистит список от дубликатов
/// e.DoubleDel(t, t.Lists.lst_TxtFiles);
/// </e.DoubleDel>
/// <param name="t">Текущий поток</param>
/// <param name="MyList">Список List<string>, который чистим от дубликатов</param>
public static void DoubleDel (z t, List<string> MyList)
{
lock(Locker.lstDuplicates)
{
HashSet<string> hs = new HashSet<string>(MyList);
MyList.Clear();
MyList.AddRange(hs);
}
}
Метод читает все строки из текстового файла в список
C#:
/// <Files.ReadAll>
/// Читаем все строки из текстового файла в список
/// Files.ReadAll(t, Locker.FileTask, @"D:\sourceList.txt", t.Lists.lst_TxtFiles);
/// </Files.ReadAll>
/// <param name="t">Текущий поток</param>
/// <param name="myLocker">Объект lock(object)</param>
/// <param name="myFilePath">Путь к текстовому документу</param>
/// <param name="MyList">Список List<string> в который отправим данные из файла</param>
public static void ReadAll (z t, object myLocker, string myFilePath, List<string> MyList)
{
lock(myLocker)
{
try
{
string data = string.Empty;
using (var stream = File.Open(myFilePath, FileMode.Open, FileAccess.Read))
using (var reader = new StreamReader(stream))
{
if (new FileInfo(myFilePath).Length > 0) while (!reader.EndOfStream) MyList.Add(reader.ReadLine());
else e.ThrowExc(t, String.Format(t._pm.m2, myFilePath));
}
}
catch (IOException exc)
{
e.ThrowExc(t, String.Format(t._pm.m3, "Files.ReadAll()", myFilePath, exc.GetType().Name));
}
}
}
Метод чистит список по файлу BlackList.txt
C#:
/// <Files.BlackList>
/// Чистим список по файлу BlackList.txt
/// > По умолчанию, BlackList.txt должен находиться в директории проекта.
/// Files.BlackList(t, Locker.FileBlackList, t.Lists.lst_TxtFiles);
/// </Files.BlackList>
/// <param name="t">Текущий поток</param>
/// <param name="myLocker">Объект lock(object)</param>
/// <param name="MyList">Список List<string>, который проверяем.</param>
public static void BlackList (z t, object myLocker, List<string> MyList)
{
lock(myLocker)
{
string myFilePath = Path.Combine(t.project.Directory, "BlackList.txt");
List<string> blackList = new List<string>();
try
{
using (var stream = File.Open(myFilePath, FileMode.Open, FileAccess.Read))
using (var reader = new StreamReader(stream))
{
if (new FileInfo(myFilePath).Length > 0)
{
while (!reader.EndOfStream) blackList.Add(reader.ReadLine());
List<string> Except = MyList.AsParallel().AsOrdered().Except(blackList.AsParallel(), StringComparer.OrdinalIgnoreCase).ToList();
MyList.Clear();
MyList.AddRange(Except);
}
else log.SendLogWarning(t, String.Format(t._pm.m2, myFilePath));
}
}
catch (IOException exc)
{
e.ThrowExc(t, String.Format(t._pm.m3, "Files.BlackList()", Path.GetFileName(myFilePath), exc.GetType().Name));
}
}
}
ЧИТАЕМОСТЬ КОДА, БЛОЧНАЯ СТРУКТУРА
Использование общего кода позволяет групировать методы в классы, а классы структурировать в удобном порядке. Важно понимать, что порядок расположения независимых классов и методов, не влияет на их работоспособность. Таким образом обернув методы и классы в регионы "#region", мы получим структуру кода в которой легко ориентироваться и не составит труда найти нужный метод, чтобы внести коретировки в код.
РЕЗУЛЬТАТЫ
Применение на практике, позволило прийти к новому способу написания шаблонов. Благодаря этим решениям я смог закрыть все поставленные задачи:
Надежность работы шаблона при обновлениях.Возможность отладки и проверки всех действий;Отсутствие однотипного повторяющегося кода;Быстрый доступ к коду, читаемость, блочная структура;Внесение правок и доработка проекта не должны ломать структуру;Быстрый старт - шаблон должен выступать основой новых проектов.
- Объявить переменные для проекта;
- Собрать xPath к элементам;
- Составить каркас базы данных;
- Прописать работу с элементами на странице (Data Collect).
ВИДЕО
Чтобы проще понять принцип устройства шаблона, я записал видео в котором даю пояснение по реализованным методам и структуре шаблона.
0 - Предыстория кода;
5:15 - Логика работы шаблона;
8:50 - Основа общего кода;
12:20 - Демонстрация работы шаблона;
15:30 - Кубик C# в шаблоне;
16:30 - Структура Name Space;
18:35 - Как быстро развернуть новый проект;
19:50 - Классы внутри шаблона;
20:30 - Переменные проекта;
29:00 - Tab / HtmlAgilityPack;
32:55 - log - Логирование проекта;
37:10 - Класс Proxy;
40:05 - Класс Task;
41:10 - Класс Files;
47:50 - Класс Captcha;
50:30 - Technical Extension Voids;
53:00 - Bug Report;
55:00 - Класс Database;
56:40 - Класс Data;
1:05:45 - Класс z;
1:06:35 - Ошибка: Object reference not set to an instance of an object.
Приношу извинения за звук в последние 7 минут видео, youtube не хотел пропускать из-за Fleetwood Mac - The Chain (Remastered Version), которая играла фоном. Пришлось делать Noise Reduction и накладывать новую дорожку с музыкой.
5:15 - Логика работы шаблона;
8:50 - Основа общего кода;
12:20 - Демонстрация работы шаблона;
15:30 - Кубик C# в шаблоне;
16:30 - Структура Name Space;
18:35 - Как быстро развернуть новый проект;
19:50 - Классы внутри шаблона;
20:30 - Переменные проекта;
29:00 - Tab / HtmlAgilityPack;
32:55 - log - Логирование проекта;
37:10 - Класс Proxy;
40:05 - Класс Task;
41:10 - Класс Files;
47:50 - Класс Captcha;
50:30 - Technical Extension Voids;
53:00 - Bug Report;
55:00 - Класс Database;
56:40 - Класс Data;
1:05:45 - Класс z;
1:06:35 - Ошибка: Object reference not set to an instance of an object.
Приношу извинения за звук в последние 7 минут видео, youtube не хотел пропускать из-за Fleetwood Mac - The Chain (Remastered Version), которая играла фоном. Пришлось делать Noise Reduction и накладывать новую дорожку с музыкой.
Спасибо за ваше внимание! Надеюсь совокупность статьи, шаблона и видео, позволили раскрыть тему. На своем примере я хотел показать, что стремление к какой-то идее, не всегда приводит к желанному результату и шаблон на 1100 строк кода, тому пример. Этой статьёй вы можете подчеркнуть какие-то идеи и выбрать оптимальный путь в написании шаблонов, чтобы это было удобно именно вам!
Если вам понравилась данная конкурсная работа, пожалуйста, проголосуйте за неё!
- Тема статьи
- Другое
- Номер конкурса статей
- Тринадцатый конкурс статей
Вложения
-
968,6 КБ Просмотры: 444
Для запуска проектов требуется программа ZennoPoster или ZennoDroid.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...
Для того чтобы запустить шаблон, откройте нужную программу. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.
Последнее редактирование модератором: