【进阶】【Python网络爬虫】【15.爬虫框架】scrapy入门(附大量案例代码)(建议收藏)

2024-01-02 13:03:19

一、爬虫框架

1. 什么是框架?

所谓的框架,其实说白了就是一个【项目的半成品】,该项目的半成品需要被集成了各种功能且具有较强的通用性。

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。所谓的框架就是一个已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)的具有很强通用性的项目模板。对于框架的学习,重点是要学习其框架的特性、各个功能的用法即可。

2. 初期如何学习框架?

只需要学习框架集成好的各种功能的用法即可!前期切勿钻研框架的源码!

二、scrapy 入门

1. 网络爬虫

网络爬虫是指在互联网上自动爬取网站内容信息的程序,也被称作网络蜘蛛或网络机器人。大型的爬虫程序被广泛应用于搜索引擎、数据挖掘等领域,个人用户或企业也可以利用爬虫收集对自身有价值的数据。

一个网络爬虫程序的基本执行流程可以总结三个过程:请求数据解析数据保存数据

请求数据

请求的数据除了普通的HTML之外,还有 json 数据、字符串数据、图片、视频、音频等。

解析数据

当一个数据下载完成后,对数据中的内容进行分析,并提取出需要的数据,提取到的数据可以以多种形式保存起来,数据的格式有非常多种,常见的有csv、json、pickle等

保存数据

最后将数据以某种格式(CSV、JSON)写入文件中,或存储到数据库(MySQL、MongoDB)中。同时保存为一种或者多种。

  • 通常,我们想要获取的数据并不只在一个页面中,而是分布在多个页面中,这些页面彼此联系,一个页面中可能包含一个或多个到其他页面的链接,提取完当前页面中的数据后,还要把页面中的某些链接也提取出来,然后对链接页面进行爬取(循环1-3步骤)。

  • 设计爬虫程序时,还要考虑防止重复爬取相同页面(URL去重)、网页搜索策略(深度优先或广度优先等)、爬虫访问边界限定等一系列问题。

  • 从头开发一个爬虫程序是一项烦琐的工作,为了避免因制造轮子而消耗大量时间,在实际应用中我们可以选择使用一些优秀的爬虫框架,使用框架可以降低开发成本,提高程序质量,让我们能够专注于业务逻辑(爬取有价值的数据)。接下来,就带你学习目前非常流行的开源爬虫框架Scrapy

2. scrapy安装

scrapy官网: https://scrapy.org/
scrapy中文文档:https://www.osgeo.cn/scrapy/intro/overview.html

安装方式

在任意操作系统下,可以使用pip安装Scrapy,例如:

Linux/mac系统:

  • pip install scrapy(任意目录下)

Windows系统:可以直接pip install scrapy安装,如果安装出错可以采用如下方式:

  • 1.pip install wheel (任意目录下)
  • 2.下载 twisted 文件,下载网址如下: http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
  • 3.终端进入下载目录,执行 pip install Twisted-17.1.0-cp35-cp35m-win_amd64.whl
    注意:如果该步骤安装出错,则换一个版本的 whl 文件即可
  • 4.pip install pywin32 (任意目录下)

如果安装好后,在终端中录入 scrapy 指令按下回车,如果没有提示找到该指令,则表示安装成功

安装完成后我们需要测试安装是否成功,通过如下步骤确认:

  • 在 终端 中测试能否执行 scrapy 这条命令:
Scrapy 2.4.0 - no active project

Usage:
  scrapy <command> [options] [args]

Available commands:
  bench				Run quick benchmark test
  fetch				Fetch a URL using the Scrapy downloader
  genspider			 Generate new spider using pre-defined templates
  runspider			 Run a self-contained spider (without creating a project)
  settings			 Get settings values
  shell				Interactive scraping console
  startproject		  Create new project
  version			 Print Scrapy version
  view				Open URL in browser, as seen by Scrapy

  [ more ]      More commands available when run from project directory

Use "scrapy <command> -h" to see more info about a command
  • 输入 scrapy bench 测试连通性,如果出现以下情况表示安装成功:

在这里插入图片描述
通过了以上两项检测,说明Scrapy安装成功了。如上所示,我们安装的是当前最新版本2.4.0。

