爬蟲0060:scrapy快速入門

b10l07發表於2018-01-17

爬蟲高階操作:Scrapy framework

章節內容

  1. scrapy概述
  2. scrapy安裝
  3. quick start 入門程式
  4. 核心API
  5. scrapy shell
  6. 深度爬蟲
  7. 請求和響應
  8. 中介軟體——下載中介軟體
  9. 常見設定操作

課程內容

1. scrapy 概述

官方網站:https://scrapy.org/,開啟官方網站,可以看到一段關於scrapy的描述

An open source and collaborative framework for extracting the data you need from websites.
In a fast, simple, yet extensible way.


Scrapy is an application framework for crawling web sites and 
extracting structured data which can be used for a wide range 
of useful applications, like data mining, information processing 
or historical archival.

Even though Scrapy was originally designed for web scraping, 
it can also be used to extract data using APIs (such as Amazon 
Associates Web Services) or as a general purpose web crawler.

Scrapy是一個為了爬取網站資料,提取結構性資料而編寫的應用框架。 可以應用在包括資料探勘,資訊處理或儲存歷史資料等一系列的程式中。

其最初是為了 頁面抓取 (更確切來說, 網路抓取 )所設計的, 也可以應用在獲取API所返回的資料(例如 Amazon Associates Web Services ) 或者通用的網路爬蟲。

2. scrapy 安裝

首先,確認我們的電腦上已經安裝瞭如下程式:

  • python2.7.+:windows直接安裝配置,ubuntu內建
  • [pip and setuptools] or [easy_install]:windows安裝包內建,ubuntu需要單獨安裝
  • lxml:一般linux系統中的ubuntu內建了,windows需要單獨安裝
  • OpenSSL:windows之外的系統預設自帶

執行命令執行安裝

pip install scrapy

在windows中,需要單獨安裝呼叫win32的模組,執行如下命令安裝

pip install pypiwin32

2. scrapy 入門程式

這一部分主要內容如下

  1. 建立一個scrapy專案
  2. 定義提取資料的資料Item
  3. 編寫採集資料的爬蟲程式
  4. 定義Pipline儲存提取到的資料

(1) 建立scrapy專案

執行下面的命令,建立第一個基於scrapy框架的爬蟲專案

scrapy startproject myspider

該命令會在當前目錄下建立如下的檔案結構

|-- myspider
    |-- scrapy.cfg
    |-- myspider/
        |-- __init__.py
        |-- items.py
        |-- pipeline.py
        |-- settings.py
        |-- spiders/
            |-- __init__.py
            ...

檔案詳細資訊:

  • scrapy.py:爬蟲專案公共配置檔案
  • myspider:爬蟲專案的python模組,以後的程式碼開發就在這個資料夾中
  • myspider/items.py:專案中的定義資料的模組item
  • myspider/pipeline.py:專案中資料儲存模組pipeline
  • myspider/settings.py:專案的設定檔案
  • myspider/spiders/..:專案中存放爬蟲程式的資料夾

(2) 定義採集資料物件:Item

Item是用來儲存爬取到資料的容器,是一個like dict物件,使用方式和python中的字典大同小異,scrapy提供了額外的保護機制避免出現拼寫錯誤出現的欄位未定義異常。

Item型別的建立可以基於scrapy.Item進行構建,然後通過scrapy.Field()構建型別的屬性,完成對採集資料的描述

首先根據需要從指定網站[智聯招聘]獲取到的資料對item進行物件ZhilianItem的建立,然後通過提取的資料[招聘崗位、薪水、釋出公司]通過scrapy.Field()來構建型別的屬性,編輯myspider/items.py內容如下:

# coding:utf-8
import scrapy


class ZhilianItem(scrapy.Item):
    '''
    基於scrapy.Item型別定義儲存智聯招聘資料的模型類
    '''    
    
    # 定義採集資料的屬性欄位
    job_name = scrapy.Field()
    salary = scrapy.Field()
    company = scrapy.Field()

通過型別對採集的資料進行封裝,開始入門就如同開始學習物件導向定義型別一樣,會感覺比較複雜,但是通過型別的封裝,可以統一進行資料管理,同時scrapy提供了更多的功能可以通過Item型別直接操作,爬蟲操作更加簡捷方便!

(3)編寫第一個爬蟲ZhilianSpider

spider爬蟲程式是開發人員編寫的用於從指定網站提取資料的型別

爬蟲類中會包含一個用於爬取資料的初始url地址,以及深度提取網頁中超連結的規則用於分析網頁中的內容,同時定義了提取生成Item的方法

