Scrapy

移动端数据爬取

- 工具:三方抓包工具(fiddler,青花瓷,miteproxy)
- 配置fiddler
    - tools->connections->设置fiddler服务器的端口->点击对勾(allow remote ....)
- 测试上一步的配置是否成功:
    - 本地pc端去访问:本机ip:8888(fiddler的端口)
        - 页面的作用:提供了一个下载证书的通道
    - 成功访问后,在页面中我们可以看到底部有一个连接,点击可以下载fiddler的证书
- 需要在手机中安装证书
    - 将手机和电脑设置成同一网段(pc启动热点,手机去连接)
    - 手机浏览器中访问ip:port,访问fillder的子页面点击连接下载证书
- 必须要在手机中手动设置信任该证书
- 在手机中进行代理设置
    - 代理ip就是fiddler所在机器的ip
    - 端口就是fiddler的端口号

图片多个页面数据爬取:

思路:
在管道中引入下面这个进行图片的请求和存储
from scrapy.pipelines.images import ImagesPipeline

- 在配置文件中加入: IMAGES_STORE = 'xiaohuaLibs',自动新建一个文件夹

- get_media_requests:发起请求
- file_path:指定文件存储路径(指定文件名称)
- item_completed:将item传递给下一个即将被执行的管道类

请求传参就是请求时候进行传参  使用meta在不同的方法中进行传递
- 作用:帮助scrapy实现深度爬取
- 深度爬取:爬取的数据没有存在同一张页面中

示例 爬取多个页面

spiders/img.py

import scrapy
from imgPro.items import ImgproItem

class ImgSpider(scrapy.Spider):
    name = 'img'
    # allowed_domains = ['www.xxx.com']
    第一页数据
    start_urls = ['http://www.521609.com/tuku/']
    通用路由 第二页
    url = 'http://www.521609.com/tuku/index_%d.html'
    page = 2
    def parse(self, response):
        li_list = response.xpath('/html/body/div[4]/div[3]/ul/li')
        for li in li_list:
            img_src = 'http://www.521609.com'+li.xpath('./a/img/@src').extract_first()
            img_name = li.xpath('./a/p/text()').extract_first()+'.jpg'
            item = ImgproItem()
            item['img_name'] = img_name
            item['img_src'] = img_src
            print('正在下载:',img_name)
            yield item
        if self.page <= 5:
            new_url = format(self.url%self.page)
            self.page += 1
            yield scrapy.Request(new_url,callback=self.parse)

items.py

import scrapy
class ImgproItem(scrapy.Item):
img_name = scrapy.Field()
img_src = scrapy.Field()

pipelines.py

import scrapy
引入这个类,是专门进行bytes类型的持久化存储
from scrapy.pipelines.images import ImagesPipeline

自定义一个类,继承
class ImgPipeLine(ImagesPipeline):
    #用来进行资源的请求发送
    def get_media_requests(self, item, info):
        #通过请求传参将item进行传递
        yield scrapy.Request(url=item['img_src'],meta={'item':item})
        
    #将请求到的数据直接进行存储,在该方法中指定存储路径(图片名称)即可,要获取到图片名称进行存储
    def file_path(self, request, response=None, info=None, *, item=None):
        #接收传递过来的item对象,使用request.meta
        item = request.meta['item']
        获取图片名称
        img_name = item['img_name']
        直接返回
        return img_name

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

settings.py

ROBOTSTXT_OBEY = False
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
LOG_LEVEL = 'ERROR'
自动创建一个文件夹,保存图片
 IMAGES_STORE = 'xiaohuaLibs'

请求传参示例

spiders/movie.py

import scrapy
from moviePro.items import MovieproItem

