CrawlSpider、分布式、增量式

ImagesPipeline

ImagesPipeline:专门用作于二进制数据下载和持久化存储的管道类。建议在爬虫文件中进行数据解析,不建议在爬虫文件中直接进行数据存储。

图片懒加载:应用到标签的伪属性,数据捕获的时候一定是基于伪属性进行。

# -*- coding: utf-8 -*-
import scrapy
from imgPro.items import ImgproItem

class ImgSpider(scrapy.Spider):
    name = 'img'
    start_urls = ['http://sc.chinaz.com/tupian/meinvtupian.html']

    def parse(self, response):
        div_list = response.xpath('//*[@id="container"]/div')
        for div in div_list:
            # 伪属性(反爬机制,也是网站优化的一种方法):不是src,而是src2,但在浏览器中随着鼠标的拖动会变为src
            img_src = div.xpath('./div/a/img/@src2').extract_first()
            item = ImgproItem()
            item['img_src'] = img_src
            # 将图片地址提交给ImagesPipeline的管道类
            yield item

----------------------

# -*- coding: utf-8 -*-

# 导入ImagesPipeline管道类
from scrapy.pipelines.images import ImagesPipeline
# 要导入scrapy进行发送请求
import scrapy

# 要重写父类方法
class ImgproPipeline(ImagesPipeline):

    # 是用来对媒体资源进行请求的(数据下载),参数item就是接收到的爬虫类提交的item对象
    def get_media_requests(self, item, info):
        # 不需要传入回调函数
        yield scrapy.Request(item['img_src'])

    # 指明数据存储的路径,只能指定图片的名称,图片的具体路径需在settings.py中指定
    def file_path(self, request, response=None, info=None):
        return request.url.split('/')[-1]

    # 将item传递个下一个即将被执行的管道类
    def item_completed(self, results, item, info):
        return item

-------------------
# settings.py
...
# 配置图片存储文件夹的路径
IMAGES_STORE = './imgLibs'


-------------------

CrawlSpider

一种基于scrapy进行全站数据爬取的一种新的技术手段。
CrawlSpider就是Spider的一个子类

  • 链接提取器:LinkExtractor,链接提取器可以递归提取网站内的所有链接(follow=True:将符合规则的链接作为首页链接继续爬取),自动去重且只能提取链接。
  • 规则解析器:Rule,将链接提取器提取到的链接进行请求发送且根据指定规则对请求到的数据进行数据解析 。

使用流程:

  1. 新建一个工程
  2. cd 工程中
  3. 新建一个爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor # 连接提取器  
from scrapy.spiders import CrawlSpider, Rule    # 规则解析器


from sunCrawlPro.items import SuncrawlproItem,Detail_item  # 手动导入

class SunSpider(CrawlSpider):
    name = 'sun'
    start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']

    # 实例化了一个链接提取器对象,作用:根据指定规则(allow=r’正则表达式‘)进行指定链接的提取。
    link = LinkExtractor(allow=r'type=4&page=\d+'# 获取页码连链接                        

    # 获取新闻详情页的连接
    link_detail = LinkExtractor(allow=r"question/\d+/\d+\.shtml")

    rules = (
        # 将link作用到了Rule构造方法的参数1中,作用:将链接提取器提取到的链接进行请求发送且根据指定规则对请求到的数据进行数据解析                               
        Rule(link, callback='parse_item', follow=False),  # follow=False,只取首页符合要求的链接并发送请求。
        # follow=True:将链接提取器继续作用到链接提取器提取到的链接所对应的页面中,从而获取整个网站中全部符合规则的链接。       
        Rule(link_detail, callback='parse_detail'),# follow默认为False
    )

    def parse_item(self, response):
        # xpath表达式中不可以出现tbody标签
        tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
        for tr in tr_list:
            title = tr.xpath('./td[2]/a[2]/text()').extract_first()
            num = tr.xpath('./td[1]/text()').extract_first()
            item = SuncrawlproItem()
            item['title'] = title
            item['num'] = num

            yield item

    def parse_detail(self,response):
        content = response.xpath('/html/body/div[9]/table[2]//tr[1]/td/text()').extract_first()
        num = response.xpath('/html/body/div[9]/table[1]//tr/td[2]/span[2]/text()').extract_first()
        num = num.split(':')[-1]
        item = Detail_item()
        item['content'] = content
        item['num'] = num
        yield item


-----------------
# -*- coding: utf-8 -*-

# Define your item pipelines here

class SuncrawlproPipeline(object):
    def process_item(self, item, spider):
        if item.__class__.__name__ == 'Detail_item':
            content = item['content']
            num = item['num']
            print(item)
        else:
            title = item['title']
            num = item['num']
            print(item)
        return item

分布式

概念:需要搭建一个分布式的机群,然后在机群的每一台电脑中执行同一组程序,让其对某一个网站的数据进行联合分布爬取。

原生的scrapy框架不可以实现分布式:因为调度器不可以被共享;管道不可以被共享。

实现分布式:scrapy+scrapy_redis实现分布式

scrapy-redis组件:

  • 作用:可以提供可被共享的调度器和管道
  • 特性:数据只可以存储到redis数据库。

分布式的实现流程

第一步:pip install scrapy-redis

第二步:创建工程.

第三步:cd 工程目录中.

第四步,创建爬虫文件(两种选择):

  • 创建基于Spider的爬虫文件
  • 创建CrawlSpider的爬虫文件

第五步,修改爬虫类:

  • 导包:from scrapy_redis.spiders import RedisCrawlSpider(from scrapy_redis.spiders import RedisSpider)
  • 修改当前爬虫类的父类为RedisCrawlSpider(RedisSpider)
  • 将allowed_domains和start_urls删除
  • 添加一个新属性:redis_key = 'fbsQueue'(任意字符串),表示的是可以被共享的调度器队列的名称
  • 编写爬虫类的其他操作(常规操作)

第六步,settings配置文件的配置:

UA伪装、Robots.

管道的指定:ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400}

