IP_Pool
前言
刚好前段时间突然萌发了自己做一个代理池的想法,于是就用了一些通俗的方法来实现,一来能方便自己理解,二来也加强学习。
这里开放给大家,给大家提供一点参考,使用前请务必要仔细查看README.md文件。
Github:Proxy_IP_Pool
总体构思
- 定期从公开的代理网站上采集ip,在进行初次验证后进行格式化并保存到指定文件;
- 定期检测已存ip的有效性;
- 提供api接口查看以存ip及获取有效代理ip。
应用技术
- python3: requests, flask, gevent,multiprocessing, logging
目录结构
--[ip_pool]:
--[pool]
--[core]:
--ip_crawl.py # 爬虫代码
--tool_lib.py # 工具函数集合
--items.py # 数据结构定义文件
--settings.py # 基础设置文件
--api.py # RESTful API接口文件
--[data]:
--ip_data.json # 有效ip存储文件(运行后生成文件夹)
--[log]:
--err.log # 错误日志(运行后生成文件夹)
--run.py # 爬虫及验证程序启动文件
--log.yml # logging配置文件
--requirements.txt # python requirments
--README.md
代码片段
仅展示部分代码片段,限于篇幅没有展示自建模块tool_lib,如需浏览完整代码请移步Github/Proxy_IP_Pool,运行前请务必细看README.md文件
ip_crawl.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
Purpose: 从公开网站抓取代理ip,创建ip池,每10分钟检测可用性进行更新一次,提供api接口,每次获取一个ip地址,并在取用之前进行检测
Author: YajunZheng
Created: 2018/10/25
"""
import datetime
import requests
from lxml import etree
from pool.items import IpDataFormat
from pool import settings
from pool.core import tool_lib
class XicidailiCrawl(object):
"""西刺代理"""
def __init__(self):
self.init_url = "http://www.xicidaili.com/nn/{0}"
self.s = requests.session()
self.s.keep_alive = False
self.proxies = tool_lib.get_valid_ip()
def start_requests(self):
"""获取第一页中包含的ip列表"""
for i in range(1, 2):
url = self.init_url.format(str(i))
headers = settings.HEADERS
res = self.s.get(url=url, headers=headers, proxies=self.proxies, timeout=60)
selector_1 = etree.HTML(res.text)
ip_table = selector_1.xpath('//*[@id="ip_list"]//tr')
print("Xicidaili: {0} ALL:{1} TIME:{2}".format(url, len(ip_table), datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
yield ip_table
def parse_ip_info(self, each_tr):
"""解析ip列表中的每一条ip信息,验证添加至储存文件"""
ip_info = IpDataFormat()
try:
ip = each_tr.xpath('./td[2]/text()')[0]
port = each_tr.xpath('./td[3]/text()')[0]
ip_type = each_tr.xpath('./td[6]/text()')[0].casefold()
ip_info['ip'] = str(ip)
ip_info['port'] = str(port)
ip_info['type'] = str(ip_type)
# print(ip_info.__dict__.values())
ip_proxies_str = "{'%s':'%s:%s'}" % (ip_info['type'], ip_info['ip'], ip_info['port'])
ip_proxies = eval(ip_proxies_str) # 字符串转列表
if tool_lib.verify_ip(ip_proxies=ip_proxies, ip=ip): # 验证ip的有效性
valid_ip_info = dict(ip=ip, proxies=ip_proxies)
tool_lib.add_ip(new_ip_info=valid_ip_info) # 添加ip
except:
pass
run.py
#!usr/bin/env python
# -*- coding:utf-8 -*-
"""
Purpose: 启动文件,通过运行该文件启动爬虫及周期校验程序
Author: YajunZheng
Created: 2018/10/25
"""
import logging
import time
from multiprocessing import Pool
import gevent
from gevent import monkey; monkey.patch_all()
from gevent.pool import Pool
from pool import settings
from pool.core import ip_crawl, tool_lib
log_pause = logging.getLogger('Crawl_Pause')
def xicidaili():
while True:
try:
th_pool = Pool(settings.VERIFY_MAX_CONCURRENT) # 初始化协程池
xici = ip_crawl.XicidailiCrawl() # 实例化西刺代理爬虫对象
for each_table in xici.start_requests():
threads = []
for each_tr in each_table:
threads.append(th_pool.spawn(xici.parse_ip_info, each_tr)) # 构造协程列表
gevent.joinall(threads) # 启动协程
time.sleep(settings.PAGE_SLEEP_TIME)
log_pause.info('XICI FINISHED')
time.sleep(settings.CRAWL_SLEEP_TIME)
except:
log_pause.warning('ERROR 503') # 利用代理ip访问西刺可能遇到封ip的情况,此时打印503错误,切换ip重连
time.sleep(settings.CRAWL_SLEEP_TIME/100)
if __name__ == '__main__':
"""程序启动后不必关闭,放置后台运行,爬虫及验证周期时长设置请参照settings.py"""
p = Pool(3) # 初始化进程池
p.apply_async(xicidaili) # 将xicidaili添加到进程池
p.apply_async(tool_lib.periodic_verify) # 将循环验证函数添加到进程池
p.join() # 启动
api.py
#!flask/bin/python
"""
Purpose: 提供有效的ip获取RESTful API接口
API-Example(127.0.0.1:5000):
获取单个有效ip(有验证): 127.0.0.1:5000/ip_pool/api/v1.0/get_ip
查看文件中所有ip(无验证): 127.0.0.1:5000/ip_pool/api/v1.0/get_ip_pool
查看文件中指定ip(无验证): 127.0.0.1:5000/ip_pool/api/v1.0/get_a_ip/0
Author: YajunZheng
Created: 2018/10/29
"""
import json
from os.path import dirname, abspath
from flask import Flask, jsonify
from flask import abort
from pool import settings
from pool.core.tool_lib import get_valid_ip
app = Flask(__name__)
abs_path = dirname(abspath(__file__))
data_path = abs_path + "/data/" + settings.DATA_FILE_NAME
def load_json_file(data_path):
"""读取json文件"""
with open(data_path, 'r') as fp:
res = json.load(fp)
fp.close()
return res
@app.route('/ip_pool/api/v1.0/get_ip', methods=['GET'])
def get_ip():
"""获取单个ip,验证有效后返回该ip"""
ip = get_valid_ip()
if ip:
return jsonify(ip)
else:
abort(404)
@app.route('/ip_pool/api/v1.0/get_ip_pool', methods=['GET'])
def get_all_ip():
"""获取整个ip池,不进行验证,返回json文件内容"""
ip_pool = load_json_file(data_path=data_path)
return jsonify({'get_ip_pool': ip_pool})
@app.route('/ip_pool/api/v1.0/get_ip/<int:ip_id>', methods=['GET'])
def get_a_ip(ip_id):
"""从json文件中获取指定ip,不进行验证,直接返回结果"""
ip_pool = load_json_file(data_path=data_path)
if ip_id > len(ip_pool):
abort(404)
else:
return jsonify(ip_pool[ip_id])
if __name__ == '__main__':
app.run(debug=True)