代理池架构及实现

Posted by grt1stnull on 2017-05-22

0x00.前言

很多网站都有反爬虫策略,当我们使用爬虫进行频繁的抓取时可能会被ban。面对这种情况,通常我们会对爬虫线程进行随机秒数的sleep。除此之外,对于不需要cookie的爬取需求,我们也可以使用多个代理(proxy)进行爬取。

本文即介绍代理池的架构、实现,给我们的爬虫用上随机代理。

0x01.基本架构

代理池的模块如下:

  • 爬取免费代理
  • 验证代理是否可用
  • 可用代理存储
  • api输出代理

0x02.代码实现

虽然github上有很多代理池项目,但是不是自己写的,用起来没有那么得心应手,所以还是想自己造轮子。在看别人的代码时遇到了一个很棒的实现,所以在他代码的基础上改了一些。

1.爬取免费代理

就是正则(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{2,4}")或者lxml提取,没什么意思,提供免费代理的网站也很多,这里跳过不表。

为了扩展性,我在配置文件里定义了一个列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PATTERN_PROXY_SITES = [
{#1
"url":"http://www.ip3366.net/",
"type":"normal",
"range":["?stype=1&page=%s" % n for n in range(1, 3)]
+["free/?stype=2&page=%s" % n for n in range(1, 3)]
+["free/?stype=3&page=%s" % n for n in range(1, 3)]
+["free/?stype=4&page=%s" % n for n in range(1, 3)],
"pattern":"//tbody/tr",
"ip_pattern":"td",
"ip_num":0,
"port_pattern":"td",
"port_num":1,
"update_time":"720",
},
]

我定义了类型来方便爬取分类,在range里分页得到一个列表,同时定义了使用lxml的方法(pattern)。

使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def fetch_normal():
proxies = []
for a in PATTERN_PROXY_SITES:
if a["type"] != "normal":
continue
for r in a["range"]:
response = get(a["url"] + r, headers={'User-Agent': get_ua()}, timeout=FETCH_TIMEOUT)
html = etree.HTML(response.content)
father = html.xpath(a["pattern"])
for l in father:
ip = l.xpath(a["ip_pattern"])[a["ip_num"]]
port = l.xpath(a["port_pattern"])[a["port_num"]]
proxies.append("%s:%s" % (ip.text, port.text))
return proxies

2.验证代理是否可用

首先使用装饰器写了一个get方法,即加了异常处理的requests的get方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def requests_exc(func):
"""
处理requests请求出现的异常
异常的请求返回False
"""
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except (
requests.RequestException, requests.HTTPError,
requests.packages.urllib3.exceptions.HTTPError,
socket.error
) as e:
return False
return wrapper
@requests_exc
def get(*args, **kwargs):
return requests.get(*args, **kwargs)

之后定义了tester函数,因为baidu的速度很快,所以使用了baidu的网址。网址也可以替换为查询ip的接口,这样既可以测试代理可用性,也可以测试代理类型(比如是不是透明代理)。

1
2
3
4
5
6
7
def tester(proxy):
r = get('http://www.baidu.com', proxies={'http': 'http://' + proxy}, timeout=TEST_TIMEOUT)
# 方便调试
if r is False:
return False
else:
return True

3.可用代理存储

因为代理数量并不是很多(1700左右),使用数据库有一点兴师动众的意味,所以我这里使用了python的集合(set)这一数据类型。

如果要使用数据库,当然是首推mongodb,速度快,扩展性强。虽然没有使用它,但我还是对mongo基本操作封装了一个类。

4.api输出代理

使用python的flask框架,几行代码就可以实现我们的api功能。

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask, request, jsonify
from config import SERVER_PASSWD
app = Flask(__name__)
@app.route('/')
def get_proxy():
passwd = request.args.get('passwd', None)
if passwd != 'YOUR_PASSWD':
return jsonify({'status': 'Incorrect password.'})
return jsonify({'status': 'Successful', 'proxy': lambda: random.choice(SET_PROXY)})

0x03.tricks

1.lambba

2.装饰器

0x04.后记

写好之后测试了一下,爬取速度很快,不到一分钟就爬取了所有的免费代理,但是测试代理可用性非常慢,不知道为什么。

代码进一步完善后再公开。

0x05.参考

constverum/ProxyBroker

leeyis/ip_proxy_pool

Greyh4t/ProxyPool

jhao104/proxy_pool

TitorX/ProxyPool