scrapy 核心组件

  • Spider(用于从特定的网页中提取自己需要的信息即所调的实体( item ).用户也可以从中提取出链接让 Scrap 继块抓取下一个页面)
  • 管道 (负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。)
  • 调度器 (用来接受引擎发过来的请求压入队列中,井在引擎再次请求的时候返回。)
  • 下载器 (用于下载网页内容,井将网页内容返回给蜘妹( Scrap 下载器是建立在 twisted 这个高效的异步模型上的))
  • 引擎 (用来处理整个系统的数据流处理,触发事务(框架核心))

流程

图片数据爬取 ImagesPipeline

基于 Scrapy 爬取字符串类型和爬取图片类型数据的区别?
  • 字符串:只需要使用 xpath 解析处理文本使用即可
  • 图片: xpath 解析出图片的 src 属性,在单独的对图片地址发起请求获取 二进制类型的数据,
ImagesPipeline
  • 将解析到的图片 src, 提交给管道 ImagesPipeline,管道会自动对 src 发起请求,获取图片的二进制数据,并储存。

spiders

# xiaohua/xiaohua/spiders/zanz_img.py
import scrapy
from xiaohua.items import XiaohuaItem

class ZanzImgSpider(scrapy.Spider):
    name = 'zanz_img'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://sc.chinaz.com/tupian/xixirenti.html']
    # https://sc.chinaz.com/tupian/meinvtupian_2.html

    def parse(self, response):
        for div in response.xpath('//*[@id="container"]/div'):
            # 图片懒加载, 这里的方式是 不再可视范围内的图片 src为src2
            src = 'https:' + div.xpath('.//img/@src2').extract_first()
            # 显示原图
            src = src.replace('_s', '', 1)
            name = div.xpath('.//img/@alt').extract_first()
            item = XiaohuaItem()
            item['img'] = {'name': name, 'src': src}
            print(item)
            yield item

pipelines or settings

# xiaohua/xiaohua/pipelines.py or settings.py
import scrapy
from itemadapter import ItemAdapter
from scrapy.pipelines.images import ImagesPipeline

# ImagesPipeline 专门由于文件下载的管道类,下载过程支持异步和多线程
class ImgPileLine(ImagesPipeline):
    # 根据图片地址,进行请求发送
    def get_media_requests(self, item, info):
        print('发送请求1')
        yield scrapy.Request(item['img']['src'])

    # 指定文件储存路径
    def file_path(self, request, response=None, info=None, *, item=None):
        if item:
            imgName = item['img']['name'] + '.jpg'
        else:
            imgName = request.url.split('/')[-1]
        return imgName

    # 将 item传递给 下一个广告类
    # def item_completed(self, results, item, info):
    #     return item

# # 配置文件 片段

# 管道文件存储目录
IMAGES_STORE = './imgs_zz'
ITEM_PIPELINES = {
   'xiaohua.pipelines.XiaohuaPipeline': 300,
   'xiaohua.pipelines.ImgPileLine': 299,
}

