首页 > 其他分享 >scrapy框架

scrapy框架

时间:2022-11-10 11:44:48浏览次数:43  
标签:框架 self item scrapy div response def

什么是框架?

  • 就是一个集成了很多功能并且具有很强通用性的一个项目模板

如何学习框架?

  • 专门学习框架封装的各种功能的详细用法

什么是scrapy?

  • 爬虫中封装好的一个明星框架。功能:异步的数据下载,高性能的数据解析,高性能的持久化存储,分布式

scrapy框架的基本使用:

  • 环境的安装:

    • - mac or linux:pip install scrapy
      - windows:以下命令可以直接在interpreter里面下载对应的包
          - pip install wheel
          - 下载twisted,下载地址为http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
          - 安装twisted:pip install Twisted‑17.1.0‑cp36‑cp36m‑win_amd64.whl
          - pip install pywin32
          - pip install scrapy
          测试:在终端里录入scrapy指令,没有报错即表示安装成功!
      
  • 创建一个工程:scrapy startproject xxxPro

  • 进入该工程:cd xxxPro

  • 在spiders子目录中创建一个爬虫文件:scrapy genspider spiderName www.xxx.com

  • 执行工程:scrapy crawl spiderName

scrapy的使用

爬虫文件:first.py

import scrapy
class FirstSpider(scrapy.Spider):
    #爬虫文件的名称:就是爬虫源文件的一个唯一标识
    name = 'first'
    #允许的域名:用来限定start_urls列表中哪些url可以进行请求发送
    # allowed_domains = ['www.baidu.com']

    #起始的url列表:该列表中存放的url会被scrapy自动进行请求的发送
    start_urls = ['https://www.baidu.com/','https://www.sogou.com']

    #用作于数据解析:response参数表示的就是请求成功后对应的响应对象
    def parse(self, response):
        print(response)

配置文件:settings.py

#常规的设置
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'

ROBOTSTXT_OBEY = False

LOG_LEVEL = 'ERROR' #显示指定类型的日志信息

scrapy持久化存储:

  • 基于终端指令:

    • - 要求:只可以将parse方法的返回值存储到本地的文本文件中
      - 注意:持久化存储对应的文本文件的类型只可以为:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle
      - 指令:scrapy crawl spiderName -o filePath
      - 好处:简介高效便捷
      - 缺点:局限性比较强(数据只可以存储到指定后缀的文本文件中)
      
    • def parse(self, response):
          #解析:作者的名称+段子内容
          div_list = response.xpath('//div[@id="content-left"]/div')
          all_data = [] #存储所有解析到的数据
          for div in div_list:
              #xpath返回的是列表,但是列表元素是Selector类型的对象
              #extract可以将Selector对象中data参数存储的字符串提取出来
              # author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
              author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
              #列表调用了extract之后,则表示将列表中每一个Selector对象中data对应的字符串提取了出来
              content = div.xpath('./a[1]/div/span//text()').extract()
              content = ''.join(content)
              dic = {
                  'author':author,
                  'content':content
              }
              all_data.append(dic)
          return all_data
      
  • 基于管道:

    • - 编码流程:
          - 数据解析
          - 在item类中定义相关的属性
          - 将解析的数据封装存储到item类型的对象
          - 将item类型的对象提交给管道进行持久化存储的操作
          - 在管道类的process_item中要将其接受到的item对象中存储的数据进行持久化存储操作
          - 在配置文件中开启管道
      - 好处:
          - 通用性强
      
    • #数据解析+将解析的数据封装存储到item类型的对象+将item类型的对象提交给管道进行持久化存储的操作
      #first.py
      from qiubaiPro.items import QiubaiproItem
      def parse(self, response):
          #解析:作者的名称+段子内容
          div_list = response.xpath('//div[@id="content-left"]/div')
          all_data = [] #存储所有解析到的数据
          for div in div_list:
              author = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span/h2/text()').extract_first()
              content = div.xpath('./a[1]/div/span//text()').extract()
              content = ''.join(content)
              item = QiubaiproItem()
              item['author'] = author
              item['content'] = content
              yield item #将item提交给了管道
      
    • #在item类中定义相关的属性
      #items.py
      import scrapy
      class QiubaiproItem(scrapy.Item):
          author = scrapy.Field()
          content = scrapy.Field()
      
    • #在管道类的process_item中要将其接受到的item对象中存储的数据进行持久化存储操作
      #pipelines.py
      
      class QiubaiproPipeline(object):
          fp = None
          #重写父类的一个方法:该方法只在开始爬虫的时候被调用一次
          def open_spider(self,spider):
              print('开始爬虫......')
              self.fp = open('./qiubai1.txt','w',encoding='utf-8')
          #专门用来处理item类型对象,该方法可以接收爬虫文件提交过来的item对象
          #该方法每接收到一个item就会被调用一次
          def process_item(self, item, spider):
              author = item['author']
              content= item['content']
              self.fp.write(author+':'+content+'\n')
              return item #就会传递给下一个即将被执行的管道类
          #重写父类的一个方法:该方法只在结束爬虫的时候被调用一次
          def close_spider(self,spider):
              print('结束爬虫!')
              self.fp.close()
      

