Kiểm soát WebBrowser trong một chuỗi mới


84

Tôi có một danh sách Uri's mà tôi muốn "nhấp vào" Để đạt được điều này, tôi đang cố gắng tạo điều khiển trình duyệt web mới cho mỗi Uri. Tôi tạo một chuỗi mới cho mỗi Uri. Vấn đề tôi gặp phải là kết thúc chuỗi trước tài liệu được tải đầy đủ, vì vậy tôi không bao giờ sử dụng được sự kiện DocumentComplete. Làm cách nào để khắc phục điều này?

var item = new ParameterizedThreadStart(ClicIt.Click); 
var thread = new Thread(item) {Name = "ClickThread"}; 
thread.Start(uriItem);

public static void Click(object o)
{
    var url = ((UriItem)o);
    Console.WriteLine(@"Clicking: " + url.Link);
    var clicker = new WebBrowser { ScriptErrorsSuppressed = true };
    clicker.DocumentCompleted += BrowseComplete;
    if (String.IsNullOrEmpty(url.Link)) return;
    if (url.Link.Equals("about:blank")) return;
    if (!url.Link.StartsWith("http://") && !url.Link.StartsWith("https://"))
        url.Link = "http://" + url.Link;
    clicker.Navigate(url.Link);
}

Câu trả lời:


151

Bạn phải tạo một chuỗi STA bơm vòng lặp thông điệp. Đó là môi trường hiếu khách duy nhất cho một thành phần ActiveX như WebBrowser. Nếu không, bạn sẽ không nhận được sự kiện DocumentCompleted. Một số mẫu mã:

private void runBrowserThread(Uri url) {
    var th = new Thread(() => {
        var br = new WebBrowser();
        br.DocumentCompleted += browser_DocumentCompleted;
        br.Navigate(url);
        Application.Run();
    });
    th.SetApartmentState(ApartmentState.STA);
    th.Start();
}

void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
    var br = sender as WebBrowser;
    if (br.Url == e.Url) {
        Console.WriteLine("Natigated to {0}", e.Url);
        Application.ExitThread();   // Stops the thread
    }
}

8
Đúng! Chỉ cần thêm System.Windows.Forms. Cũng đã lưu ngày của tôi. Cảm ơn
zee

4
Tôi đang cố gắng điều chỉnh mã này cho phù hợp với tình huống của mình. Tôi phải giữ cho WebBrowserđối tượng tồn tại (để lưu trạng thái / cookie, v.v.) và thực hiện nhiều Navigate()lệnh gọi theo thời gian. Nhưng tôi không chắc nơi đặt Application.Run()lệnh gọi của mình , vì nó chặn không cho thực thi thêm mã. Bất kì manh mối nào?
dotNET

Bạn có thể gọi Application.Exit();để Application.Run()trả lại.
Mike de Klerk

26

Dưới đây là cách tổ chức vòng lặp thông báo trên một chuỗi không phải giao diện người dùng, để chạy các tác vụ không đồng bộ như WebBrowsertự động hóa. Nó sử dụng async/awaitđể cung cấp luồng mã tuyến tính thuận tiện và tải một tập hợp các trang web trong một vòng lặp. Mã là một ứng dụng bảng điều khiển sẵn sàng chạy, một phần dựa trên bài đăng tuyệt vời này .

