利用Python建立和维护爬虫代理池

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)

猜你喜欢

转载自blog.csdn.net/zhengyajun_email/article/details/83898069