如何构建我自己的ip代理池

本文详细记述了我是如何构建和维护我的ip代理池。

首先我们要有一些爬虫去网上的免费代理网站爬取代理,解析处理后存到数据库中,我选用了NoSQL界的翘楚MongoDB作为我的后端数据库,当然你也可以选择mysql,这纯粹是因为个人喜好。好了,废话不多说,上代码。

1
2
3
4
5
6
def spider0():
url = 'http://cn-proxy.com/'
r = requests.get(url)
for ip,port in re.findall('<td>(\d+\.\d+\.\d+\.\d+)</td>\n<td>(\d+)</td>',r.text):
ip_port = ip + ':' + port
database.insert(ip_port)

为了爬虫代理的扩展性,函数命名采用spider+数字的格式。
以上爬虫就是爬取了http://cn-proxy.com/这个网站,这网站给出的代理大多都是可用的,我们将在下文看到。因为这个cn-proxy在国内无法直接访问,我用了代理去爬取。

因为爬虫的质量参差不齐,我们必须有机制来筛选可用的爬虫,于是有了下面的这个check函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def check(ip_port):
check_url_list = [
'http://www.baidu.com',
'http://www.taobao.com',
]
timeout = 3
proxies = {
'http':ip_port,
}
for check_url in check_url_list:
try:
r = requests.get(check_url,proxies=proxies,timeout=timeout)
if r.status_code != 200:
raise HttpError
except:
print(ip_port+'已舍弃')
return False
return ip_port

check函数用获取的代理去尝试打开baidu.com以及taobao.com,并且设置timeout为3秒,这足够满足我们一般使用爬虫的需求了。当此代理可以在3秒内打开百度或者淘宝的时候,我们就应该将此代理存入数据库,否则丢掉此代理。下面的函数就是写入数据库的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def insert(ip_port):
ip_port = check(ip_port)
if ip_port:
client = MongoClient(MONGODB)
db = client['proxy_pool']
collection = db['proxies']
post = {
"_id":ip_port,
}
existence = collection.find_one({"_id": ip_port})
if not existence:
post_id = collection.insert_one(post,{"upsert":"true"}).inserted_id
print(post_id+'已写入')
else:
print(existence['_id']+'已存在')

另外需要说明的是,我们在写入数据库前应检查数据库中是否已经存在了这个代理,印象中Mongo提供了upsert方法,即存在就更新不存在则创建,但是在我使用pymongo写数据库时并没找到关于upsert的资料,于是只能自己实现了一个queryandinsert的功能。

再下一步,就是从数据库中提取爬虫代理供我们使用了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def query():
collection = MongoClient(MONGODB)['proxy_pool']['proxies']
try:
ip_port = list(collection.aggregate([{"$sample": {"size":1}}]))[0]['_id']
if check(ip_port):
return ip_port
else:
delete(ip_port)
ip_port = list(collection.aggregate([{"$sample": {"size":1}}]))[0]['_id']
while not check(ip_port):
delete(ip_port)
ip_port = list(collection.aggregate([{"$sample": {"size":1}}]))[0]['_id']
return ip_port
except IndexError:
print('数据库为空,请先抓取IP_PORT')
return False

可以看到,query函数中我调用了一个名为delete的函数,这个delete函数的作用就是检查代理是否已经过期,因为我们在写数据库时爬虫代理是可用的,而当我们提取这个代理时有相当的概率让此代理废弃掉,所以我们为了筛选出可用代理,在query中不仅check代理可用性,也对数据库做了清洗。当然,我们也可以用一个实时检查数据库内代理是否可用的程序来做这样一件事情,本例中我并未完成这个工作,或许会在代理达到一定数量时补写这个程序。

delete函数的代码如下:

1
2
3
4
5
def delete(ip_port):
if ip_port:
collection = MongoClient(MONGODB)['proxy_pool']['proxies']
collection.remove({'_id':ip_port})
print(ip_port+'已删除')

以下代码用于扩展代理来源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def spider1():
url = 'http://www.xicidaili.com/'
r = requests.get(url,headers=headers)
ips = re.findall('<td>(\d+\.\d+\.\d+\.\d+)</td>',r.text)
ports = re.findall('<td>(\d+)</td>',r.text)
for ip,port in zip(ips,ports):
ip_port = ip+':'+port
database.insert(ip_port)
def spider2():
url = 'http://proxy.com.ru/'
r = requests.get(url)
for ip,port in re.findall('<td>(\d+\.\d+\.\d+\.\d+)</td><td>(\d+)</td>',r.text):
ip_port = ip + ':' + port
database.insert(ip_port)
def spider3():
pass
def spider4():
pass
def spider5():
pass
def spider6():
pass

其中有些网站需要验证User-Agent,所以我们带headers访问,有些网站在国内因为某些原因无法打开,我们用proxies代理访问。

最后是一个轮询函数,实时去请求爬虫代理网站获取新的代理:

1
2
3
4
5
while True:
for i in range(spider_numbers):
function_name = 'spider'+str(i)
getattr(sys.modules[__name__], function_name)()
time.sleep(3600)