2024 高级爬虫笔记(六)scrapy框架基础知识

一、Scrapy框架基础知识

1.1、什么是scrapy?

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,我们只需要实现少量的代码,就能够快速的抓取。

Scrapy 使用了Twisted异步网络框架,可以加快我们的下载速度。

1.2、scrapy的工作流程

1. 调度器把requests-->引擎-->下载中间件--->下载器
2. 下载器发送请求,获取响应---->下载中间件---->引擎--->爬虫中间件--->爬虫
3. 爬虫提取url地址,组装成request对象---->爬虫中间件--->引擎--->调度器
4. 爬虫提取数据--->引擎--->管道
5. 管道进行数据的处理和保存

1.3、scrapy中每个模块的作用:

   引擎(engine):负责数据和信号在不同模块间的传递 
   调度器(scheduler):实现一个队列,存放引擎发过来的request请求对象
   下载器(downloader):发送引擎发过来的request请求,获取响应,并将响应交给引擎
   爬虫(spider):处理引擎发过来的response,提取数据,提取url,并交给引擎
   管道(pipeline):处理引擎传递过来的数据,比如存储
   下载中间件(downloader middleware):可以自定义的下载扩展,比如设置代理ip
   爬虫中间件(spider middleware):可以自定义request请求和进行response过滤

1.4、scrapy的入门使用

1.4.1 安装scrapy

安装scrapy命令:
    pip install scrapy==2.5.1
    pip install -i https://pypi.tuna.tsinghua.edu.cn/simple scrapy==2.5.1
    pip install scrapy-redis==0.7.2

如果安装失败. 请先升级一下pip.  然后重新安装scrapy即可.
最终你的控制台输入`scrapy version`能显示版本号. 就算成功了

1.4.2、scrapy项目实现流程

1. 创建一个scrapy项目:scrapy startproject mySpider
2. 生成一个爬虫:scrapy genspider myspider [www.xxx.cn](www.xxx.cn)
3. 提取数据:完善spider,使用xpath等方法
4. 保存数据:pipeline中保存数据

1.4.3、创建scrapy项目

创建scrapy项目的命令:scrapy startproject +<项目名字>
示例:scrapy startproject myspider

生成的目录和文件结果如下:
在这里插入图片描述

1.4.4、创建爬虫

命令:
在项目路径下执行:scrapy genspider +<爬虫名字> + <允许爬取的域名>

示例:

  • scrapy startproject duanzi01
  • cd duanzi01/
  • scrapy genspider duanzi duanzixing.com

生成的目录和文件结果如下:
在这里插入图片描述

1.4.5、完善spider

完善spider即通过方法进行数据的提取等操作
在/duanzi01/duanzi01/spiders/duanzi.py中修改内容如下:

import scrapy
# 自定义spider类,继承scrapy.spider
class DuanziSpider(scrapy.Spider):
     # 爬虫名字
     name = 'duanzi'
     # 允许爬取的范围,防止爬虫爬到别的网站
     allowed_domains = ['duanzixing.com']
     # 开始爬取的url地址
     start_urls = ['http://duanzixing.com/']
     # 数据提取的方法,接受下载中间件传过来的response 是重写父类中的parse方法
     def parse(self, response, **kwargs):
         # 打印抓取到的页面源码
         # print(response.text)

         # xpath匹配每条段子的article列表
         article_list = response.xpath('//article[@class="excerpt"]')
         # print(article_list)

         # 循环获取每一个article
         for article in article_list:
             # 匹配标题
             # title = article.xpath('./header/h2/a/text()')[0].extract()
             # 等同于
             title = article.xpath('./header/h2/a/text()').extract_first()
             # 获取段子内容
             con = article.xpath('./p[@class="note"]/text()').extract_first()
             print('title', title)
             print('con', con)

启动爬虫命令: scrapy crawl  duanzi

response响应对象的常用属性
- response.url:当前响应的url地址
- response.request.url:当前响应对应的请求的url地址
- response.headers:响应头
- response.request.headers:当前响应的请求头
- response.body:响应体,也就是html代码,byte类型
- response.text  返回响应的内容 字符串
- response.status:响应状态码

注意:
1. response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法
2. extract() 返回一个包含有字符串的列表  
3. extract_first() 返回列表中的第一个字符串,列表为空没有返回None
4. spider中的parse方法必须有
5. 需要抓取的url地址必须属于allowed_domains,但是start_urls中的url地址没有这个限制
6. 启动爬虫的时候注意启动的位置,是在项目路径下启动

1.4.6、配置settings文件

