使用同一个IP地址频繁抓取数据,IP容易被封。可以使用代理proxy解决这个问题。
1. 代理proxy
使用同一个IP地址频繁抓取数据,IP容易被封。使用HTTP代理可以解决这个问题。HTTP代理有两种:
(1)普通代理
该代理扮演着中间人角色,替客户向端向服务发送请求,并替服务器将响应发送给客户端,如下图所示:
图1 HTTP普通代理示意图(图来源于《HTTP权威指南》)
代理服务器对接收到的请求进行解析,重新封装后发给服务器;服务器响应后,代理服务器对响应进行解析,重新封装再发送给客户端。
(2)隧道代理
隧道协议(Tunneling Protocol)使用一种发送协议,将另一个不同的网络协议封装在发送协议的正文部分。使用隧道可以在不兼容的网络上传输数据,或为不安全网络提供一个安全访问。
图2 隧道代理示意图(图来源于《HTTP权威指南》)
本文先介绍如何使用普通代理实现抓取。
2. 使用普通代理
(1)使用requests
调用requests.get(url, params=None, **kwargs),传入关键字proxies
,proxies
是字典型,将协议或者协议+主机映射到代理的URL,如{‘http’: ‘foo.bar:3128’, ‘http://host.name’: ‘foo.bar:4012’}
。简单例子如下:
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
ua=UserAgent()
proxies = {'http' : '123.207.11.119:1080', 'http' : '140.143.142.218:1080'}
response = requests.get('http://baidu.com', proxies=proxies, headers={'User-Agent': ua.random})
soup = BeautifulSoup(response.text, 'lxml')
(2)使用urllib
直接上代码,如下:
import urllib
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
ua=UserAgent()
proxies = {'http' : '123.207.11.119:1080', 'http' : '140.143.142.218:1080'}
proxy_handler = urllib.request.ProxyHandler(proxies)# Cause requests to go through a proxy
opener = urllib.request.build_opener(proxy_handler) # construct a new opener using your proxy settings
urllib.request.install_opener(opener) # install the opener on the module-level
req = urllib.request.Request('http://baidu.com', headers={'User-Agent': ua.random})
f = urllib.request.urlopen(req)
soup = BeautifulSoup(f, 'lxml')
尽管使用了代理,但使用一个代理频繁抓取,也会面临代理IP被封的问题。解决该问题,收集一些代理IP,形成代理IP池,轮流使用。
3. 代理IP池
有不少网站提供免费代理IP,比如我之前用的国内高匿免费HTTP代理xicidaili.com,可惜现在用不了。
写一段简单代码,将代理IP保存成字典列表http_type : "{http_type}://{ip}:{port}"
格式,如下:
def get_list_proxy_dicts():
urls = ['https://www.xicidaili.com/nn/{}'.format(i) for i in range(2, 5)]
for url in urls:
web_data = requests.get(url, headers=headers)
soup = BeautifulSoup(web_data.text, 'lxml')
rows = soup.find_all('tr')
list_proxy_dicts = []
for i, row in enumerate(rows):
tds = row.find_all('td')
http_type = tds[5].text.lower()
if http_type not in ['http', 'https']:
continue
proxy_dict = {http_type : "{http_type}://{ip}:{port}".format(
http_type=http_type,
ip=tds[1].text,
port=tds[2].text)}
if is_available_proxy(proxy_dict):
list_proxy_dicts.append(proxy_dict)
return list_proxy_dicts
抓取到的IP池,不见得每个都有效,需要事先验证,确认有效再加入IP池。这里用的验证方法很简单,看该代理能否正常访问百度。
def is_available_proxy(proxy_dict):
try:
proxy_handler = urllib.request.ProxyHandler(proxy_dict)
opener = urllib.request.build_opener(proxy_handler)
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
urllib.request.install_opener(opener)
req = urllib.request.Request('http://www.baidu.com')
sock = urllib.request.urlopen(req)
except urllib.error.HTTPError as e:
print('Error code: ', e.code)
return False
except Exception as detail:
print("ERROR:", detail)
return False
return True
参考资料:
[1] 普通代理:RFC 7230 - HTTP/1.1: Message Syntax and Routing
[2] 隧道代理:Tunneling TCP based protocols through Web proxy servers