持久化存储的拓展:将爬取到的数据一份存储到本地一份存储到数据库,如何实现?

  • 管道文件中一个管道类对应的是将数据存储到一种平台

  • 爬虫文件提交的item只会给管道文件中第一个被执行的管道类接收

  • process_item中的return item表示将item传递给下一个即将被执行的管道类

  • #pipelines.py
    
    import pymysql
    
    class QiubaiproPipeline(object):
        fp = None
        def open_spider(self,spider):
            print('开始爬虫......')
            self.fp = open('./qiubai1.txt','w',encoding='utf-8')
        def process_item(self, item, spider):
            author = item['author']
            content= item['content']
            self.fp.write(author+':'+content+'\n')
            return item #就会传递给下一个即将被执行的管道类
        def close_spider(self,spider):
            print('结束爬虫!')
            self.fp.close()
    
    class mysqlPileLine(object):
        conn = None
        cursor = None
        
        def open_spider(self,spider):
            self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',db='qiubai',charset='utf8')
            
        def process_item(self,item,spider):
            self.cursor = self.conn.cursor()
            try:
                self.cursor.execute('insert into qiubai values("%s","%s")'%(item["author"],item["content"]))
                self.conn.commit()
            except Exception as e:
                print(e)
                self.conn.rollback()
            return item
            
        def close_spider(self,spider):
            self.cursor.close()
            self.conn.close()
    
  • #settings.py
    ITEM_PIPELINES = {
       'qiubaiPro.pipelines.QiubaiproPipeline': 300,
       'qiubaiPro.pipelines.mysqlPileLine': 301,
        #300表示的是优先级,数值越小优先级越高
    }
    

基于Spider的全站数据爬取

基于Spider的全站数据爬取:就是将网站中某板块下的全部页码对应的页面数据进行爬取(递归调用)

需求:爬取校花网中的照片的名称

实现方式:

  • 将所有页面的url添加到start_urls列表(不推荐)
  • 自行手动进行请求发送(推荐):yield scrapy.Request(url,callback): callback专门用做于数据解析
#xiaohua.py

import scrapy

class XiaohuaSpider(scrapy.Spider):
    name = 'xiaohua'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://www.521609.com/meinvxiaohua/']

    #生成一个通用的url模板(不可变)
    url = 'http://www.521609.com/meinvxiaohua/list12%d.html'
    page_num = 2

    def parse(self, response):
        li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')
        for li in li_list:
            img_name = li.xpath('./a[2]/b/text() | ./a[2]/text()').extract_first()
            print(img_name)

        if self.page_num <= 11:
            new_url = format(self.url%self.page_num)
            self.page_num += 1
            #手动请求发送:callback回调函数是专门用作于数据解析
            yield scrapy.Request(url=new_url,callback=self.parse)