- ROBOTSTXT_OBEY = False
  robots是一种反爬协议。在协议中规定了哪些身份的爬虫无法爬取的资源有哪些。
  在配置文件中setting,取消robots的监测:

- 在配置文件中配置全局的UA:USER_AGENT='xxxx'

- 在配置文件中加入日志等级:LOG_LEVEL = 'ERROR'  只输出错误信息

  其它日志级别:
  - CRITICAL  严重错误
  - ERROR  错误
  - WARNING  警告
  - INFO  消息
  - DEBUG   调试

1.4.7、数据存储

1.4.7.1、使用终端命令行进行存储
- 代码配置
  /myspider/myspider/spiders/ITSpider.py

   class ITSpider(scrapy.Spider):
       name = 'ITSpider'
       start_urls = ['https://duanzixing.com/page/2/']
   
       # 通过终端写入文件的方式
       def parse(self, response):
           article_list = response.xpath('/html/body/section/div/div/article')
           # 创建列表, 存储数据
           all_data = []
           for article in article_list:
               title = article.xpath('./header/h2/a/text()').extract_first()
               con = article.xpath('./p[2]/text()').extract_first()
               dic = {
                   'title': title,
                   'con': con
               }
               all_data.append(dic)
           return all_data

- 终端命令
  scrapy crawl ITSpider -o ITSpider.csv  
  将文件存储到ITSpider.csv  文件中
1.4.7.2、利用管道pipeline来处理(保存)数据(写入文件中)
1、打开 myspider/myspider/items.py文件,添加如下代码:
   import scrapy

   class DuanziproItem(scrapy.Item):
       title = scrapy.Field()
       con = scrapy.Field()

2、打开/myspider/myspider/spiders/ITSpider.py文件,添加如下代码:
   import scrapy
   from myspider.items import DuanziproItem
   
   class ITSpiderSpider(scrapy.Spider):
       name = 'ITSpider'
       start_urls = ['https://duanzixing.com/page/2/']
   
       # 写入管道 持久化存储
       def parse(self, response):
           article_list = response.xpath('/html/body/section/div/div/article')
           for article in article_list:
               title = article.xpath('./header/h2/a/text()').extract_first()
               con = article.xpath('./p[2]/text()').extract_first()
               item = DuanziproItem()
               item['title'] = title
               item['con'] = con
               yield item

2.1、为什么要使用yield?
  让整个函数变成一个生成器,遍历这个函数的返回值的时候,挨个把数据读到内存,不会造成内存的瞬间占用过高
  注意:yield能够传递的对象只能是:BaseItem,Request,dict,None

3、打开管道文件myspider/myspider/pipelines.py 添加如下代码:、
   class ITSpiderPipeline:
       f = None
       def open_spider(self, spider):
           print('爬虫开始时被调用一次')
           self.f = open('./duanzi.text', 'w')
           
       # 爬虫文件中提取数据的方法每yield一次item,就会运行一次
       # 该方法为固定名称函数
       def process_item(self, item, spider):
           print(item)
           self.f.write(item['title']+item['con']+'\n')
           return item
   
       def close_spider(self, spider):
           print('爬虫结束时被调用')
           self.f.close()

3.1、open_spider方法
    重写父类中open_spider方法,只有爬虫开始时被调用一次

3.2、close_spider 方法
    重写父类中lose_spider方法  爬虫结束时被调用一次

4、在settings.py设置开启pipeline
  将默认被注释的管道打开
   ITEM_PIPELINES = {
      'myspider.pipelines.MyspiderPipeline': 300,
   }
  其中数值代表优先级,数值越小优先级越高

1.4.8、运行scrapy

命令:在项目目录下执行scrapy crawl +<爬虫名字>
示例:scrapy crawl ITSpider

二、Scrapy的进阶知识-存储

2.1、了解scrapy的debug信息

在这里插入图片描述

2.2、了解scrapyShell

scrapy shell是scrapy提供的一个终端工具,能够通过它查看scrapy中对象的属性和方法,以及测试xpath

使用方法:
scrapy shell http://www.baidu.com

在终端输入上述命令后,能够进入python的交互式终端,此时可以使用:
- response.xpath():直接测试xpath规则是否正确
- response.url:当前响应的url地址
- response.request.url:当前响应对应的请求的url地址
- response.headers:响应头
- response.body:响应体,也就是html代码,默认是byte类型
- response.request.headers:当前响应的请求头

2.3、settings.py中的设置信息

1、USER_AGENT 设置ua

2、ROBOTSTXT_OBEY 是否遵守robots协议,默认是遵守

