Python爬虫基础之 Scrapy

五、Scrapy

1. 初识Scrapy

1.1 Scrapy是什么

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。

1.2 安装Scrapy

(1)在pycharm中安装

(2)使用anaconda

1.3 Scrapy的项目

(1)在终端创建Scrapy项目

eg:scrapy startproject 项目名称

(2)项目的组成:

​ spiders

​ —init—.py

​ 自定义的爬虫文件.py --> 由我们自己创建,是实现爬虫核心功能的文件

​ —init—.py

​ items.py --> 定义数据结构的地方,是一个继承自scrapy.Item的类

​ middlewares.py --> 中间件 代理

​ pipelines.py --> 管道文件,里面只有一个类,用于处理下载数据的后续处理,

​ 默认是300优先级,值越小优先级越高(1-1000)

​ settings.py --> 配置文件,比如:是否遵守robots协议,User-Agent定义等

(3)创建爬虫文件:

​ (1)跳转到spiders文件夹 cd 目录名字/目录名字/spiders

​ (2)scrapy genspider 爬虫名字 网页的域名

​ (3)爬虫文件的基本组成:

​ 继承scrapy.Spider类

​ name = ‘baidu’ --> 运行爬虫文件时使用的名字

​ allowed_domains --> 爬虫允许的域名,在爬取的时候,如果不是此域名之下的url

​ 会被过滤掉

​ start_urls --> 声明了爬虫的起始地址,可以写多个url,一般是一个

parse(self, response) --> 解析数据的回调函数

​ response.text --> 响应的是字符串

​ response.body --> 响应的是二进制文件

​ response.xpath() --> xpath方法的返回值类型是selector列表

​ extract() --> 提取的是selector对象的是data

​ extract_first --> 提取的是selector列表中的第一个数据

​ (4)运行爬虫文件:

​ scrapy crawl 爬虫名称

​ 注意:应在spiders文件夹内执行

2. Scrapy 的应用

2.1 Scrapy Shell

(1)什么是scrapy shell

Scrapy终端,是一个交互终端,供您在未启动spider的情况下尝试及调试您的爬取代码。 其本意是用来测试提取数据的代码,不过您可以将其作为正常的Python终端,在上面测试任何的Python代码。

该终端是用来测试XPath或CSS表达式,查看他们的工作方式及从爬取的网页中提取的数据。 在编写您的spider时,该 终端提供了交互性测试您的表达式代码的功能,免去了每次修改后运行spider的麻烦。

一旦熟悉了Scrapy终端后,您会发现其在开发和调试spider时发挥的巨大作用。

语法:
	(1)response对象: 
                    response.body 
                    response.text 
                    response.url 
                    response.status 
	(2)response的解析: 
			response.xpath() (常用) 
				使用xpath路径查询特定元素,返回一个selector列表对象 
			response.css()
				使用css_selector查询元素,返回一个selector列表对象 
				获取内容 :response.css('#su::text').extract_first() 
				获取属性 :response.css('#su::attr(“value”)').extract_first() 
	(3)selector对象(通过xpath方法调用返回的是seletor列表) 
			extract() 
				提取selector对象的值 
				如果提取不到值 那么会报错 
				使用xpath请求到的对象是一个selector对象,需要进一步使用extract()方法拆 包,转换为unicode字符串
			extract_first() 
                提取seletor列表中的第一个值 
                如果提取不到值 会返回一个空值 
                返回第一个解析到的值,如果列表为空,此种方法也不会报错,会返回一个空值 
			xpath() 
			css() 
				注意:每一个selector对象可以再次的去使用xpath或者css方法

2.2 yield

(1)带有yield的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代

(2)yield是一个类似return的关键字,迭代一次遇到yield时就返回yield后面(右边)的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行。

(3)简要理解:yield就是return返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始案例:

1.当当网 (1)yield(2).管道封装(3).多条管道下载 (4)多页数据下载

2.电影天堂 (1)一个item包含多级页面的数据

2.3 CrawlSpider

1.继承自scrapy.Spider

2.独门秘笈

CrawlSpider可以定义规则,再解析html内容的时候,可以根据链接规则提取出指定的链接,然后再向这些链接发 送请求所以,如果有需要跟进链接的需求,意思就是爬取了网页之后,需要提取链接再次爬取,使用CrawlSpider是非常合适的 。

3.提取链接

链接提取器,在这里就可以写规则提取指定链接 