scrapy五大核心组件:

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

请求传参

  • 使用场景:如果爬取解析的数据不在同一张页面中(深度爬取)
  • 需求:爬取boss的岗位名称,岗位描述
#boss.py
import scrapy

class BossSpider(scrapy.Spider):
    name = 'boss'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.zhipin.com/job_detail/?query=python&city=101010100&industry=&position=']

    url = 'https://www.zhipin.com/c101010100/?query=python&page=%d'
    page_num = 2

   #回调函数接受item
    def parse_detail(self,response):
        item = response.meta['item']
        job_desc = response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()').extract()
        job_desc = ''.join(job_desc)
        print(job_desc)
        item['job_desc'] = job_desc
        yield item #传递给管道

    #解析首页中的岗位名称
    def parse(self, response):
        li_list = response.xpath('//*[@id="main"]/div/div[3]/ul/li')
        for li in li_list:
            item = BossproItem()
            job_name = li.xpath('.//div[@class="info-primary"]/h3/a/div[1]/text()').extract_first()
            item['job_name'] = job_name
            print(job_name)
            detail_url = 'https://www.zhipin.com'+li.xpath('.//div[@class="info-primary"]/h3/a/@href').extract_first()
            #对详情页发请求获取详情页的页面源码数据
            #手动请求的发送
            #请求传参:meta={},可以将meta字典传递给请求对应的回调函数
            yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})

        #分页操作
        if self.page_num <= 3:
            new_url = format(self.url%self.page_num)
            self.page_num += 1

            yield scrapy.Request(new_url,callback=self.parse)
#items.py
import scrapy
class BossproItem(scrapy.Item):
    job_name = scrapy.Field()
    job_desc = scrapy.Field()

图片数据爬取之ImagesPipeline

基于scrapy爬取字符串类型数据和爬取图片类型数据的区别?

  • 字符串:只需要基于xpath进行解析且提交管道进行持久化存储
  • 图片:xpath解析出图片src的属性值,单独的对图片地址发起请求获取图片二进制类型的数据

ImagesPipeline:

  • 只需要将img的src的属性值进行解析,提交到管道,管道就会对图片的src进行请求发送获取图片的二进制类型的数据,且还会帮我们进行持久化存储

需求:爬取站长素材中的高清图片

使用流程:

  • 数据解析(图片的地址)
  • 将存储图片地址的item提交到制定的管道类
  • 在管道文件中自定制一个基于ImagesPipeLine的一个管道类
    • get_media_request
    • file_path
    • item_completed
  • 在配置文件中:
    • 指定图片存储的目录:IMAGES_STORE = './imgs_bobo'
    • 指定开启的管道:自定制的管道类
#items.py
import scrapy

class ImgsproItem(scrapy.Item):
    src = scrapy.Field()
#img.py

import scrapy
from imgsPro.items import ImgsproItem

class ImgSpider(scrapy.Spider):
    name = 'img'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://sc.chinaz.com/tupian/']

    def parse(self, response):
        div_list = response.xpath('//div[@id="container"]/div')
        for div in div_list:
            #注意:使用伪属性
            src = div.xpath('./div/a/img/@src2').extract_first()
            item = ImgsproItem()
            item['src'] = src
            yield item
#pipelines.py
from scrapy.pipelines.images import ImagesPipeline
import scrapy

class imgsPileLine(ImagesPipeline):
    #可以根据图片地址进行图片数据的请求
    def get_media_requests(self, item, info):
        yield scrapy.Request(item['src'])
    #指定图片存储的路径
    def file_path(self, request, response=None, info=None):
        imgName = request.url.split('/')[-1]
        return imgName
    def item_completed(self, results, item, info):
        return item #返回给下一个即将被执行的管道类
#settings.py
#指定图片存储的目录
IMAGES_STORE = './imgs_bobo'
ITEM_PIPELINES = {
   'imgsPro.pipelines.imgsPileLine': 300,
}

中间件

爬虫中间件

  • 位置:引擎和spider之间