3、CONCURRENT_REQUESTS 设置并发请求的数量,默认是164、DOWNLOAD_DELAY 下载延迟,默认无延迟 (下载器在从同一网站下载连续页面之前应等待的时间(以秒为单位)。这可以用来限制爬行速度,以避免对服务器造成太大影响)

5、COOKIES_ENABLED 是否开启cookie,即每次请求带上前一次的cookie,默认是开启的

6、DEFAULT_REQUEST_HEADERS 设置默认请求头,这里加入了USER_AGENT将不起作用

7、SPIDER_MIDDLEWARES 爬虫中间件,设置过程和管道相同

8、DOWNLOADER_MIDDLEWARES 下载中间件

9、LOG_LEVEL 控制终端输出信息的log级别,终端默认显示的是debug级别的log信息
  - LOG_LEVEL = "WARNING"
    - CRITICAL  严重
    - ERROR  错误
    - WARNING  警告
    - INFO  消息
    - DEBUG   调试

10、LOG_FILE 设置log日志文件的保存路径,如果设置该参数,终端将不再显示信息
  LOG_FILE = "./test.log"

2.4、pipeline管道的深入使用

2.4.1 创建工程(省略),创建爬虫,配置爬虫代码如下:

1、打开items.py文件,添加如下代码:

注意:属性名称和当前爬虫db.py中抓到要存储数据的变量一致,否则报错
class DoubanfileItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    img_src = scrapy.Field()
    name = scrapy.Field()
    to_star = scrapy.Field()

2、打开db.py文件,添加如下代码:
import scrapy
import re
from doubanfile.items import DoubanfileItem

class DbSpider(scrapy.Spider):
    name = 'db'
    allowed_domains = ['https://movie.douban.com/chart']
    start_urls = ['https://movie.douban.com/chart']

    def parse(self, resp, **kwargs):
        item = DoubanfileItem()  # 实例化item类
        # print(resp.text)
        # 先获取到每一行数据的tr
        tr_list = resp.xpath('//div[@class="indent"]/div/table/tr[@class="item"]')
        for tr in tr_list:
            # 获取封面
            item['img_src'] = tr.xpath('./td[1]/a/img/@src').extract_first()
            # 电影名称
            name = tr.xpath('./td[2]/div[@class="pl2"]/a//text()').extract_first()
            # 去除空白字符使用replace替换
            # name = name.replace('\n', '').replace('\r', '').replace('/', '').replace(' ', '')
            # 去除空白字符使用正则替换
            item['name'] = re.sub('(/)|(\s)', '', name)
            # 主演
            item['to_star'] = tr.xpath('./td[2]/div[@class="pl2"]/p[@class="pl"]/text()').extract_first()
            yield item

2.4.2、 开启管道存储数据

2.4.2.1、前期准备
pipeline中常用的方法:
1. process_item(self,item,spider):实现对item数据的处理
2. open_spider(self, spider): 在爬虫开启的时候仅执行一次
3. close_spider(self, spider): 在爬虫关闭的时候仅执行一次

settings.py 打开当前注释:
ITEM_PIPELINES = {
   'doubanfile.pipelines.DoubanfilePipeline': 300,
}
2.4.2.2、存储方式一——文件存储
打开pipelines.py文件,添加如下代码:

class DoubanfilePipeline:
    f = None
    def open_spider(self, item):
        self.f = open('./db.text', 'w')

    def process_item(self, item, spider):
        print(item)
        self.f.write(item['img_src']+'\n')
        self.f.write(item['name']+'\n')
        self.f.write(item['to_star']+'\n')
        return item

    def close_spider(self, item):
        self.f.close()

注意:当前process_item中的return item必须存在,如果当前爬虫存在于多个管道的时候,如果没有return item 则下一个管道不能获取到当前的item数据
2.4.2.3、存储方式二——存储到MySQL数据库中
1、创建数据库表
create database douban character set utf8;
use douban
CREATE TABLE `douban` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `img_src` varchar(200) NOT NULL COMMENT '封面地址',
  `name` varchar(50) NOT NULL COMMENT '电影名称',
  `to_star` varchar(250) NOT NULL COMMENT '主演',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

2、打开pipelines.py文件,添加如下代码:

from itemadapter import ItemAdapter
import pymysql

class DoubanmysqlPipeline:
    db = None
    cursor = None
    def open_spider(self, spider):
        # 判断当前运行的是否为db爬虫,不是db爬虫则下面代码不执行
        # 当前仅限于一个scrapy下有多个爬虫工程
        if spider.name == 'db':
            self.db = pymysql.connect(host='127.0.0.1', port=3306, db='douban', user='root', passwd='123456', charset='utf8')
            self.cursor = self.db.cursor()

    def process_item(self, item, spider):
        # 判断当前运行的是否为db爬虫
        if spider.name == 'db':
            try:
                sql = f'insert into douban(img_src, name, to_star) values("{item["img_src"]}", "{item["name"]}", "{item["to_star"]}")'
                self.cursor.execute(sql)
                self.db.commit()
            except Exception as e:
                print(e)
                print(sql)
                self.db.rollback()
                return item

    def close_spider(self, item):
        # 关闭数据库连接
        self.db.close()