中间件

  • 下载中间件
  • 爬虫中间件(使用很少暂且不表

下载中间件

  • 位置:引擎和下载器之间
  • 作用:批量拦截整个工程中所有的请求和响应
  • 拦截请求:对单个请求对象进行 UA 伪装, ip 代理等操作。
  • 拦截响应:篡改响应数据,(如默认的请求方式不满足需求(页面动态数据,自己封装响应数据返回给 Spider.parse

拦截请求, 拦截响应(结合 selemium 便捷的获取动态加载数据)/ 代码片段

Spiders

# Middle/Middle/spiders/wangYiNew.py
import scrapy
from selenium import webdriver

from Middle.items import MiddleItem

"""
需求:爬取网易新闻首页下 五大板块下的新闻标题和内容。
注意:每一个板块下 的新闻标题都是动态加载的
"""

class WangyinewSpider(scrapy.Spider):
    name = 'wangYiNew'
    start_urls = ['https://news.163.com/']

    models_urls = []

    # 实例化一个浏览器对象
    def __init__(self):
        self.sel = webdriver.Chrome(executable_path=r'C:\Users\chuan\ITEMS\爬虫\Package\chromedriver.exe')
        self.sel.set_window_size(1366, 768)

    def parse(self, response):

        lis = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
        # 五大板块的位置
        a_list = [3, 4, 6, 7, 8]

        for i in a_list:
            url = lis[i].xpath('./a/@href').extract_first()
            self.models_urls.append(url)

        # 对板块详情页发送请求
        for i in self.models_urls:
            yield scrapy.Request(i, callback=self.parse_model)

    def parse_model(self, response):
        # 解析每一个板块块页面中 新闻的标题和详情页的url
        divs = response.xpath('//li/div[@class="ndi_main"]/div')
        for div in divs:
            new_detail_url = div.xpath('.//div[@class="news_title"]/h3/a/@href').extract_first()

            yield scrapy.Request(new_detail_url, callback=self.parse_detail)

    def parse_detail(self, response):
        title = response.xpath('//*[@id="epContentLeft"]/h1/text()').extract_first()
        content = response.xpath('//*[@id="endText"]//text()').extract()
        content = ''.join(content)

        item = MiddleItem()
        item['new'] = {
            'title': title,
            'content': content
        }
        yield item

    # 爬虫结束时执行
    def closed(self, spider):
        self.sel.quit()

middleware 请求拦截,响应篡改

# Middle/Middle/middlewares.py

import random
from scrapy.http import HtmlResponse
from scrapy import signals

# useful for handling different item types with a single interface
from itemadapter import is_item, ItemAdapter


class MiddleDownloaderMiddleware:
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.

    user_agent_list = [
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
        "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
        "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
    ]
    proxy_http = [
        '27.206.177.191:9000',
        '123.149.141.4:9999'
    ]
    proxy_https = [
        '223.241.78.15:8888'
        '175.44.109.169:9999'
    ]

    # 拦截请求
    def process_request(self, request, spider):
        # Called for each request that goes through the downloader
        # middleware.

        request.headers['User-Agent'] = random.choice(self.user_agent_list)
        # 仅验证ip代理是否生效,一般在请求失败时进行 ip代理
        return None

    # 拦截所有的响应
    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.

        # spider 爬虫对象

        # 挑选出 指定的响应对象(请求的url)进行篡改
        if request.url in spider.models_urls:
            # 仅对 五大板块的 url进行响应对象篡改(动态加载的
            # 基于selemium 便捷的获取动态加载数据
            bro = spider.sel  # 获取爬虫类中定义的 selenium对象
            bro.get(request.url)
            page_text = bro.page_source
            return HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
        else:
            return response

    # 拦截发生的异常
    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # ip 代理
        if request.url.split(':')[0] == 'http':
            request.meta['proxy'] = 'http://' + random.choice(self.proxy_http)
        else:
            request.meta['proxy'] = 'https://' + random.choice(self.proxy_https)

        # 将发生异常的 请求重新发送
        return request

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

items, settings 代码片段

# items
import scrapy

class MiddleItem(scrapy.Item):
    new = scrapy.Field()

# settings 
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'Middle.pipelines.MiddlePipeline': 300,
}

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
    'Middle.middlewares.MiddleDownloaderMiddleware': 543,
}

CrawlSpider 类的使用

Spider 的一个子类,主要用于全站数据爬取

使用:

  • 创建爬虫文件: scrapy genspider -t crawl spiderName xxx.com
  • Rule 规则解析器:将链接提取器提取到的链接自动的发送请求(去重)。callback: 指定响应体进行解析的方法。follow: 为 true 则跟随解析出来的新页面更新 链接提取器接受的起始连接
  • LinkExtractor 链接提取器:根据指定规则( allow=正则)对起始链接的响应体( start_urls)进行 url 的提取

Spider

# sunPro/sunPro/spiders/sun.py
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

from sunPro.items import SunproItem, DetailItem

class SunSpider(CrawlSpider):
    name = 'sun'
    start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=2']

    rules = (
        # Rule 规则解析器。
        # LinkExtractor 链接提取器:根据指定规则(allow=正则)对起始链接的响应体(start_urls)进行url的提取
        # callback: 指定响应体进行解析的方法。
        # follow 为true则跟随解析出来的新页面更新 链接提取器 

        # 单页链接
        Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=True),
        # 详情页链接
        Rule(LinkExtractor(allow=r'/politics/index\?id=\d+'), callback='parse_detail')
    )

    def parse_item(self, response):
        lis = response.xpath('//ul[@class="title-state-ul"]/li')
        for li in lis:
            num = li.xpath('./span[1]/text()').get()
            title = li.xpath('./span[3]/a/text()').extract_first()
            item = SunproItem()
            item['title'] = {
                'id': num,
                'content': title
            }
            yield item

    def parse_detail(self, response):
        num = response.xpath('/html/body/div[3]/div[2]/div[2]/div[1]/span[4]/text()').extract_first()
        content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
        item = DetailItem()
        item['content'] = {
            'id': num,
            'content': content
        }
        yield item

