爬虫入门第8课:实现代理池的爬虫模块
Posted 黑马程序员官方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爬虫入门第8课:实现代理池的爬虫模块相关的知识,希望对你有一定的参考价值。
爬虫学习知识点及案例篇(汇总):
Python爬虫项目:Bilibili模拟登陆(滑动验证码)
本阶段带大家从代理池的设计开始,学习Python爬虫及项目实战,详情关注上方专栏 ↑↑↑
1. 爬虫模块的需求
需求
: 抓取各个代理IP网站上的免费代理IP, 进行检测, 如果可用存储到数据库中- 需要抓取代理IP的页面如下:
2. 爬虫模块的设计思路
- 通用爬虫:通过指定URL列表, 分组XPATH和组内XPATH, 来提取不同网站的代理IP
- 原因代理IP网站的页面结构几乎都是Table, 页面结构类似
- 具体爬虫: 用于抓取具体代理IP网站
- 通过继承通用爬虫实现具体网站的抓取,一般只需要指定爬取的URL列表, 分组的XPATH和组内XPATH就可以了.
- 如果该网站有特殊反爬手段, 可以通过重写某些方法实现反爬
- 爬虫运行模块: 启动爬虫, 抓取代理IP, 进行检测, 如果可用, 就存储到数据库中;
- 通过配置文件来控制启动哪些爬虫, 增加扩展性; 如果将来我们遇到返回json格式的代理网站, 单独写一个爬虫配置下就好了.
3. 实现通用爬虫
目标
: 实现可以指定不同URL列表, 分组的XPATH和详情的XPATH, 从不同页面上提取代理的IP,端口号和区域的通用爬虫;-
步骤
:- 在base_spider.py文件中,定义一个BaseSpider类, 继承object
- 提供三个类成员变量:
- urls: 代理IP网址的URL的列表
- group_xpath: 分组XPATH, 获取包含代理IP信息标签列表的XPATH
- detail_xpath: 组内XPATH, 获取代理IP详情的信息XPATH,格式为: 'ip':'xx', 'port':'xx', 'area':'xx'
- 提供初始方法, 传入爬虫URL列表, 分组XPATH, 详情(组内)XPATH
- 对外提供一个获取代理IP的方法
- 遍历URL列表, 获取URL
- 根据发送请求, 获取页面数据
- 解析页面, 提取数据, 封装为Proxy对象
- 返回Proxy对象列表
-
代码
-
通用爬虫类
#!/usr/bin/python3 # -*- coding: utf-8 -*- import requests from lxml import etree from domain import Proxy from utils.http import get_request_header class BaseSpider(object): urls = [] # 代理IP网址的URL的列表 group_xpath='' # 分组XPATH, 获取包含代理IP信息标签列表的XPATH detail_xpath = # 组内XPATH, 获取代理IP详情的信息XPATH def __init__(self, urls=[], group_xpath=None, detail_xpath=): if urls: # 如果urls中有数据 self.urls = urls if group_xpath: # 如果group_xpath中有数据 self.group_xpath = group_xpath if detail_xpath: # 如果detail_xpath中有数据 self.detail_xpath = detail_xpath def get_page_from_url(self, url): response = requests.get(url, headers=get_request_header()) return response.content def get_first(self, lis): return lis[0].strip() if len(lis) != 0 else '' def get_proxyes_from_page(self, page): """解析页面数据""" element = etree.html(page) trs = element.xpath(self.group_xpath) # print(len(trs)) for tr in trs: ip = self.get_first(tr.xpath(self.detail_xpath['ip'])) port = self.get_first(tr.xpath(self.detail_xpath['port'])) area = self.get_first(tr.xpath(self.detail_xpath['area'])) proxy = Proxy(ip, port, area=area) # 返回代理IP yield proxy def get_proxies(self): """获取代理IP信息""" # - 遍历URL列表, 获取URL for url in self.urls: # - 根据发送请求, 获取页面数据 page = self.get_page_from_url(url) # - 解析页面, 提取数据 proxies = self.get_proxyes_from_page(page) # - 把数据返回 yield from proxies if __name__ == '__main__': config = 'urls':['https://www.xicidaili.com/nn/1'.format(i) for i in range(1, 2)], 'group_xpath': '//*[@id="ip_list"]/tr[position()>1]', 'detail_xpath': 'ip':'./td[2]/text()', 'port':'./td[3]/text()', 'area':'./td[4]/a/text()', # 创建通用代理对象 base_spider = BaseSpider(**config) for proxy in base_spider.get_proxies(): print(proxy)
-
4. 实现具体的爬虫类
目标
: 通过继承通用爬虫, 实现多个具体爬虫, 分别从各个免费代理IP网站上抓取代理IP-
步骤
:-
实现
西刺代理
爬虫:http://www.xicidaili.com/nn/1
- 定义一个类,继承通用爬虫类(BasicSpider)
- 提供urls, group_xpath 和 detail_xpath
-
实现
ip3366代理
爬虫:http://www.ip3366.net/free/?stype=1&page=1
- 定义一个类,继承通用爬虫类(BasicSpider)
- 提供urls, group_xpath 和 detail_xpath
- 实现
快代理
爬虫:https://www.kuaidaili.com/free/inha/1/
- 定义一个类,继承通用爬虫类(BasicSpider)
- 提供urls, group_xpath 和 detail_xpath
- 实现
proxylistplus代理
爬虫:https://list.proxylistplus.com/Fresh-HTTP-Proxy-List-1
- 定义一个类,继承通用爬虫类(BasicSpider)
- 提供urls, group_xpath 和 detail_xpath
- 实现
66ip
爬虫:http://www.66ip.cn/1.html
- 定义一个类,继承通用爬虫类(BasicSpider)
- 提供urls, group_xpath 和 detail_xpath
- 由于66ip网页进行js + cookie反爬, 需要重写父类的
get_page_from_url
方法
-
-
完整代码
import requests
import re
import js2py
from spiders.base_spider import BaseSpider
from utils.http import get_request_header
from domain import Proxy
"""
1. 实现`西刺代理`爬虫: `http://www.xicidaili.com/nn/1`
- 定义一个类,继承通用爬虫类(BasicSpider)
- 提供urls, group_xpath 和 detail_xpath
"""
class XiciSpider(BaseSpider):
urls = ['http://www.xicidaili.com/nn/'.format(i) for i in range(1, 10)]
group_xpath = '//*[@id="ip_list"]/tr[position()>1]'
detail_xpath = 'ip': './td[2]/text()', 'port': './td[3]/text()', 'area': './td[4]/a/text()'
"""
2. 实现`ip3366代理`爬虫: `http://www.ip3366.net/free/?stype=1&page=1`
- 定义一个类,继承通用爬虫类(BasicSpider)
- 提供urls, group_xpath 和 detail_xpath
"""
class Ip3366Spider(BaseSpider):
urls = ['http://www.ip3366.net/free/?stype=&page='.format(i, j) for j in range(1, 10) for i in range(1, 4, 2)]
group_xpath = '//*[@id="list"]/table/tbody/tr'
detail_xpath = 'ip':'./td[1]/text()', 'port':'./td[2]/text()','area':'./td[5]/text()'
"""
3. 实现`快代理`爬虫: `https://www.kuaidaili.com/free/inha/1/`
- 定义一个类,继承通用爬虫类(BasicSpider)
- 提供urls, group_xpath 和 detail_xpath
"""
class KuaiSpider(BaseSpider):
urls = ['https://www.kuaidaili.com/free/inha//'.format(i) for i in range(1, 10) ]
group_xpath = '//*[@id="list"]/table/tbody/tr'
detail_xpath = 'ip': './td[1]/text()', 'port': './td[2]/text()', 'area': './td[5]/text()'
"""
4. 实现`proxylistplus代理`爬虫: `https://list.proxylistplus.com/Fresh-HTTP-Proxy-List-1`
- 定义一个类,继承通用爬虫类(BasicSpider)
- 提供urls, group_xpath 和 detail_xpath
"""
class ProxylistplusSpider(BaseSpider):
urls = ['https://list.proxylistplus.com/Fresh-HTTP-Proxy-List-'.format(i) for i in range(1, 7)]
group_xpath = '//*[@id="page"]/table[2]/tr[position()>2]'
detail_xpath = 'ip':'./td[2]/text()', 'port':'./td[3]/text()', 'area':'./td[5]/text()'
"""
5. 实现`66ip`爬虫: `http://www.66ip.cn/1.html`
- 定义一个类,继承通用爬虫类(BasicSpider)
- 提供urls, group_xpath 和 detail_xpath
"""
class Ip66Spider(BaseSpider):
urls = ['http://www.66ip.cn/.html'.format(i) for i in range(1, 10)]
group_xpath = '//*[@id="main"]/div/div[1]/table/tr[position()>1]'
detail_xpath = 'ip': './td[1]/text()', 'port': './td[2]/text()', 'area': './td[3]/text()'
def get_page_from_url(self, url):
"""发送请求, 获取响应的方法"""
# 获取session对象, session可以记录服务器设置过来的cookie信息
session = requests.session()
session.headers = get_request_header()
respsone = session.get(url)
# 如果响应码是521
if respsone.status_code == 521:
# 通过正则获取, 需要执行的js
rs = re.findall('window.onload=setTimeout\\("(\\w+\\(\\d+\\))", \\d+\\); (function \\w+\\(\\w+\\).+?)</script>', respsone.content.decode())
# 获取js2py的js执行环境
context = js2py.EvalJs()
# 把执行执行js, 修改为返回要执行的js
func = rs[0][1].replace('eval("qo=eval;qo(po);");', 'return po;')
# 让js执行环境, 加载要执行的js
context.execute(func)
# 把函数的执行结果赋值给一个变量
context.execute( "a=".format(rs[0][0]))
# 从变量中取出cookie信息
cookie = re.findall("document.cookie='(\\w+)=(.+?);", context.a)
# 把从js中提取的cookie信息设置给session
session.cookies[cookie[0][0]] = cookie[0][1]
# print(session.cookies)
respsone = session.get(url)
return respsone.content.decode('gbk')
if __name__ == '__main__':
# spider = XiciSpider()
# spider = Ip3366Spider()
# spider = IphaiSpider()
# spider = ProxylistplusSpider()
spider = Ip66Spider()
for proxy in spider.get_proxies():
print(proxy)
5. 实现运行爬虫模块
目标
: 根据配置文件信息, 加载爬虫, 抓取代理IP, 进行校验, 如果可用, 写入到数据库中-
思路
:- 在run_spider.py中, 创建RunSpider类
- 提供一个运行爬虫的
run
方法, 作为运行爬虫的入口, 实现核心的处理逻辑- 根据配置文件信息, 获取爬虫对象列表.
- 获取爬虫对象, 遍历爬虫对象的get_proxies方法, 获取代理IP
- 检测代理IP(代理IP检测模块)
- 如果可用,写入数据库(数据库模块)
- 处理异常, 防止一个爬虫内部出错了, 影响其他的爬虫.
- 使用异步来执行每一个爬虫任务, 以提高抓取代理IP效率
- 在
init
方法中创建协程池对象 - 把处理一个代理爬虫的代码抽到一个方法
- 使用异步执行这个方法
- 调用协程的
join
方法, 让当前线程等待协程
任务的完成.
- 在
- 使用
schedule
模块, 实现每隔一定的时间, 执行一次爬取任务- 定义一个
start
的类方法 - 创建当前类的对象, 调用run方法
- 使用
schedule
模块, 每隔一定的时间, 执行当前对象的run方法
- 定义一个
-
步骤
:- 在run_spider.py中, 创建RunSpider类
- 修改 setting.py 增加 代理IP爬虫的配置信息
# 配置代理爬虫列表 PROXIES_SPIDERS = [ 'spiders.proxy_spiders.Ip66Spider', 'spiders.proxy_spiders.Ip3366Spider', 'spiders.proxy_spiders.IphaiSpider', 'spiders.proxy_spiders.ProxylistplusSpider', 'spiders.proxy_spiders.XiciSpider', ]
-
实现根据配置文件, 加载爬虫, 把爬虫对象放到列表中的方法
- 定义一个列表, 用于存储爬虫对象
- 遍历爬虫配置信息, 获取每一个爬虫路径
- 根据爬虫路径获取模块名和类名
- 使用importlib根据模块名, 导入该模块
- 根据类名, 从模块中获取类
- 使用类创建对象, 添加到对象列表中
def _auto_import_instances(self): """根据配置信息, 自动导入爬虫""" instances = [] # 遍历配置的爬虫, 获取爬虫路径 for path in settings.PROXIES_SPIDERS: # 根据路径, 获取模块名 和 类名 module_name, cls_name = path.rsplit('.', maxsplit=1) # 根据模块名导入模块 module = importlib.import_module(module_name) # 根据类名, 从模块中, 获取爬虫类 cls = getattr(module, cls_name) # 创建爬虫对象, 添加到列表中 instances.append(cls()) # 返回爬虫对象列表 return instances
-
实现run方法, 用于运行整个爬虫
- 获取代理爬虫列表
- 遍历代理爬虫列表
- 遍历爬虫的get_proxies()方法, 获取代理IP
- 如果代理IP为None, 继续一次循环
- 检查代理, 获取代理协议类型, 匿名程度, 和速度
- 如果代理速度不为-1, 就是说明该代理可用, 保存到数据库中
-
处理下异常, 防止一个爬虫内部错误, 其他爬虫都运行不了
class RunSpider(object): def __init__(self): self.proxy_pool = MongoPool() def run(self): """启动爬虫""" # 获取代理爬虫 spiders = self._auto_import_instances() # 执行爬虫获取代理 for spider in spiders: try: for proxy in spider.get_proxies(): if proxy is None: # 如果是None继续一个 continue # 检查代理, 获取代理协议类型, 匿名程度, 和速度 proxy = check_proxy(proxy) # 如果代理速度不为-1, 就是说明该代理可用 if proxy.speed != -1: # 保存该代理到数据库中 self.proxy_pool.save(proxy) except Exception as e: logger.exception(e) logger.exception("爬虫 出现错误".format(spider))
-
使用协程池异步来运行每一个爬虫, 以提高爬取的速度
- 实现init方法, 创建协程池
- 把执行处理每一个爬虫的代码抽取一个方法
- 使用异步调用这个方法
- 调用协程的join方法, 让当前线程等待队列任务的完成.
class RunSpider(object): def __init__(self): self.proxy_pool = MongoPool() self.pool = Pool() ... def run(self): """启动爬虫""" # 获取代理爬虫 spiders = self._auto_import_instances() # 执行爬虫获取代理 for spider in spiders: # 使用协程异步调用该方法,提高爬取的效率 self.pool.apply_async(self.__run_one_spider, args=(spider, )) # 等待所有爬虫任务执行完毕 self.pool.join() def __run_one_spider(self, spider): try: for proxy in spider.get_proxies(): if proxy is None: # 如果是None继续一个 continue # 检查代理, 获取代理协议类型, 匿名程度, 和速度 proxy = check_proxy(proxy) # 如果代理速度不为-1, 就是说明该代理可用 if proxy.speed != -1: # 保存该代理到数据库中 self.proxy_pool.save(proxy) except Exception as e: logger.exception(e) logger.exception("爬虫 出现错误".format(spider))
-
每隔一定的时间, 执行一次爬取任务
-
修改
setting.py
文件, 爬虫间隔时间的配置# 抓取IP的时间间隔, 单位小时 SPIDER_INTERVAL = 2
- 安装
schedule
:pip3 install schedule
-
在
RunSpider
中提供start的类方法, 用于启动爬虫的运行, 每间隔指定时间, 重新运行一次.@classmethod def start(cls): # 创建本类对象 run_spider = RunSpider() run_spider.run() # 每隔 SPIDER_INTERVAL 小时检查下代理是否可用 schedule.every(settings.SPIDER_INTERVAL).hours.do(run_spider.run) while True: schedule.run_pending() time.sleep(1)
- 安装
-
-
爬虫运行模块完整代码
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from gevent import monkey
monkey.patch_all()
from gevent.pool import Pool
import importlib
import schedule
import time
import settings
from validator.httpbin_validator import check_proxy
from db.mongo_pool import MongoPool
from utils.log import logger
class RunSpider(object):
def __init__(self):
self.pool = Pool()
self.proxy_pool = MongoPool()
def _auto_import_instances(self):
"""根据配置信息, 自动导入爬虫"""
instances = []
# 遍历配置的爬虫, 获取爬虫路径
for path in settings.PROXIES_SPIDERS:
# 根据路径, 获取模块名 和 类名
module_name, cls_name = path.rsplit('.', maxsplit=1)
# 根据模块名导入模块
module = importlib.import_module(module_name)
# 根据类名, 从模块中, 获取爬虫类
cls = getattr(module, cls_name)
# 创建爬虫对象, 添加到列表中
instances.append(cls())
# 返回爬虫对象列表
return instances
def run(self):
"""启动爬虫"""
# 获取代理爬虫
spiders = self._auto_import_instances()
# 执行爬虫获取代理
for spider in spiders:
# 使用协程异步调用该方法,提高爬取的效率
self.pool.apply_async(self.__run_one_spider, args=(spider, ))
# 等待所有爬虫任务执行完毕
self.pool.join()
def __run_one_spider(self, spider):
try:
for proxy in spider.get_proxies():
if proxy is None:
# 如果是None继续一个
continue
# 检查代理, 获取代理协议类型, 匿名程度, 和速度
proxy = check_proxy(proxy)
# 如果代理速度不为-1, 就是说明该代理可用
if proxy.speed != -1:
# 保存该代理到数据库中
self.proxy_pool.insert(proxy)
except Exception as e:
logger.exception(e)
logger.exception("爬虫 出现错误".format(spider))
@classmethod
def start(cls):
# 创建本类对象
run_spider = RunSpider()
run_spider.run()
# 每隔 SPIDER_INTERVAL 小时检查下代理是否可用
schedule.every(settings.SPIDER_INTERVAL).hours.do(run_spider.run())
while True:
schedule.run_pending()
time.sleep(1)
if __name__ == '__main__':
RunSpider.start()
- 小结:
- 实现通用爬虫, 根据指定URL列表, 分组XPATH和详情XPATH提取代理IP
- 实现具体爬虫: 继承通用爬虫, 实现不同网站的抓取
- 实现运行爬虫模块:
- 根据配置文件创建爬虫对象, 抓取代理IP, 进行检查, 如果可用入库;
- 为了提高爬取的效率, 使用协程池, 异步执行来处理每一个爬虫任务
- 使用sechdule模块实现定时抓取.
以上是关于爬虫入门第8课:实现代理池的爬虫模块的主要内容,如果未能解决你的问题,请参考以下文章