通過繼承scrapy.Spider可以很方便的構建一個爬蟲處理類,型別中要包含如下三個屬性:

  • name:爬蟲程式的名稱,在一個scrapy專案中可能會存在多個爬蟲程式,名稱主要用於區別不同的爬蟲程式
  • start_urls:包含了爬蟲程式啟動時進行爬取的url列表,第一個採集的網頁是從其中的某個url中直接獲取,後續的url則是從初始url獲取到的資料中提取
  • parse():爬蟲的核心處理函式,在程式執行時自動呼叫,每個初始url完成下載後,自動封裝成response物件傳遞給parse()函式,函式中負責解析採集到的資料response.data,提取資料封裝成Item物件以及篩選進一步需要處理的url地址

建立[智聯招聘]爬蟲程式:myspider/spiders/zhilianspider.py

# coding:utf-8
# 引入scrapy模組
import scrapy


class ZhilianSpider(scrapy.Spider):
    '''
    智聯招聘爬蟲程式
    '''
    # 定義屬性
    name = "zlspider"
    # 定義域名限制
    allowed_domains = ['zhaopin.com']
    # 定義起始url地址
    start_urls = [
        'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=1',
        'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=2',
        'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=3',
        'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=4',
        'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=5',
    ]

    # 定義採集資料的函式
    def parse(self, response):
        # 儲存資料
        filename = response.url.split("&")[-1] + ".html"
        with open(filename, "w") as f:
            f.write(response.body)

接下來,進入爬蟲根目錄,執行下面的命令執行爬蟲程式

scrapy crawl zlspider

出現如下的資訊

(python2_lib) D:\resp_work\py_1709\back_cursor\S-scrapy\myspider>scrapy crawl zlspider

# 程式開始啟動~Scrapy 1.5.0 started
2018-01-15 18:09:15 [scrapy.utils.log] INFO: Scrapy 1.5.0 started (bot: myspider)
2018-01-15 18:09:15 [scrapy.utils.log] INFO: Versions: lxml 4.1.1.0, libxml2 2.9.5, cssselect 1.0.3, parsel 1.3.1, w3lib 1.18.0, Twisted
 17.9.0, Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:53:40) [MSC v.1500 64 bit (AMD64)], pyOpenSSL 17.5.0 (OpenSSL 1.1.0g  2 No
v 2017), cryptography 2.1.4, Platform Windows-10-10.0.16299

# 載入配置操作
2018-01-15 18:09:15 [scrapy.crawler] INFO: Overridden settings: {'NEWSPIDER_MODULE': 'myspider.spiders', 'SPIDER_MODULES': ['myspider.sp
iders'], 'ROBOTSTXT_OBEY': True, 'BOT_NAME': 'myspider'}
2018-01-15 18:09:15 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.logstats.LogStats',
 'scrapy.extensions.telnet.TelnetConsole',
 'scrapy.extensions.corestats.CoreStats']
 
 # 啟用下載中介軟體內建功能
2018-01-15 18:09:16 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
 
 # 啟用爬蟲中介軟體內建功能
2018-01-15 18:09:16 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
 'scrapy.spidermiddlewares.referer.RefererMiddleware',
 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
 'scrapy.spidermiddlewares.depth.DepthMiddleware']
 
 # 啟用Pipeline內建功能
2018-01-15 18:09:16 [scrapy.middleware] INFO: Enabled item pipelines:
[]

# 爬蟲程式啟動
2018-01-15 18:09:16 [scrapy.core.engine] INFO: Spider opened
2018-01-15 18:09:16 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2018-01-15 18:09:16 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2018-01-15 18:09:16 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302)

# 開始採集資料
to <GET http://sou.zhaopin.com/FileNotFound.htm> fr
om <GET http://sou.zhaopin.com/robots.txt>
2018-01-15 18:09:16 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/FileNotFound.htm> (referer: None)
2018-01-15 18:09:16 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&k
w=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=5> (referer: None)
2018-01-15 18:09:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&k
w=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=1> (referer: None)
2018-01-15 18:09:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&k
w=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=2> (referer: None)
2018-01-15 18:09:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&k
w=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=4> (referer: None)
2018-01-15 18:09:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&k
w=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=3> (referer: None)
2018-01-15 18:09:17 [scrapy.core.engine] INFO: Closing spider (finished)

# 回顯採集狀態
2018-01-15 18:09:17 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 2019,
 'downloader/request_count': 7,
 'downloader/request_method_count/GET': 7,
 'downloader/response_bytes': 241042,
 'downloader/response_count': 7,
 'downloader/response_status_count/200': 6,
 'downloader/response_status_count/302': 1,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2018, 1, 15, 10, 9, 17, 674000),
 'log_count/DEBUG': 8,
 'log_count/INFO': 7,
 'response_received_count': 6,
 'scheduler/dequeued': 5,
 'scheduler/dequeued/memory': 5,
 'scheduler/enqueued': 5,
 'scheduler/enqueued/memory': 5,
 'start_time': datetime.datetime(2018, 1, 15, 10, 9, 16, 319000)}
2018-01-15 18:09:17 [scrapy.core.engine] INFO: Spider closed (finished)

