爬虫入门第8课:实现代理池的爬虫模块

爬虫学习知识点及案例篇(汇总):

爬虫入门第1课:代理池概述及开发环境

爬虫入门第2课:代理池的设计

爬虫入门第3课:实现代理池思路

爬虫入门第4课:定义代理IP的数据模型类

爬虫入门第5课:实现代理池工具模块

爬虫入门第6课:实现代理池的校验模块

爬虫入门第7课:实现代理池的数据库模块

Python爬虫项目:Bilibili模拟登陆(滑动验证码)

本阶段带大家从代理池的设计开始,学习Python爬虫及项目实战,详情关注上方专栏 ↑↑↑


1. 爬虫模块的需求

2. 爬虫模块的设计思路

  • 通用爬虫:通过指定URL列表, 分组XPATH和组内XPATH, 来提取不同网站的代理IP
    • 原因代理IP网站的页面结构几乎都是Table, 页面结构类似
  • 具体爬虫: 用于抓取具体代理IP网站
    • 通过继承通用爬虫实现具体网站的抓取,一般只需要指定爬取的URL列表, 分组的XPATH和组内XPATH就可以了.
    • 如果该网站有特殊反爬手段, 可以通过重写某些方法实现反爬
  • 爬虫运行模块: 启动爬虫, 抓取代理IP, 进行检测, 如果可用, 就存储到数据库中;
    • 通过配置文件来控制启动哪些爬虫, 增加扩展性; 如果将来我们遇到返回json格式的代理网站, 单独写一个爬虫配置下就好了.

3. 实现通用爬虫

  • 目标: 实现可以指定不同URL列表, 分组的XPATH和详情的XPATH, 从不同页面上提取代理的IP,端口号和区域的通用爬虫;
  • 步骤:

    1. 在base_spider.py文件中,定义一个BaseSpider类, 继承object
    2. 提供三个类成员变量:
      • urls: 代理IP网址的URL的列表
      • group_xpath: 分组XPATH, 获取包含代理IP信息标签列表的XPATH
      • detail_xpath: 组内XPATH, 获取代理IP详情的信息XPATH,格式为: {'ip':'xx', 'port':'xx', 'area':'xx'}
    3. 提供初始方法, 传入爬虫URL列表, 分组XPATH, 详情(组内)XPATH
    4. 对外提供一个获取代理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
  • 步骤:

    1. 实现西刺代理爬虫: http://www.xicidaili.com/nn/1

      • 定义一个类,继承通用爬虫类(BasicSpider)
      • 提供urls, group_xpath 和 detail_xpath
    2. 实现ip3366代理爬虫: http://www.ip3366.net/free/?stype=1&page=1

      • 定义一个类,继承通用爬虫类(BasicSpider)
      • 提供urls, group_xpath 和 detail_xpath
    3. 实现快代理爬虫: https://www.kuaidaili.com/free/inha/1/
      • 定义一个类,继承通用爬虫类(BasicSpider)
      • 提供urls, group_xpath 和 detail_xpath
    4. 实现proxylistplus代理爬虫: https://list.proxylistplus.com/Fresh-HTTP-Proxy-List-1
      • 定义一个类,继承通用爬虫类(BasicSpider)
      • 提供urls, group_xpath 和 detail_xpath
    5. 实现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',
      ]
      
    • 实现根据配置文件, 加载爬虫, 把爬虫对象放到列表中的方法

      1. 定义一个列表, 用于存储爬虫对象
      2. 遍历爬虫配置信息, 获取每一个爬虫路径
      3. 根据爬虫路径获取模块名和类名
      4. 使用importlib根据模块名, 导入该模块
      5. 根据类名, 从模块中获取类
      6. 使用类创建对象, 添加到对象列表中
      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方法, 用于运行整个爬虫

    1. 获取代理爬虫列表
    2. 遍历代理爬虫列表
    3. 遍历爬虫的get_proxies()方法, 获取代理IP
    4. 如果代理IP为None, 继续一次循环
    5. 检查代理, 获取代理协议类型, 匿名程度, 和速度
    6. 如果代理速度不为-1, 就是说明该代理可用, 保存到数据库中
    7. 处理下异常, 防止一个爬虫内部错误, 其他爬虫都运行不了

      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))
      
  • 使用协程池异步来运行每一个爬虫, 以提高爬取的速度

    1. 实现init方法, 创建协程池
    2. 把执行处理每一个爬虫的代码抽取一个方法
    3. 使用异步调用这个方法
    4. 调用协程的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))
    
  • 每隔一定的时间, 执行一次爬取任务

    1. 修改 setting.py 文件, 爬虫间隔时间的配置

       # 抓取IP的时间间隔, 单位小时
       SPIDER_INTERVAL = 2
      
      1. 安装 schedulepip3 install schedule
      2. 在 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模块实现定时抓取.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值