2.4.2.4、存储方式三——存储到MongoDB数据库中
打开pipelines.py文件,添加如下代码:

from itemadapter import ItemAdapter
from pymongo import MongoClient

class DoubanmongodbPipeline:
    con = None
    collection = None
    def open_spider(self, spider):  # 在爬虫开启的时候仅执行一次
        if spider.name == 'db':
            self.con = MongoClient(host='127.0.0.1', port=27017) # 实例化mongoclient
            self.collection = self.con.spider.douban  # 创建数据库名为spider,集合名为douban的集合操作对象

    def process_item(self, item, spider):
        if spider.name == 'db':
            # print(spider.name)
            self.collection.insert_one(dict(item)) # 此时item对象需要先转换为字典,再插入
        # 不return的情况下,另一个权重较低的pipeline将不会获得item
        return item

    def close_spider(self, item):
        # 关闭数据库连接
        self.con.close()

注意:
需要开启mongo服务
mongod.exe  --dbpath=C:/User/xxx/db

新开终端
mongo.exe
2.4.2.5、存储方式四——数据同时存储到文件、MySQL、MongoDB中
1、打开pipelines.py文件,添加如下代码:

from itemadapter import ItemAdapter
import pymysql
from pymongo import MongoClient

class DoubanFilePipeline:
    '''
    设置文件存储
    '''
    f = None
    def open_spider(self, item):
        self.f = open('./db.text', 'w')

    def process_item(self, item, spider):
        print(item)
        self.f.write(item['img_src'] + '\n')
        self.f.write(item['name'] + '\n')
        self.f.write(item['to_star'] + '\n')
        return item

    def close_spider(self, item):
        self.f.close()

class DoubanmysqlPipeline:
    '''
    存储到MySQL数据库中
    '''
    db = None
    cursor = None
    def open_spider(self, spider):
        # 判断当前运行的是否为db爬虫,不是db爬虫则下面代码不执行
        # 当前仅限于一个scrapy下有多个爬虫工程
        if spider.name == 'db':
            self.db = pymysql.connect(host='127.0.0.1', port=3306, db='douban', user='root', passwd='123456', charset='utf8')
            self.cursor = self.db.cursor()

    def process_item(self, item, spider):
        # 判断当前运行的是否为db爬虫
        if spider.name == 'db':
            try:
                sql = f'insert into douban(img_src, name, to_star) values("{item["img_src"]}", "{item["name"]}", "{item["to_star"]}")'
                self.cursor.execute(sql)
                self.db.commit()
            except Exception as e:
                print(e)
                print(sql)
                self.db.rollback()
        return item
    
    def close_spider(self, item):
        # 关闭数据库连接
        self.db.close()

class DoubanmongodbPipeline:
    '''
    存储到MongoDB数据库中
    '''
    con = None
    collection = None
    def open_spider(self, spider):  # 在爬虫开启的时候仅执行一次
        if spider.name == 'db':
            self.con = MongoClient(host='127.0.0.1', port=27017) # 实例化mongoclient
            self.collection = self.con.spider.douban  # 创建数据库名为spider,集合名为douban的集合操作对象

    def process_item(self, item, spider):
        if spider.name == 'db':
            # print(spider.name)
            self.collection.insert_one(dict(item)) # 此时item对象需要先转换为字典,再插入
        # 不return的情况下,另一个权重较低的pipeline将不会获得item
        return item

    def close_spider(self, item):
        # 关闭数据库连接
        self.con.close()

2、修改settings.py 添加管道
ITEM_PIPELINES = {
   'douban.pipelines.DoubanFilePipeline': 300,  # 300表示权重
   'douban.pipelines.DoubanmysqlPipeline': 400,
   'douban.pipelines.DoubanmongodbPipeline': 500,
}

注意:在此设置中分配给类的整数值决定了它们运行的顺序:项目从低值到高值的类。通常将这些数字定义在 0-1000 范围内。

思考:pipeline在settings中能够开启多个,为什么需要开启多个?
1. 不同的pipeline可以处理不同爬虫的数据,通过spider.name属性来区分
2. 不同的pipeline能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存
3. 同一个管道类也可以处理不同爬虫的数据,通过spider.name属性来区分