另外我們在爬蟲程式所在的目錄中,也看到對應的所有start_urls中包含的url地址所在的網頁全部被爬蟲採集到了本地。

那麼接下來,就是通過指定的方式篩選資料,將資料封裝在Item中進行後續的處理,scrapy提供了各種選擇器可以方便的在response.data中進行資料的提取,官方推薦也是專案中經常出現的選擇器如下

  • xpath(): 傳入xpath表示式,返回xpath所對應的節點的select list列表
  • css(): 傳入css表示式,返回表示式所對應的節點列表
  • extract(): 序列化節點並返回unicode字串
  • re(): 傳入正規表示式,進行資料的提取,返回unicode字串的list列表

注意:CSS vs XPath: 您可以僅僅使用CSS Selector來從網頁中 提取資料。不過, XPath提供了更強大的功能。其不僅僅能指明資料所在的路徑, 還能檢視資料: 比如,您可以這麼進行選擇: 包含文字 ‘Next Page’ 的連結 。 正因為如此,即使您已經瞭解如何使用 CSS selector, 我們仍推薦您使用XPath。

接下來,我們修改myspider/spiders.py/ZhilianSpider爬蟲程式,通過xpath提取Item中需要的資料

def parse(self, response):
    # 定義儲存資料的列表
    items = []
    
    for each in response.xpath("//div[@class='zhaopin']"):
        # 將我們得到的資料封裝到一個 `ZhaopinItem` 物件
        item = ZhaopinItem()
        #extract()方法返回的都是unicode字串
        job_name = each.xpath("p[1]/text()").extract()
        salary = each.xpath("p[2]/text()").extract()
        company = each.xpath("p[3]/text()").extract()

        #xpath返回的是包含一個元素的列表
        item['job_name'] = job_name[0]
        item['salary'] = salary[0]
        item['company'] = company[0]

        items.append(item)

    # 直接返回最後的所有資料
    return items

可以通過如下命令將資料在任意時候匯出成想要的結果:

# json格式,預設為Unicode編碼
scrapy crawl zlspider -o job.json

# json lines格式,預設為Unicode編碼
scrapy crawl zlspider -o job.jsonl

# csv 逗號表示式,可用Excel開啟
scrapy crawl zlspider -o job.csv

# xml格式
scrapy crawl zlspider -o job.xml

同時可以將資料直接通過協程的方式交給pipeline進行後續的資料篩選、驗證或者儲存資料的操作

from items import ZhaopinItem
..

def parse(self, response):
    for each in response.xpath("//div[@class='zhaopin']"):
        # 將我們得到的資料封裝到一個 `ZhaopinItem` 物件
        item = ZhaopinItem()
        #extract()方法返回的都是unicode字串
        job_name = each.xpath("p[1]/text()").extract()
        salary = each.xpath("p[2]/text()").extract()
        company = each.xpath("p[3]/text()").extract()

        #xpath返回的是包含一個元素的列表
        item['job_name'] = job_name[0]
        item['salary'] = salary[0]
        item['company'] = company[0]

        items.append(item)

        # yield資料給pipeline進行處理 
        yield item

(4) pipelines處理資料

當資料由spider採集完成時候,封裝在Item物件中通過yield資料交給pipelines進行處理,在pipelines中按照定義的順序執行Item物件的處理,每個Pipelines都是python中的型別,可以執行後續的資料篩選、驗證、儲存等操作

在實際開發過程中,參考官方文件,Item型別中預設定義如下方法:

  • open_spider(self, spider):當爬蟲程式啟動的時候呼叫
  • process_item(self, item, spider):當爬蟲處理完資料交給pipelines處理時呼叫,必須實現該方法
  • close_spider(self, spider):當爬蟲程式關閉時呼叫

如下:

# coding:utf-8

class SomePipeline():
    
    def __init__(self):
        # 可選:主要進行程式中資料初始化操作使用
        
    def open_spider(self, spider):
        # 可選,當爬蟲啟動時呼叫
        
    def process_item(self, item, spider):
        # 必須,當爬蟲程式yield item資料時呼叫
        
    def close_spider(self, spider):
        # 可選,當爬蟲程式關閉時呼叫

處理完成之後,需要修改爬蟲程式設定檔案settings.py中的PIPELINES配置項啟用Pipeline,同時通過一個0~1000之間的整數來定義執行的優先順序[值越小優先順序越高]

ITEM_PIPELINES = {
    'myspider.pipelines.SomePipeline': 200
}

重新開發我們的招聘爬蟲程式的pipelines處理模組

# coding:utf-8

class ZhaopinPipeline(object):
    
    def process_item(self, item, spider):
        # 這裡可以執行item中資料的驗證、儲存等工作
        print(item)
        return item

那麼,請思考,如何在pipelines中,將採集到的資料儲存到資料庫中進行記錄呢?

_編輯:大牧莫邪,未完待續,下一節更精彩~智聯招聘資料採集

相關文章