class MovieSpider(scrapy.Spider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567kan.com/index.php/vod/show/id/1.html']

    def parse(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()
            item = MovieproItem()
            item['name'] = name
            获取路径,然后发送请求获取详情页数据
            detail_url = 'https://www.4567kan.com'+li.xpath('./div/a/@href').extract_first()
            #meta将字典传递给callback解析方法
            yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
            
     response只能每次接受一个响应
    def parse_detail(self,response):  
      获取item对象,为的是将解析的数据保存到item返回给管道
        item = response.meta['item']
        获取文本进行
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['desc'] = desc
        yield item

items.py

	class MovieproItem(scrapy.Item):
    name = scrapy.Field()
    desc = scrapy.Field()

pipelines.py

from itemadapter import ItemAdapter
class MovieproPipeline:
    def process_item(self, item, spider):
        print(item)
        return item

Scrapy 五大核心组件

引擎(Scrapy)
    用来处理整个系统的数据流处理, 触发事务(框架核心)
调度器(Scheduler)
    用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
    
下载器(Downloader)
    用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
爬虫(Spiders)
    爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
项目管道(Pipeline)
    负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

中间件

- 两类
    - 爬虫中间件
    - 下载中间件(重点)
- 作用:
    - 批量拦截scrapy中发起的所有请求和响应
- 拦截请求干什么
    - 篡改请求头
    - 代理设置
- 拦截响应干什么
    - 篡改响应内容
        - 如果你获得的响应对象对应的响应数据是不满足需求的则可以使用中间件篡改响应数据

对创建的工程中的middlewares.py进行简单的介绍

middlewares.py

from scrapy import signals
from itemadapter import is_item, ItemAdapter

下载中间件
class MiddleproDownloaderMiddleware:
    #拦截所有的请求对象
    #request:拦截到的请求对象
    #spider:爬虫类实例化好的对象(作用:可以实现爬虫文件和中间件之间的数据交互)
    def process_request(self, request, spider):
        print('i am process_request')
        #请求头修改的操作 正对所有的请求
        # request.headers['Cookie'] = 'xxxx'
        return None
        
    #拦截所有的响应
    #response:拦截到的响应对象
    #request:拦截到的响应对象对应的请求对象
    def process_response(self, request, response, spider):
        print('i am process_response')
        return response 返回响应对象
        
    #拦截异常的请求
    #request:拦截到的异常的请求
    def process_exception(self, request, exception, spider):
        print('i am process_exception')
        #对异常的请求进行修正完毕后,需要重新进行请求发送
        #代理的设置(视为对异常请求的修正操作,写到这里)
        # request.meta['proxy'] = 'https://ip:port'
        return request  再次发送请求

示例 使用scrapy(异步)和selenium获取动态多个页面数据

spiderls/wangyi.py

import scrapy
from wangyiPro.items import WangyiproItem
from selenium import webdriver

class WangyiSpider(scrapy.Spider):
    name = 'wangyi'
    start_urls = ['https://news.163.com/']
    model_urls = [] #保存是五个板块对应页面的url
    #实例化一个浏览器对象
    bro = webdriver.Chrome(executable_path=r'C:\Users\Administrator\Desktop\Bobo\爬虫\day06\chromedriver.exe')
    
    #解析五个板块对应的url,对其进行请求发送,也是动态加载的,但是只是获取5个,就谈不上漫步满足需求了
    def parse(self, response):
        #解析出5个板块对应页面的url(国内,国际,军事,航空,无人机)
        返回的li_list是一个列表,获取指定的这几个。通过索引下标获取这几个,是从0 开始
        li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
        获取他们几个的索引下标
        model_index = [3,4,6,7,8]
        循环索引下标
        for index in model_index:
            通过索引获取想要的li标签
            li = li_list[index]
            然后获取到li标签里面的属性值url
            model_url = li.xpath('./a/@href').extract_first()
            self.model_urls.append(model_url)
        循环这个url列表
        for url in self.model_urls:
        然后发送请求
            yield scrapy.Request(url=url,callback=self.parse_model)
            
            
    #解析新闻的标题和新闻详情页的url
    #注意:当前解析的页面中新闻标题和详情页的url是动态加载的数据,所以解析的内容是不满足的,需要在中间件中使用selenuim来进行动态的获取内容,也就是对响应的内容进行拦截,再次获取
    #在整个的实现中发起请求的url数量:1(首页)+5(那5个页面)+n(每个详情页的url),意味着有1+5+n个响应对象
    #在1+5+n个响应对象中只有5个响应对象是不满足需求的,因为只获取的那5个页面数据是动态的。需要单独处理!
    def parse_model(self,response):
        #这里遇到了一个不满足需求的响应对象response,需要修改中间件的响应对象,然后这里的response是接收的是新的响应对象。只要是动态页面数据要全部获取,那么就需要在中间件重新去返回响应对象
        div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div')
        for div in div_list:
           获取每个url页面下面的标题
            title = div.xpath('./a/img/@alt').extract_first()
            获取详情页面的url
            new_detail_url = div.xpath('./a/@href').extract_first()
            item = WangyiproItem()
            item['title'] = title
            if new_detail_url:
            再次发送请求获取详情页面的内容
                yield scrapy.Request(url=new_detail_url,callback=self.parse_new,meta={'item':item})
	
	
	获取详情页的内容
    def parse_new(self,response):#解析新闻内容
        item = response.meta['item']
        返回列表
        content = response.xpath('//*[@id="content"]/div[2]//text()').extract()
        content = ''.join(content)
        item['content'] = content
        yield item

    #重写父类的一个方法,该方法是程序最后一刻会执行
    def closed(self,spider):
        self.bro.quit()

items.py

class WangyiproItem(scrapy.Item):
    title = scrapy.Field()
    content = scrapy.Field()

middlewares.py

from scrapy import signals
from itemadapter import is_item, ItemAdapter
from time import sleep
from scrapy.http import HtmlResponse

下载中间件
class WangyiproDownloaderMiddleware:
    def process_request(self, request, spider):
        return None
    
    对筛选出的响应的数据进行篡改
    这里的response响应的是所有的响应数据
    def process_response(self, request, response, spider):
        #筛选出那5个不满足需求的响应对象对其进行修正即可,其他的响应对象不动
        #可以通过url->request->response(新的响应对象)
        通过爬虫对象spider获取那5个不满足需求的请求的url,进行中间件和爬虫间的交互
        models_url = spider.model_urls
        如果请求的url在这个列表中,也就是不满足的url,重新发送请求,获取新的响应对象返回
        if request.url in models_url:
            #获取浏览器对象
            bro = spider.bro
            通过selenuim来发送请求,获取动态的数据
            bro.get(request.url)
            sleep(1)
            bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            获取到原码数据
            page_text = bro.page_source
            #实例化一个新的响应对象.在新响应对象中加入满足需求的响应数据,从而使得新的响应对象成为满足需求的响应对象
           新的响应对象。通过HtmlResponse来实现。body就是通过selenuim重新获取的动态数据
            new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
            然后将新的响应对象返回,也就是满足需求的响应对象
            return new_response
        else:
        否则就没有不满足的响应数据,直接返回
            return response

    def process_exception(self, request, exception, spider):

        pass

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

pipelines.py

from itemadapter import ItemAdapter
class WangyiproPipeline:
    def process_item(self, item, spider):
        print(item)
        return item

settings.py

ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
ITEM_PIPELINES = {
   'wangyiPro.pipelines.WangyiproPipeline': 300,
}
DOWNLOADER_MIDDLEWARES = {
   'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
}

CrawlSpider

- Spider的一个子类.
- 作用:用于全站数据的爬取,也可以理解为是将页码链接全部爬取到,然后对页面链接进行发送请求,获取到页面链接对应的页面链接数据
- 使用:
    - 创建爬虫文件指令:scrapy genspider -t crawl spiderName www.xxx.com

对创建的工程中的spiders/movie.py进行简单的介绍

spiders/movie.py

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


class MovieSpider(CrawlSpider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567kan.com/index.php/vod/show/id/1.html']
    #LinkExtractor 连接提取器:根据指定的要求(allow='正则')进行连接(url)的提取,这个正则是从页面(访问start_urls 页面)中进行匹配。想要请求什么样的url就进行什么样的正则匹配
    link = LinkExtractor(allow=r'page/\d+\.html')
    rules = (
        #Rule叫规则解析器:接收link提取到的连接,然后对其进行请求发送,然后根据callback表示的规则进行数据解析
        #follow=True:将连接提取器继续作用到连接提取器提取出的页码所对应的页面中
        Rule(link, callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        print(response)

示例 全站数据爬取

spiders/movie.py

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from deepCrawlPro.items import TitleItem,DescItem

class MovieSpider(CrawlSpider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567kan.com/index.php/vod/show/id/1/page/1.html']

    rules = (
        #对页码连接进行处理
        Rule(LinkExtractor(allow=r'page/\d+\.html'), callback='parse_item', follow=False),
        #对电影详情页做处理
        Rule(LinkExtractor(allow=r'/movie/index\d+\.html'),callback='parse_detail',follow=False)
    )
	
	
    def parse_item(self, response):
        #解析电影名称
        #这里也可以通过parse_item方法解析电影简介,然后通过scrapy发送请求获取详情,就不需要下面的parse_detail这个方法来进行解析,和上面的rules 里面的对电影详情做处理。这样就可以使用mata来进行item传参返回,那么在管道中就不需要判断来获取了
        li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
        for li in li_list:
            title = li.xpath('./div/a/@title').extract_first()
            item = TitleItem()  如果没有使用meta进行item传参,实例化两次item,那么item文件就分类定义
            item['title'] = title
            yield item
    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 = DescItem()
        item['desc'] = desc
        yield item

items.py

import scrapy 实例化两次item就需要定义两个item
class TitleItem(scrapy.Item):
    title = scrapy.Field()
class DescItem(scrapy.Item):
    desc = scrapy.Field()

pipelines.py

from itemadapter import ItemAdapter
class DeepcrawlproPipeline:
返回两个不同类的item,需要进行判断类来获取
    def process_item(self, item, spider):
        if item.__class__.__name__ == 'TitleItem':
            print(item['title'])
        else:
            print(item['desc'])
        return item

redis指令:

- 查看所有数据:keys *
- 删除所有的数据:flushall
- 创建set集合,向其中添加元素:sadd name bobo
- 查看set集合的数据:smembers name
- 创建一个队列,向其中添加元素:lpush name bobo
- 查看队列元素:llen name
    - lrange name 0 -1

分布式

- 概念:搭建一个分布式的机群.然后让其对同一组资源进行联合且分布的数据爬取

- 原生的scrapy框架是无法实现分布式,原因是因为:
    - 分布式集群对应的调度器无法被共享
    - 管道无法被共享
    
    
- 让scrapy借助一个组件scrapy-redis从而来实现分布式
- scrapy-redis组件的作用:
    - 给scrapy提供可以被共享的管道和调度器
    - 只能将爬取到的数据存储到redis数据库中

- 组件进行下载:
    - pip install scrapy-redis
    
- 分布式具体实现:
    - 1.创建工程
    - 2.创建爬虫文件
        - 创建Crawlspider
        - 创建Spider
    - 3.修改爬虫文件
        - 导包:from scrapy_redis.spiders import  RedisCrawlSpider(Crawlspider)
        或
        from scrapy_redis.spiders import  RedisSpider(spider)
        - 将爬虫类的父类修改为RedisCrawlSpider
        - 删除start_urls,分布式机群不需要每个都要有开始url
        - 添加一个新的属性字段:redis_key = 'xxx',表示在定义调度器队列的名称
        - 正常的编写请求和解析的操作即可
    - 4.修改settings配置文件
        - 指定调度器
            # 使用scrapy-redis组件的去重队列
            DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
            # 使用scrapy-redis组件自己的调度器
            SCHEDULER = "scrapy_redis.scheduler.Scheduler"
            # 是否允许暂停
            SCHEDULER_PERSIST = True
        - 指定管道
            ITEM_PIPELINES = {
                'scrapy_redis.pipelines.RedisPipeline': 400
            }

        - 指定redis数据库
            REDIS_HOST = 'redis服务的ip地址'
            REDIS_PORT = 6379
    - 5.配置redis的配置文件(redis.window.conf)
        - 56行:bind进行注释
        - 75行:将yes改为no(关闭redis的保护模式)
    - 6.启动redis的服务端和客户端
    - 7.执行当前的工程
    - 8.只需要一个人在redis客户端将起始的url手动放置到调度器的队列中
         #fbs_queue是对列名称 
        - lpush fbs_queue www.xxx.com

示例

spiders/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 = 'fbs_queue'
    rules = (
        Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
        for li in li_list:
            title = li.xpath('./span[3]/a/text()').extract_first()
            item = FbsproItem()
            item['title'] = title	
            yield item

items.py

import scrapy
class FbsproItem(scrapy.Item):
    title = scrapy.Field()

settings.py

#指定调度器
# 使用scrapy-redis组件的去重队列
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 是否允许暂停
SCHEDULER_PERSIST = True

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

#指定redis
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379

增量式

- 监测网站数据更新的情况.
- 核心:
    - 去重
- 记录表:(可持久化存储,可去重)
    - 记录爬取过的数据的标识
    - 数据指纹:数据的唯一标识

示例

	import scrapy
	from redis inport Redis
	from scrapy.linkextractors import LinkExtractor
	from scrapy.spiders import CrawlSpider, Rule
	from scrapy_redis.spiders import  RedisCrawlSpider
	from fbsPro.items import FbsproItem
	conn=Redis(host=‘127.0.0.1’,port=6379)
	
	class FbsSpider(RedisCrawlSpider):
	    name = 'fbs'
	    # allowed_domains = ['www.xxx.com']
	    # start_urls = ['http://www.xxx.com/']
	    redis_key = 'fbs_queue'
	    rules = (
	        Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=True),
	    )
	
	    def parse_item(self, response):
	        li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
	        for li in li_list:
	            title = li.xpath('./span[3]/a/text()').extract_first()
	            item = FbsproItem()
	            item['title'] = title	
	            detail_url=li.xpath('./span[3]/a/text()').extract_first()
	            ex=self.conn.sadd('detail_url'.detail_url)
	            if ex ==0:
	            	print('有了)
				else:
					发送请求
	在定义一个详情解析

pipelines.py

     class  XX:
     	conn=None
     	def open_spider(selfspider):
     		self.conn=spider.conn
     	def process_item():
     		self.conn.lpush('vvv',item)
     		return iten 
     		然后在配置文件中开启

爬虫回顾

requests
get/post方法中常用的参数
url
headers
data/params/json
proxies
数据解析
bs4
xpath
cookie的处理
手动处理
自动处理(Session)
代理
代理池
代理精灵购买代理
验证码
打码平台
动态变化的请求参数
隐藏在前台页面中
在抓包工具中全局搜索在进行相关的逆向操作
动态加载数据的捕获
基于抓包工具进行全局搜索
生成方式
ajax
js
爬虫逆向
逆向方式:
手动逆向
自动逆向
js混淆,js反混淆
selenium
图片懒加载
异步爬虫
线程池
协程
生产者消费者模式
scrapy
数据解析
extract_first(),extract()
持久化存储
基于管道
基于终端指令
手动请求发送
Request()
FormRequest()
scrapy是可以自动处理cookie
settings中将COOKIE_ENABLE = True
五大核心组件
ImagesPipeLine
请求传参
作用:实现深度爬取
中间件
CrawlSpider
分布式
增量式
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值