Привет вам, дорогие форумчане!
Как и в предыдущей статье я буду рассказывать о том что интересно мне и в итоге попытаюсь заинтересовать и Вас!
Я люблю экспериментировать, изучать языки программирования, искать нестандартные технические решения. Всем, кто читал мою предыдущую конкурсную статью и не нашел в ней смысла, предлагаю не тратить свое драгоценное время на выяснение какой же континент я здесь пытаюсь открыть. Надеюсь, что кто-нибудь хоть как-то да оценит мои эксперименты.
И в этот раз моя любознательность завела меня в область параллельных вычислений и что с помощью них можно сделать.
Говоря параллельных вычислениях я подразумеваю те способы или методы, реализованные в языках программирования, которые позволяют выполнять определенные параллельно, независимо друг от друга операции. В противовес можно поставить последовательные операции, а самым наглядным примером будет последовательность выполнения задач в шаблоне и многопоточное выполнение шаблона.
В принципе мультизадачность очень серьезная и обширная тема, а мне бы хотелось рассказать конкретные моменты, которые действительно можно применить в программе, когда есть такая возможность или желание.
Иногда так бывает, что перед шаблоном стоит задача просто получать данные с интернет-ресурсов, а потом что-то с ними делать. Очень часто эти задачи однотипные и в основном повторяются если не в цикле то в многопотоке шаблона.
Допустим, мне нужно собрать данные с определенного сайта, перебирая страницы с 1 по N. Обязательным условием должна быть возможность не использовать браузер и это очень важно. Обычная логика будет завернуть запрос и обработку в цикл и подкорректировать логику для возможности работы шаблона в многопотоке, если страниц много.
В принципе это нормально, но я нашел альтернативное решение с использованием параллельных циклов C# Parallel.For и Parallel.ForEach.
Теперь я постараюсь описать работу одного из трех шаблонов, которые будут в приложении. Перед нами стоит задача «выпарсить» весь список сайтов по домену высшего уровня с сайта domaintyper.com.
Для начала нам нужно подготовить данные, которые мы будем использовать для параллельного цикла.
int threads = int.Parse(project.Variables["threads"].Value);
var proxy = project.Variables["proxy"].Value;
string url = project.Variables["url"].Value;
var path = project.Variables["path"].Value;
int start_page = int.Parse(project.Variables["start_page"].Value);
project.Variables["start_page"].Value = (start_page + threads).ToString();
var regex = new System.Text.RegularExpressions.Regex("(?<=<tr\\sclass=\"wsTR[^<]*?<td>[^<]*?</td>[^<]*?<td>)[^<]+");
List<int> emptypages = new List<int>();
List<string> rlist = new List<string>();
int lastpage = 0;
int extracted = 0;
threads – отвечает у нас за количество «потоков», то есть сколько параллельных запросов мы собираемся сделать за раз.
proxy – адрес прокси если вдруг кому захочется поэкспериментировать. Сразу же говорю, скорость будет относительно медленней, так как данный пример не предусматривает использования уникальных прокси для каждого запроса в отдельности, но в принципе это возможно.
url - является корневой ссылкой, его значение можете увидеть открыв шаблон.
path отвечает за путь к файлу куда мы будем дописывать список
start_page – номер страницы с которой мы будем продолжать скачивать.
List<int> emptypages будет собирать те номера страниц где не удалось по какой-то причине обнаружить нужные нам строки.
List<string> rlist – главный список, в который мы будем складывать все найденные на странице сайты.
lastpage будет сигнализировать о том что мы дошли до последней страницы и дальше делать запросы не нужно.
extracted покажет сколько мы нашли ссылок на странице
А вот и сам параллельный цикл.
System.Threading.Tasks.Parallel.For(start_page, start_page+threads, p => {
___var response = ZennoPoster.HttpGet(url: url+p.ToString(), proxy: proxy, Encoding:"UTF-8", Timeout: 60000);
___List<string> domains = regex.Matches(response).Cast<System.Text.RegularExpressions.Match>().Select(s=>s.Value).Where(s=>s!="").ToList();
___project.SendInfoToLog(domains.Count.ToString(), p.ToString());
___rlist.AddRange(domains);
___extracted += domains.Count;
___if(response.Contains("<div class=\"pagingDivDisabled\">Next</div>")){
______emptypages.Add(p);
______if(domains.Count > 0){
_________lastpage = p;
_________project.SendInfoToLog("lastpage", p.ToString());
______}
___}
___else{
______if(domains.Count <= 0)
_________emptypages.Add(p);
___}
});
Метод Parallel.For работает как обычный цикл от start_page до start_page+threads только метод, который передается третьим параметром будет выполняться параллельно для каждого итератора p.
В этом методе производится запрос по адресу url+p , вытаскиваются из ответа данные в список domains , а уже только после добавляем domains в результирующий - rlist.AddRange(domains) . Дальше идет определение, является ли наша страница последней и если нет, плюс добавляем в emptypages номер страницы, которая неудачно подгрузилась.
Завершение работы Parallel.ForEach означает что мы скачали все страницы, обработали их, собрали все что можно в результирующие списки rlist и emptypages.
Все, что идет ниже параллельного цикла, это анализ, сохранение, логирование и вызов исключения в случае если emptypages хранит в себе не ту страницу которую стоило ожидать последней на сайте.
В итоге мы получаем отдельный сниппет, который собирает для нас данные в параллельном режиме в один поток зеннопостера.
Два других шаблона работают по тому же принципу, но выполняют другие задачи.
ahrefs.com(top_domains).xmlz – может работать со списком прокси. Рекомендуемое число потоков от 50 до 1000;
gplusparser2.xmlz – скачивает аватарки на диск по ссылкам из уже подготовленного списка. Ставьте смело 10 000 потоков и смотрите что получится.
Вот и все. Удачи всем и спасибо за внимание!