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,您很有可能能够并行处理此代码以从多个网站检索数据(假设您没有将网络加载到容量) .
我正在制作网络爬虫,我刚刚发现我的方法之一 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 theReadToEnd()
method it takes 1000 MS.
就是这样 - 能够调用 Response.GetResponseStream()
并不意味着你 "got a response"。您得到的只是对响应存在的确认。
在现实世界中,这类似于收到包裹,您必须在 post 办公室签收包裹。 Post 办公室会在您的邮箱中放一张 post 卡片,上面写着 post 办公室有货物等着您。那是你的 Response.GetResponseStream()
电话。但此时您还没有包裹,只有一张 post 卡片,上面写着包裹在那里。现在您需要去 post 办公室,向他们出示卡片,然后取回包裹。那是 StreamReader.ReadToEnd()
电话。
时间几乎翻了一番,因为 1000 毫秒的大部分时间都花在了与远程服务器的通信上。如果您需要完整的响应,那么您几乎无能为力以加快响应速度。好消息是,由于时间花费在 I/O,您很有可能能够并行处理此代码以从多个网站检索数据(假设您没有将网络加载到容量) .