目录
一、基础项目结构及解释
二、具体代码实现
2.1 采集模块
2.2 校验模块
2.3 数据模块
2.4 检测模块
2.5 API模块
2.6 工具模块
2.7 数据模型、配置文件、主程序
三、项目结果展示
3.1 数据库展示
3.2 Web页面展示(三种,挑选了一个展示)
一、基础项目结构及解释
代理池目的:当同一个IP对某网站访问次数过多,就会限制IP访问,所以我们需要从网上不稳定的代理IP中抽取高可用IP,供爬虫使用。
爬取代理池工作流程:多个代理IP网站-pyspider抓取-proxy_validate校验(响应速度,协议类型,匿名类型)-将可用代理IP存入数据库
使用代理IP流程:proxy_test检测数据库中的代理IP-更新或者删除代理IP(分数减一,为0则删除)-proxy_api提供稳定代理IP服务接口(获取质量/得分高的IP)
模块抽取:采集模块,校验模块,数据模块,检测模块,API模块,工具模块(日志记录,http模块)
二、具体代码实现
2.1 采集模块
# base_spider.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author : {Jack Zhao}
@Time : 2020/5/9 8:45
@Contact : {zc_dlmu@163.com}
@Desc : 通用爬虫,作为父类,指定URL列表,分组Xpath(包含ip列表的标签)和组内Xpath(ip: ...port:..)
'''
import requests
from domain import Proxy
from utils.http import get_request_headers
from lxml import etree
class BaseSpider(object):
# 指定URL列表,分组Xpath(包含ip列表的标签)和组内Xpath(ip: ...port:..)
urls = []
group_xpath = ''
detail_xpath = {}
def __init__(self,urls=[],group_xpath='',detail_xpath={}):
if urls:
self.urls = urls
if group_xpath:
self.group_xpath = group_xpath
if detail_xpath:
self.detail_xpath = detail_xpath
def get_page_from_url(self,url):
'''根据url请求,获取页面数据'''
response = requests.get(url,headers=get_request_headers())
return response.content
def get_first_from_list(self,lis):
# 检查是否为空
return lis[0] if len(lis)!=0 else ''
def get_proxies_from_page(self,page):
'''解析界面,提取数据,封装为Proxy对象'''
element = etree.HTML(page)
# 获取包含ip的标签列表
trs = element.xpath(self.group_xpath)
# 遍历,获取相关信息
for tr in trs:
ip = self.get_first_from_list(tr.xpath(self.detail_xpath['ip']))
port = self.get_first_from_list(tr.xpath(self.detail_xpath['port']))
area = self.get_first_from_list(tr.xpath(self.detail_xpath['area']))
proxy = Proxy(ip,port,area=area)
# 使用生成器返回提取到的数据
yield proxy
def get_proxies(self):
# 遍历url
for url in self.urls:
# 发送请求,获取页面数据
page = self.get_page_from_url(url)
# 解析界面,提取数据,封装为Proxy对象
# 这里是个生成器
proxies = self.get_proxies_from_page(page)
# 返回Proxy对象列表
yield from proxies
if __name__ == '__main__':
config = {
'urls': ['http://www.ip3366.net/free/?stype=1&page={}'.format(i) for i in range(4)],
# 使用Xpath-Helper工具,同时在Network面板中检查对应js查看是否真的具有tbody
'group_xpath':'/*;q=0.8,application/signed-exchange;v=b3',
'Accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': 'https://www.baidu.com',
'Accept-Encoding': 'gzip, deflate,br',
'Connection': 'keep-alive',
}
return headers
if __name__ == '__main__':
# 测试随机效果
print(get_request_headers())
print("------------" * 20)
print(get_request_headers())
# log.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author : {Jack Zhao}
@Time : 2020/5/9 8:47
@Contact : {zc_dlmu@163.com}
@Desc : 记录日志信息
'''
import sys, os
# Python的标准日志模块:logging
import logging
from settings import LOG_LEVEL, LOG_FMT, LOG_DATEFMT, LOG_FILENAME
# 将上级目录添加到搜索路径中
sys.path.append("../")
class Logger(object):
def __init__(self):
# 获取一个logger对象
self._logger = logging.getLogger()
# 设置format对象
self.formatter = logging.Formatter(fmt=LOG_FMT, datefmt=LOG_DATEFMT)
# 日志输出——文件日志模式
self._logger.addHandler(self._get_file_handler(LOG_FILENAME))
# 日志输出——终端日志模式
self._logger.addHandler(self._get_console_handler())
# 4. 设置日志等级
self._logger.setLevel(LOG_LEVEL)
def _get_file_handler(self, filename):
'''
:return: 文件日志handler
'''
# 获取一个输出为文件日志的handler
filehandler = logging.FileHandler(filename=filename, encoding="utf-8")
# 设置日志格式
filehandler.setFormatter(self.formatter)
# 返回
return filehandler
def _get_console_handler(self):
'''
:return 输出到终端日志handler
'''
# 获取一个输出到终端的日志handler,标准输出流
console_handler = logging.StreamHandler(sys.stdout)
# 设置日志格式
console_handler.setFormatter(self.formatter)
# 返回handler
return console_handler
# 属性装饰器,返回一个logger对象,可以直接使用该对象调用方法
@property
def logger(self):
return self._logger
# 初始化并配一个logger对象,达到单例
# 使用时,直接导入logger就可以使用
logger = Logger().logger
if __name__ == '__main__':
print(logger)
logger.debug("调试信息")
logger.info("状态信息")
logger.warning("警告信息")
logger.error("错误信息")
logger.critical("严重错误信息")
2.7 数据模型、配置文件、主程序
# domain.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author : {Jack Zhao}
@Time : 2020/5/9 8:46
@Contact : {zc_dlmu@163.com}
@Desc : 定义代理IP的数据模型类
'''
from settings import MAX_SCORE
class Proxy(object):
def __init__(self,ip,port,protocol=-1,nick_type=-1,speed=-1,area=None,score=MAX_SCORE,disable_domains=[]):
# ip: ip地址
self.ip = ip
# port: 端口号
self.port = port
# protocol: http为0,https为1,http和https都支持是2
self.protocol = protocol
# nick_type: 高匿为0,匿名为1,透明为2
self.nick_type = nick_type
# speed: 响应速度,s
self.speed = speed
# area: 代理ip所在地区
self.area =area
# score: 代理ip评分,衡量可用性
self.score = score
# 默认分值在settings进行配置,请求失败-1,减为0删除
# disable_domains:不可用域名列表,有些ip在有些域名下可用,但在其他ip下不可用
self.disable_domains = disable_domains
def __str__(self):
# 返回数据字符串
return str(self.__dict__)
# settings.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author : {Jack Zhao}
@Time : 2020/5/9 8:48
@Contact : {zc_dlmu@163.com}
@Desc : 常见的变量修改
'''
MAX_SCORE = 50 # 代理ip的默认分数
# 日志配置信息
import logging
LOG_LEVEL = logging.DEBUG # 日志等级
LOG_FMT = '%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s: %(message)s' # 输出日志的格式
LOG_DATEFMT = '%Y-%m-%d %H:%M:%S' # 日志时间的格式
LOG_FILENAME = 'log.log' # 日志输出文件的名称
#测试代理IP超时时间
TEST_TIMEOUT = 10
#MongoDB数据库的URL
MONGO_URL = 'mongodb://127.0.0.1:27017'
# 配置爬虫,根据字符串创建类的对象
'''爬虫的全类名,路径:模块.类名'''
PROXIES_SPIDERS = [
'core.proxy_spider.proxy_spiders.Free89ipSpider',
'core.proxy_spider.proxy_spiders.Ip3666Spider',
'core.proxy_spider.proxy_spiders.KuaiSpider',
'core.proxy_spider.proxy_spiders.ProxylistplusSpider',
'core.proxy_spider.proxy_spiders.XiciSpider',
]
# 运行爬虫时间间隔,间隔2h
RUN_SPIDERS_INTERVAL = 2
# 配置检测代理IP的异步数量
TETS_PROXIES_ASYNC_COUNT = 10
# 配置检测代理IP的时间间隔
TETS_PROXIES_INTERVAL = 1
# 获取代理IP的最大数量,这个值越小可用性越高,随机性越差
PROXIES_MAX_COUNT = 20
# main.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author : {Jack Zhao}
@Time : 2020/5/9 8:47
@Contact : {zc_dlmu@163.com}
@Desc : 统一入口,开启三个进程,分别用于启动爬虫,检测代理和web服务统一
'''
from multiprocessing import Process
from core.proxy_spider.run_spiders import RunSpider
from core.proxy_test import ProxyTester
from core.proxy_api import ProxyApi
def run():
'''
定义列表,用于存储要启动的进程,然后将进程添加到列表中
'''
process_list = []
process_list.append(Process(target=RunSpider.start))
process_list.append(Process(target=ProxyTester.start))
process_list.append(Process(target=ProxyApi.start))
# 遍历进程列表,启动进程
for process in process_list:
# 最好设置守护进程,不会相互等待
process.daemon = True
process.start()
# 遍历进程列表,让主进程等待子进程完成
for process in process_list:
process.join()
if __name__ == '__main__':
run()