注意:
成功安装后,在CMD下运行scrapy出现上图不算真正成功,检测真正是否成功使用scrapy bench测试,如果没有提示错误,就代表成功安装。

具体Scrapy安装流程参考:http://doc.scrapy.org/en/latest/intro/install.html##intro-install-platform-notes 里面有各个平台的安装方法

全局命令

Scrapy 2.4.0 - no active project

Usage:
  scrapy <command> [options] [args]

Available commands:
  bench				Run quick benchmark test
  				   # 测试电脑性能
  fetch				Fetch a URL using the Scrapy downloader
  				   # 将源代码下载下来并显示出来
  genspider			 Generate new spider using pre-defined templates
  				   # 创建一个新的 spider 文件
  runspider			 Run a self-contained spider (without creating a project)
  				   # 这个和通过crawl启动爬虫不同,scrapy runspider 爬虫文件名称
  settings			 Get settings values
  				   # 获取当前的配置信息
  shell				Interactive scraping console
  				   # 进入 scrapy 的交互模式
  startproject		  Create new project
  					# 创建爬虫项目
  version			 Print Scrapy version
  					# 显示scrapy框架的版本
  view				Open URL in browser, as seen by Scrapy
  					# 将网页document内容下载下来,并且在浏览器显示出来

  [ more ]      More commands available when run from project directory

Use "scrapy <command> -h" to see more info about a command

项目命令

  • scrapy startproject projectname项目名称
## 创建一个项目
firstBlood   # 项目所在文件夹, 建议用pycharm打开该文件夹
    ├── firstBlood  		# 项目跟目录
    │   ├── __init__.py
    │   ├── items.py  		# 封装数据的格式
    │   ├── middlewares.py  # 所有中间件
    │   ├── pipelines.py	# 所有的管道
    │   ├── settings.py		# 爬虫配置信息
    │   └── spiders			# 爬虫文件夹, 稍后里面会写入爬虫代码
    │       └── __init__.py
    └── scrapy.cfg			# scrapy项目配置信息,不要删它,别动它,善待它. 
  • cd project_name(进入项目目录)

  • scrapy genspider 爬虫文件的名称 (自定义一个名字即可) 起始url (随便写一个网址即可)

    创建好爬虫项目以后,还需要创建爬虫。

  • scrapy crawl spidername

    运行爬虫。注意该命令运行时所在的目录

案例 - scrapy 下厨房网爬取
settings.py
BOT_NAME = "First"

SPIDER_MODULES = ["First.spiders"]
NEWSPIDER_MODULE = "First.spiders"
# 指定输出的日志类型
LOG_LEVEL = 'ERROR'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36"

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
spiders
blood.py
import scrapy

class BloodSpider(scrapy.Spider):
    # 爬虫文件的唯一标识
    name = "blood"
    # 允许的域名
    allowed_domains = ["www.baidu.com"]
    # 起始的 url 列表(重要):列表内部的 url 都会被框架进行异步的请求发送
    start_urls = ["https://www.xiachufang.com/category/40076/"]

    # 数据解析 : parse 调用的次数取决于 start_urls 列表元素的个数
    def parse(self, response):  # response 参数就表示响应对象
        # 如何实现数据解析 : Xpath
        li_list = response.xpath('/html/body/div[4]/div/div/div[1]/div[1]/div/div[2]/div[2]/ul/li')

        for li in li_list:
            # xpath 最终会返回的是 Selector 对象,想要的解析的数据是存储在该对象的 data 属性中(extract可以实现该功能)
            # title = li.xpath('./div/div/p[1]/a/text()')[0].extract()  # 一般不用

            # extract_first 可以将 xpath 返回类别中的第一个 Selector 对象中的 data 属性值获取
            # title = li.xpath('./div/div/p[1]/a/text()').extract_first()

            # extract 可以将 xpath 返回列表中的每一个 Selector 对象中的 data 属性值获取
            title = li.xpath('./div/div/p[1]/a/text()').extract()
            # 如果 xpath 返回的列表元素只有一个则使用 extract_first ,否则使用 extract
            print(title)
案例 - scrapy爬取哔哩哔哩网
settings.py
BOT_NAME = "biliPro"

SPIDER_MODULES = ["biliPro.spiders"]
NEWSPIDER_MODULE = "biliPro.spiders"

