Python爬虫基础(五):使用scrapy框架

系列文章索引

Python爬虫基础(一):urllib库的使用详解
Python爬虫基础(二):使用xpath与jsonpath解析爬取的数据
Python爬虫基础(三):使用Selenium动态加载网页
Python爬虫基础(四):使用更方便的requests库
Python爬虫基础(五):使用scrapy框架

一、scrapy简介

1、什么是scrapy

Scrapy 是一个为了抓取网页数据、提取结构性数据而编写的应用框架,该框架是封装的,包含 request (异步调度和处理)、下载器(多线程的 Downloader)、解析器(selector)和 twisted(异步处理)等。对于网站的内容爬取,其速度非常快捷。

2、scrapy安装

# 进入到python安装目录的Scripts目录
d:
cd D:\python\Scripts
# 安装 可以使用国内源
pip install scrapy

3、scrapy架构组成

(1)引擎:自动运行,无需关注,会自动组织所有的请求对象,分发给下载器。
(2)下载器:从引擎处获取到请求对象后,请求数据。
(3)spiders:spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。换句话说,Spider就是定义爬取的动作及分析某个网页的地方。
(4)调度器:有自己的调度规则,无需关注。
(5)管道(Item pipeline):最终处理数据的管道,会预留接口供我们处理数据。当Item在Spider中被收集之后,它将会被传递到Item pipeline,一些组件会按照一定的顺序执行对Item的处理。每个item pipeline组件是实现了简单方法的Python类,他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。

以下是item pipeline的一些典型应用:
(1)清理HTML数据,(2)验证爬取的数据(检查item包含某些字段),(3)查重(并丢弃),(4)将爬取结果保存到数据库中。

4、scrapy工作原理

spiders->scheduler(调度器)->scrapy engine(引擎)->downloader(下载器)->互联网进行下载
->下载后的数据从引擎到spiders->通过引擎xpath进行数据解析->使用pipeline对数据进行存储

在这里插入图片描述
在这里插入图片描述

二、scrapy基本使用

1、创建项目

进入到项目目录,打开cmd:

# 创建scrapy_test_001项目,项目名不能以数字、汉字开头
scrapy startproject scrapy_test

2、创建爬虫文件

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

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

创建爬虫文件,注意,不需要添加http协议了:

# scrapy genspider 爬虫文件的名字  要爬取网页
scrapy genspider baidu  www.baidu.com

此时,在spiders目录中,会生成一个baidu.py:
在这里插入图片描述
我们看一下baidu.py的内容:

import scrapy

class BaiduSpider(scrapy.Spider):
    # 爬虫的名字  用于运行爬虫的时候 使用的值
    name = "baidu"
    # 允许访问的域名
    allowed_domains = ["www.baidu.com"]
    # 起始的url地址  指的是第一次要访问的域名
    start_urls = ["https://www.baidu.com"]

    # 是执行了start_urls之后 执行的方法   方法中的response 就是返回的那个对象
    # 相当于 response = urllib.request.urlopen()
    #       response  = requests.get()
    def parse(self, response):
        pass

后续我们可以在parse方法中,对response进行处理,这就是最终爬取的结果。

3、(附)项目组成

在这里插入图片描述

4、运行爬虫代码

(1)修改baidu.py

在parse方法中,自定义输出:

def parse(self,response):
    print('输出正确!')

在spiders目录中,执行以下命令,就可以运行爬虫代码:

# scrapy crawl 爬虫的名字
scrapy crawl baidu

会输出很多内容,但是并没有我们打印的东西。

(2)robots文件

控制台中,打印出了百度的robots协议:
在这里插入图片描述

每个网站都会有一个robots君子协议,里面定义了哪些不允许爬取,我们看百度的robots:
https://www.baidu.com/robots.txt

在项目的settings.py文件中,默认是ROBOTSTXT_OBEY=True,表示遵循这个君子协议。

我们只需要将这一行注释掉:
在这里插入图片描述
此时我们再执行爬虫代码:

# scrapy crawl 爬虫的名字
scrapy crawl baidu

