C# - StreamReader.ReadToEnd() 非常慢

C# - StreamReader.ReadToEnd() is extremely slow

我正在制作网络爬虫,我刚刚发现我的方法之一 GetHTML 非常慢,因为它使用 StreamReader 来获取 HTML 的字符串HttpWebResponse 对象。

方法如下:

static string GetHTML(string URL)
      {
           HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(URL);
           Request.Proxy = null;
           HttpWebResponse Response = ((HttpWebResponse)Request.GetResponse());
           Stream RespStream = Response.GetResponseStream();
           return new StreamReader(RespStream).ReadToEnd(); // Very slow
      }

我用 Stopwatch 进行了测试,并在 YouTube 上使用了这个方法。

Time it takes to get an HTTP response: 500 MS

Time it takes to convert the HttpWebResponse object to a string: 550 MS

所以 HTTP 请求没问题,只是 ReadToEnd() 太慢了。

除了 ReadToEnd() 方法之外,是否还有其他方法可以从响应对象中获取 HTML 字符串?我尝试使用 WebClient.DownloadString() 方法,但它只是 HttpWebRequest 的包装器,它也使用流。

编辑: 尝试使用套接字,速度更快:

static string SocketHTML(string URL)
      {
           string IP = Dns.GetHostAddresses(URL)[0].ToString();
           Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
           s.Connect(new IPEndPoint(IPAddress.Parse(IP), 80));
           s.Send(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n"));
           List<byte> HTML = new List<byte>();
           int Bytes = 1;
           while (Bytes > 0)
           {
                byte[] Data = new byte[1024];
                Bytes = s.Receive(Data);
                foreach (byte b in Data) HTML.Add(b);
           }
           s.Close();
           return Encoding.ASCII.GetString(HTML.ToArray());
      }

不过,将它与 Sockets 一起使用的问题是,大多数时候 returns 会出现 "Moved Permanently" 或 "Your browser sent a request that the server could not understand".

错误

When I call this method but return String.Empty instead of the ReadToEnd, the method takes about 500 MS.

也就是说,开始 获得响应需要 500 毫秒。调用 GetResponseStream 不会消耗所有数据。

ReadToEnd 还将进行从二进制数据到文本的转换,但我怀疑这是否重要 - 我强烈怀疑它只是在等待数据通过网络到达。为了验证这一点,您应该将日志记录添加到代码的每个方面 and 运行 Wireshark - 然后您应该能够逐个数据包地查看数据到达,并将其与日志相关联。

作为附带问题,您应该绝对有一个using响应语句:

using (var response = ((HttpWebResponse)Request.GetResponse())
{
    // The stream will be disposed when the response is.
    return new StreamReader(response.GetResponseStream())
        .ReadToEnd();
}

如果您不处理响应,您将占用连接,直到垃圾收集器完成连接。这可能会导致超时。

不是ReadToEnd方法慢,是等待数据需要时间。

ReadToEnd方法足够快了。我刚刚测试使用流reader从内存流中读取一兆字节的数据,只需要3毫秒。

当您从请求中获取响应流时,它才刚刚开始获取所请求的数据。一旦您读取了已收到的数据,它就必须等待其余数据到达。这就是 ReadToEnd 调用所花费的时间。使用任何其他方式读取流不会使其更快。

I made this comparison to see if the StreamReader.ReadToEnd() is the bottleneck, and I've seen it is.

您在这里得出了错误的结论:瓶颈在于整个方法,而不仅仅是它的 StreamReader.ReadToEnd() 部分。

When I receive the response and I don't use the ReadToEnd() method, it takes about 500 MS, but if I use the ReadToEnd() method it takes 1000 MS.

就是这样 - 能够调用 Response.GetResponseStream() 并不意味着你 "got a response"。您得到的只是对响应存在的确认。

在现实世界中,这类似于收到包裹,您必须在 post 办公室签收包裹。 Post 办公室会在您的邮箱中放一张 post 卡片,上面写着 post 办公室有货物等着您。那是你的 Response.GetResponseStream() 电话。但此时您还没有包裹,只有一张 post 卡片,上面写着包裹在那里。现在您需要去 post 办公室,向他们出示卡片,然后取回包裹。那是 StreamReader.ReadToEnd() 电话。

时间几乎翻了一番,因为 1000 毫秒的大部分时间都花在了与远程服务器的通信上。如果您需要完整的响应,那么您几乎无能为力以加快响应速度。好消息是,由于时间花费在 I/O,您很有可能能够并行处理此代码以从多个网站检索数据(假设您没有将网络加载到容量) .