none
crewler עם tasks RRS feed

  • שאלה

  • שלום לכולם!!

    יצרתי web crewler, ויעצו לי כדי שיהיה  יותר מהיר לחלק אותו לtasks

    יצרתי כמה ExecutionDataflowBlockOptions וחיברתי בינהם, הבעיה שלי היא שהם מסתיימים לפני הזמן,

    יש לי crewler שעובד עם threds, וכשאני מריצה את שנהם על אותו האתר השני מצליח לסרוק הרבה יותר מהראשון.

    יכול להיות שתנאי העצירה שלי לא נכון..

    אני מצרפת את הcrewler אשמח אם משהו יוכל לעזור לי, תודה!!!

    static async void StartCrawling()
            {
                try
                {
                    #region Dataflow block Options
    
                    var downloaderOptions = new ExecutionDataflowBlockOptions
                    {
                        // enforce fairness, after handling n messages 
                        // the block's task will be re-schedule.
                        // this will give the opportunity for other block 
                        // to actively process there messages (to avoid over subscription 
                        // the Tpl dataflow does not schedule all task at once if the machine
                        // does not have enough cores)
                        MaxMessagesPerTask = DOWNLOADER_MAX_MESSAGE_PER_TASK,
                        // by default Tpl dataflow assign a single task per block, 
                        // but you can control it by using the MaxDegreeOfParallelism
                        MaxDegreeOfParallelism = DOWNLOADER_MAX_DEGREE_OF_PARALLELISM,
                        // the size of the block input buffer
                        BoundedCapacity = DOWNLOADER_BOUNDED_CAPACITY
                    };
    
                    var transformerOptions = new ExecutionDataflowBlockOptions
                    {
                        MaxMessagesPerTask = MAX_MESSAGE_PER_TASK,
                    };
    
                    var writerOptions = new ExecutionDataflowBlockOptions
                    {
                        MaxMessagesPerTask = WRITER_MAX_DEGREE_OF_PARALLELISM,
                    };
    
                    var linkOption = new DataflowLinkOptions { PropagateCompletion = true };
    
                    #endregion // Dataflow block Options
    
                   
                    #region GetResponse
    
                    var getResponse = new TransformBlock<string, ResposeDetails>(
                            async (url) =>
                            {
                                
    
                                
                                #region Validation
    
    
                                #endregion // Validation
    
                                url = CommonUtil.getConversionDomain(url);
                                if(url!=null)
                                    WriteToConsole("Start1: [{0}]", ConsoleColor.Magenta, url);
                                
                                System.Net.ServicePointManager.Expect100Continue = StaticAtt.Expect100Continue;
                                HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
    
    
                                myHttpWebRequest.KeepAlive = StaticAtt.keepAlive;
                                myHttpWebRequest.Accept = StaticAtt.accept;
                                myHttpWebRequest.UserAgent = StaticAtt.userAgent;
                                myHttpWebRequest.Headers = publicAtt.headers;
    
                                //for facebook
                                myHttpWebRequest.CookieContainer = publicAtt.cookieContainer;
    
                                myHttpWebRequest.AllowAutoRedirect = true;
                                myHttpWebRequest.Timeout = DOWNLOAD_TIMEOUT_SEC * 1000;
    
                                ProxyUtil.ProxyData prox = null;// proxU.getIpForSite();
                                if (prox != null)
                                {
                                    IWebProxy proxy = myHttpWebRequest.Proxy;
                                    WebProxy myProxy = new WebProxy();
                                    string proxyAddress = "http://" + prox.ip + ":" + prox.port;
                                    string username = prox.userName;
                                    string password = prox.pass;
                                    Uri newUri = new Uri(proxyAddress);
                                    myProxy.Address = newUri;
                                    myProxy.Credentials = new NetworkCredential(username, password);
                                    myHttpWebRequest.Proxy = myProxy;
                                }
    
                                string html = "";
                                Task<WebResponse> response = myHttpWebRequest.GetReponseAsync();
                                Task cancel = Task.Delay(DOWNLOAD_TIMEOUT_SEC * 1000);
                                Task any = await Task.WhenAny(response, cancel);
    
                                #region Timeout validation
    
                                if (any == cancel)
                                {
                                    WriteToConsole("Cancel: [{0}]", ConsoleColor.Gray, url);
                                    return null;
                                }
                                #endregion
                                var resp = new ResposeDetails();
                                resp.url = url;
                                resp.response = response.Result;
                                return resp;
                            }
                        , downloaderOptions);
                    
    
                    #endregion // GetResponse
    
                   
                    #region GethtmlCode
                    var htmlCode = new TransformBlock<ResposeDetails, HtmlDetails>(
                            response =>
                            {
                                try
                                {
                                    HttpWebResponse myWebResponse = (HttpWebResponse)response.response;
    
                                    Stream responseStream = myWebResponse.GetResponseStream();
                                    if (myWebResponse.ContentEncoding.ToLower().Contains("gzip"))
                                        responseStream = new GZipStream(responseStream, CompressionMode.Decompress);
                                    else if (myWebResponse.ContentEncoding.ToLower().Contains("deflate"))
                                        responseStream = new DeflateStream(responseStream, CompressionMode.Decompress);
                                    MemoryStream ms = ConvertStreamToMemoryStream(responseStream);
    
    
                                    StreamReader reader;
                                    string encodding;
    
                                    if (myWebResponse.CharacterSet == null || myWebResponse.CharacterSet == "" || myWebResponse.CharacterSet == "ISO-8859-1")
                                    {
                                        //read with default encodding
                                        reader = new StreamReader(ms, System.Text.Encoding.GetEncoding("ISO-8859-1"));
    
                                        //get the right encodding
                                        encodding = GetCharsetInHtml(reader.ReadToEnd());
    
                                        ms.Seek(0, SeekOrigin.Begin);
    
                                        reader = new StreamReader(ms, System.Text.Encoding.GetEncoding(encodding));
                                    }
                                    else
                                    {
                                        reader = new StreamReader(ms, System.Text.Encoding.GetEncoding(myWebResponse.CharacterSet.Replace("\"", "")));
                                        encodding = myWebResponse.CharacterSet;
                                    }
    
                                    string html = reader.ReadToEnd();
                                    var parser = new HtmlParser();
                                    var node = parser.Parse(html);
    
                                    var details = new HtmlDetails();
                                    details.url = response.url;
                                    details.html = node;
                                    return details;
                                }
                                #region Exceptions
    
                                catch (WebException ex)
                                    {
                                        HttpWebResponse errorResponse = ex.Response as HttpWebResponse;
                                        int errorCode = (int)errorResponse.StatusCode;
                                        string message = ex.Message;
                                        return null;
                                    }
                                #endregion
                            }
                        );
                    #endregion // GethtmlCode
    
                    #region Create Page Dada
                    var createPageData = new TransformBlock<HtmlDetails, HtmlDetails>(
                        (htmlDetails) =>
                        {
                            PageData page = new PageData();
                            if (!CheckCanonical(htmlDetails))
                            {
                                page.toCheck = 0;
                                page.noScanInSearchEngine = 1;
                                page.note = page.note + CANONICAL;
                            }
    
                            if (!CheckNoIndex(htmlDetails))
                            {
                                page.toCheck = 0;
                                page.noScanInSearchEngine = 1;
                                page.note = page.note + NO_INDEX;
                            }
    
                            pages[htmlDetails.url] = page;
    
                            return htmlDetails;
                        });
    
                    #endregion //Create Page Dada
    
                    
                    #region Link Parser
    
                    var linkParser = new TransformManyBlock<HtmlDetails,string>(
                        (htmlDetails) =>
                        {
                            #region Validation
    
                            
                            #endregion // Validation
                            var links = new ConcurrentBag<string>();
    
                            var finder = new FindTagsVisitor(TagBuilder => TagBuilder.Name == "a" && TagBuilder.Attributes.ContainsKey("href"));
                            htmlDetails.html.AcceptVisitor(finder);
                            string value;
                             foreach (Majestic13.HtmlNode.Tag n in finder.Result)
                            {
                                
                                value = ((Dictionary<string, string>)n.Attributes)["href"];
                                
                                value = GetCorrectUrl(value, htmlDetails.url);
                                
                                if (value != "" && !pages.Keys.Contains(value))
                                {
                                    pages.TryAdd(value, null);
                                    links.Add(value);
                                }
                            }
    
                            try
                            {
                                var result = links.ToArray();
                                return result;
                            }
                            #region Exception Handling
    
                            catch (Exception ex)
                            {
                                WriteToConsole("Unexpected error: {0}", ConsoleColor.Red, ex.Message);
                                return Enumerable.Empty<string>();
                            }
    
                            #endregion // Exception Handling
                        });
    
                    #endregion // Link Parser               
    
                    var getResponseError = DataflowBlock.NullTarget<ResposeDetails>();
                    var htmlCodeError = DataflowBlock.NullTarget<HtmlDetails>();
    
    
                    #region Link To
                    getResponse.LinkTo(getResponseError, linkOption, webResponse => webResponse==null || webResponse.response == null);
                    getResponse.LinkTo(htmlCode, linkOption, webResponse => webResponse != null && webResponse.response != null);
                    htmlCode.LinkTo(htmlCodeError, linkOption,htmlDetails=>htmlDetails==null);
                    htmlCode.LinkTo(createPageData, linkOption,htmlDetails=>htmlDetails!=null);
                    createPageData.LinkTo(linkParser,linkOption);
                    linkParser.LinkTo(getResponse, linkOption);
    
                    #endregion //Link To
    
                    getResponse.Post(URL_CRAWL_TARGET);
    
                    Console.WriteLine("Crawling");
                    
                    #region Complete
    
                    
    
                    Task completeAll = Task.WhenAll(
                        getResponse.Completion,
                        htmlCode.Completion,
                        createPageData.Completion,
                        linkParser.Completion);
    
                    await Task.Run(async () =>
                    {
                        while (!completeAll.IsCompleted)
                        {
                            await Task.Delay(2000);
    
                            #region WriteToConsole (status)
    
                            
                        }
    
                        sw.Stop();
                long performance = sw.ElapsedMilliseconds;
                WriteToConsole("FINISHED {0} seconds, {1} pages", ConsoleColor.DarkYellow, performance,pages.Count);
                        var finished = true;
                            #endregion // WriteToConsole (status)
                    });
    
                    #endregion // Complete
                }
                catch (Exception ex)
                {
                    WriteToConsole("EXCEPTION: {0}", ConsoleColor.DarkRed, ex);
                }
            }
    

    יום שלישי 14 ינואר 2014 05:08

