公司的爬虫在爬取个别网站内容时候发现目标服务器对访问频率做了限制,这样只能限制爬虫访问的频率,造成了信息的滞后和数量始终上不去。于是就研究能不能让访问请求依次通过不同的IP地址访问目标服务器来增大访问的频率。让爬虫在不同的服务器上运行,这是另外一种解决思路,涉及到分布式爬虫的很多问题,不在本文讨论范围内。
Google搜索了半天,找到了一些蛛丝马迹,发现Socket.Bind方法理论上可行。接下来就想如何干涉HttpWebRequest的这个过程。一路查看HttpWebRequest的源代码,果然在ServicePoint中找到了BindIPEndPointDelegate属性,这个属性是一个名为BindIPEndPoint的代理,签名如下:
public delegate IPEndPoint BindIPEndPoint(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount);
通过设置这个属性,可以在发出连接的时候绑定客户端发出连接所使用的IP地址。
一个简单的示例程序:
1 class MyWebClient : WebClient
2 {
3 IPEndPoint m_OutIPEndPoint;
4 MyWebClient(IPEndPoint outIp)
5 {
6 if (outIp == null)
7 throw new ArgumentNullException("outIp");
8
9 m_OutIPEndPoint = outIp;
10 CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
11 }
12
13 protected override WebRequest GetWebRequest(Uri address)
14 {
15 var request = (HttpWebRequest)base.GetWebRequest(address);
16 request.ServicePoint.BindIPEndPointDelegate = (servicePoint, remoteEndPoint, retryCount) =>
17 {
18 return m_OutIPEndPoint;
19 };
20 return request;
21 }
22
23 public static void Run()
24 {
25 MyWebClient client2 = new MyWebClient(new IPEndPoint(IPAddress.Parse("192.168.1.105"), 0));
26 Console.WriteLine(client2.DownloadString("http://192.168.1.15:8000/Default.asp"));
27 client2.Dispose();
28 Thread.Sleep(ServicePointManager.MaxServicePointIdleTime);
29 MyWebClient client = new MyWebClient(new IPEndPoint(IPAddress.Parse("192.168.1.204"), 0));
30 Console.WriteLine(client.DownloadString("http://192.168.1.15:8000/"));
31 client.Dispose();
32 }
33 }
我在本机的网卡绑定了2个IP地址,192.168.1.105和192.168.1.204.而http://192.168.1.15:8000/Default.asp是我写的一个测试页面,只输出客户端请求的IP地址。请注意,其中有一行Thread.Sleep(ServicePointManager.MaxServicePointIdleTime);如果没有加入这行,你会得到两行192.168.1.105的结果,在代理那行(return m_OutIPEndPoint;)加入断点会发现只命中了一次。加入这行以后会得到192.168.1.105和192.168.1.204这个期望的结果.
原因就得从ServicePointManager说起,这个类缓存了ServicePoint,代表了到目标的连接。只要这个连接已经存在,出去的IP地址就已经确定了,所以不会再调用BindIPEndPointDelegate来获取出去的IP地址。加入了Thread.Sleep这行代码,只是等待这个连接释放,这样再次访问页面时候就会重新建立连接,从新绑定出去的IP地址了。这样做只是为了验证本文的目的。
当然这个示例程序没有办法真正工作,因为爬虫不可能休眠这么长时间,也不可能把ServicePointManager.MaxServicePointIdleTime设置为很短的时间,虽然实现了目的,但是降低了效率。这个示例程序只是揭示了使用不同IP地址访问目标服务器的可能性和实现途径。