tdf sample
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using GearUp.Crawler.Entities; using HtmlAgilityPack; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using System.Text.RegularExpressions; using System.Collections.Concurrent; using System.Threading; namespace GearUp.Crawler { public class Crawler { private ILoreBookItemRepository repository; private ILorebookItemParser parser; private LinkManager linkManager; private string linkDomain; private static ConcurrentDictionary<string, bool> urls = new ConcurrentDictionary<string, bool>(); private const int DownloadTimeout = 10; public Crawler(ILoreBookItemRepository repository, ILorebookItemParser parser, LinkManager linkManager) { this.repository = repository; this.parser = parser; this.linkManager = linkManager; } public async void StartCrawl(string targetUrl) { var cts = new CancellationTokenSource(); var ct = cts.Token; linkDomain = LinkManager.LinkDomain(targetUrl); var downloaderOptions = new ExecutionDataflowBlockOptions { MaxMessagesPerTask = 3, MaxDegreeOfParallelism = 4, BoundedCapacity = 10 }; var downloader = new TransformBlock<string, PageAndUrl>(async (url) => await DownloadUrl(url), downloaderOptions); var pipelineOptions = new ExecutionDataflowBlockOptions { MaxMessagesPerTask = 2, CancellationToken = ct }; var linkParser = new TransformManyBlock<PageAndUrl, string>(page => ExtactLinksFromPage(page), pipelineOptions); var writer = new ActionBlock<PageAndUrl>(async page => await SaveEntry(page), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 }); var contentBroadcaster = new BroadcastBlock<PageAndUrl>(p => p, new ExecutionDataflowBlockOptions() { CancellationToken = ct }); // Flow setup downloader.LinkTo(contentBroadcaster); contentBroadcaster.LinkTo(linkParser); contentBroadcaster.LinkTo(writer); linkParser.LinkTo(downloader); //Kick off the TPL dataflow here downloader.Post(targetUrl); WriteToConsole("Crawling...", ConsoleColor.Green); PromptUser("Press <Esc> to Stop:", ConsoleColor.White, ConsoleKey.Escape); cts.Cancel(); WriteToConsole("Stopping...", ConsoleColor.Green); await Task.WhenAll(downloader.Completion, contentBroadcaster.Completion, linkParser.Completion, writer.Completion); } public IEnumerable<string> ExtactLinksFromPage(PageAndUrl page) { if (page == null) return Enumerable.Empty<string>(); var discoveredLinks = new List<string>(); var document = new LorebookDocument(page.Html); foreach (var link in document.LinksInArticleBodyDiv()) { var fullUrl = linkManager.FullyQualifyLink(page.Url, link); if (linkDomain.Equals(LinkManager.LinkDomain(fullUrl))) discoveredLinks.Add(fullUrl); } WriteToConsole(" {0} --> {1} links", ConsoleColor.Gray, page.Url, discoveredLinks.Count); return discoveredLinks; } public LorebookItem ExtractLoreBookItem(LorebookDocument document, string url) { WriteToConsole("Parsing: {0}", ConsoleColor.Cyan, url); var itemDetails = document.OfficialLorebookEntry(); var item = parser.ParseHtmlNode(itemDetails, url); return item; } public async Task<PageAndUrl> DownloadUrl(string url) { try { if (urls.ContainsKey(url)) return null; urls.TryAdd(url, true); var client = new WebClient(); WriteToConsole("Fetching: {0}", ConsoleColor.DarkGreen, url); var download = client.DownloadStringTaskAsync(url); var cancel = Task.Delay(DownloadTimeout * 1000); var any = await Task.WhenAny(download, cancel); if (any == cancel) { client.CancelAsync(); WriteToConsole("Cancel: [{0}]", ConsoleColor.Gray, url); return null; } string result = download.Result; WriteToConsole("Downloaded: {0}", ConsoleColor.White, url); return new PageAndUrl() { Url = url, Html = result }; } catch (WebException ex) { WriteToConsole("Error: [{0}]\r\n\t{1}", ConsoleColor.Red, url, ex.Message); } catch (AggregateException ex) { foreach (var exc in ex.Flatten().InnerExceptions) { WriteToConsole("Error: [{0}]\r\n\t{1}", ConsoleColor.Red, url, exc.Message); } } catch (Exception ex) { WriteToConsole("Unexpected error: {0}", ConsoleColor.Red, ex.Message); } return null; } public async Task SaveEntry(PageAndUrl page) { if (page == null) return; var document = new LorebookDocument(page.Html); var item = ExtractLoreBookItem(document, page.Url); if (item != null) await repository.Save(page.Url, item); } private static void WriteToConsole(string format, ConsoleColor color, params object[] texts) { Console.ForegroundColor = color; Console.WriteLine(format, texts); Console.ResetColor(); } private void PromptUser(string message, ConsoleColor color, ConsoleKey? key = null) { WriteToConsole(message, color); if (key == null) Console.ReadLine(); else { ConsoleKeyInfo entry; do { entry = Console.ReadKey(true); } while (key != entry.Key); } } } }