实例演示Scrapy的基本用法

Scrapy是一个非常强大的异步爬虫框架,里边已经写好了许许多多的组件,有了它,就可以只关心爬虫的逻辑了。本文通过一个项目,梳理一遍流程,以此来大致了解scrapy的原理和用法。

目标站点分析

http://quotes.toscrape.com/
这是scrapy官方提供的一个抓取网站,主要显示了一些名人名言,以及作者、标签等等信息。
在这里插入图片描述
点击页面底端的next翻页后,可以看到page变为2:
在这里插入图片描述
也就是说我们只需要改变url就可以进行翻页了。
此外网页的结构也比较简单。

流程框架

  • 1.抓取第一页

请求第一页的URL并得到源代码,进行下一步分析。

  • 2.获取内容和下一页链接

分析源代码,提取首页内容,获取下一页链接等待进一步爬取

  • 3.翻页爬取

请求下一页信息,分析内容并请求再下一页的链接。

  • 4.保存爬取结果

将爬取结果保存为特定格式如文本、数据库

爬虫实战

首先新建一个项目:

scrapy startproject quotetutorial
cd quotetutorial

创建一个spider(名为quotes):

scrapy genspider quotes quotes.toscrape.com

我们可以使用pycharm来打开已经在本地生成的项目:
在这里插入图片描述
从图中可以看出整个项目的结构了。
其中,“scrapy.cfg”为配置文件 ;“items.py”是用来保存数据的数据结构;“middlewares.py”是在爬取过程中定义的一些中间件,可以用来处理Request,Response以及Exceptions等操作,也可以用来修改Request, Response等相关的配置;“pipelines.py”即项目管道,可以用来输出一些items;另外,最重要的就是“settings.py”,里面定义了许多配置信息。最主要的运行代码是在“quotes.py”里面。

在Terminal中使用命令:scrapy crawl quotes
就可以执行这个爬虫程序了:
在这里插入图片描述
可以看到控制台中打印出了许多调试信息。可以看出,它和普通的爬虫不太一样,Scrapy提供了很多额外的输出。
在这里插入图片描述
QuotesSpider这个类中的parse方法会在请求完url之后自动调用,所以我们可以改下此方法,打印一下请求到的text内容:scrapy crawl quotes
在这里插入图片描述
再次运行就可以发现,打印出了网页的源代码。
在这里插入图片描述
此时分析一下网页的结构:名言、作者、标签,结构还是十分清晰的。
借助Scrapy提供的“items.py”定义统一的数据结构,指定一些字段之类的,将爬取到的结果作为一个个整体存下来。根据提示更改文件如下:
在这里插入图片描述

接下来我们在parse方法里面,写出解析网页的方法(可以看出在scrapy中,我们只需要关注如何解析,而不需要在意request之类的)
先通过css选中quote这个区块:
在这里插入图片描述
接下来遍历所以的quote区块,并依次选择区块中的三个信息:text,author,tags:

    def parse(self, response):
        quotes = response.css('.quote')
        for quote in quotes:
            text = quote.css('.text::text').extract_first()
            author = quote.css('.author::text').extract_first()
            tags = quote.css('.tags . tag::text').extract()

可以看到,这样的解析方法和pyquery非常相似,“.text”指的是标签的class,“::text”是Scrapy特有的语法结构,表示输出标签里面的文本内容,“extract_first()”方法表示获取第一个内容,而“extract”会把所有结果都找出来(类似于find和findall)。

Scrapy还为我们提供了一个非常强大的工具–shell,在命令行中输入
“scrapy shell quotes.toscrape.com”,可以进入命令行交互模式:
在这里插入图片描述
例如,直接输入response:
在这里插入图片描述
回车后会直接执行这条语句。
我们来试试刚才写的方法的效果:先查看“response.css(’.quote’)”的输出:

 response.css('.quote')

在这里插入图片描述
这是一个list类型的数据,里面的内容是Selector选择器,查看第一个结果:
此时若直接输入quotes会报错
先执行quotes = response.css('.quote')
然后quotes[0]
在这里插入图片描述
继续使用css选择器选择class为text的部分,可以看到结果也是一个list,内容只有一个,也是一个Selector。

quotes[0].css('.text')

在这里插入图片描述
使用“::text”会有什么不同呢?让我们来看一下:

quotes[0].css('.text::text')

在这里插入图片描述
可以看到,不同之处是里面的data变成了字符串格式的数据,而不带“::text”获取到的是整个标签的内容。

再加上extract()会发生什么呢?

quotes[0].css('.text::text').extract()

在这里插入图片描述
返回的依然是list,长度为1,内容是一个字符串。如果去掉“::text”,则返回的是内容为标签的list,如下。也就是说,使用extract(),是把原来Selector中的data单独提取了出来。
在这里插入图片描述
我们再看看extract_first()。可以猜到,使用extract_first()获取到的应该是原来list中的第一个值,也就是说,现在返回的应该是一个字符串,而不再是一个列表了。

In [9]: quotes[0].css('.text::text').extract_first()
Out[9]: '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'

而class为tags的标签就不一样了,里面包含了可能不止一个元素,如下,我们获取到的就是一个包含了多个元素的list。使用extract_fist()依然可以获取list中的第一个元素。

In [10]: quotes[0].css('.tags .tag::text').extract()
Out[10]: ['change', 'deep-thoughts', 'thinking', 'world']

In [11]: quotes[0].css('.tags .tag::text').extract_first()
Out[11]: 'change'

接下来我们要在parse方法中调用我们刚才定义的items,将提取出的网页信息存储到item,然后调用yield方法将item生成出来。

        for quote in quotes:
            item = QuoteItem()
            text = quote.css('.text::text').extract_first()
            author = quote.css('.author::text').extract_first()
            tags = quote.css('.tags .tag::text').extract()
            item['text'] = text
            item['author'] = author
            item['tags'] = tags
            yield item