item, pipelines 代码片段(

# sunPro/sunPro/items.py

import scrapy

class SunproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()


class DetailItem(scrapy.Item):
    content = scrapy.Field()

# sunPro/sunPro/pipelines.py
from itemadapter import ItemAdapter


class SunproPipeline:
    def process_item(self, item, spider):
        # 有多个 Item
        if item.__class__.__name__ == 'DetailItem':
            print(item['content'])
        elif item.__class__.__name__ == 'SunproItem' :
            print(item['title'])

        return item

分布式爬虫

概念:搭建一个分布式的机群,让其对一组资源进行分布联合爬取

作用:提升爬取的效率。

原生的 scrapy 不支持实现分布式。需要借助模块 scrapy-redis 实现。 scrapy-redis 实现 调度器和管道 的分布式机群共享

通过 scrapy-redis 实现:

  1. 修改爬虫文件:将 CrawlSpider 的父类替换为 scrapy-redis 提供的类. from scrapy_redis.spiders import RedisCrawlSpider.
  2. 将 爬虫文件中的:allowed_domains start_urls 注释。替换为 redis_key = 'sun' sum 可以被共享的 调度器名称
  3. 数据解析 ???
  4. 修改配置文件, 指定可以被共享的管道和调度器。
  5. 配置 redis 的配置文件。
  6. 执行工程 scrapy runspider spiderName.py
  7. 向调度器的队列中放入一个起始的 url。 调度器的队列在 redis 中 lpusp sun(redis_key指定的调度器名称) url
  8. 在 reids 中查看爬取到的数据: lrange ProjectName:items 0 -1

spider

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

from scrapy_redis.spiders import RedisCrawlSpider

from fbs.items import FbsItem


class TestSpider(RedisCrawlSpider):
    name = 'test'
    # allowed_domains = ['xx.com']
    # start_urls = ['http://xx.com/']

    # 指定可以被共享的 调度器名称
    redis_key = 'sun'

    rules = (
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        print(response)
        lis = response.xpath('//ul[@class="title-state-ul"]/li')
        for li in lis:
            num = li.xpath('./span[1]/text()').get()
            title = li.xpath('./span[3]/a/text()').extract_first()
            item = FbsItem()
            item['title'] = {
                'id': num,
                'content': title
            }
            yield item

settings 指定可以被共享的管道和调度器 代码片段

# 指定可以被共享的管道
ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 400,
}

# 指定调度器:

# 增加了一个去重容器的配置,作为使用Redis的set集合来储存请求的指纹数据,从而实现去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# 需要将调度器的类和去重的类替换为 Scrapy-Redis 提供的类
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 配置调度器是否要持久化。Scrapy-Redis 默认会在爬取全部完成后清空爬取队列和去重指纹集合。
SCHEDULER_PERSIST = True

# 指定存入的 redis服务器, 默认本机
# REDIS_HOST = 'ip'
# REDIS_PORT = 6397

redis.conf 代码片段

# 注释此条,允许其他分布式机群访问
# bind 127.0.0.1


# 关闭保护模式,允许其他计算机进行数据写入
protected-mode no

增量式爬虫

检测网站中更新的数据,只会爬取网站最新更新出来的数据。

实现:

  1. 指定起始 url,
  2. 基于 CrawlSpider 获取其他页码链接。
  3. 对页码下 子数据的 url 进行检测,之前爬取过的 url 不再进行爬取。
  4. 对页面下 子数据的 url 进行存储(便于下次爬取跳过

相关推荐:

来自系列:Python 爬虫学习笔记

分类 python下文章:

1.0 爬虫的介绍,和requests模块的简单使用

1.1 数据解析的三种方式。正则表达式, bs4, xpath

2.0 多任务(进程,协程,线程)爬虫:验证码识别,返回头储存,ip代理 介绍。 异步是什么,爬虫异步的方式。线程,进程,介绍

2.0.1 协程的 async/await 实现 爬虫 单线程 + 异步协程的实现

3.0 基于selenium 模块的 爬虫操作。 selenium 是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。

更多...

评论([[comments.sum]])

发表

加载更多([[item.son.length-2]])...

发表

2020-11 By chuan.

Python-flask & bootstrap-flask

图片外链来自:fghrsh

互联网ICP备案号:蜀ICP备2020031846号