scrapy.linkextractors.LinkExtractor( 

allow = (), # 正则表达式 提取符合正则的链接 

deny = (), # (不用)正则表达式 不提取符合正则的链接 

allow_domains = (), # (不用)允许的域名 

deny_domains = (), # (不用)不允许的域名 

restrict_xpaths = (), # xpath,提取符合xpath规则的链接 

restrict_css = () # 提取符合选择器规则的链接) 

4.模拟使用

正则用法:links1 = LinkExtractor(allow=r'list_23_\d+.html') 

xpath用法:links2 = LinkExtractor(restrict_xpaths=r'//div[@class="x"]') 

css用法:links3 = LinkExtractor(restrict_css='.x') 

5.提取连接

link.extract_links(response) 

6.注意事项

【注1】callback只能写函数名字符串, callback='parse_item' 

【注2】在基本的spider中,如果重新发送请求,那里的callback写的是 callback=self.parse_item 【注‐ 

‐稍后看】follow=true 是否跟进 就是按照提取连接规则进行提取

7.CrawlSpider案例

​ 需求:读书网数据入库

1.创建项目:scrapy startproject dushuproject 
2.跳转到spiders路径 cd\dushuproject\dushuproject\spiders 
3.创建爬虫类:scrapy genspider ‐t crawl read www.dushu.com 
4.items 
5.spiders 
6.settings 
7.pipelines数据保存到本地 数据保存到mysql数据库

2.4 数据入库

1)settings配置参数: 
	DB_HOST = '192.168.231.128' 
	DB_PORT = 3306 DB_USER = 'root' 
	DB_PASSWORD = '1234' 
	DB_NAME = 'test' 
	DB_CHARSET = 'utf8'2)管道配置 
    from scrapy.utils.project 
    import get_project_settings 
    import pymysql 
    class MysqlPipeline(object): 
    #__init__方法和open_spider的作用是一样的
    #init是获取settings中的连接参数 
    def __init__(self): 
    	settings = get_project_settings() 
    	self.host = settings['DB_HOST']
        self.port = settings['DB_PORT'] 
        self.user = settings['DB_USER'] 
        self.pwd = settings['DB_PWD'] 
        self.name = settings['DB_NAME'] 
        self.charset = settings['DB_CHARSET'] 
       
        self.connect()
        # 连接数据库并且获取cursor对象 
        def connect(self): 
        	self.conn = pymysql.connect(host=self.host, 
        								port=self.port, 
        								user=self.user, 
        								password=self.pwd, 
        								db=self.name, 
        								charset=self.charset) 
        	self.cursor = self.conn.cursor()
        	
        	def process_item(self, item, spider): 
        		sql = 'insert into book(image_url, book_name, author, info) values("%s", "%s", "%s", "%s")' % (item['image_url'], item['book_name'], item['author'], item['info']) 
        		
        		sql = 'insert into book(image_url,book_name,author,info) values ("{}","{}","{}","{}")'.format(item['image_url'], item['book_name'], item['author'], item['info']) 
        		# 执行sql语句 
        		self.cursor.execute(sql) 
        		self.conn.commit() 
        		return item

2.5 日志信息和日志等级

(1)日志级别:

​ CRITICAL:严重错误

​ ERROR: 一般错误

​ WARNING: 警告

​ INFO: 一般信息

​ DEBUG: 调试信息

​ 默认的日志等级是DEBUG

​ 只要出现了DEBUG或者DEBUG以上等级的日志

​ 那么这些日志将会打印

(2)settings.py文件设置:

​ 默认的级别为DEBUG,会显示上面所有的信息

​ 在配置文件中 settings.py

​ LOG_FILE : 将屏幕显示的信息全部记录到文件中,屏幕不再显示,注意文件后缀一定是.log

​ LOG_LEVEL : 设置日志显示的等级,就是显示哪些,不显示哪些

2.6 Scrapy的post请求

1)重写start_requests方法: 
		def start_requests(self) 
 (2) start_requests的返回值: 
 		scrapy.FormRequest(url=url, headers=headers, callback=self.parse_item, formdata=data) 
 		url: 要发送的post地址 
 		headers:可以定制头信息 
 		callback: 回调函数 
 		formdata: post所携带的数据,这是一个字典

2.7 代理

1)到settings.py中,打开一个选项 
		DOWNLOADER_MIDDLEWARES = { 'postproject.middlewares.Proxy': 543, }2)到middlewares.py中写代码 
		def process_request(self, request, spider): 
		request.meta['proxy'] = 'https://113.68.202.10:9999' 
		return None