此时在命令行中,会打印出我们自定义的那句话了。

5、response的属性和方法

response.xpath(xpath_expression):根据XPath表达式选择并提取数据。
response.css(css_expression):根据CSS选择器选择并提取数据。
response.follow(url):根据给定的URL创建新的请求,并通过回调方法继续处理。
response.url:返回当前响应的URL。
response.status:返回当前响应的状态码。
response.headers:返回当前响应的头部信息。
response.body:返回当前响应的原始二进制内容。
response.text:返回当前响应的文本内容。
response.css(‘a::attr(href)’).getall():使用CSS选择器提取所有匹配的元素属性值。
response.xpath(‘//a/@href’).extract():使用XPath表达式提取所有匹配的元素属性值

6、实战:获取百度的【百度一下】按钮的内容

在这里插入图片描述

    def parse(self, response):
        print('=====================')
        input = response.xpath('//input[@id="su"]/@value')[0]
        print(input.extract()) # 百度一下
        print('=====================')

7、实战:获取汽车之家汽车价格表

在这里插入图片描述

import scrapy

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('//div[@class="main-lever"]//span/span/text()')

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

三、使用scrapy shell

1、什么是scrapy shell

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

该终端是用来测试xPath或css表达式,查看他们的工作方式及从爬取的网页中提取的数据。在编写您的spider时,该终端提供了交互性测试您的表达式代码的功能,免去了每次修改后运行spider的麻烦。一旦熟悉了scrapy终端后,您会发现其在开发和调试spider时发挥的巨大作用。

2、安装ipython(非必须)

# 进入到python安装目录的Scripts目录
d:
cd D:\python\Scripts
# 安装 可以使用国内源
pip install ipython

如果安装了 IPython,scrapy终端将使用 IPython (替代标准Python终端)。 IPython 终端与其他相比更为强大,提供智能的自动补全,高亮输出,及其他特性。

使用:命令行直接输入ipython,进入的新命令窗口自带高亮及自动补全:
在这里插入图片描述

3、使用scrapy shell

# 直接在window的终端中输入scrapy shell 域名
# 直接在命令终端(不需要进入python或者ipython终端),执行完毕之后,自动进入ipython终端
scrapy shell www.baidu.com

可以直接获取到response对象,可以直接在此进行调试。
在这里插入图片描述

四、实战:获取当当网商品数据

1、初始化项目

# 在自定义目录中创建项目
scrapy startproject scrapy_dangdang

# 创建爬虫文件
# cd 项目的名字\项目的名字\spiders
cd scrapy_dangdang\scrapy_dangdang\spiders

# scrapy genspider 爬虫文件的名字  要爬取网页
# 中国古典小说网址:http://category.dangdang.com/cp01.03.32.00.00.00.html
scrapy genspider dang http://category.dangdang.com/cp01.03.32.00.00.00.html

# 运行
scrapy crawl dang

2、定义item文件

在项目自动生成的item.py中,定义要爬取的数据格式:

import scrapy

class ScrapyDangdangItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 通俗的说就是你要下载的数据都有什么,固定写法:scrapy.Field()

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

3、爬取图片、名字、价格

在这里插入图片描述
我们先分析这三者的xpath:
拿到图片的xpath://ul[@id="component_59"]/li/a/img/@src 因为图片懒加载,应该取图片的data-original属性。
在这里插入图片描述
拿到标题的xpath://ul[@id="component_59"]/li/p[@class="name"]/a/@title
在这里插入图片描述
拿到价格的xpath://ul[@id="component_59"]/li/p[@class="price"]/span[1]/text()
在这里插入图片描述

import scrapy

class DangSpider(scrapy.Spider):
    name = "dang"
    allowed_domains = ["category.dangdang.com"]
    start_urls = ["http://category.dangdang.com/cp01.03.32.00.00.00.html"]

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

        for li in li_list:
            src = li.xpath('./a/img/@data-original').extract_first()
            # 第一张图片和其他的图片的标签的属性是不一样的
            # 第一张图片的src是可以使用的  其他的图片的地址是data-original
            if src:
                src = src
            else:
                src = li.xpath('./a/img/@src').extract_first()

            name = li.xpath('./p[@class="name"]/a/@title').extract_first()
            price = li.xpath('./p[@class="price"]/span[1]/text()').extract_first()
            # 拿到所有信息
            print(src + name + price)

4、管道封装

(1)熟悉yield

带有tield的函数不再是一个普通函数,而是一个生成器generator,可用于迭代。

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

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

(2)构造item对象并交给pipeline

上面我们已经获取到图片、名称和价格了,继续在parse方法中构造item对象并交给pipeline:

from scrapy_dangdang.items import ScrapyDangdangItem
            # 构造item对象
            book = ScrapyDangdangItem(src=src,name=name,price=price)

            # 获取一个book就将book交给pipelines
            yield book

(3)在settings.py中开启pipeline

管道可以有很多个 那么管道是有优先级的 优先级的范围是1到1000 值越小优先级越高。

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

(4)编辑pipeline.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

# 如果想使用管道的话 那么就必须在settings中开启管道
class ScrapyDangdangPipeline:
    # 在爬虫文件开始的之前就执行的一个方法 :open_spider
    def open_spider(self,spider):
        self.fp = open('book.json','w',encoding='utf-8')

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

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

        self.fp.write(str(item))
        self.fp.write('\n')

        return item

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

(5)执行,查看写入的json文件

scrapy crawl dang

5、多条管道使用

(1)pipelines.py定义管道类

pipelines.py中可以定义多个类,直接写即可。
类中有三个默认的方法,可以直接使用。

import urllib.request
class ScrapyDangdangDownloadPipeline:
    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

(2)settings.py开启管道

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

   'scrapy_dangdang.pipelines.ScrapyDangdangDownloadPipeline':301
}