# 指定输出的日志类型
LOG_LEVEL = 'ERROR'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36"

# Obey robots.txt rules
ROBOTSTXT_OBEY = False


REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
items.py
import scrapy

class BiliproItem(scrapy.Item):
    title = scrapy.Field()
    author = scrapy.Field()
pipelines.py
# 存储 txt 文件
class BiliproPipeline:# 一个管道类只负责将数据存储到一个载体中
    fp = None

    # 全程只会被调用一次
    def open_spider(self, spider):
        print('i am open_spider()')
        self.fp = open('bili.txt', 'w')

    # process_item 函数就是用来接受爬虫文件提交过来的item对象,且可以将item对象中的数据存储到任何载体中
    def process_item(self, item, spider):  # 参数 item 就是管道接收到item对象
        title = item['title']
        author = item['author']
        # 数据存储到文件里
        self.fp.write(author + ':' + title + '\n')
        return item

    # process_item 函数调用的次数取决于爬虫文件给管道提交的 item 的次数

    def close_spider(self, spider):
        print('i am close_spider()')
        # 该函数只会在爬虫结束前被调用一次
        self.fp.close()
iders
libi.py - 基于终端指令的持久化存储(简单)不推荐
import scrapy

class LibiSpider(scrapy.Spider):
    name = "libi"
    allowed_domains = ["www.xxx.com"]
    start_urls = [
        "https://search.bilibili.com/all?keyword=%E5%AE%8F%E8%A7%82%E7%BB%8F%E6%B5%8E&from_source=webtop_search&spm_id_from=333.1007&search_source=5"]

    # 基于终端指令的持久化存储(简单):只可以将 parse 方法的返回值存储写入到指定后缀的文本文件中
    def parse(self, response):
        div_list = response.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[3]/div/div')
        all_data = []
        for div in div_list:
            title = div.xpath('./div/div[2]/div/div/a/h3/text()').extract()
            title = ''.join(title)
            author = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()

            dic = {
                'title': title,
                'author': author,
            }

            all_data.append(dic)
        return all_data  # all_data里面就存储了爬取到的数据
    
 # 指令 scrapy crawl bili -o bili.csv
libi,py - 基于管道的持久化存储方式(通用)
import scrapy

from ..items import BiliproItem

class LibiSpider(scrapy.Spider):
    name = "libi"
    allowed_domains = ["www.xxx.com"]
    start_urls = [
        "https://search.bilibili.com/all?keyword=%E5%AE%8F%E8%A7%82%E7%BB%8F%E6%B5%8E&from_source=webtop_search&spm_id_from=333.1007&search_source=5"]

    # 基于管道的持久化存储方式(通用)
    def parse(self, response):
        div_list = response.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[3]/div/div')
        all_data = []
        for div in div_list:
            title = div.xpath('./div/div[2]/div/div/a/h3/text()').extract()
            title = ''.join(title)
            author = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()

            # 创建一个 item 类型的对象
            item = BiliproItem(title=title, author=author)
            yield item

    # 编码流程:1.解析数据 2.创建一个 item 类的对象(存储解析出来的数据)3.将解析出来的数据存储到该 item 类型的对象中 4.将item对象提交给管道

    # 爬虫文件:libi.py  进行请求发送和数据解析
    # item文件: items.py  定义n个变量
    # 管道文件: pipelines.py  接收item对象进行数据持久化存储
案例 - scrapy数据保存到数据库
  • 如何将数据存储到数据库
    • 注意:一个管道类负责将数据存储到一个具体的载体中。如果想要将爬取到的数据存储到多个不同的载体/数据库中,则需要定义多个管道类。
  • 思考:
    • 在有多个管道类的前提下,爬虫文件提交的item会同时给每一个管道类还是单独的管道类?
      • 爬虫文件只会将item提交给优先级最高的那一个管道类。优先级最高的管道类的process_item中需要写return item操作,该操作就是表示将item对象传递给下一个管道类,下一个管道类获取了item对象,才可以将数据存储成功!
settings.py
BOT_NAME = "biliPro"
SPIDER_MODULES = ["biliPro.spiders"]
NEWSPIDER_MODULE = "biliPro.spiders"

# 指定输出的日志类型
LOG_LEVEL = 'ERROR'

USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36"

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

