反爬虫机制说明
背景
近期在学习爬虫的过程中遇到了一个问题:
我使用Requests+BeautifulSoup制作了一个爬取目标网站:
陕西省教育厅教育要闻
的全部新闻标题以及URL
但是在爬取的过程中遇到了这样的问题,那就是我的ip被网站封禁了。
这才意识到原来各大网站还有反爬虫机制这回事。
为此,需要总结一些应对网站反爬虫机制的方法,主要参考自:避免网络爬虫IP被封的策略 - 王陸 - 博客园 (cnblogs.com)
User_agent伪装与轮换
user_agent简介
不同浏览器,不同内核版本在进行Http请求时,会在请求头中使用不同的User_agent
字段。有些网站会根据请求头中的这些信息来判断是不是有爬虫正在无情的爬取网页的数据。
类似的还有使用referer字段进行检测的。
对于这些简单的反爬虫机制,我们只需要伪装正常的请求头就能解决问题。
对于user_agent的检测,我们可以简单粗暴的使用一个user_agent列表,每次从中随机的提取出一个来构造请求即可,这里我们构造了一个用于产生随机user_agent的工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class UserAgentUtils (object ): __UserAgentList = [ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36" , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36" , "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36" , "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36" , "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko" , "Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko" , "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0" , "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 7.0; InfoPath.3; .NET CLR 3.1.40767; Trident/6.0; en-IN)" , "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)" , "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)" , "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)" , "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/4.0; InfoPath.2; SV1; .NET CLR 2.0.50727; WOW64)" , "Mozilla/5.0 (compatible; MSIE 10.0; Macintosh; Intel Mac OS X 10_7_3; Trident/6.0)" , "Mozilla/4.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)" , "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) ChromePlus/4.0.222.3 Chrome/4.0.222.3 Safari/532.2" , "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.28.3 (KHTML, like Gecko) Version/3.2.3 ChromePlus/4.0.222.3 Chrome/4.0.222.3 Safari/525.28.3" , "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16" , "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14" , "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14" , "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0) Opera 12.14" , "Opera/12.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.02" , "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00" , "Opera/9.80 (Windows NT 5.1; U; zh-sg) Presto/2.9.181 Version/12.00" , "Opera/12.0(Windows NT 5.2;U;en)Presto/22.9.168 Version/12.00" , "Opera/12.0(Windows NT 5.1;U;en)Presto/22.9.168 Version/12.00" , "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1" , "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0" , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0" , "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0" , "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0" , "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0" , ] def __init__ (self ): super .__init__() @classmethod def getRadomUserAgent (self ): max = 30 min = 0 index = random.randint(min , max ) print (index) return self .__UserAgentList[index]
使用代理IP和轮换
有些反爬虫机制比较严格的网站,比如拉勾网 ,它们的机制大致就是:
在没有登录的情况下,程序只能连续访问 3 个 Url。如果再继续访问,网站会将链接重定向,然后提示我们登录。
如果在登录情况下,连续请求部分 url 之后,我们的 IP 会被封。
对于这类直接检测IP并进行封禁的网站,则需要使用IP代理池来突破。
代理按匿名度区分有如下三种:
透明代理 :目标网站知道你使用了代理并且知道你的源IP地址,这种代理显然不符合我们这里使用代理的初衷
匿名代理 :匿名程度比较低,也就是网站知道你使用了代理,但是并不知道你的源IP地址
高匿代理 :这是最保险的方式,目标网站既不知道你使用的代理更不知道你的源IP
代理的获取方式可以去购买,当然也可以去自己爬取免费的。
通常免费代理不够稳定。
不使用代理
首先,不是用代理IP的情况如下:
1 2 3 4 5 6 7 8 9 import requestsurl = 'http://icanhazip.com' try : response = requests.get(url) print (response.status_code) if response.status_code == 200 : print (response.text) except requests.ConnectionError as e: print (e.args)
运行上面的程序,会返回我们电脑本机的 IP,可以通过百度查询 IP 地址对比一下就知道了。
1 2 200 124.238.223.xxx # 后三位隐去了
使用代理
常见的代理包括 HTTP 代理和 SOCKS5 代理,前者可以找一些免费代理 IP 进行测试,由于我电脑上使用的是 Shadowsocks,所以就介绍一下 SOCKS5 代理的设置。
启动该软件后默认会在 1080 端口下创建 SOCKS5 代理服务,代理为:127.0.0.1:1080
,然后我们在 Requests 中使用该代理,方法很简单只需要添加一项 proxies 参数即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 proxies = [ {'http' :'socks5://127.0.0.1:1080' }, {'https' :'socks5://127.0.0.1:1080' } ] proxies = random.choice(proxies) print (proxies)url = 'http://icanhazip.com' try : response = requests.get(url,proxies=proxies) print (response.status_code) if response.status_code == 200 : print (response.text) except requests.ConnectionError as e: print (e.args)
random 函数用来随机选择一个代理,我们来看一下结果:
1 2 3 {'http' : 'socks5://127.0.0.1:1080' } 200 45.78 .42 .xxx
可以看到,这里随机选择了 http 协议的代理后,返回的 IP 就不是真实的 IP 代理地址了,成功代理后就可以爬一些墙外的网页了。
延伸一下,假如随机选择的是 https 代理,那么返回的 IP 结果还一样么?我们尝试重复运行一下上面的程序:
1 2 3 {'https' : 'socks5://127.0.0.1:1080' } 200 124.238 .223 .xxx
可以看到这次使用了 https 代理,返回的 IP 却是本机的真实 IP,也就是说代理没有起作用。
进一步地,我们将 url 改为 https 协议 'https://icanhazip.com'
,然后再尝试分别用 http 和 https 代理请求,查看一下结果:
1 2 3 4 5 6 7 8 9 {'http' : 'socks5://127.0.0.1:1080' } 200 124.238 .223 .xxx{'https' : 'socks5://127.0.0.1:1080' } 200 45.78 .42 .xxx
可以看到,两种请求的结果和之前的刚好相反了,由于 url 采用了 https 协议,则起作用的是 https 代理,而 http 代理则不起作用了,所以显示的是本机 IP。
因此,可以得到这样的一个结论:
HTTP 代理,只代理 HTTP 网站,对于 HTTPS 的网站不起作用,也就是说,用的是本机 IP 。
HTTPS 代理则同理。
使用付费代理
上面,我们只使用了一个代理,而在爬虫中往往需要使用多个代理,那有如何构造呢,这里主要两种方法,一种是使用免费的多个 IP,一种是使用付费的 IP 代理,免费的 IP 往往效果不好,那么可以搭建 IP 代理池,但对新手来说搞一个 IP 代理池成本太高,如果只是个人平时玩玩爬虫,完全可以考虑付费 IP,几块钱买个几小时动态 IP,多数情况下都足够爬一个网站了。
这里推荐一个付费代理「阿布云代理 」,最近使用了一下,效果非常不错,5 块钱买了 5个小时,爬完了一个网站,所以没有必要为了省 5 块钱,而费劲地去搞 IP 代理池。
首次使用的话,可以选择购买一个小时的动态版试用下,点击生成隧道代理信息作为凭证加入到代码中。
将信息复制到官方提供的 Requests 代码中,运行来查看一下代理 IP 的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import requeststargetUrl = "http://icanhazip.com" def get_proxies (): proxyHost = "http-dyn.abuyun.com" proxyPort = "9020" proxyUser = "HS77K12Q77V4G9MD" proxyPass = "4131FFDFCE27F104" proxyMeta = "http://%(user)s:%(pass)s@%(host)s:%(port)s" % { "host" : proxyHost, "port" : proxyPort, "user" : proxyUser, "pass" : proxyPass, } proxies = { "http" : proxyMeta, "https" : proxyMeta, } for i in range (1 ,6 ): resp = requests.get(targetUrl, proxies=proxies) print ('第%s次请求的IP为:%s' %(i,resp.text)) get_proxies()
可以看到每次请求都会使用不同的 IP,是不是很简单?比搞 IP 代理池省事多了。
设置访问时间间隔
很多网站的反爬虫机制都设置了访问间隔时间,一个IP如果短时间内超过了指定的次数就会进入“冷却CD”。甚至有些小网站经受不起如此高的并发度,以至于被我们写的不够好的爬虫爬宕机。
所以除了轮换IP和user_agent,还可以设置访问的时间间间隔长一点,比如没抓取一个页面休眠一个随机时间:
1 2 import time,randomtime.sleep(random.random()*3 )
对于一个crawler来说,这是一个比较responsible的做法。
因为本来爬虫就可能会给对方网站造成访问的负载压力,所以这种防范既可以从一定程度上防止被封,还可以降低对方的访问压力。
参考文献
爬代理ip
爬虫IP被禁的简单解决方法 - 木白的菜园 - 博客园
Python 爬虫的代理 IP 设置方法汇总 - 三度 - 博客园
爬虫与反爬虫的博弈 - 知乎
盘点一些网站的反爬虫机制 - 知乎
避免网络爬虫IP被封的策略 - 王陸 - 博客园