Python爬蟲5.1 — scrapy框架簡單入門

綜述

本系列文檔用於對Python爬蟲技術的學習進行簡單的教程講解,鞏固自己技術知識的同時,萬一一不小心又正好對你有用那就更好了。
Python 版本是3.7.4

前面我們學習都是爬蟲相關的一些基本知識,學會掌握了前面的技術知識我們可以解決90%爬蟲相關的問題。但是我們如何更快更高效的解決這些問題呢(不光是開發快還有請求處理爬取快),這就用到了框架。下面我們開始一步一步學習Scrapy框架。

Scrapy 框架

Scrapy 框架介紹

寫一個爬蟲,需要做很多的事情。比如:發送網絡請求、數據解析、數據存儲、反反爬蟲機制(更換ip代理、設置請求頭等)、異步請求 等。這些工作如果每次都要自己從零開始寫的話,比較浪費時間。因此Scrapy把一些基礎的東西都封裝好了,在它上面開發爬蟲可以變得更加的高效(爬取效率和開發效率)。因此真正在公司裏,一些上了量的爬蟲,都是使用Scrapy框架來解決(關於框架的概念在這裏就不再做說明)。

Scrapy 架構圖

Scrapy架構圖1

Scrapy架構圖2

Scrapy 框架模塊功能

  1. Scrapy Engine(引擎) : Scrapy框架的核心,負責在SpiderItem PipelineDownloaderScheduler中間通信、傳輸數據等。
  2. Spider(爬蟲) : 發送需要爬取的鏈接給引擎,最後引擎把其他模塊請求回來的數據再發給爬蟲,爬蟲就去解析想要的數據。這部分是我們開發者自己寫的,因爲要爬取哪些鏈接,頁面中的哪些數據是我們需要的,都是由程序員自己決定。
  3. Scheduler(調度器) : 複製接收引擎發送過來的請求,並按照一定的方式進行排列和整理,負責調度請求的順序等。
  4. Downloader(下載器) : 負責接收引擎傳過來的下載請求,然後去網絡上下載對應的數據在交還給引擎。
  5. Item Pipeline(管道) : 負責將Spider(爬蟲)傳遞過來的數據進行保存,具體保存在哪裏,因該看開發者自己的需求。
  6. Downloader Middlewares(下載中間件) : 可以擴展下載器和引擎之間通信功能的中間件。
  7. Spider Middlewares(Spider中間件) : 可以擴展引擎和爬蟲之間通信功能的中間件。

Scrapy 執行流程

Scrapy中的數據流由執行引擎控制,其過程如下:

  1. 引擎從Spiders中獲取到最初的要爬取的請求(Requests);
  2. 引擎安排請求(Requests)到調度器中,並向調度器請求下一個要爬取的請求(Requests);
  3. 調度器返回下一個要爬取的請求(Requests)給引擎;
  4. 引擎將上步中得到的請求(Requests)通過下載器中間件(Downloader Middlewares)發送給下載器(Downloader ),這個過程中下載器中間件(Downloader Middlewares)中的process_request()函數會被調用到;
  5. 一旦頁面下載完畢,下載器生成一個該頁面的Response,並將其通過下載中間件(Downloader Middlewares)發送給引擎,這個過程中下載器中間件(Downloader Middlewares)中的process_response()函數會被調用到;
  6. 引擎從下載器中得到上步中的Response並通過Spider中間件(Spider Middlewares)發送給Spider處理,這個過程中Spider中間件(Spider Middlewares)中的process_spider_input()函數會被調用到;
  7. Spider處理Response並通過Spider中間件(Spider Middlewares)返回爬取到的Item及(跟進的)新的Request給引擎,這個過程中Spider中間件(Spider Middlewares)的process_spider_output()函數會被調用到;
  8. 引擎將上步中Spider處理的其爬取到的Item給Item 管道(Pipeline),將Spider處理的Request發送給調度器,並向調度器請求可能存在的下一個要爬取的請求(Requests);
  9. (從第二步)重複直到調度器中沒有更多的請求(Requests)。

Scrapy 安裝和文檔

  1. 安裝:通過pip install scrapy命令安裝即可。
  2. Scrapy官方文檔:http://doc.scrapy.org/en/latest
  3. Scrapy中文文檔:https://scrapy-chs.readthedocs.io/zh_CN/latest/index.html (文檔版本比較老,建議還是看官方文檔)

注意:
1. 在Ubuntu上安裝 Scrapy 之前需要先安裝以下依賴:
sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zliblg-dev libffi-dev libssl-dev然後再通過pip install scrapy安裝。
2. 如果在Windows系統下,提示這個錯誤ModuleNotFoundError:No module named 'win32api',那麼使用以下命令 可以解決:pip install pypiwin32

Scrapy 快速入門

創建項目

要使用Scrapy框架創建目錄,需要通過命令來創建,首先進入到你想把這個項目存放的目錄。然後使用以下命令創建:

scrapy startproject [項目名稱]

不一定非要使用命令來創建,也可以按照其項目目錄結構進行手動創建,但是手動創建不是很方便並且創建起來很麻煩。