תשובות

  • אהלן

    1. באופן חד משמעי מה שהציעו לך נכון :-)
    והעבודה עם Dataflow ממש מתאימה כאן בצורה מלאה.

    עכביש WEB מבצע פעולות בלתי תלויות כשהוא סורק את הרשת בצורת עץ ובהחלט צריך וכדאי לבצע את הפעולות במשימות מרובות.

    2. אין לי זמן לעבור על הקוד לעומק כרגע (קוד ארוך מדי להציץ בחצי דקה של הפסקת הקפה) אבל אם תוכלי לצרף את כל הפרוייקט יהיה יותר קל לעקוב אחרי דברים.

    במבט מהיר (ולכן בלי התחייבות שאני לא טועה כרגע) ניראה שאת מערבבת גישות של עבודה עם ריבוי משימות בשיטה הישנה יותר (הישירה) עם שימוש בשיטה החדשה של Dataflow.

    אני רואה שאת עושה שמוש במתודה Task.WhenAll שמטרתה להמתין עד לסיום כל המשימות בפרמטרים ואז להריץ משימה כלשהיא, אבל ל Dataflow יש ניהול משימות בצורה ייחודית. למשל המתודה Completion.Wait נועדה להמתנה לסיום כל הפעולות של ActionBlock. צריך לחשוב על Dataflow כשמו, תרשים זרימה. מאוד נוח לעבוד בצורה גרפית עם המחלקה הזו וממש לצייר תרשים זרימה של פעולות שמבצעים.

    תבדקי אם הקוד הקצר הבא עוזר לך (זה הקוד הכי פשוט שאני יכול לחשוב עליו שמדגים יפה את העבודה עם Completion.Wait).

    // ActionBlock class is a TargetBlock that calls a delegate when data is received.
                var MyActionBlockList = new ActionBlock<int>(
                    (x) =>
                    {
                        Console.WriteLine(">{1} : x = {0}", x, DateTime.Now);
                        System.Threading.Thread.Sleep(1000);
                    }, 
                    new ExecutionDataflowBlockOptions(){
                        MaxDegreeOfParallelism = 4
                    }
                );
    
                Console.WriteLine(DateTime.Now);
    
                // Post values to the block.
                MyActionBlockList.Post(0);
                MyActionBlockList.Post(1);
                MyActionBlockList.Post(2);
                MyActionBlockList.Post(3);
                MyActionBlockList.Post(4);
                MyActionBlockList.Post(5);
                
    Console.WriteLine(DateTime.Now);
    // we wait 2 more sec System.Threading.Thread.Sleep(2000);

                // since we waited 1 sec for each ActionBlock post,
    // and before this line we wait 2 second, therefor this will execute after second post Console.WriteLine(DateTime.Now); // Completion Id = 1, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}" System.Threading.Tasks.Task {System.Threading.Tasks.Task<System.Threading.Tasks.Dataflow.Internal.VoidResult>} MyActionBlockList.Post(21); MyActionBlockList.Post(22); MyActionBlockList.Post(23); MyActionBlockList.Complete(); // Wait for completion MyActionBlockList.Completion.Wait(); Console.WriteLine("Completion Ended"); // This will execute only after all tasks ended Console.ReadLine();

    בינתיים בלי קשר לשאלה ולעבודה עם Dataflow של Dot.Net 4.5 , אולי תוכלי להעזר במערכת מוכנה של עכביש רשת בפרוייקט הבא (אני לא מכיר את הפרוייקט והגעתי אליו רק דרך גוגל):

    open source C# web crawler:
    https://code.google.com/p/abot/

    [Personal Site] [Blog] [Facebook]signature

    • נערך על-ידי pituachMVP, Moderator יום שלישי 14 ינואר 2014 16:28
    • סומן כתשובה על-ידי Eran Sharvit יום ראשון 19 ינואר 2014 10:35
    יום שלישי 14 ינואר 2014 15:02
    מנחה דיון