(5)执行,查看写入的json文件与图片

先在spiders下创建books目录。

# 执行
scrapy crawl dang

6、获取多页数据

import scrapy
from scrapy_dangdang.items import ScrapyDangdangItem


class DangSpider(scrapy.Spider):
    name = "dang"
    allowed_domains = ["category.dangdang.com"]
    start_urls = ["http://category.dangdang.com/cp01.03.32.00.00.00.html"]

    base_url = 'http://category.dangdang.com/cp'
    page = 1
    def parse(self, response):
		# 。。。省略


#       每一页的爬取的业务逻辑全都是一样的,所以我们只需要将执行的那个页的请求再次调用parse方法
# 就可以了
#         http://category.dangdang.com/pg2-cp01.03.32.00.00.00.html
#         http://category.dangdang.com/pg3-cp01.03.32.00.00.00.html
#         http://category.dangdang.com/pg4-cp01.03.32.00.00.00.html

        if self.page < 100:
            self.page = self.page + 1

            url = self.base_url + str(self.page) + '-cp01.03.32.00.00.00.html'

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


五、实战:获取电影天堂不同页面的数据

1、效果

获取电影天堂,第一页列表的电影名:
在这里插入图片描述
然后点击电影详情,再从第二页获取到详情中的图片:
在这里插入图片描述

2、核心代码

mv.py核心代码

import scrapy

from scrapy_movie_099.items import ScrapyMovie099Item

class MvSpider(scrapy.Spider):
    name = 'mv'
    allowed_domains = ['www.dygod.net']
    start_urls = ['https://www.dygod.net/html/gndy/china/index.html']

    def parse(self, response):
#         要第一个的名字 和 第二页的图片
        a_list = response.xpath('//div[@class="co_content8"]//td[2]//a[2]')

        for a in a_list:
            # 获取第一页的name 和 要点击的链接
            name = a.xpath('./text()').extract_first()
            href = a.xpath('./@href').extract_first()

            # 第二页的地址是
            url = 'https://www.dygod.net' + href

            # 对第二页的链接发起访问 并将name参数传入
            yield  scrapy.Request(url=url,callback=self.parse_second,meta={
    
    'name':name})

    def parse_second(self,response):
        # 注意 如果拿不到数据的情况下  一定检查你的xpath语法是否正确
        src = response.xpath('//div[@id="Zoom"]//img/@src').extract_first()
        # 接受到请求的那个meta参数的值
        name = response.meta['name']

        movie = ScrapyMovie099Item(src=src,name=name)

        yield movie