再次运行,就可以在调试信息中看到我们要获取到的内容了:
在这里插入图片描述

翻页并爬取其它页面信息

由于网页的请求方式非常简单,比如第二页就是“http://quotes.toscrape.com/page/2/”,请求其它页只要将“2”替换成对应数字即可。在页面底端点击“Next”也可以切换到下一页,这是因为“Next”对应着下一页的超链接,同样地,我们可以从网页源代码中提取出这个超链接。
在这里插入图片描述

    def parse(self, response):
        quotes = response.css('.quote')

        for quote in quotes:
            item = QuoteItem()
            text = quote.css('.text::text').extract_first()
            author = quote.css('.author::text').extract_first()
            tags = quote.css('.tags .tag::text').extract()
            item['text'] = text
            item['author'] = author
            item['tags'] = tags
            yield item

        next = response.css('.pager .next a::attr(href)').extract_first()
        url = response.urljoin(next)
        yield scrapy.Request(url=url, callback=self.parse)

urljoin方法是为了拼凑出完整的url——我们获取到的“next”只是类似于“/page/3/”这样。
在这里插入图片描述
最后调用Request,第一个参数就是要请求的url,第二个参数“callback”是回调函数的意思,也就是请求之后得到的response由谁来处理,这里我们还是调用parse,因为parse方法就是用来处理索引页的,这就相当于完成了一个递归的调用,可以一直不断地调用parse方法获取下一页的链接并对访问得到的信息进行处理。

再次重新运行程序,可以看到输出了10页的内容,这是因为该网站只有10页内容。
在这里插入图片描述

保存爬取到的信息

如何把抓取到的信息保存下来呢?
在原来的命令后面增加“-o 文件名称.json”,爬取完成后就会生成一个“quotes.json”文件,把获取到的信息保存成了标准的json格式。

scrapy crawl quotes -o quotes.json

在这里插入图片描述
数据不仅可以保存成json格式,Scrapy还提供了其它存储格式,比如“jl”格式,在命令行输入如下命令就可以得到jl格式文件。相比于json格式,它没有了最前面和最后面的的大括号,每条数据独占一行。

scrapy crawl quotes -o quotes.jl

在这里插入图片描述
或者保存成csv格式:

scrapy crawl quotes -o quotes.csv

在这里插入图片描述
它还支持xml、pickle和marshal等格式
Scrapy还提供了一种远程ftp的保存方式,可以将爬取结果通过ftp的形式进行保存,例如:

scrapy crawl quotes -o ftp://user:pass@ftp.example.com/path/quotes.csv

数据处理

在将爬取到的内容进行保存之前,还需要对item进行相应的处理,因为在解析完之后,有一些item可能不是我们想要的,或者我们想把item保存到数据库里面,就需要借助Scrapy的Pipeline工具。

如下,在“pipelines.py”里我们写了两个pipeline,第一个TextPipeline类是对item进行一些处理,这里实现的功能是长度限制,如果text长度大于50,则舍弃后面的部分,并用省略号代替。在item的text不存在时,DropItem抛出异常。MongoPipeline类是用来将数据保存到MongoDB数据库,其中,类方法from_crawler用来从settings里面拿到配置信息,当然,我们同时需要在“settings.py”文件里面添加配置信息。open_spider方法是在爬虫刚要启动时需要执行的操作,在这里进行pymongo的一些初始化操作。复写process_item方法,将数据写入数据库。最后close_spider方法将MongoDB的连接关闭。

ps:要想让pipeline生效,需要在settings里面指定pipeline
后面的序号300和400这样,代表pipeline运行的优先级顺序,序号越小表示优先级越高,会优先进行调用。

# pipelines.py
 
# -*- coding: utf-8 -*-
 
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
import pymongo
from scrapy.exceptions import DropItem
 
 
class TextPipeline(object):
    
    def __init__(self):
        self.limit = 50
        
    def process_item(self, item, spider):
        if item['text']:
            if len(item['text']) > self.limit:
                item['text'] = item['text'][0:self.limit].rstrip() + '...'
            return item
        else:
            return DropItem('Missing Text')
 
 
class MongoPipeline(object):
    
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
    
    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DB')
        )
    
    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]
        
    def process_item(self, item, spider):
        name = item.__class__.__name__
        self.db['quotes'].insert(dict(item))
        return item
    
    def close_spider(self, spider):
        self.client.close()

下面是向settings.py中添加的内容:

# settings.py
 
MONGO_URI = 'localhost'
MONGO_DB = 'quotestutorial'
 
ITEM_PIPELINES = {
	'quotetutorial.pipelines.TextPipeline': 300,
	'quotetutorial.pipelines.MongoPipeline': 400,
}

将程序写好后我们可以再次运行,(命令行输入“scrapy crawl quotes”),可以看到输出的text过长的话,后面就被省略号代替了,同时数据也被存入了MongoDB数据库。
在这里插入图片描述
在这里插入图片描述
如果运行过程中出现了下面这种错误,是因为MongoDB服务器没有开启,解决方法就是在CMD中cd到mongodb/bin目录下,执行命令 mongod --dbpath “e:\mongodb\data”(将路径换成你的mongodb安装路径)就可以了,注意不要把命令窗口关闭,然后就可以进行MongoDB数据库的相关操作了。

pymongo.errors.ServerSelectionTimeoutError: localhost:27017: [WinError 10061] 由于目标计算机积极拒绝,无法连接。

猜你喜欢

转载自blog.csdn.net/z714405489/article/details/84000464
今日推荐