Các câu trả lời liên quan:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ConsoleApplicationWebBrowser
{
    // by Noseratio - https://stackoverflow.com/users/1768303/noseratio
    class Program
    {
        // Entry Point of the console app
        static void Main(string[] args)
        {
            try
            {
                // download each page and dump the content
                var task = MessageLoopWorker.Run(DoWorkAsync,
                    "http://www.example.com", "http://www.example.net", "http://www.example.org");
                task.Wait();
                Console.WriteLine("DoWorkAsync completed.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("DoWorkAsync failed: " + ex.Message);
            }

            Console.WriteLine("Press Enter to exit.");
            Console.ReadLine();
        }

        // navigate WebBrowser to the list of urls in a loop
        static async Task<object> DoWorkAsync(object[] args)
        {
            Console.WriteLine("Start working.");

            using (var wb = new WebBrowser())
            {
                wb.ScriptErrorsSuppressed = true;

                TaskCompletionSource<bool> tcs = null;
                WebBrowserDocumentCompletedEventHandler documentCompletedHandler = (s, e) =>
                    tcs.TrySetResult(true);

                // navigate to each URL in the list
                foreach (var url in args)
                {
                    tcs = new TaskCompletionSource<bool>();
                    wb.DocumentCompleted += documentCompletedHandler;
                    try
                    {
                        wb.Navigate(url.ToString());
                        // await for DocumentCompleted
                        await tcs.Task;
                    }
                    finally
                    {
                        wb.DocumentCompleted -= documentCompletedHandler;
                    }
                    // the DOM is ready
                    Console.WriteLine(url.ToString());
                    Console.WriteLine(wb.Document.Body.OuterHtml);
                }
            }

            Console.WriteLine("End working.");
            return null;
        }

    }

    // a helper class to start the message loop and execute an asynchronous task
    public static class MessageLoopWorker
    {
        public static async Task<object> Run(Func<object[], Task<object>> worker, params object[] args)
        {
            var tcs = new TaskCompletionSource<object>();

            var thread = new Thread(() =>
            {
                EventHandler idleHandler = null;

                idleHandler = async (s, e) =>
                {
                    // handle Application.Idle just once
                    Application.Idle -= idleHandler;

                    // return to the message loop
                    await Task.Yield();

                    // and continue asynchronously
                    // propogate the result or exception
                    try
                    {
                        var result = await worker(args);
                        tcs.SetResult(result);
                    }
                    catch (Exception ex)
                    {
                        tcs.SetException(ex);
                    }

                    // signal to exit the message loop
                    // Application.Run will exit at this point
                    Application.ExitThread();
                };

                // handle Application.Idle just once
                // to make sure we're inside the message loop
                // and SynchronizationContext has been correctly installed
                Application.Idle += idleHandler;
                Application.Run();
            });

            // set STA model for the new thread
            thread.SetApartmentState(ApartmentState.STA);

            // start the thread and await for the task
            thread.Start();
            try
            {
                return await tcs.Task;
            }
            finally
            {
                thread.Join();
            }
        }
    }
}

1
Cảm ơn vì câu trả lời tuyệt vời và thông tin đó! Đó chính xác là những gì tôi đang tìm kiếm. Tuy nhiên, dường như bạn đã (cố ý?) Đặt sai câu lệnh Dispose ().
wodzu

@ Paweł, bạn nói đúng, mã đó thậm chí còn không được biên dịch :) Tôi nghĩ rằng đã dán một phiên bản sai, hiện đã được sửa. Cảm ơn vì đã phát hiện ra điều này. Bạn có thể muốn kiểm tra một cách tiếp cận chung chung hơn: stackoverflow.com/a/22262976/1768303
noseratio

Tôi đã cố gắng chạy mã này, nhưng nó bị kẹt task.Wait();. Tôi đang làm điều gì sai ?
0014

1
Xin chào, có thể bạn có thể giúp tôi với cái này: stackoverflow.com/questions/41533997/… - phương pháp hoạt động tốt, nhưng nếu Biểu mẫu được khởi tạo trước MessageLoopWorker, nó sẽ ngừng hoạt động.
Alex Netkachov

3

Theo kinh nghiệm của tôi trong quá khứ, trình duyệt web không thích hoạt động bên ngoài chuỗi ứng dụng chính.

Hãy thử sử dụng httpwebrequests để thay thế, bạn có thể đặt chúng là không đồng bộ và tạo một trình xử lý cho phản hồi để biết khi nào nó thành công:

how-to-use-httpwebrequest-net-asynchronously