ITEM_PIPELINES = {
   # value 值表示的数字代表了管道类的优先级,数字越小表示优先级越高
   "biliPro.pipelines.BiliproPipeline": 300,
   "biliPro.pipelines.MysqlPipeline": 301,
   "biliPro.pipelines.RedisPipeLine": 302,
   "biliPro.pipelines.MongoPipeline": 303,
}

REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
items.py
import scrapy

class BiliproItem(scrapy.Item):
    title = scrapy.Field()
    author = scrapy.Field()
pipelines.py
import pymysql  # pip install pymysql

''' 第一个管道类:存储 txt 文件 '''
class BiliproPipeline:  # 一个管道类只负责将数据存储到一个载体中
    fp = None

    # 全程只会被调用一次
    def open_spider(self, spider):
        print('i am open_spider()')
        self.fp = open('bili.txt', 'w')

    # process_item 函数就是用来接受爬虫文件提交过来的item对象,且可以将item对象中的数据存储到任何载体中
    def process_item(self, item, spider):  # 参数 item 就是管道接收到item对象
        title = item['title']
        author = item['author']
        # 数据存储到文件里
        self.fp.write(author + ':' + title + '\n')
        return item

    # process_item 函数调用的次数取决于爬虫文件给管道提交的 item 的次数

    def close_spider(self, spider):
        print('i am close_spider()')
        # 该函数只会在爬虫结束前被调用一次
        self.fp.close()


''' 第二个管道类:存储到 mysql 数据库 '''
class MysqlPipeline:
    conn = None  # 链接对象
    cursor = None  # 游标对象

    def open_spider(self, spider):
        # 链接数据库的操作只需要被执行一次
        self.conn = pymysql.Connect(
            host='127.0.0.1',  # mysql 数据库服务器的ip地址
            port=3306,  # 端口号
            user='root',  # 用户名
            password='root',  # 密码
            db='spider',  # 数据仓库名称
        )
        # 创建一个游标对象(用来使用python程序执行sql语句)
        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):  # 参数 item 就是管道接收到item对象,由上级优先级高的管道类传递过来
        title = item['title']
        author = item['author']
        # 使用游标对象 cursor 执行 sql语句
        sql = 'insert into bili values ("%s","%s")' % (title, author)
        self.cursor.execute(sql)
        # 提交事物
        self.conn.commit()
        return item

    def close_spider(self, spider):
        self.conn.close()
        self.cursor.close()
        
        
''' 第三个管道类:存储 Redis 数据库 '''
class RedisPipeLine:
    conn = None

    def open_spider(self, spider):
        # 创建 redis 的链接对象
        self.conn = Redis(
            host='127.0.0.1',
            port=3308
        )

    def process_item(self, item, spider):  # 参数 item 就是管道接收到item对象,由上级优先级高的管道类传递过来
        # item 本身就是一个字典
        self.conn.lpush('libi', item)
        return item

    def close_spider(self, spider):
        pass


''' 第四个管道类:存储 MongoDB 数据库 '''
import pymongo
class MongoPipeline:
    conn = None  # 链接对象
    db_sanqi = None  # 数据仓库

    def open_spider(self, spider):
        self.conn = pymongo.MongoClient(
            host='127.0.0.1',
            port=27017
        )
        self.db_sanqi = self.conn['sanqi']

    def process_item(self, item, spider):
        self.db_sanqi['xiaoshuo'].insert_one({'title': item['title']})
        print('插入成功!')
        return item
spiders
libi.py
import scrapy

from ..items import BiliproItem