3. Scrapy的使用案例

3.1 Scrapy的基本框架

Scrapy创建的项目和爬虫文件以及运行爬虫代码都是在控制台(可以使用pycharm中的控制台)进行,当然,编写具体的功能需要在爬虫文件中。

 1. scrapy 项目的结构
     项目名字
         项目文件
             spiders文件夹 (存储的是爬虫文件)
                 init
                 自定义的爬虫文件    核心功能文件****
             init
             items       定义数据结构的地方 爬取的数据都包含哪些
             middleware  中间件 代理
             pipelines   管道  用来处理下载的数据
             settings    匹配文件 robots协议 ua定义等

创建一个scrapy项目的步骤

(1)创建爬虫的项目

scrapy startproject 项目的名字

​ 注意:项目的名字不允许使用数字开头,也不能包含中文;

​ 每个爬虫程序都需要单独创建一个项目文件;

(2)创建爬虫文件

​ 要在 spiders文件夹中去创建爬虫文件

​ 进入spiders文件夹

cd 项目的名字\项目的名字\spiders

​ eg: cd scrapy_baidu\scrapy_baidu\spiders

​ 创建爬虫文件

scrapy genspider 爬虫文件的名字 要爬取的网页

​ eg:scrapy genspider baidu http://www.baidu.com

注意:一般情况下不需要添加http协议,因为start urls的值是根据allowed domains修改的,所以添加了http的话,那么start urls就多了个http,还需要我们去手动删除。

(3)运行爬虫代码

scrapy crawl 爬虫的名字

eg:scrapy crawl baidu

爬虫文件内的框架

即文件 百度.py 里的代码

import scrapy
	class BaiduSpider(scrapy.spider):
        # 爬虫的名字,用于运行爬虫的时候使用的值
        name = "baidu"
        # 允许访问的域名
        allowed_domains = ["www.baidu.com"]
        # 起始的url地址
        # start_urls 是 在 allowed_domains 的前面加了 http://
        #			    在 allowed_domains 的后面加了 /
        start_urls = ["http://www.baidu.com/"]
        
        # 执行了strat_urls 之后执行的方法 方法中的response就是返回的那个对象
        # 相当于response = urllib.requests.urlopen()
        #      response = requests.get()
        def parse(self, response):
            print("helloworld")

3.2 Scrapy的Xpath使用

以 汽车之家 网站为例 https://car.autohome.com.cn/price/brand-15.html

爬虫文件内容

import scrapy

# 注意:如果请求的接口是以html结尾的不需要加 /
class CarSpider(scrapy.Spider):
    name = "car"
    allowed_domains = ["https://car.autohome.com.cn/price/brand-15.html"]
    start_urls = ["https://car.autohome.com.cn/price/brand-15.html"]

    def parse(self, response):
        print('============================')
        name_list = response.xpath('//div[@class="main-title"]/a/text()')
        price_list = response.xpath('//span[@class="lever-price red"]/span/text()')

        for i in range(len(name_list)):
            name = name_list[i].extract()
            price = price_list[i].extract()
            print(name, price)

3.3 Scrapy使用pipeline模块下载爬取的数据

下载文件和图片
Scrapy为下载item中包含的文件(比如再爬取到产品时,同时也想保存到对应图片)提供了一个可重用的item pipeline。这些pipeline有些共同的方法和结构(我们称之为media Pipeline),一般来说我们会使用到File Pipeline和Images Pipeline。

使用Scrapy框架内置方法的好处
我们为什么要选择用Scrapy内置的下载文件的防范:

避免重复下载最近已经下载过的数据。

可以方便的指定文件存储的路径。
可以将下载的图片转存成通用的格式,比如png或jpg
可以方便的生成缩略图。
可以方便的检测图片的宽和高,确保他们满足最小限制。
异步下载,效率非常高。

下载文件的File Pipeline
当使用File Pipeline下载文件的时候,按照以下步骤来完成:

定义好一个Item,然后再这个Item中定义两个属性,分别为file_urls以及files。file_urls是用来存储需要下载的文件的url链接,需要给一个列表。
当文件下载完成后,会把文件下载相关的信息存储到item的files属性中。比如下载路径、下载的url和文件的校验码等。
在配置文件setting.py中配置FILES_STORE,这个配置时用来设置文件下载下来的路径。
启动pipeline:在ITEM_PIPELINES中设置scrapy.pipelines.files.FilesPipeline:1。