Vấn đề của tôi với đó là điều này. Việc nhấp vào Uri yêu cầu trang web phải được đăng nhập. Tôi không thể đạt được điều này với WebRequest. Bằng cách sử dụng WebBrowser, nó đã sử dụng bộ nhớ cache của IE, vì vậy các trang web đã đăng nhập. Có cách nào để khắc phục điều đó không? Các liên kết liên quan đến facebook. Vậy tôi có thể đăng nhập vào facebook và nhấp vào liên kết bằng webwrequest không?
Art W

@ArtW Tôi biết đây là một bình luận cũ, nhưng mọi người có lẽ có thể giải quyết bằng cách thiết lậpwebRequest.Credentials = CredentialsCache.DefaultCredentials;
vapcguy

@vapcguy Nếu đó là một API thì có, nhưng nếu đó là một trang web có các phần tử HTML để đăng nhập thì nó sẽ cần sử dụng cookie hoặc bộ nhớ cache của IE, nếu không ứng dụng khách không biết phải làm gì với thuộc tính Credentialsđối tượng và cách điền HTML.
ColinM

@ColinM Bối cảnh mà toàn bộ trang này đang nói đến là sử dụng đối tượng HttpWebRequest và C # .NET, không phải HTML và các phần tử biểu mẫu đơn giản đang được đăng, giống như bạn có thể làm với JavaScript / AJAX. Nhưng bất kể, bạn có một người nhận. Và để đăng nhập, bạn nên sử dụng Windows Authentication và IIS tự động xử lý việc này. Nếu bạn cần kiểm tra chúng theo cách thủ công, bạn có thể sử dụng WindowsIdentity.GetCurrent().Namesau khi thực hiện mạo danh và kiểm tra nó với tìm kiếm AD, nếu bạn muốn. Không chắc chắn cách cookie và bộ nhớ cache sẽ được sử dụng cho bất kỳ điều gì trong số đó.
vapcguy

@vapcguy Câu hỏi đang nói về WebBrowserđiều này sẽ chỉ ra rằng các trang HTML đang được tải, OP thậm chí đã nói rằng điều đó WebRequestsẽ không đạt được những gì anh ta muốn, do đó nếu một trang web mong đợi đầu vào HTML để đăng nhập thì thiết lập Credentialsđối tượng sẽ không hoạt động. Ngoài ra, như OP nói, các trang web bao gồm Facebook; Xác thực Windows sẽ không hoạt động trên điều này.
ColinM

0

Một giải pháp đơn giản để xảy ra hoạt động đồng thời của một số Trình duyệt Web

  1. Tạo một ứng dụng Windows Forms mới
  2. Đặt nút có tên là button1
  3. Đặt hộp văn bản có tên textBox1
  4. Đặt thuộc tính của trường văn bản: Multiline true và ScrollBars Cả hai
  5. Viết trình xử lý button1 click sau:

    textBox1.Clear();
    textBox1.AppendText(DateTime.Now.ToString() + Environment.NewLine);
    int completed_count = 0;
    int count = 10;
    for (int i = 0; i < count; i++)
    {
        int tmp = i;
        this.BeginInvoke(new Action(() =>
        {
            var wb = new WebBrowser();
            wb.ScriptErrorsSuppressed = true;
            wb.DocumentCompleted += (cur_sender, cur_e) =>
            {
                var cur_wb = cur_sender as WebBrowser;
                if (cur_wb.Url == cur_e.Url)
                {
                    textBox1.AppendText("Task " + tmp + ", navigated to " + cur_e.Url + Environment.NewLine);
                    completed_count++;
                }
            };
            wb.Navigate("/programming/4269800/webbrowser-control-in-a-new-thread");
        }
        ));
    }
    
    while (completed_count != count)
    {
        Application.DoEvents();
        Thread.Sleep(10);
    }
    textBox1.AppendText("All completed" + Environment.NewLine);
    
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.