כל התגובות

  • בקרוב מומחי הקהילה ינסו לעזור לך. תודה.

    מיקרוסופט מציעה שירות זה ללא תשלום, למטרת סיוע למשתמשים והעשרת הידע הקשור בטכנולוגיות ובמוצרים של מיקרוסופט. תוכן זה מתפרסם כפי שהוא והוא אינו מעיד על כל אחריות מצד מיקרוסופט.

    יום שלישי 14 ינואר 2014 12:46
  • אהלן

    1. באופן חד משמעי מה שהציעו לך נכון :-)
    והעבודה עם Dataflow ממש מתאימה כאן בצורה מלאה.

    עכביש WEB מבצע פעולות בלתי תלויות כשהוא סורק את הרשת בצורת עץ ובהחלט צריך וכדאי לבצע את הפעולות במשימות מרובות.

    2. אין לי זמן לעבור על הקוד לעומק כרגע (קוד ארוך מדי להציץ בחצי דקה של הפסקת הקפה) אבל אם תוכלי לצרף את כל הפרוייקט יהיה יותר קל לעקוב אחרי דברים.

    במבט מהיר (ולכן בלי התחייבות שאני לא טועה כרגע) ניראה שאת מערבבת גישות של עבודה עם ריבוי משימות בשיטה הישנה יותר (הישירה) עם שימוש בשיטה החדשה של Dataflow.

    אני רואה שאת עושה שמוש במתודה Task.WhenAll שמטרתה להמתין עד לסיום כל המשימות בפרמטרים ואז להריץ משימה כלשהיא, אבל ל Dataflow יש ניהול משימות בצורה ייחודית. למשל המתודה Completion.Wait נועדה להמתנה לסיום כל הפעולות של ActionBlock. צריך לחשוב על Dataflow כשמו, תרשים זרימה. מאוד נוח לעבוד בצורה גרפית עם המחלקה הזו וממש לצייר תרשים זרימה של פעולות שמבצעים.

    תבדקי אם הקוד הקצר הבא עוזר לך (זה הקוד הכי פשוט שאני יכול לחשוב עליו שמדגים יפה את העבודה עם Completion.Wait).

    // ActionBlock class is a TargetBlock that calls a delegate when data is received.
                var MyActionBlockList = new ActionBlock<int>(
                    (x) =>
                    {
                        Console.WriteLine(">{1} : x = {0}", x, DateTime.Now);
                        System.Threading.Thread.Sleep(1000);
                    }, 
                    new ExecutionDataflowBlockOptions(){
                        MaxDegreeOfParallelism = 4
                    }
                );
    
                Console.WriteLine(DateTime.Now);
    
                // Post values to the block.
                MyActionBlockList.Post(0);
                MyActionBlockList.Post(1);
                MyActionBlockList.Post(2);
                MyActionBlockList.Post(3);
                MyActionBlockList.Post(4);
                MyActionBlockList.Post(5);
                
    Console.WriteLine(DateTime.Now);
    // we wait 2 more sec System.Threading.Thread.Sleep(2000);

                // since we waited 1 sec for each ActionBlock post,
    // and before this line we wait 2 second, therefor this will execute after second post Console.WriteLine(DateTime.Now); // Completion Id = 1, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}" System.Threading.Tasks.Task {System.Threading.Tasks.Task<System.Threading.Tasks.Dataflow.Internal.VoidResult>} MyActionBlockList.Post(21); MyActionBlockList.Post(22); MyActionBlockList.Post(23); MyActionBlockList.Complete(); // Wait for completion MyActionBlockList.Completion.Wait(); Console.WriteLine("Completion Ended"); // This will execute only after all tasks ended Console.ReadLine();

    בינתיים בלי קשר לשאלה ולעבודה עם Dataflow של Dot.Net 4.5 , אולי תוכלי להעזר במערכת מוכנה של עכביש רשת בפרוייקט הבא (אני לא מכיר את הפרוייקט והגעתי אליו רק דרך גוגל):

    open source C# web crawler:
    https://code.google.com/p/abot/

    [Personal Site] [Blog] [Facebook]signature

    • נערך על-ידי pituachMVP, Moderator יום שלישי 14 ינואר 2014 16:28
    • סומן כתשובה על-ידי Eran Sharvit יום ראשון 19 ינואר 2014 10:35
    יום שלישי 14 ינואר 2014 15:02
    מנחה דיון
  • טוב היה לי מעט זמן לעבור על הקוד עצמו מעט (שוב רק באופן כללי מפני שאין לנו את הפרוייקט לבדוק)
    * תסתכלי על השעה שאני שם את ההודעה... זה השעה הכי טובה לעבוד :-)

    תקראי לעומק מה שכתבתי מעל ותעברי על הדוגמה בקוד שלי, זה ניראה בדיוק מה שאת מחפשת. כמו שחשדתי מבט על הקוד שלך מראה שאין לך בכלל נקודת עצירה מסודרת של המתנה לסיום האלמנט האחרון בתרשים הזרימה שלך.

    Completion.Wait()

    * אני רואה באופן כללי שאת מבצעת הגדרה של כמה אלמנטים בתרשים הזרימה (נראה טוב בסך הכל)

    * אני רואה שאת מקשרת את האלמטים בצורה טובה (למחות במבט מהיר ניראה שיש קישור LinkTo מכל אלמנט לאלמנט הבא... ז"א נוצר תרשים זרימה מסודר)

    * אבל אני לא רואה שאת מבצעת המתנה בסיום לאלמנט האחרון בתרשים הזרימה שלך. במקום זה את פונה לעבוד עם המשימות בצורה ישירה TASK. אני מניח שהתכוונת לבצע Delay של 2 שניות אם עדיין יש הודעות אבל אין בכך צורך בעבודה עם Dataflow. זה חלק מהייתרונות של ניהול תרשים זרימה. (אם אני מנסה לתאר בצורה כללית להבנה) כל אלמנט בתרשים הוא מעין בלוק של פעולות שמנהל את ההודעות שמגיעות אליו (ו/או נשלחות על ידו). כל עוד ההודעות מתקדמות בתרשים אין סיבה לעצור כלום. בסיום התרשים יש להמתין להגעת כל ההודעות.

    * אפשר כמובן בדרך גם לבצע נקודות המתנה נוספות ולא רק בסיום, נקודות פיצול ונקודות חיבור של אלמנטים שעובדים במקביל, אבל בסיום חייבים נקודת המתנה בעקרון. במקרה שלך מסודר על תרשים פשוט יחסית של "טור" פעולות וההודעות עוברות אלמנט אחרי אלמנט בהתאם להדרת ה LinkTo.

    אני מציע להוסיף בסוף התרשים אלמנט פשוט מסוג ActionBlock ועליו לבצע את ההמתנה. בתוך אלמנט זה תוכלי לבצע כתיבה ל CONSOLE למשל.

    אני מקוה שההסבר הכללי הזה מספיק טוב והשעה לא משפיעה עלי בכתיבת הסבר מגומם יותר מדי :-)
    לצערי אני לא מכיר אפילו שום מדריך טוב לנושא כדי לתת קישור כמו שאני בדרך אוהב לצרף לשאלות, אולי בסוף השבוע אני אכתוב בלוג בנושא או אנסה למצוא משהו בגוגל... אני מציע לחפש מדריך בסיגנון צעד אחרי צעד אם עדיין לא ברור הרעיון ולרוץ איתו עד הסיום ורק אז לחזור לנושא שלך. כמו כן צירוף הפרוייקט שלך אולי יוכל לכוון אותנו להבין מה הרמה והידע וכמובן מה נעשה עד כה, ולעזור לנו להדריך אותך יותר טוב (אבל אם הוא ארוך אני לא יודע כמה אנשים יוכלו להעמק בו).


    [Personal Site] [Blog] [Facebook]signature

    יום רביעי 15 ינואר 2014 01:27
    מנחה דיון
  • תודה רבה על הזמן והטרחה!!!!!!

    זה עזר המון, וזו לא פעם ראשונה שאני נעזרת בך!!

    ראיתי את מה שכתבת, אבל אצלי יש משהו אחר,

    אני לא יכולה לעשות לקרוא לפונקציה:

    MyActionBlockList.Complete();
    בגלל שאני לא יכולה לדעת מתי מסתיימים כל הדפים, אני סורקת הרבה אתרים ולכל אחד יכול להיות מס' דפים לא ידוע.

    מה שכין עשיתי זה:

    linkParser.Completion.Wait();

    לaction האחרון, שהתוכנית לא מגיע לשם אף פעם...

    פעולת הסיום שלי צריכה להיות כשכל הactions הסתיימו (לכן הוא עובר עם הtask כל 2 שניות לבדוק אם כולם כבר סיימו...)



    יש לי עוד בעיה, כמו שכתבתי קודם יש לי 2 פרויקטים שעושים את העבודה, אחד עם threads ואחד עם Dataflow,

    בDataflow יש לי המון נפילות של timeout כשאני מחכה לresponse, ובthreads אין לי כמעט,
    כשאני מקטינה את הMaxDegreeOfParallelism זה עוזר, אבל מאט את התוכנית.



    שוב תודה!!!! לך ולכל מי שמנסה!!!!!
    יום רביעי 15 ינואר 2014 10:25
  • אהלן

    כמה נקודות:

    1. Complete מבצעים לבלוק הראשון של תרשים הזרימה. הרעיון באופן כללי זה שכאשר ההודעה האחרונה עוברת את הבלוק הראשון אז הוא יכול להודיע לבלוק הבא אחריו שזו היתה ההודעה האחרונה. אצלי בדוגמה יש רק בלוק אחד של ActionBlock ולכן ניראה שביצעתי את ה Complete על אותו בלוק שעליו גם ביצעתי את ההמתנה לסיום Completion.Wait. את ההמתנה לסיום מבצעים על הבלוק האחרון בתרשים בעיקרון, כדי להמתין לסיום עיבוד ההודעה האחרונה בבלוק האחרון.

    את לא אמורה "לדעת" מתי מסתיימים הדפים, תרשים הזרימה אמור לנהל זאת. ברגע שאין יותר הודעות שמגיעות לבלוק הראשון ובוצעה הפעולה של Complete הוא כבר יעביר הלאה שהיסתימו ההודעות (דפים של URL במקרה שלך). אני ממליץ להישתמש במושג הנכון של ה Dataflow וזה ההודעות, הכל מבוסס על הרעיון של הודעות שנכנסות לבלוק והודעות שיוצאות מהבלוק הלאה בתרשים הזרימה שלנו.

    2. כדי לבדוק שכל ההודעות הגיעו אפשר בלדוק מה הסטאטוס של הבלוק. שימי נקודת עצירה בתוכנית ותחקר מעט את הערכים השונים של האלמנטים השונים תוך כדי ריצה. תבדקי מה קורה מאחורי הלקעים על כל אלמנט ואלמנט שורה אחרי שורה (תריצי אתצ התוכנית בעזרת f11 שורה אחרי שורה תוך כניסה למתודות פנימיות אם את רוצה להעמיק יותר את הידע, או עם f10 כדי לעבור באופן כללי על השכבה החיצונית).

    3.באופן כללי לא ניתן לבצע ניטור בלי קוד ובטח שלא השוואה בין 2 פרוייקטים שאין לנו גישה אליהם ולכן אני לא יכול לעזור בהבנה של למה אחד כן ואחד לא. אני יודע שכל אחד חושב שהקוד שלו סודי והוא לפחות המציא את הגלגל , אבל אני מאוד ממליץ לצרף קודים ופרוייקטים מלאים כשאפשר.

    באופן כללי ההרגשה שלי היא שאת יותר מנוסה בעבודה ישירה על משימות מאשר עם תרשים זרמה ורק מסיבה זו יש לך במערכת מבוססת עבודה ישירה עם משימות פחות שגיאות פיתוח/תכנון. בכל מקרה ברגע שיש לך נפילה אחת זה אומר שהמערכת שלך צריכה לעבור ניטור ותיקון. נפילה היא בעיה של פיתוח לקוי בדרך כלל (בפיתוח אני כולל ארכיטקטורה של האפליקציה והמערכת כמובן). ניתן לזהות מהקוד הבסיסי שלך אפשרות לגישת עבודה של כיבוי שרפות, ואם כך הדבר אז כדאי מאוד לשנות זאת :-(. ברגע שראיתי את החלק של הבדיקה של כל 2 שניות הבנתי שיש פוטנציאל לבעיה הקשורה בחלק זה. למה כל 2 שניות ולא כל שנייה? למה נאלצת לבצע פעולה יזומה במקום שימוש באירוע. הערך 2 שניות הוא תלוי מערכת כמובן, אז למה 2 ולא 10. זו דרך שמצאת לעקוף בעיה שלא ידעת לפתור אותה :-) לפעמים זו יוזמה זמנית מאוד מבורכת להיות מקורי ובעל יכולת לאילתור פתרונות זמניים, מצד שני צריך לזכור שזה רק פתרון זמני. נקודת המוצא היא אירועים והרצון לשימוש בגישה של PUSH במקום PULL ()ז"א במקום לבדוק מה המצב ולמשוך את הנתון של מה המצב, להמתין להודעה שמגיעה ביוזמת המבצע שדוחפת הודעה של סיימנו הכל מוכן להמשיך. זה כמובן הערה כללית שאני לא יודע אם היא מתאימה / נכונה בלי להכיר את המערכת והמשתתפים בתכנון והאפיון המלא :-)

    4. הקטנת הפרמטר MaxDegreeOfParallelism יכול כמובן לעזור לשחרר חלק מהמעבדים. לכן ייתכן שמערכת מסויימת זה עזר למנוע נפילות. המערכת שלנו חייבת להמשיך לעבוד ותוכנית שמשתלטת על כל המשאבים של המערכת יכולה לגרום למערכת ליפול או למערכת להפיל את התוכנית. אני הייתי ממליץ לקבוע את הערך דינאמית לפי הכמות שקיימת במחשב למשל או לתת למשתמש אפשרות לקבוע ערך זה. יש לקת בחשבון מה עוד צורך משאבים ולהשארי מספיק לכולם.

    5. נקודה אחרונה מינהלתית :-)

    אני ממליץ כל שאלה לפתוח שירשור חדש. הרבה יותר נוח לעקוב אחרי שרשורים קצרים יחסית שדנים בנושא מסויים ולא אחרי שרשורים שוללים שאלות אחרי שאלות אחרי שאלות המשך ובתוך השרשור מתחיל להיות פיצול שלא מאפשר מעקב. בפורום מאוד פעיל בדרך מבקשים הפוך, שכל אחד יכניס את כל השאלות המקושרות שלו ביחד, אבל אנחנו לא שם והפעילות בפורום כאן מאפשר לנו עדיין לפצל שרשורים אם זה עוזר לסדר.

    באותו נושא זה גם עוזר לבצע סגירה של נושאים מהירה יותר (סימון כל התשובות והצבעה על הודעות מועילות).

    ולפני סיום תודה על התודה :-)

    לסיום נקודה כללית לגבי מנועי חיפוש ותוכנות עכבישי WEB למניהן: תוכנות אלו יכולות להיות כבדות מאוד (בלשון המעטה... הכוונה למאוד). מערכות מקצועיות מחזיקות חומרה בסדרי גודל של ביניינים שלמים אם לא חוות שלמות כדי לעמוד בדרישות. אלו לא מערכות ביתיות ותכנון מערכת כזו צריך להיות בהתאם. כדאי פעם לבקר בחוות של גוגל כדי להבין על איזה סידרי גודל אנחנו מדברים :-). הרבה פעמים במערכת ביתית אפשר להיתבסס על API של מערכת חיצונית או לבצע האטה מכוונת בפעילות. למשל הכנסת השהיה לפעולות שמבצעים SLEEP ל THREAD . הכוונה לא לפעולת בדיקה כל X זמן אלא להשהיה מכוונת... ראי למשל אצלי בדוגמה כיצד ביצעתי השהיה של שנייה ל מנת לאפשר למערכת לבצע פעולות נוספות במקביל... שנייה כמובן שזה המון ונועד בשביל להדגים, אבל צריך לחשוב על נקודה זו.


    [Personal Site] [Blog] [Facebook]signature

    יום רביעי 15 ינואר 2014 12:13
    מנחה דיון
  • שוב, תודה!!!! אין מילים!
    אני אכן אתמקד רק בבעיה הראשונה שיש לי, נקודת העצירה.
    עדין לא הבנתי איך אני יכולה לדעת מתי לבצע complete.

    בעקרון ברגע שכל הflow נגמר ואין flow חדש ברקע, זה סימן שהcrewler סיים את פעולתו, ואז בעקרון אני אמורה לעשות complete.

    השאלה היא איך אני אמורה לדעת שזה קורה?

    אם זה היה מערכת "ישירה" היה לי ququ וכשהququ מתרוקן ואין לי משהו שפועל ברקע אני יודעת שהכל הסתיים, ו"יוצאת" מהסריקה.

    אבל פה, אין לי ququ, אני קוראת לflow בצורה מעגלית, ואין לי מושג אם הflow הנוכחי שאני קוראת לו הוא הflow האחרון, או שיש עוד אחד בקנה..

    אולי זה מאד מובן ולכן לא חשבת שעל זה אני מתעקבת, אבל זה הדבר שלא מובן לי פה...

    אני ממש לא מרגישה שהמצאתי את הגלגל.. הקוד שלי (הקטע של הdataflow) ברובו לקוח מדוגמאות שראיתי ברשת (כן, אפילו הקטע של ה2 שניות :)) לא העלתי את הפרויקט כי היה לי מאד לא נעים לקחת מזמנך!

    אני מעלה אותו, אבל בשום אופן אל תרגיש חובה לעבור עליו...

    הפרויקט נמצא פה

    אני חושבת שאגיד שוב תודה בסוף....

    
    יום רביעי 15 ינואר 2014 19:22
  • שוב, תודה!!!! אין מילים!
    אני אכן אתמקד רק בבעיה הראשונה שיש לי, נקודת העצירה.
    עדין לא הבנתי איך אני יכולה לדעת מתי לבצע complete.

    בעקרון ברגע שכל הflow נגמר ואין flow חדש ברקע, זה סימן שהcrewler סיים את פעולתו, ואז בעקרון אני אמורה לעשות complete.

    השאלה היא איך אני אמורה לדעת שזה קורה?

    אם זה היה מערכת "ישירה" היה לי ququ וכשהququ מתרוקן ואין לי משהו שפועל ברקע אני יודעת שהכל הסתיים, ו"יוצאת" מהסריקה.

    אבל פה, אין לי ququ, אני קוראת לflow בצורה מעגלית, ואין לי מושג אם הflow הנוכחי שאני קוראת לו הוא הflow האחרון, או שיש עוד אחד בקנה..

    אולי זה מאד מובן ולכן לא חשבת שעל זה אני מתעקבת, אבל זה הדבר שלא מובן לי פה...

    מה שאת מתארת כאן זה בעיה שקשורה לתכנון האפליקציה שלך. ארכיטקטורה של אפליקציה היא נושא נירחב שאי אפשר לכסות בפורום. את צריכה לתכנן את האפליקציה שלך בהתאם לצרכים, משאבים, מטרות ויעדים שלך.

    מנוע עכביש שסורק את כל האינטרנט מבצע פעולה שתיאורטית אין לה סוף, אחרי הכל גם בכמה שנים לא תצליחי בחיבור רגיל לאינטרנט ועם מחשב משרדי/ביתי לסרוק את כל האינטרנט. אבל אפליקצהי שמתוכננת טוב צריכה נקודות יציאה, נקודות שחזור וכו'

    תחשבי למשל אם את רוצה להכניס את כל הנתונים למסד הנתונים, אז האם נמתין עד לסיום הסריקה וכניס הכל בפעם אחת בעזרת BULK INSERT? תיאורטית זה הכי מהיר, מעשית אם היתה נפילה באמצע אז נאלץ להתחיל הכל מההתחלה, אם היתה בעיית של משאבים אז יכולה להיווצר נפילה... וכן הלאה.

    הרעיון הוא לתכנן את הפאליקציה בהתאם לאפיון המערכת שלך.

    כאמור את יכולה ליצור נקודות עצירה שלך. הדרך הכי פשוטה בעבודה הירארית היא לקבוע את רמת ההריראכיה, אפשר לקבוע נקודת עצירה כל X זמן ועוד... 

    אני ממש לא מרגישה שהמצאתי את הגלגל.. הקוד שלי (הקטע של הdataflow) ברובו לקוח מדוגמאות שראיתי ברשת (כן, אפילו הקטע של ה2 שניות :)) לא העלתי את הפרויקט כי היה לי מאד לא נעים לקחת מזמנך!

    אני מעלה אותו, אבל בשום אופן אל תרגיש חובה לעבור עליו...

    הפרויקט נמצא פה

    אני חושבת שאגיד שוב תודה בסוף....

    כרגע כבר הבנו את העיות המרכזיות מולן את עומדת, אבל אולי אני אציץ במהירות בסוף השבוע ואראה אם יש לי עוד תובנות. היום לא יהיה זמן.

    [Personal Site] [Blog] [Facebook]signature

    יום חמישי 16 ינואר 2014 09:55
    מנחה דיון
  • כנראה הייתה אי הבנה, 

    אני לא מעוניינת לסרוק את כל האינטרנט.. :)

    יש לי אתרים מסוימים שאיתם אני עובדת ורק אותם אני סורקת. סריקה פנימית באתר. חיפוש דפים שהתווספו לאתר.

    נקודת העצירה היא מאד ברורה,

    מה שהתוכנית עושה:

    יש list סטטי שמכיל את כל הurls באתר מסויים.

    מכל דף לוקחים את הלינקים הפנמיים, אם הם לא מופיעים בlist מוסיפים אותם לlist, ושולחים אותם לflow.

    נקודת העצירה היא, ברגע שכל הדפים נסרקו, ואין אף אחד בקנה.

    כמו שכתבתי קודם, אם היה לי ququ הייתי יכולה להגדיר את זה, ברגע שהququ מסתיים ואין לי אף threads פעיל, זו נקודת העצירה.

    מה שאני מנסה לשאול כבר מהשאלה הראשונה, איך אני מגדירה נקודת עצירה כזו בdataflow??

    אני מקווה שאני מספיק ברורה....

    תודה!!!!!!!!!!!!!!!!!!!!

    יום חמישי 16 ינואר 2014 14:32
  • שבת שלום

    אין הבדל בין סריקה של מערכת סורה של גוגל לבין סריקה של מערכת סגורה שלכם פרט אולי לכמות הנתונים והעבודה. הרעיון והעבודה זהים בעקרון.

    כאמור מה שחסר לך זה להחליט ולתכנן את האפליקציה כך שתיצרו נקודות "סיום". במקרה של סריקה ארוכה לפעמים מתאים לחלק לחלקים ובמקרה של סריקה קצרה אפשר להבצע הכל ביחד אולי אבל כאמור הרעיון זהה.

    למעשה יותר לעבוד עם מערכת שמכירים אותה כדי לקבוע את הטריגר של סיום העבודה.

    באופן כללי: יש לך מישהו שדוחף הודעות בתחילת הדרך של תרשים הזרימה. אותו "מישהו", הוא זה שאמור לדעת שהגענו לסיום.

    במקרה של הקוד שלי לא היה צורך בפעולות דינאמיות ולכן ביצעתי הכנסה של הודעות בצורה "ידנית" על ידי ההוראה

    MyActionBlockList.Post(0);

    את ההוראה הזו ביצעתי כמה פעמים בצורה בילתי תלויה והעברתי הודעה אחרי הודעה. אם למשל הייתי רוצה להעביר כמות הודעות לא ידועה והייתי עובד עם מתודה רקורסיבית, אז המתודה הרקורסיבית אמורה לדעת מתי היא הסתיימה. לכן המתודה הזו יכלה להריץ את הפעולה של Complete בסיום כשאין עוד רמות היראריכה לסרוק. את צריכה לחשוב על טריגר ש"יודע" שהגענו לסיום.

    *דרך אגב לא הצלחתי להוריד את הפרוייקט. ניראה לי שצריך לפתוח חשבון שם כדי להוריד קבצים ובכל מקרה האתר ניראה ככה ב portable chrome:

    * דרך אגב, זה בדיוק החלק המורכב של תכנון :-)

    את שואלת כאן שאלה ספציפית שתלויה באפליקציה קיימת אבל אולי אם הייתי מתכנן את האפליקציה הייתי מראש מתכנן את כל הישה באופן שונה כך שלא הייתי נתקל במורכבות כזו. למשל אני נוטה בכלל לכך שכל השימוש בסריקה היא פעולה שצריכה להיות חד פעמית ואת התוצאות יש להכניס למסד נתונים למשל (כמו כל מנוע חיפוש). במערכת סגורה שמכירים אפשר ידנית להוסיף כל עמוד חדש למסד הנתונים בלי סריקה ועוד... כאמור רק לארח שמכירים את האפיון אפשר לתכנן מערכת יעילה :-)


    [Personal Site] [Blog] [Facebook]signature

    שבת 18 ינואר 2014 16:02
    מנחה דיון
  • דרך פשוטה לאתר את הסיום יכולה דרך אגב להיות דומה לרעיון שלך בהתחלה של שימוש בבדיקה כל X זמן. זו אינה דרך יעילה אבל היא עובדת ופושטה למימוש. כמובן שאפשר לבדוק דינאמית האם יש לנו משימות שרצות כרגע, אבל כמובן שצריך לאתר רק משימות של הבלוק הראשון בתרשים הזרימה ולכן זה יכול להיות עמט מורכב ולא מתאים, אבל אנחנו כן יכולים לבדוק משהו שונה לחלוטין כמו למשל האם יש לנו נתון חדש שנכנס לתור של הבלוק הראשון.

    אם ההנחה היא שבכל שנייה צריכים להיכנס אלף נתונים חדשים לתרשים הזרימה ונבדוק כל שנייה ונראה שאין לנו נתונים חדשים אז נדע שהגענו לסיוןם כניראה (או שיש נו האטה שגם כדאי לעצור אולי בגללה את התהליך).

    עוד נקודה למחשבה: לא כל עמוד בתאר בהכר יהיה לו קישור מעמוד אחר וצריך לוודא שאנחנו עוברים באמת על כל המערכת אם רוצים סריקה מלאה... יש לתכנן בהתאם.

    עוד רעיון לאפשרות שיכולה להיות יעילה: אפשר להגדיר זמן מסויים מתחילת התהליך שבו נבצע את הפעולה של הסגירה. זה ייצתן למרכת לנוח קצת. בינתיים אפשר לשמור את ההודעות האחרונות שלנו כדי שנוכל להפעיל שוב את התהליך מחדש. למשל תהיה לנו פונקציה שמפעילה הודעות לבלוק הראשון וכעבור 10 שניות מתחיל האפליקציה הפונקציה מבצעת הפסקה. ניתן להגדיר משתנה של סטאטוס ולבדוק אותו בזמן התהליך ואם אנחנו בסטאטוס של הפסקה אז נאסוף את התוצאות לאוסף זמני. עתה נוכל לבצע את הפעולה של WAIT לסיום התהליך ואז נוכל להפעיל את המשך הסריקה על רשימת התוצאות שנכנסו לאוסף שלנו... זה רק רעיון כללי שחשבתי עליו כרגע...

    ועוד אופציה היא שימוש בבלוק מסוג BufferBlock דרכו נעביר את כל ההודעות. כל X הודעות נוכל לבצע איפוס של התהליך... את הבדיקרה נוכל לבצע ב BufferBlock למשל.


    [Personal Site] [Blog] [Facebook]signature

    שבת 18 ינואר 2014 16:37
    מנחה דיון