創建爬蟲

進入到項目所在的路徑,執行命令:

scrapy genspider [爬蟲名稱] [爬蟲的域名]

(注意:爬蟲的名稱不能和項目名稱一樣)。

目錄介紹

下面爲項目主要的文件目錄及文件作用:

project_folder -- 項目文件夾名稱
|
├──project_name -- 該項目的python模塊,一般和項目文件夾名稱相同
|  |
|  ├──spider -- 放置spider代碼的包,以後所有的爬蟲,都存放在這個裏面
|  |
|  ├──items.py -- 用來存放爬蟲怕寫來的數據的模型
|  |
|  ├──middlewares.py -- 用來存放各種中間件的文件
|  |
|  ├──pipelines.py -- 用來對items裏面提取的數據做進一步處理,如保存到本地磁盤等
|  |
|  ├──settings.py -- 本爬蟲的一些配置信息(如請求頭、多久發送一次請求、ip代理池等)
|
├──scrapy.cfg -- 項目的配置文件

使用Scrapy框架爬取糗事百科

使用命令創建糗百爬蟲

創建一個名字叫做qsbk的爬蟲,並且能爬取的網頁只會限制在qiushibaike.com這個域名下(注意:爬蟲的名稱不能和項目名稱一樣)。

scrapy genspider qsbk_spider 'qiushibaike.com'

爬蟲代碼解析

  1. items.py文件代碼
    import scrapy
    
    class QsbkItem(scrapy.Item):
        # define the fields for your item here like:
    
        # 定義item數據字段
        author = scrapy.Field()
        content = scrapy.Field()
  1. qsbk_spider.py文件代碼
    import scrapy
    
    from qsbk.items import QsbkItem
    
    
    class QsbkSpiderSpider(scrapy.Spider):
        name = 'qsbk_spider'
        allowed_domains = ['qiushibaike.com']
        start_urls = ['https://www.qiushibaike.com/text/page/1/']
    
        def parse(self, response):
            # SelectorList
            # 解析頁面
            content_left = response.xpath('//div[@id="content-left"]/div')
            # 提取數據
            for dz_div in content_left:
                # Selector
                author = dz_div.xpath(".//h2/text()").get().strip()
                content_tmp = dz_div.xpath(".//div[@class='content']//text()").getall()
                content = ''.join(content_tmp).strip()
                item = QsbkItem(author=author, content=content)
                # 使用yield返回給pipeline
                yield item
  1. pipelines.py文件代碼
    import json
    
    
    class QsbkPipeline(object):
        def __init__(self):
            """
            打開文件,也可放在open_spider中
            """
            self.fp = open('duanzi.json', 'w', encoding='utf-8')
    
        def open_spider(self, spider):
            """
            爬蟲被打開的時候執行
            :param spider:
            :return:
            """
            print("爬蟲開始....")
    
        def process_item(self, item, spider):
            """
            爬蟲有item傳過來的時候會被調用
            :param item:
            :param spider:
            :return:
            """
            item_json = json.dumps(dict(item), ensure_ascii=False)
            # 數據寫入文件
            self.fp.write(item_json + '\n')
            return item
    
        def close_spider(self, spider):
            """
            爬蟲關閉的時候被調用
            :param spider:
            :return:
            """
            # 關閉文件
            self.fp.close()
            print("爬蟲結束....")

運行爬蟲

運行爬蟲只要我們運行命令即可:

scrapy crawl qsbk_spider

每次運行爬蟲我們都需要執行命令,所以我們可以在項目根目錄下創建一個文件start.pyl裏面放置需要執行的命令,文件代碼如下:

from scrapy import cmdline

cmdline.execute("scrapy crawl qsbk_spider".split())

# execute裏面需要傳遞列表數據,等價於
# cmdline.execute(["scrapy", "crawl", "qsbk_spider"])

運行爬蟲會打印很多log信息,我們可以在setting.py文件中進行設置僅打印WARNING等級以上的錯誤。在文件中增加LOG_LEVEL = 'WARNING'配置即可。

糗事百科Scrapy爬蟲筆記

  1. response是一個scrapy.http.response.html.HtmlResponse對象,可以執行xpathcss語法來提取數據。
  2. 提取出來的數據是一個Selector或者SelectorList對象,如果想要獲取其中的字符串,那麼應該執行getall()或者get()方法。
  3. getall()方法:獲取Selector中所有文本,返回的是一個列表。
  4. get()方法:獲取的是Selector中的第一個文本,返回德是一個string類型。
  5. 如果數據解析回來,要傳給pipeline處理,那個可以使用yield來返回。或者是收集所有的item,最後統一使用return返回。
  6. item:建議在items.py中定義好模型,以後就不要使用字典。
  7. pipeline:這個是專門用來保存詩句的,其中三個方法是會經常使用的:
    • open_spider(self,spider):當爬蟲被打開的時候執行;
    • process_item(self,item,spider):當爬蟲有item傳過來的時候會被調用;
    • close_spider(self,spider):當爬蟲關閉的時候被調用。
    • 要激活pipeline,應該在setting.py中,設置ITEM_PIPELINES。示例如下:
    ITEM_PIPELINES = {
        'qsbk.pipelines.QsbkPipeline': 300,
    }
    