下载中间件

  • 位置:引擎和下载器之间
  • 作用:批量拦截到整个工程中所有的请求和响应
  • 拦截请求:
    • UA伪装: process_request
    • 代理IP: process_exception: return request
  • 拦截响应:
    • 篡改响应数据,响应对象
    • 需求:爬取网易新闻中的新闻数据(标题和内容)
      • 1.通过网易新闻的首页解析出五大板块对应的详情页的url(没有动态加载)
      • 2.每一个板块对应的新闻标题都是动态加载出来的(动态加载)
      • 3.通过解析出每一条新闻详情页的url获取详情页的页面源码,解析出新闻内容

拦截请求:

#middle.py

import scrapy

class MiddleSpider(scrapy.Spider):
    #爬取百度
    name = 'middle'
    # allowed_domains = ['www.xxxx.com']
    start_urls = ['http://www.baidu.com/s?wd=ip']

    def parse(self, response):
        page_text = response.text
        with open('./ip.html','w',encoding='utf-8') as fp:
            fp.write(page_text)
#middlewares.py

import random

class MiddleproDownloaderMiddleware(object):
    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",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
    ]
    PROXY_http = [
        '153.180.102.104:80',
        '195.208.131.189:56055',
    ]
    PROXY_https = [
        '120.83.49.90:9000',
        '95.189.112.214:35508',
    ]
    #拦截请求
    def process_request(self, request, spider):
        #UA伪装
        request.headers['User-Agent'] = random.choice(self.user_agent_list)

        #为了验证代理的操作是否生效
        request.meta['proxy'] = 'http://183.146.213.198:80'
        return None
        
    #拦截所有的响应
    def process_response(self, request, response, spider):
        return response
        
    #拦截发生异常的请求
    def process_exception(self, request, exception, spider):
        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  #将修正之后的请求对象进行重新的请求发送
#settings.py
DOWNLOADER_MIDDLEWARES = {
   'middlePro.middlewares.MiddleproDownloaderMiddleware': 543,
}

拦截响应:

#items.py

import scrapy

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

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

class WangyiSpider(scrapy.Spider):
    name = 'wangyi'
    # allowed_domains = ['www.cccom']
    start_urls = ['https://news.163.com/']
    models_urls = []  #存储五个板块对应详情页的url
    #解析五大板块对应详情页的url

    #实例化一个浏览器对象
    def __init__(self):
        self.bro = webdriver.Chrome(executable_path='/Users/bobo/Desktop/小猿圈爬虫课程/chromedriver')

    def parse(self, response):
        li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
        alist = [3,4,6,7,8]
        for index in alist:
            model_url = li_list[index].xpath('./a/@href').extract_first()
            self.models_urls.append(model_url)

        #依次对每一个板块对应的页面进行请求
        for url in self.models_urls:#对每一个板块的url进行请求发送
            yield scrapy.Request(url,callback=self.parse_model)

    #每一个板块对应的新闻标题相关的内容都是动态加载
    def parse_model(self,response): #解析每一个板块页面中对应新闻的标题和新闻详情页的url
        # response.xpath()
        div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div')
        for div in div_list:
            title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
            new_detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()

            item = WangyiproItem()
            item['title'] = title

            #对新闻详情页的url发起请求
            yield scrapy.Request(url=new_detail_url,callback=self.parse_detail,meta={'item':item})
            
    def parse_detail(self,response):#解析新闻内容
        content = response.xpath('//*[@id="endText"]//text()').extract()
        content = ''.join(content)
        item = response.meta['item']
        item['content'] = content
        yield item

    def closed(self,spider):
        self.bro.quit()
#middlewares.py

from scrapy.http import HtmlResponse
from time import sleep

