實例演示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] 由於目標計算機積極拒絕,無法連接。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章