传统下载方式
传统的下载方式在这里就简单说下步骤:

使用scrapy命令创建项目;
使用命令创建爬虫;
更改setting.py文件配置信息;
编写items.py代码;
编写spider模块下代码(爬取网页中需要下载的图片链接);
编写pipelines.py代码(进行数据处理,图片的保存等)。

案例如下:

以当当网为例 http://category.dangdang.com/cp01.01.02.00.00.00.html

首先需要再scrapy项目下的 item.py 文件里定义你需要下载的东西

pipelines.py代码如下:

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import urllib.request

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

# 如果想使用管道的话就必须在settings中开启管道
class ScrapyDangdang39Pipeline:

#   在爬虫文件开始之前就执行的一个方法
    def open_spider(self, spider):
        self.fp = open('book.json', 'w', encoding='utf-8')

#   参数item就是yield后面的book对象
    def process_item(self, item, spider):
        # 以下这种模式不推荐,因为每传递一个对象就打开一次文件,对文件的操作过于频繁

        # write方法必须需要写一个字符串,而不能是其他的对象
        # w模式 会每一个对象都打开一次文件 覆盖之前的内容
        # a模式 追加
        # with open('book.json', 'a', encoding='utf-8') as fp:
        #     fp.write(str(item))

        self.fp.write(str(item))

        return item

#   在爬虫文件执行完之后再执行的方法
    def close_spider(self, spider):
        self.fp.close()

# 多条管道开启
# 定义管道类
# 在settings中开启管道
# 'scrapy_dangdang_39.pipelines.DangDangDownloadPipeline"': 301

class DangDangDownloadPipeline:
    def process_item(self, item, spider):

        url = 'http:' + item.get('src')
        filename = './books/' + item.get('name') + '.jpg'

        urllib.request.urlretrieve(url=url, filename=filename)

        return item

items.py代码如下:

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy

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

    #图片
    src = scrapy.Field()
    # 名字
    name = scrapy.Field()
    # 价格
    price = scrapy.Field()

配置文件中启动pipeline:在ITEM_PIPELINES中设置scrapy.pipelines.images.ImagesPipeline:1。修改代码如下:

ITEM_PIPELINES = {
#     管道可以有很多个 管道是有优先值的 优先值的范围是1到1000 值越小优先级越高
   "scrapy_dangdang_39.pipelines.ScrapyDangdang39Pipeline": 300,

#      DangDangDownloadPipeline
   'scrapy_dangdang_39.pipelines.DangDangDownloadPipeline': 301
}

spiders文件夹下的dang.py代码如下:

import scrapy

# 编译器问题所以会报错,下面用到时不会报错
from scrapy_dangdang_39.items import ScrapyDangdang39Item

class DangSpider(scrapy.Spider):
    name = "dang"
    # 如果是多页下载的话 需要调整allowed_domains的范围 一般情况下只写域名
    allowed_domains = ["category.dangdang.com"] # 允许在哪些域名下的url
    start_urls = ["http://category.dangdang.com/cp01.01.02.00.00.00.html"]  # 起始爬取的url

    base_url = 'http://category.dangdang.com/pg'
    page = 1

    def parse(self, response):
#          src = //ul[@id="component_59"]/li//img/@src
#          alt = //ul[@id="component_59"]/li//img/@alt
#          price = //ul[@id="component_59"]/li//p[@class="price"]/span[1]/text()
#          所有的selector的对象 都可以再次调用xpath方法
        li_list = response.xpath('//ul[@id="component_59"]/li')

        for li in li_list:
#           网页做了懒加载所以把@src 换成 @data-original
#           第一张图片和其他图片的标签属性是不一样的
#           第一张图片的src是可以使用的,其他的图片地址是@data-original
            src = li.xpath('.//img/@data-original').extract_first()
            if src: #若拿到的数据不为空(None)
                src = src
            else:
                src = li.xpath('.//img/@src').extract_first()   # 若为空则拿src的

            name = li.xpath('.//img/@alt').extract_first()
            price = li.xpath('.//p[@class="price"]/span[1]/text()').extract_first()

            book = ScrapyDangdang39Item(src=src, name=name, price=price)
#           yield 就是return一个值
#           获取一个book就将book交给pipelines
            yield book