class LibiSpider(scrapy.Spider):
    name = "libi"
    allowed_domains = ["www.xxx.com"]
    start_urls = [
        "https://search.bilibili.com/all?keyword=%E5%AE%8F%E8%A7%82%E7%BB%8F%E6%B5%8E&from_source=webtop_search&spm_id_from=333.1007&search_source=5"]

    # 基于管道的持久化存储方式(通用)
    def parse(self, response):
        div_list = response.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[3]/div/div')
        all_data = []
        for div in div_list:
            title = div.xpath('./div/div[2]/div/div/a/h3/text()').extract()
            title = ''.join(title)
            author = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()

            # 创建一个 item 类型的对象
            item = BiliproItem(title=title, author=author)
            yield item  # item会提交给那个管道类?一定是提交给优先级最高的管道类!!

    # 编码流程:1.解析数据 2.创建一个 item 类的对象(存储解析出来的数据)3.将解析出来的数据存储到该 item 类型的对象中 4.将item对象提交给管道

    # 爬虫文件:libi.py  进行请求发送和数据解析
    # item文件: items.py  定义n个变量
    # 管道文件: pipelines.py  接收item对象进行数据持久化存储
  1. 在爬虫文件中进行数据爬取和数据解析
  2. 在 items.py 文件中进行相关变量的定义(变量的个数取决于爬虫文件中解析字段的个数)
  3. 在爬虫文件中将解析到的数据存储到item类型的对象中
  4. 将 item 类型的对象提交给管道
  5. 管道的 process_item 函数中接收item对象,且将 item 对象的数据存储到指定的平台或者载体中
  6. 在配置文件中开启管道的机制
案例 - scrapy爬取二进制数据

使用一个专有的管道类ImagesPipeline

  • http://pic.netbian.com/4kmeinv/

首先安装插件 : pip install PIL / pip install Pillow

  • 具体的编码流程:
    • 1.在爬虫文件中进行图片/视频的链接提取
    • 2.将提取到的链接封装到items对象中,提交给管道
    • 3.在管道文件中自定义一个父类为ImagesPipeline的管道类,且重写三个方法即可:
def get_media_requests(self, item, info):接收爬虫文件提交过来的item对象,然后对图片地址发起网路请求,返回图片的二进制数据

def file_path(self, request, response=None, info=None, *, item=None):指定保存图片的名称
def item_completed(self, results, item, info):返回item对象给下一个管道类
settings.py
BOT_NAME = "imgPro"

SPIDER_MODULES = ["imgPro.spiders"]
NEWSPIDER_MODULE = "imgPro.spiders"

# 指定输出的日志类型
LOG_LEVEL = 'ERROR'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False
IMAGES_STORE = 'girlsLib'

ITEM_PIPELINES = {
    "imgPro.pipelines.BytesPipeLine": 300,
}

REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
items.py
import scrapy

class ImgproItem(scrapy.Item):
    img_src = scrapy.Field()
pipelines.py
import scrapy
from scrapy.pipelines.images import ImagesPipeline

# 普通的管道类:将文本数据持久化
class ImgproPipeline:
    def process_item(self, item, spider):
        return item

# 特殊的管道类:将二进制数据持久化
# 自定义了一个管道类,该类的父类为 ImagesPipeline
class BytesPipeLine(ImagesPipeline):
    # 重写三个父类的方法来完成图片二进制数据的请求和持久化存储
    # 可以根据图片地址,对其进行请求,获取图片数据
    # 接收爬虫文件提交过来的item对象,并且可以对相关的多媒体资源进行网络请求
    def get_media_requests(self, item, info):
        # 提取图片地址或者视频地址
        img_src = item['img_src']
        # 可以对 img_src 进行网络请求获取图片数据
        yield scrapy.Request(img_src)

    def file_path(self, request, response=None, info=None, *, item=None):  # 指定保存图片的名称
        # 用来将请求到的多媒体数据进行指定路径的存储
        # 返回存储文件的名字
        img_src = request.url  # 图片地址
        img_title = img_src.split('/')[-1]
        print(img_title, '下载保存成功!')
        return img_title

    # 如果没有下一个管道类,该方法可以不写
    def item_completed(self, results, item, info):  # 返回item对象给下一个管道类
        return item  # 可以将当前的管道类接收到item对象传递给下一个管道类2.
spiders
img.py
import scrapy
from ..items import ImgproItem

class ImgSpider(scrapy.Spider):
    name = "img"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["http://pic.netbian.com/4kmeinv/"]

    def parse(self, response):
        # 解析图片地址
        li_list = response.xpath('//*[@id="main"]/div[3]/ul/li')
        for li in li_list:
            img_src = 'http://pic.netbian.com' + li.xpath('./a/img/@src').extract_first()
            # 图片地址封装到item对象中,且将item提交给管道即可
            item = ImgproItem(img_src=img_src)
            print(item)
            yield item
            # 特殊的管道类:主要是对二进制的数据进行持久化存储

文章来源:https://blog.csdn.net/weixin_43612602/article/details/135333830
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。