指定调度器:

  • 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
  • 使用scrapy-redis组件自己的调度器SCHEDULER = "scrapy_redis.scheduler.Scheduler"
  • 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据SCHEDULER_PERSIST = True

指定redis数据库:

REDIS_HOST = 'redis服务的ip地址'

REDIS_PORT = 6379

第七步,redis的配置文件(redis.windows.conf)进行配置:

  • 关闭默认绑定:# bind 127.0.0.1
  • 关闭保护模式:protected-mode no

第八步,启动redis的服务端和客户端:

  • redis-server.exe redis.windows.conf(一定要携带配置文件进行启动)
  • redis-cli

第九步,启动程序:scrapy runspider xxx.py

第十步,向调度器的队列中仍入一个起始的url:

  • 队列是存在于redis中
  • 开启redis的客户端: lpush fbsQueue http://wz.sun0769.com/index.php/question/questionType?type=4&page= [value …]
# -*- coding: utf-8 -*-
# scrapy/fbs.py
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

from scrapy_redis.spiders import RedisCrawlSpider # 手动导入
from fbsPro.items import FbsproItem
class FbsSpider(RedisCrawlSpider):
    name = 'fbs'
    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']
    redis_key = 'fbsQueue'# 表示的是可以被共享的调度器队列的名称
    rules = (
        Rule(LinkExtractor(allow=r'type=4&page=\d+'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
        for tr in tr_list:
            title = tr.xpath('./td[2]/a[2]/text()').extract_first()
            status= tr.xpath('./td[3]/span/text()').extract_first()

            item = FbsproItem()
            item['title'] = title
            item['status'] = status

            yield item
# settings.py
BOT_NAME = 'fbsPro'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
SPIDER_MODULES = ['fbsPro.spiders']
NEWSPIDER_MODULE = 'fbsPro.spiders'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 3


ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline'400
}

# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。
# 如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据,可实现增量式。                         
SCHEDULER_PERSIST = True

REDIS_HOST = '192.168.18.36'
REDIS_PORT = 6379
# items.py
import scrapy
class FbsproItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    status = scrapy.Field()

增量式

概念:用于监测网站数据更新的情况。

核心机制:去重。可以使用redis的set实现去重。

# settings.py
# -*- coding: utf-8 -*-

BOT_NAME = 'zjsPro'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
SPIDER_MODULES = ['zjsPro.spiders']
NEWSPIDER_MODULE = 'zjsPro.spiders'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'zjsPro.pipelines.ZjsproPipeline'300,
}
# scrapy/zjs.py
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


from zjsPro.items import ZjsproItem
from redis import Redis

class ZjsSpider(CrawlSpider):
    conn = Redis(host='127.0.0.1',port=6379)
    name = 'zjs'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567tv.tv/index.php/vod/show/class/%E7%88%B1%E6%83%85/id/1.html']

    rules = (
        Rule(LinkExtractor(allow=r'/page/\d+\.html'), callback='parse_item', follow=False),
    )

    def parse_item(self, response):
        li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
        for li in li_list:
            name = li.xpath('./div/a/@title').extract_first()
            detail_url = 'https://www.4567tv.tv'+li.xpath('./div/a/@href').extract_first()
            item = ZjsproItem()
            item['name'] = name
            # 可以将爬过的电影的详情页的url记录起来
            # ex == 0:数据插入失败  ex==1:数据插入成功
            ex = self.conn.sadd('movie_detail_urls',detail_url)
            if ex:
                print('捕获到最新更新出来的数据.')
                yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
            else:
                print('暂无数据的更新.')

    def parse_detail(self,response):
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item = response.meta['item']
        item['desc'] = desc

        yield item
# items.py
# -*- coding: utf-8 -*-

import scrapy


class ZjsproItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()
    desc = scrapy.Field()
# pipelines.py
# -*- coding: utf-8 -*-

class ZjsproPipeline(object):
    def process_item(self, item, spider):
        conn = spider.conn
        conn.lpush('moiveData',item)
        return item

常见反爬机制:

  • robots
  • UA伪装
  • 验证码
  • 代理
  • cookie
  • 动态变化的请求参数
  • js加密
  • js混淆
  • 图片懒加载
  • 动态数据的捕获
  • seleium:规避检测
posted @ 2020-08-15 15:01  虫萧  阅读(146)  评论(0编辑  收藏  举报