class WangyiproDownloaderMiddleware(object):
    def process_request(self, request, spider):
        return None
        
    #该方法拦截五大板块对应的响应对象,进行篡改
    def process_response(self, request, response, spider):#spider爬虫对象
        bro = spider.bro#获取了在爬虫类中定义的浏览器对象
        #挑选出指定的响应对象进行篡改
        #通过url指定request
        #通过request指定response
        if request.url in spider.models_urls:
            bro.get(request.url) #五个板块对应的url进行请求
            sleep(3)
            page_text = bro.page_source  #包含了动态加载的新闻数据
            #response #五大板块对应的响应对象
            #针对定位到的这些response进行篡改
            #实例化一个新的响应对象(符合需求:包含动态加载出的新闻数据),替代原来旧的响应对象
            #如何获取动态加载出的新闻数据?
            #基于selenium便捷的获取动态加载数据
            new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
            return new_response
        else:
            #response #其他请求对应的响应对象
            return response

    def process_exception(self, request, exception, spider):
        pass
#settings.py
DOWNLOADER_MIDDLEWARES = {
   'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
}
ITEM_PIPELINES = {
   'wangyiPro.pipelines.WangyiproPipeline': 300,
}

基于CrawlSpider的全站数据爬取

CrawlSpider类,Spider的一个子类

全站数据爬取的方式:

  • 基于Spider:手动请求
  • 基于CrawlSpider

CrawlSpider的使用:

  • 创建一个工程
  • cd XXX
  • 创建爬虫文件(CrawlSpider):scrapy genspider -t crawl xxx www.xxxx.com

链接提取器作用:根据指定的规则(allow)进行指定链接的提取

规则解析器作用:将链接提取器提取到的链接进行指定规则(callback)的解析

需求:爬取sun网站中的编号,新闻标题,新闻内容,标号

分析:爬取的数据没有在同一张页面中

  • 1.可以使用链接提取器提取所有的页码链接

  • 2.让链接提取器提取所有的新闻详情页的链接

#items.py

import scrapy

class SunproItem(scrapy.Item):
    title = scrapy.Field()
    new_num = scrapy.Field()

class DetailItem(scrapy.Item):
    new_id = scrapy.Field()
    content = scrapy.Field()
#sun.py

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.items import SunproItem,DetailItem

#需求:爬取sun网站中的编号,新闻标题,新闻内容,标号
class SunSpider(CrawlSpider):
    name = 'sun'
    start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']

    #链接提取器:根据指定规则(allow="正则")进行指定链接的提取
    link = LinkExtractor(allow=r'type=4&page=\d+')
    link_detail = LinkExtractor(allow=r'question/\d+/\d+\.shtml')
    rules = (
        #规则解析器:将链接提取器提取到的链接进行指定规则(callback)的解析操作
        #follow=True:可以将链接提取器继续作用到 连接提取器提取到的链接 所对应的页面中
        Rule(link, callback='parse_item', follow=True),
        Rule(link_detail,callback='parse_detail')
    )
    #解析新闻编号和新闻的标题
    #如下两个解析方法中不可以实现请求传参!
    #如下将两个解析方法解析的数据存储到同一个item中,可以以此存储到两个item
    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:
            new_num = tr.xpath('./td[1]/text()').extract_first()
            new_title = tr.xpath('./td[2]/a[2]/@title').extract_first()
            item = SunproItem()
            item['title'] = new_title
            item['new_num'] = new_num
            yield item

    #解析新闻内容和新闻编号
    def parse_detail(self,response):
        new_id = response.xpath('/html/body/div[9]/table[1]//tr/td[2]/span[2]/text()').extract_first()
        new_content = response.xpath('/html/body/div[9]/table[2]//tr[1]//text()').extract()
        new_content = ''.join(new_content)
        item = DetailItem()
        item['content'] = new_content
        item['new_id'] = new_id
        yield item
#pipelines.py
class SunproPipeline(object):
    def process_item(self, item, spider):
        #如何判定item的类型
        if item.__class__.__name__ == 'DetailItem':
            print(item['new_id'],item['content'])
            pass
        else:
            print(item['new_num'],item['title'])
        return item

标签:框架,self,item,scrapy,div,response,def
From: https://www.cnblogs.com/vahan/p/16876555.html

相关文章