優化實例爬蟲數據存儲(Scrapy導出器)

上面例子我們在進行存儲數據的時候需要先將字典轉換成json,然後再做一些其他的存儲處理操作,這樣有點麻煩。我們可以使用Scrapy框架自帶的Exporter導出器scrapy.exporters,其中就有一個JSON的導出模塊JsonItemExporter

修改pipelines.py文件如下:

# 引入JsonItemExporter類庫
from scrapy.exporters import JsonItemExporter


class QsbkPipeline(object):
    def __init__(self):
        """
        打開文件,也可放在open_spider中
        """
        self.fp = open('duanzi1.json', 'wb')
        # 初始化導出器
        # 導出文件必須以二進制打開
        self.exporter = JsonItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')

    def open_spider(self, spider):
        """
        爬蟲被打開的時候執行
        :param spider:
        :return:
        """
        print("爬蟲開始....")

    def process_item(self, item, spider):
        """
        爬蟲有item傳過來的時候會被調用
        :param item:
        :param spider:
        :return:
        """
        # 進行導出
        self.exporter.export_item(item)
        return item

    def close_spider(self, spider):
        """
        爬蟲關閉的時候被調用
        :param spider:
        :return:
        """
        # 完成導出
        self.exporter.finish_exporting()
        # 關閉文件
        self.fp.close()
        print("爬蟲結束....")

我們在使用JsonItemExporter導出器的時候,它是把所有的數據都當成列表中的一項進行導出。這種方式有一個缺陷,它的導出過程是先把你傳回來的所有字典保存在一個列表當中,在最後執行finish_exporting()的時候再統一寫到json文件中。那麼這樣的話如果傳入的json數據比較大的話就不是很好了,它會把所有要導出的json先放到內存中,如果內存不夠大的話就對內存的使用不是很友好。

所以我們可以使用另外一種導出器的使用JsonLinesItemExporter,我們使用這種就像第一次數據處理時的效果,它會將json每行寫入到文件中。

修改pipelines.py文件如下:

# 引入JsonLinesItemExporter類庫
from scrapy.exporters import JsonLinesItemExporter


class QsbkPipeline(object):
    def __init__(self):
        """
        打開文件,也可放在open_spider中
        """
        self.fp = open('duanzi2.json', 'wb')
        # 初始化導出器
        # 導出文件必須以二進制打開
        self.exporter = JsonLinesItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')

    def open_spider(self, spider):
        """
        爬蟲被打開的時候執行
        :param spider:
        :return:
        """
        print("爬蟲開始....")

    def process_item(self, item, spider):
        """
        爬蟲有item傳過來的時候會被調用
        :param item:
        :param spider:
        :return:
        """
        # 進行導出
        self.exporter.export_item(item)
        return item

    def close_spider(self, spider):
        """
        爬蟲關閉的時候被調用
        :param spider:
        :return:
        """
        # 關閉文件
        self.fp.close()
        print("爬蟲結束....")

Scrapy導出器不光有JSON格式導出,還有XML、CSV、PICK等方式格式化數據導出。

JsonItemExporter和JsonLinesItemExporter

保存Json數據的時候,可以使用這兩個類,讓操作變得更簡單:

  1. JsonItemExporter : 這個時每次把數據添加到內存中,最後統一寫入到磁盤中,好處時存儲的數據是一個滿足json規則的數據,壞處是如果數據量比較大,那麼比較耗內存。
  2. JsonLinesItemExporter : 這個每次調用export_item的時候就把這個item存儲到硬盤中。患處是每一個字典是一行,整個文件不是一個滿足json格式的文件,好處是每次處理數據的時候就直接存儲到了硬盤中,這樣就不會耗內存。數據也比較安全。

爬取多頁數據

修改qsbk_spider.py文件如下即可:

import scrapy

from qsbk.items import QsbkItem


class QsbkSpiderSpider(scrapy.Spider):
    name = 'qsbk_spider'
    allowed_domains = ['qiushibaike.com']
    start_urls = ['https://www.qiushibaike.com/text/page/1/']
    base_url = 'https://www.qiushibaike.com'

    def parse(self, response):
        # SelectorList
        # 解析頁面
        content_left = response.xpath('//div[@id="content-left"]/div')
        # 提取數據
        for dz_div in content_left:
            # Selector
            author = dz_div.xpath(".//h2/text()").get().strip()
            content_tmp = dz_div.xpath(".//div[@class='content']//text()").getall()
            content = ''.join(content_tmp).strip()
            item = QsbkItem(author=author, content=content)
            # 使用yield返回給pipeline
            yield item
        # 獲取下一頁地址
        next_url = response.xpath('//ul[@class="pagination"]/li[last()]/a/@href').get()
        if not next_url:
            # 沒有下一頁地址結束爬蟲
            return
        else:
            # 將下一頁請求返回給調度器
            yield scrapy.Request(self.base_url + next_url, callback=self.parse)

日誌設置

需另寫文章進行記錄日誌學習

其他博文鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章