pipelines.py:

from itemadapter import ItemAdapter

class ScrapyMovie099Pipeline:

    def open_spider(self,spider):
        self.fp = open('movie.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()

settings.py中开启pipeline:

ITEM_PIPELINES = {
    
    
   'scrapy_movie_099.pipelines.ScrapyMovie099Pipeline': 300,
}

六、实战:使用CrawlSpider获取读书网的数据

1、CrawlSpider简介

CrawlSpider继承自scrapy.Spider,可以定义规则,再解析html内容的时候,可以根据链接规则提取出指定的链接,然后再向这些链接发送请求。

所以,如果有需要跟进链接的需求,就是爬取了网页之后,需要提取链接再次爬取,使用CrawlSpider是非常合适的。

提取链接常用语法
链接提取器,在这里就可以写规则提取指定链接:

scrapy.linkextractors.LinkExtractor(
	allow = (), # 正则表达式,提取符合正则的链接
	deny = (), # (不用)正则表达式 不提取符合正则的链接
	allow_domains = (), # (不用)允许的域名
	deny_domains = (), # (不用)不允许的域名
	restrict_xpaths = (), # xpath,提取符合xpath规则的链接
	restrict_css = () # 提取符合选择器规则的链接
)

# 使用实例
# 正则用法:
links = LinkExtractor(allow = r'list_23_\d+\.html')
# xpath:
links = LinkExtractor(restrict_xpaths = r'//div[@class="x"]')
# css用法:
links = LinkExtractor(restrict_css='.x')

# 提取链接
links.extract_links(response)

2、创建项目

# 创建项目:scrapy startproject 项目的名字
scrapy startproject readbook

# 创建爬虫文件
# cd 项目名字\项目名字\spiders
cd readbook/readbook/Spiders
# scrapy genspider -t crawl 爬虫文件的名字  爬取的域名
scrapy genspider -t crawl read www.dushu.com/book/1188_1.html

我们发现,read.py内容和我们之前不太一样了:
在这里插入图片描述

3、定义item

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

4、提取数据

在这里插入图片描述

read.py:

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from readbook.items import ReadbookItem

class ReadSpider(CrawlSpider):
    name = "read"
    allowed_domains = ["www.dushu.com"]
    # 注意,第一个url也要匹配规则!不然会跳过第一页
    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('./@data-original').extract_first()
            src = img.xpath('./@alt').extract_first()

            book = ReadbookItem(name=name,src=src)
            yield book

5、定义pipeline

# settings.py
ITEM_PIPELINES = {
    
    
   "readbook.pipelines.ReadbookPipeline": 300,
}
# pipelines.py
from itemadapter import ItemAdapter

class ReadbookPipeline:
    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()

6、启动

 scrapy crawl read

执行完毕之后,查看book.json文件。

7、保存至mysql

安装pymysql:

# 进入到python安装目录的Scripts目录
d:
cd D:\python\Scripts
# 安装 可以使用国内源
pip install pymysql

添加一个pipeline:

# settings.py
ITEM_PIPELINES = {
    
    
   "readbook.pipelines.ReadbookPipeline": 300,
   # MysqlPipeline
   'readbook.pipelines.MysqlPipeline':301
}
# 参数中一个端口号 一个是字符集 都要注意
DB_HOST = '192.168.1.1'
# 端口号是一个整数
DB_PORT = 3306
DB_USER = 'root'
DB_PASSWROD = '123'
DB_NAME = 'spider01'
# utf-8的杠不允许写
DB_CHARSET = 'utf8'

pipelines.py:

# 加载settings文件
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_PASSWROD']
        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()

七、实战:发送post请求

关键代码:

import scrapy

import json

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



	# start_requests是一个固定方法
    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='utf-8')

        print(obj)

猜你喜欢

转载自blog.csdn.net/A_art_xiang/article/details/132807935