#           http://category.dangdang.com/cp01.01.02.00.00.00.html
#           http://category.dangdang.com/pg2-cp01.01.02.00.00.00.html

            if self.page < 100:
                self.page = self.page + 1
                url = self.base_url + str(self.page) + '-cp01.01.02.00.00.00.html'
                print(url)

#           怎么去调用parse方法
                # scrapy.Request 就是 scrapy 的get请求
                # url就是请求地址
                # callback是你要执行的那个函数,注意不需要加()
                yield scrapy.Request(url=url, callback=self.parse)

3.4 Scrapy的post请求

还是以百度翻译为例

spiders文件夹下的testpost.py代码如下:

import scrapy
import json

class TestpostSpider(scrapy.Spider):
    name = "testpost"
    allowed_domains = ["https://fanyi.baidu.com/sug"]
    # post请求如果没有参数,那么这个请求没有任何意义
    # 所以start_urls也没有用了
    # 所以parse方法也没用了
    # start_urls = ["http://www.baidu.com/"]
    #
    # def parse(self, response):
    #     pass

    def start_requests(self):
        url = 'https://fanyi.baidu.com/sug'
        data = {
            'kw': 'final'
        }

        yield scrapy.FormRequest(url=url, formdata=data, callback=self.parse_second)

    def parse_second(self, response):

        content = response.text
        obj = json.loads(content)   # encoding 去掉。
        print(obj)

3.5 Scrapy的CrawlSpider的使用

以读书网下载书名和图片并保存到mysql数据库中为例 https://www.dushu.com/book/1188_1.html

pipelines.py代码如下:

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
from scrapy.utils.project import get_project_settings


class ScrapyReadbook41Pipeline:

    def open_spider(self, spider):
        self.fp = open("book.json", 'w', encoding='utf-8')

    def process_item(self, item, spider):
        self.fp.write(str(item))    # 只能写字符串
        return item

    def close_spider(self, spider):
        self.fp.close()


from scrapy.utils.project import get_project_settings
import pymysql
class MysqlPipeline:

    def open_spider(self, spider):
        settings = get_project_settings()
        self.host = settings['DB_HOST']
        self.port = settings['DB_PORT']
        self.user = settings['DB_USER']
        self.password = settings['DB_PASSWORD']
        self.name = settings['DB_NAME']
        self.charset = settings['DB_CHARSET']

        self.connect()

    def connect(self):
        self.conn = pymysql.connect(
                        host=self.host,
                        port=self.port,
                        user=self.user,
                        password=self.password,
                        db=self.name,
                        charset=self.charset
        )
        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        sql = 'insert into book(name, src) values("{}", "{}")'.format(item["name"], item["src"])
        # 执行sql语句
        self.cursor.execute(sql)
        # 提交
        self.conn.commit()
        return item

    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()

item.py代码如下:

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


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

配置文件settings代码修改如下:

# Configure pymysql
DB_HOST = 'localhost'   # 本地数据库为 localhost   远程数据库为 ip地址
# port 端口号是一个整数
DB_PORT = 3306
DB_USER = 'root'
DB_PASSWORD = 'hadoop'
DB_NAME = 'spider01'
DB_CHARSET = 'utf8'

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   "scrapy_readbook_41.pipelines.ScrapyReadbook41Pipeline": 300,
# MysqlPipeline
   'scrapy_readbook_41.pipelines.MysqlPipeline': 301,
}

spiders文件夹下的read.py代码如下:

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

from scrapy_readbook_41.items import ScrapyReadbook41Item

# CrawlSpider为全站爬取而生

class ReadSpider(CrawlSpider):
    name = "read"
    allowed_domains = ["www.dushu.com"]
    start_urls = ["https://www.dushu.com/book/1188_1.html"]

    rules = (
        Rule(LinkExtractor(allow=r"/book/1188_\d+\.html"),
             callback="parse_item",
             follow=True),)

    def parse_item(self, response):

        img_list = response.xpath('//div[@class="bookslist"]//img')

        for img in img_list:
            name = img.xpath('./@alt').extract_first()
            src = img.xpath('./@data-original').extract_first()

            book = ScrapyReadbook41Item(name=name, src=src)

            yield book

#   实操中出现了问题,只下载了每页的最后一个数据,且第一页每页下载图片链接
#   解决: yield book 未缩进
  • 9
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

向之 所欣

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

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

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

打赏作者

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

抵扣说明:

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

余额充值