最近要进行某网站的信息抓取工作,不过这个网站作出了一些限制:如果某IP过于频繁的访问则会把这个IP列入黑名单。

不过这个网站使用了CDN技术进行全球加速,那么访问时候只要在请求头中指定HOST,url中指定加速IP即可一定程度上缓解被封问题。

在网上找到一篇有关文章节选如下(原文链接http://www.hack0nair.me/?p=615):

第一种解决方案是利用http数据包头部中的“Host”属性。

在发送HTTP请求的时候,数据包的头部总是会带上各种各样的属性,比如Data、Referer、Cookie等。(Quick reference to HTTP headers)

其中的Host属性是指,当前访问资源对应的主机名和端口号。

假设我们要访问的url是 http://www.hack0nair.me/wp-admin/ ,需要域名固定解析为 127.0.0.1 这个IP。

使用python中urllib库的request做法是: urllib.request(url="http://127.0.0.1/wp-admin/", headers={"Host" : "www.hack0nair.me"}) 访问会产生如下的数据包:

1
2
3
4
5
6
7
8
GET http://127.0.0.1/wp-admin/ HTTP/1.1
Host: www.hack0nair.me
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:16.0) Gecko/20100101 Firefox/16.0
Accept: */*
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://www.hack0nair.me/

这样的访问会告诉127.0.0.1服务器,当前需要访问的/wp-admin是www.hack0nair.me这个域名的资源。

看来问题已经解决了,其实未必。

对于不需要cookie支持的访问来说,问题确实解决了。但如果是需要多次访问并要验证cookie的话,这样做是不行的。因为这样访问产生的cookie对应的作用域是IP而不是域名!于是就有了下面一种更完美的解决方案。


第二种解决方案是直接修改socket的address。
方法来自StackOverFlow的MattH:http://stackoverflow.com/questions/2236498/tell-urllib2-to-use-custom-dns

具体的意思是,建立HTTP连接的时候仍然使用域名,但是通过修改底层socket的address来达到目的。

这里理解起来需要一点计算机网络的基础。在建立HTTP连接的时候,在应用层必然会使用一个socket跟远方服务器进行通信,而socket建立的时候需 要指定服务器的地址,这时候我们只需要填入我们预设的IP地址即可。由于应用层是不会去关心它的底层如何建立连接的,它只知道自己正在对哪个域名进行访 问,这样就相当于“欺骗”了处于上层的HTTP服务。(我的语言表达能力略拙计的说= =)

大概就是上面这个意思了,那么要在python中实现上面的想法,首先需要建立一个httplib.HTTPConnection的子类,在里面定义一个 connect方法求修改socket.create_connection的默认参数;然后继承HTTPHandler这个类,重写里面的 http_open方法,使得http_open去调用第一步的子类;最后就是把HTTPHandler放到我们的opener里面。

MattH同时给出了HTTP和HTTPS的修改方案,不过我只要HTTP的修改就够了。下面是经过我修改的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def MyHost(host):
if host == 'www.hack0nair.me':
return '127.0.0.1' # 指定的IP地址
else:
return host
class MyHTTPConnection(httplib.HTTPConnection):
def connect(self):
self.sock = socket.create_connection((MyHost(self.host),self.port),self.timeout)
class MyHTTPHandler(urllib2.HTTPHandler):
def http_open(self,req):
return self.do_open(MyHTTPConnection,req)
opener = urllib2.build_opener(MyHTTPHandler)
urllib2.install_opener(opener)
url = 'http://www.hack0nair.me/wp-admin'
req = request.Request(url)
req = f.read()

上面例子的含义是,当opener遇到域名是www.hack0nair.me时,MyHTTPHandler会让所建立的socket的address为指定的IP地址。由于没有对urllib相关对象进行修改,所以HTTP神马都不知道~

如果访问要带上cookie,那么还要加上HTTPCookieProcessor这个handler。


我使用第一种方式即解决了问题。对于需要登录的方式,我抓取的网站直接把从浏览器提取出来的COOKIE添加到请求头中即可实现登录访问,这个根据网站设置不同。