2.4.3 pipeline使用注意点

1. 使用之前需要在settings中开启
2. pipeline在setting中键表示位置(即pipeline在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过
3. 有多个pipeline的时候,process_item的方法必须return item,否则后一个pipeline取到的数据为None4. pipeline中process_item的方法必须有,否则item没有办法接受和处理
5. process_item方法接受item和spider,其中spider表示当前传递item过来的spider
6. open_spider(spider) :能够在爬虫开启的时候执行一次
7. close_spider(spider) :能够在爬虫关闭的时候执行一次
8. 上述俩个方法经常用于爬虫和数据库的交互,在爬虫开启的时候建立和数据库的连接,在爬虫关闭的时候断开和数据库的连接

三、Scrapy的存储实战案例

3.1、需求

抓取网站:https://movie.douban.com/chart 中子页面的数据
实现思路:
    + 先对第一层url进行请求
    + 请求返回数据进行解析循环  找到每一条子页面的url
    + 找到子页面的url以后进行再次请求
    + 请求解析子页面请求返回的数据
    + 结束

3.2、创建工程

+ scrapy startproject doubandetail
+ cd doubandetail
+ scrapy genspider db movie.douban.com/chart

3.3、配置settings.py

# 设置日志级别
LOG_LEVEL = 'ERROR'
ROBOTSTXT_OBEY = False
COOKIES_ENABLED = False  # cookies的中间件将不起作用,下面的cookie起作用

# 设置请求头
DEFAULT_REQUEST_HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Language': 'en',
  'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36',
  'Cookie': '设定cookie 防止反扒'
}

# 开启管道
ITEM_PIPELINES = {
   'doubandetail.pipelines.DoubandetailPipeline': 300,
}

3.4、爬虫实现

+ 打开db.py,添加如下代码:

  import scrapy
  from doubandetail.items import DoubandetailItem
  import re

  class DbSpider(scrapy.Spider):
      name = 'db'
      start_urls = ['http://movie.douban.com/chart/']

      def parse(self, resp, **kwargs):
          print(resp.text)
          # 先获取到每一行数据的tr
          tr_list = resp.xpath('//div[@class="indent"]/div/table/tr[@class="item"]')
          for tr in tr_list:
              # 获取每个详情页的url
              detail_url = tr.xpath('./td[1]/a/@href').extract_first()
              # 请求子页面
              print(detail_url)
              yield scrapy.Request(detail_url, callback=self.parse_deatil)

      # 解析子页面数据
      def parse_deatil(self, response):
          # 默认携带我们settings.py中所配置的请求头进行请求
          # print(response.request.headers)
          item = DoubandetailItem()
          item['name'] = response.xpath('//*[@id="content"]/h1/span[1]/text()').extract_first()  # 电影名称
          item['director'] = response.xpath('//*[@id="info"]/span[1]/span[2]/a/text()').extract_first()  # 导演
          item['screenwriter'] = ''.join(response.xpath('//*[@id="info"]/span[2]/span[2]//text()').extract())  # 编剧
          item['to_star'] = ''.join(response.xpath('//*[@id="info"]/span[3]/span[2]//text()').extract())  # 主演
          item['type'] = '/'.join(response.xpath('//span[@property="v:genre"]//text()').extract())  # 类型
          item['link_report'] = re.sub('(/)|(\s)|(\u3000)|(\'\n\')', '', link_report)
          print(item)
          return item

+ 注意:
  需要将allowed_domains注释掉,否则详情页url不符合当前允许,所以会出现不请求的问题

3.5、配置数据传输格式items.py

import scrapy

class DoubandetailItem(scrapy.Item):
    name = scrapy.Field()  # 电影名称
    director = scrapy.Field()  # 导演
    screenwriter = scrapy.Field()  # 编剧
    to_star = scrapy.Field()  # 主演
    type = scrapy.Field()  # 类型

3.6、管道代码

from itemadapter import ItemAdapter
from pymongo import MongoClient

class DoubandetailPipeline:
    con = None
    collection = None
    def open_spider(self, spider):  # 在爬虫开启的时候仅执行一次
        self.con = MongoClient(host='127.0.0.1', port=27017)  # 实例化mongoclient
        self.collection = self.con.spider.douban  # 创建数据库名为spider,集合名为douban的集合操作对象

    def process_item(self, item, spider):
        print(item)
        self.collection.insert_one(dict(item))  # 此时item对象需要先转换为字典,再插入
        return item

    def close_spider(self, item):
        # 关闭数据库连接
        self.con.close()

注意:
如果访问频率过高被禁止访问,可以携带登录后的cookie进行访问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小陈工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值