1. 程式人生 > >scrapy簡單入門

scrapy簡單入門

scrapy 是一個用 python 語言編寫的,為了爬取網站資料,提取結構性資料而編寫的應用框架。

環境 本文使用的環境: python 3.5.2  pip 9.0.1  作業系統: Ubuntu 16.04

目標爬取網站:區塊鏈幣圈網

pythton 環境搭建 在官網下載 Ubuntu 環境下的python3.5的安裝包,安裝,安裝完成後,檢查一下 python 的安裝情況,一般pyhton安裝的時候,pip 也是一起安裝好的,如果沒有安裝完全,再將 pip 也一起安裝好。

虛擬環境搭建 現在Ubuntu預設是安裝 python2.7 的,避免兩個環境之間切換的麻煩,我們安裝 python 虛擬環境來解決這個問題。

pip install virtualenv pip install virtualwrapper pip list # 檢視已安裝 1 2 3 virtualenv 能夠通過根據不同的 python 版本建立對應不同版本的虛擬環境,virtualwrapper 能夠方便的在不同的虛擬環境之間進行切換。安裝完成之後,下面我們建立一個 python3.5.2 版本的虛擬環境

source /usr/local/bin/virtualwrapper.sh #這個與 windows 不一樣,需要先執行一下指令碼才能生效,大家可以開啟這個檔案看一下 # 建立一個名為 py3Scrapy 的虛擬環境 mkvirtualenv py3Scrapy -p /usr/bin/python3.5 # workon 檢視建立好的虛擬環境,虛擬環境的儲存路徑可以通過 `VIRTUALENV_PYTHON` 來配置 workon workon py3Scrapy # 進入選擇的虛擬環境 1 2 3 4 5 6 如下圖所示: 

python的版本也能檢視得到,進入虛擬環境之後,在shell前面會出現虛擬環境的名稱,退出虛擬環境

deactivate 1 好了,建立好環境之後,現在來開始我們的 scrapy 之旅吧。

scrapy 環境搭建 scrapy 是基於 twisted 框架的,大家會發現,安裝 scrapy 的時候,會需要安裝很多包。

pip install scrapy 1 使用 pip 進行安裝,方便,但是這種預設的安裝方式,實在官網下載安裝包來進行安裝的,比較慢,大家可以使用豆瓣源來進行安裝

pip install scrapy -i https://pypi.douban.com/simple 1 這種方式,下載會非常的快,安裝其他的包都可以使用這種方法,但是,如果使用豆瓣源安裝的時候,提示找不到符合版本的安裝包,那就使用第一種辦法進行下載安裝,因為豆瓣源可能沒有官網那麼及早更新。

因為每個人的環境都可能存在差異,安裝過程中會出現一些問題。當如果報錯,twisted 安裝失敗的時候,建議從官網下載 twisted 安裝包,自行進行安裝,安裝完成之後,再接著繼續上面 scrapy 的安裝,安裝完成之後,檢查一些安裝結果

scrapy -h 1 使用 scrapy 獲取某一個文章的資訊 好了,環境準備好之後,接下來我們來分析一下伯樂線上的文章網頁結構

分析伯樂線上某一篇文章的網頁結構和url 伯樂線上網站不需要我們先登入,然後才能訪問其中的內容,所以不需要先模擬登入,直接就能訪問網頁。伯樂線上地址為 https://www.biiquan.com,這上面的文章質量還是不錯的,大家有時間可以看看。

我們隨便找一篇文章試圖來分析一下,比如 http://blog.jobbole.com/111469/,F12進入瀏覽器除錯視窗,從全文分析,比如我們想獲取文章標題,文章內容,文章建立時間,點贊數,評論數,收藏數,文章所屬類別標籤,文章出處等資訊 

使用 scrapy shell 的方法獲取解析網頁資料 開啟文章連結,我們獲取到的是一個html頁面,那麼如何獲取上面所說的那些資料呢,本文通過 CSS 選擇器來獲取(不瞭解 CSS selector的小夥伴可以先去熟悉一下 http://www.w3school.com.cn/cssref/css_selectors.asp)。 scrape 為我們提供了一個 shell 的環境,可以方便我們進行除錯和實驗,驗證我們的css 表示式能夠成功獲取所需要的值。下面啟動 scrapy shell

scrapy shell "http://blog.jobbole.com/111469/" 1 scrapy 將會幫助我們將http://blog.jobbole.com/111469/這個連結的資料捕獲,現在來獲取一下文章標題,在瀏覽器中找到文章標題,inspect element 審查元素,如下圖所示:

文章標題為王垠:如何掌握所有的程式語言,從上圖獲知,這個位於一個 class 名為 entry-header 的 div 標籤下的子標籤 h1 中,那我們在 scrapy shell 通過 css 選擇器來獲取一下,如下圖所示:

仔細檢視上圖,注意一些細節。通過 response.css 方法,返回的結果是一個 selector,不是字串,在這個 selector 的基礎上可以繼續使用 css 選擇器。通過 extract() 函式獲取提取的標題內容,返回結果是一個 list,注意,這裡是一個 list ,仍然不是字串 str,使用 extract()[0] 返回列表中的第一個元素,即我們需要的標題。

但是,如果標題沒有獲取到,或者選擇器返回的結果為空的話,使用 extract()[0] 就會出錯,因為試圖對一個空連結串列進行訪問,這裡使用 extract_first() 方法更加合適,可是使用一個預設值,當返回結果為空的時候,返回這個預設值

extract_first("")   # 預設值為 "" 1 此處僅僅是將 title 標題作為一個例子進行說明,其他的就不詳細進行解釋了,主要程式碼如下所示:

    title = response.css(".entry-header h1::text").extract()[0]     match_date = re.match("([0-9/]*).*",                           response.css(".entry-meta-hide-on-mobile::text").extract()[0].strip())     if match_date:         create_date = match_date.group(1)

    votes_css = response.css(".vote-post-up h10::text").extract_first()     if votes_css:         vote_nums = int(votes_css)     else:         vote_nums = 0

    ma_fav_css = re.match(".*?(\d+).*",                           response.css(".bookmark-btn::text").extract_first())     if ma_fav_css:         fav_nums = int(ma_fav_css.group(1))     else:         fav_nums = 0

    ma_comments_css = re.match(".*?(\d+).*",                                response.css("a[href='#article-comment'] span::text").extract_first())     if ma_comments_css:         comment_nums = int(ma_comments_css.group(1))     else:         comment_nums = 0

    tag_lists_css = response.css(".entry-meta-hide-on-mobile a::text").extract()     tag_lists_css = [ele for ele in tag_lists_css if not ele.strip().endswith('評論')]     tags = ','.join(tag_lists_css)

    content = response.css(".entry *::text").extract() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 解釋一下 create_date,通過獲取到的值,存在其他非時間的資料,通過 re.match 使用正則表示式來提取時間。

好了,所有需要的值都提取成功後,下面通過 scrapy 框架來建立我們的爬蟲專案。

建立爬蟲專案 開始我們的爬蟲專案

scrapy startproject ArticleSpider 1 scrapy 會為我們建立一個名為 ArticleSpider 的專案  進入到 ArticleSpider 目錄,使用basic模板建立

scrapy genspider jobbole blog.jobbole.com 1 建立完成之後,我們使用 pycharm 這個IDE開啟我們建立的爬蟲專案,目錄結構如下所示: 

├── ArticleSpider │   ├── items.py │   ├── middlewares.py │   ├── pipelines.py │   ├── __pycache__ │   ├── settings.py │   ├── spiders │   │   ├── __init__.py │   │   ├── jobbole.py │   │   └── __pycache__ └── scrapy.cfg 1 2 3 4 5 6 7 8 9 10 11 我們可以在 items.py 裡面定義資料儲存的格式,在 middlewares.py 定義中介軟體,在 piplines.py 裡面處理資料,儲存到檔案或者資料庫中等。在 jobbole.py 中對爬取的頁面進行解析。

下面,我們首先需要做的,就是利用我們編寫的 css 表示式,獲取我們提取的文章的值。在 jobbole.py 中,我們看到

class JobboleSpider(scrapy.Spider):     name = 'jobbole'     allowed_domains = ['blog.jobbole.com']     start_urls = ['http://blog.jobbole.com/all-posts/']

    def parse(self, response):     pass 1 2 3 4 5 6 7 scrapy 為我們建立了一個 JobboleSpider 的類,name 是爬蟲專案的名稱,同時定義了域名以及爬取的入口連結。scrapy 初始化的時候,會初始化 start_urls 入口連結列表,然後通過 start_requests 返回 Request 物件進行下載,呼叫 parse 回撥函式對頁面進行解析,提取需要的值,返回 item。

所以,我們需要做的,就是將我們在上一小節編寫的程式碼放在 parse 函式中,同時,將 start_urls 的值,改為上面我們在 scrapy shell 爬取的頁面的地址http://blog.jobbole.com/111469/,因為我們這裡還沒有講到通過 item 獲取我們提取的值,此處你可以通過 print() 函式將值進行列印。在 shell 中啟動爬蟲(先進入我們的工程目錄)

scrapy crawl jobbole 1 既然我們使用了 pycharm 這個IDE,那麼我們就不用 shell 來啟動爬蟲,在 ArticleSpider 目錄下建立一個 main.py 檔案

from scrapy.cmdline import execute import sys import os

sys.path.append(os.path.dirname(os.path.abspath(__file__))) execute(["scrapy", "crawl", "jobbole"]) 1 2 3 4 5 6 上面的程式碼,就是將當前專案路徑加入到 path 中,然後通過呼叫scrapy 命令列來啟動我們的工程。然後,通過設定斷點除錯,一步一步檢視我們的提取的變數的值是否正確。

注意:啟動之前,將 settings.py 中的 ROBOTSTXT_OBEY 這個引數設定為 False

這樣,我們就爬取到了伯樂線上的這一篇文章了。

擴充套件,爬取所有的文章 既然我們已經能夠獲取到某一篇文章的資料,那麼下面就來獲取所有文章的連結。

擴充套件一:獲取所有 url 連結 伯樂線上所有文章連結的入口地址為 http://blog.jobbole.com/all-posts/,通過瀏覽器進入除錯模式檢視文章列表的連結,如下圖所示

文章連結是在 id 為 archive 的 div 標籤下的子 div 標籤之下, class 為 post-thumb,這個下面的子標籤 a 的 href 屬性,仍使用上面說的 scrapy shell 的方法,如下圖所示 

可以看出,獲得了當前頁面所有的文章的 url,這僅僅是當前頁面的所有 url,我們還需要獲取下一頁的 url,然後通過下一頁的 url 進入到下一頁,獲取下一頁的所有文章的 url,依次類推,知道爬取完所有的文章 url。

在文章列表的最後,有翻頁,分析如下

下一頁是 class 為 next page-numbers 的 a 標籤中,如下圖 

既然現在所有的 url 都能夠獲取到了,那麼現在我們將 jobbole.py 中的 parse 函式修改一下

def parse(self, response):     post_nodes = response.css("#archive .floated-thumb .post-thumb")    # a selector, 可以在這個基礎上繼續做 selector

    for post_node in post_nodes:         post_url = post_node.css("a::attr(href)").extract_first("")         yield Request(url=parse.urljoin(response.url, post_url),                       callback=self.parse_detail)

    # 必須考慮到有前一頁,當前頁和下一頁連結的影響,使用如下所示的方法     next_url = response.css("span.page-numbers.current+a::attr(href)").extract_first("")      if next_url:          yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse)

def parse_detail(self, response): """作為回撥函式,在上面呼叫""" title = response.css(".entry-header h1::text").extract()[0]     match_date = re.match("([0-9/]*).*",                           response.css(".entry-meta-hide-on-mobile::text").extract()[0].strip())     if match_date:         create_date = match_date.group(1)

    votes_css = response.css(".vote-post-up h10::text").extract_first()     if votes_css:         vote_nums = int(votes_css)     else:         vote_nums = 0

    ma_fav_css = re.match(".*?(\d+).*",                           response.css(".bookmark-btn::text").extract_first())     if ma_fav_css:         fav_nums = int(ma_fav_css.group(1))     else:         fav_nums = 0

    ma_comments_css = re.match(".*?(\d+).*",                                response.css("a[href='#article-comment'] span::text").extract_first())     if ma_comments_css:         comment_nums = int(ma_comments_css.group(1))     else:         comment_nums = 0

    tag_lists_css = response.css(".entry-meta-hide-on-mobile a::text").extract()     tag_lists_css = [ele for ele in tag_lists_css if not ele.strip().endswith('評論')]     tags = ','.join(tag_lists_css)

    # cpyrights = response.css(".copyright-area").extract()     content = response.css(".entry *::text").extract() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 1. 獲取文章列表頁中的文章url,交給 scrapy 下載後並進行解析,即呼叫 parse 函式解析  2. 然後獲取下一頁的文章 url,按照1 2 迴圈

對於 parse 函式,一般做三種事情  a. 解析返回的資料 response data  b. 提取資料,生成 ITEM  c. 生成需要進一步處理 URL 的 Request 物件 

某些網站中,url 僅僅只是一個字尾,需要將當前頁面的url+字尾進行拼接,使用的是 parse.urljoin(base, url),如果 urljoin 中的 url 沒有域名,將使用base進行拼接,如果有域名,將不會進行拼接,此函式在 python3 的 urllib 庫中。Request(meta引數):meta引數是一個字典{},作為回撥函式的引數

這樣,我們就獲得了所有的文章

擴充套件二:使用item,並儲存圖片到本地 上一小節提到了, parse 函式提取資料之後,生成 item,scrapy 會通過 http 將 item 傳到 pipeline 進行處理,那麼這一小節,我們使用 item 來接收 parse 提取的資料。在 items.py 檔案中,定義一個我們自己的資料類 JobBoleArticleItem,並繼承 scrapy.item 類

class JobBoleArticleItem(scrapy.Item):     title = scrapy.Field()          # Field()能夠接收和傳遞任何型別的值,類似於字典的形式     create_date = scrapy.Field()    # 建立時間     url = scrapy.Field()            # 文章路徑     front_img_url_download = scrapy.Field()     fav_nums = scrapy.Field()       # 收藏數     comment_nums = scrapy.Field()   # 評論數     vote_nums = scrapy.Field()      # 點贊數     tags = scrapy.Field()           # 標籤分類 label     content = scrapy.Field()        # 文章內容     object_id = scrapy.Field()      # 文章內容的md5的雜湊值,能夠將長度不定的 url 轉換成定長的序列 1 2 3 4 5 6 7 8 9 10 11 Field() 物件,能夠接收和傳遞任何型別的值,看原始碼,就能發現,Field() 類繼承自 dict 物件,具有字典的所有屬性。

注意,在上面定義的類中,我們增加了一個新的成員變數 front_img_url_download,這是儲存的是文章列表中,每一個文章的圖片連結。我們需要將這個圖片下載到本地環境中。既然使用了 item 接收我們提取的資料,那麼 parse 函式就需要做相應的改動

def parse(self, response):     post_nodes = response.css("#archive .floated-thumb .post-thumb")    # a selector, 可以在這個基礎上繼續做 selector

    for post_node in post_nodes:         post_url = post_node.css("a::attr(href)").extract_first("")         img_url = post_node.css("a img::attr(src)").extract_first("")         yield Request(url=parse.urljoin(response.url, post_url),                       meta={"front-image-url":img_url}, callback=self.parse_detail)

    # 必須考慮到有前一頁,當前頁和下一頁連結的影響,使用如下所示的方法     next_url = response.css("span.page-numbers.current+a::attr(href)").extract_first("")      if next_url:          yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse) 1 2 3 4 5 6 7 8 9 10 11 12 13 同時,解析函式 parse_detail 也需要修改,將資料儲存到我們的item中,只需要新增下面的部分就可

    front_img_url = response.meta.get("front-image-url", "")     article_item = JobBoleArticleItem() # 例項化 item 物件     # 賦值 item 物件     article_item["title"] = title     article_item["create_date"] = create_date     article_item["url"] = response.url     article_item["front_img_url_download"] = [front_img_url] # 這裡傳遞的需要是列表的形式,否則後面儲存圖片的時候,會出現型別錯誤,必須是可迭代物件     article_item["fav_nums"] = fav_nums     article_item["comment_nums"] = comment_nums     article_item["vote_nums"] = vote_nums     article_item["tags"] = tags     # article_item["cpyrights"] = cpyrights     article_item["content"] = ''.join(content)      # 取出的 content 是一個 list ,存入資料庫的時候,需要轉換成字串     article_item["object_id"] = gen_md5(response.url)     yield article_item 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 這裡,parse 函式成功生成了我們定義的 item 物件,將資料傳遞到 pipeline。那麼,圖片連結已經獲取到了,我們如下將圖片下載下來呢。

解釋一下上面程式碼中的 front-img-url,這個是在 parse 函式中作為引數 meta 傳遞給 Request() 函式,回撥函式呼叫 parse_detail,返回的 response 物件中的 meta 成員,將包含這個元素, meta 就是一個字典, response.meta.get(“front-image-url”) 將獲取到我們傳遞過來的圖片url

scrapy 提供了一個 ImagesPipeline 類,可直接用於圖片操作,只需要我們在 settings.py 檔案中進行配置即可。

在 settings.py 中,有一個配置引數為 ITEM_PIPELINE,這其實就是一個字典,當需要用到 pipeline 時,就需要在這個字典中進行配置,字典中存在的, scrapy 才會使用。字典中的 key 就是 pipeline 的類名,後面的數字表示優先順序,數字越小表示越先呼叫,越大越靠後。既然我們現在需要使用到 scrapy 提供的圖片下載功能,那麼需要在這個字典中配置 ImagesPipeline

ITEM_PIPELINES = {    'scrapy.pipelines.images.ImagesPipeline': 1, } 1 2 3 同時,還需要在 settings.py 中配置,item 中哪一個欄位是圖片 url,以及圖片需要存放什麼位置

IMAGES_URLS_FIELD = "front_img_url_download"     # ITEM 中的圖片 URL,用於下載 PROJECT_IMAGE_PATH = os.path.abspath(os.path.dirname(__file__))   # 獲取當前檔案所在目錄 IMAGES_STORE = os.path.join(PROJECT_IMAGE_PATH, "images")         # 下載圖片的儲存位置 1 2 3 這些引數,可以在 ImagesPipeline 類的原始碼中檢視到

注意:上面配置好後,上面的程式碼是在工程路徑下面建立一個 images 的目錄,用於儲存圖片,執行 main.py,可能會出現如下錯誤: no module named PIL,這是因為圖片操作需要 pillow 庫,只需要安裝即可  pip install pillow,快速安裝,就按照我上面說的豆瓣源的方法。 還可能出現”ValueError: Missing scheme in request url: h”的錯誤,這是因為圖片操作,要求 front_img_url_download 的值為 list 或者可以迭代的物件,所以我們在 parse 函式中給 item 賦值的時候, front_img_url_download 就是賦值的 list 物件

好了,這些注意了之後,應該能夠下載圖片了。

擴充套件三:使用 itemloader 相信大家已經發現,雖然使用了 item,但是使用 css selecotor,我們的 parse 函式顯得很長,而且,當資料量越來越大之後,一大堆的 css 表示式是很難維護的。在加上正則表示式的提取,程式碼會顯得很臃腫。這裡,給大家推薦是用 itemloader。itemloader 可以看成是一個容器。

首先,在 items.py 中,我們需要定義一個繼承自 ItemLoader 的類

class ArticleItemLoader(ItemLoader):     """     自定義 ItemLoader, 就相當於一個容器     """     # 這裡表示,輸出獲取的 ArticleItemLoader 提取到的值,都是 list 中的第一個值     # 如果有的預設不是取第一個值,就在 Field() 中進行修改     default_output_processor = TakeFirst() 1 2 3 4 5 6 7 將預設輸出函式定為 TakeFirst(),即取結果 list 中的第一個值,定義了 ItemLoader 類之後,需要修改 jobbole.py 中的 parse_detail 函數了,現在就不再直接使用 css selector 了,使用 itemloader 中的 css 進行資料提取,新的 parse_detail 如下所示:

def parse_detail(self, response):     front_img_url = response.meta.get("front-image-url", "")     item_loader = ArticleItemLoader(item=JobBoleArticleItem(), response=response)     article_item_loader = JobBoleArticleItem()     item_loader.add_css("title", ".entry-header h1::text")  # 通過 css 選擇器獲取值     item_loader.add_value("url", response.url)     item_loader.add_css("create_date", ".entry-meta-hide-on-mobile::text")     item_loader.add_value("front_img_url_download", [front_img_url])     item_loader.add_css("fav_nums", ".bookmark-btn::text")     item_loader.add_css("comment_nums", "a[href='#article-comment'] span::text")     item_loader.add_css("vote_nums", ".vote-post-up h10::text")     item_loader.add_css("tags", ".entry-meta-hide-on-mobile a::text")     item_loader.add_css("content", ".entry *::text")     item_loader.add_value("object_id", gen_md5(response.url))     # item_loader.add_xpath()     # item_loader.add_value()     article_item_loader = item_loader.load_item()     yield article_item_loader 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 這樣,資料提取就全部交給了 itemloader 來執行了。程式碼整體都簡潔和工整了很多。ItemLoader 有三個方法用於提取資料,分別是 add_css(), add_xpath(), add_value(),前兩個分別是 css 選擇器和 xpath 選擇器,如果是值,就直接使用 add_value() 即可。

最後 load_item() 函式,將根據上面提供的規則進行資料解析,每一個解析的值都是以 list 結果的形式呈現,同時,將結果賦值 item。

但是,大家應該已經發現,之前我們直接使用 css selector 提取資料的時候,對於某些資料,需要使用正則表示式進行匹配才能獲取所需的值,這裡什麼都沒做,僅僅是通過 itemloader 提取了資料而已。所以,我們還需要重新定義我們的 item 類,這些操作在 item 中進行處理。修改 items.py 中的 JobBoleArticleItem 類,具體如下:

class JobBoleArticleItem(scrapy.item):     title = scrapy.Field()     create_date = scrapy.Field(     # 建立時間         input_processor = MapCompose(get_date),         output_processor = Join("")     )     url = scrapy.Field()            # 文章路徑     front_img_url_download = scrapy.Field(    # 文章封面圖片路徑,用於下載,賦值時必須為陣列形式         # 預設 output_processor 是 TakeFirst(),這樣返回的是一個字串,不是 list,此處必須是 list         # 修改 output_processor         output_processor = MapCompose(return_value)     )     front_img_url = scrapy.Field()     fav_nums = scrapy.Field(        # 收藏數         input_processor=MapCompose(get_nums)     )     comment_nums = scrapy.Field(    # 評論數         input_processor=MapCompose(get_nums)     )     vote_nums = scrapy.Field(       # 點贊數         input_processor=MapCompose(get_nums)     )     tags = scrapy.Field(           # 標籤分類 label         # 本身就是一個list, 輸出時,將 list 以 commas 逗號連線         input_processor = MapCompose(remove_comment_tag),         output_processor = Join(",")     )     content = scrapy.Field(        # 文章內容         # content 我們不是取最後一個,是全部都要,所以不用 TakeFirst()         output_processor=Join("")     )     object_id = scrapy.Field()      # 文章內容的md5的雜湊值,能夠將長度不定的 url 轉換成定長的序列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 input_processor 對傳入的值進行預處理, output_processor 對處理後的值按照規則進行處理和提取,比如 TakeFirst() 就是對處理的結果取第一個值。

input_processor = MapCompose(func1, func2, func3, ...) 這行程式碼,說明的是, Item 傳入的這個欄位的值,將會分別呼叫 MapCompose 中的所有傳入的方法進行逐個處理,這個方法也是可以是 lambda 的匿名函式。

因為上面定義 ArticleItemLoader 類的時候,使用了預設的 default_output_processor,如果不想使用預設的這個方法,就在 Field() 中,使用 output_processor 引數覆蓋預設的方法,哪怕什麼都不做,也不會使用預設方法獲取資料了。對上面那些方法定義如下:

def get_nums(value):     """     通過正則表示式獲取 評論數,點贊數和收藏數     """     re_match = re.match(".*?(\d+).*", value)     if re_match:         nums = (int)(re_match.group(1))     else:         nums = 0

    return nums

def get_date(value):     re_match = re.match("([0-9/]*).*?", value.strip())     if re_match:         create_date = re_match.group(1)     else:         create_date = ""     return create_date

def remove_comment_tag(value):     """     去掉 tag 中的 “評論” 標籤     """     if "評論" in value:         return ""     else:         return value

def return_value(value):     """     do nothing, 只是為了覆蓋 ItemLoader 中的 default_processor     """     return value 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 千萬注意:這些方法,每一個最後,都必須有 return,否則程式到後面將獲取不到這個欄位的資料,再次訪問這個欄位的時候,就會報錯。

擴充套件四:將資料匯出到 json 檔案中 好了,既然已經將資料通過 ItemLoader 獲取到了,那麼我們現在就將資料從 pipeline 輸出到 json 檔案中。

將資料以 json 格式輸出,可以通過 json 庫的方法,也可以使用 scrapy.exporters 的方法。

json 庫 我們已經知道,對資料的處理,scrapy 是在 pipeline 中進行的,所以,我們需要在 pipelines.py 中定義我們對資料的匯出操作。建立一個新類

class JsonWithEncodingPipeline(object):     """     處理 item 資料,儲存為json格式的檔案中     """     def __init__(self):         self.file = codecs.open('article.json', 'w', encoding='utf-8')

    def process_item(self, item, spider):         lines = json.dumps(dict(item), ensure_ascii=False) + '\n'   # False,才能夠在處理非acsii編碼的時候,不會出錯,尤其         #中文         self.file.write(lines)         return item     # 必須 return

    def spider_close(self, spider):         """         把檔案關閉         """         self.file.close() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 __init__() 構造物件的時候,就開啟檔案,scrapy 會呼叫 process_item() 函式對資料進行處理,在這個函式中,將資料以 json 的格式寫入檔案中。操作完成之後,將檔案關閉。思路很簡單。

scrapy.exporters 的方式 class JsonExporterPipeline(object):

    def __init__(self):         """         先開啟檔案,傳遞一個檔案         """         self.file = open('articleexporter.json', 'wb')         #呼叫 scrapy 提供的 JsonItemExporter匯出json檔案         self.exporter = JsonItemExporter(self.file, encoding="utf-8", ensure_ascii=False)         self.exporter.start_exporting()

    def spider_close(self, spider):         self.exporter.finish_exporting()         self.file.close()

    def process_item(self, item, spider):         self.exporter.export_item(item)         return item 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 scrapy.exporters 提供了幾種不同格式的檔案支援,能夠將資料輸出到這些不同格式的檔案中,檢視 JsonItemExporter 原始碼即可獲知

__all__ = ['BaseItemExporter', 'PprintItemExporter', 'PickleItemExporter',            'CsvItemExporter', 'XmlItemExporter', 'JsonLinesItemExporter',            'JsonItemExporter', 'MarshalItemExporter'] 1 2 3 這些就是 scrapy 支援的檔案。方法名稱都差不多,這算是 scrapy 執行 pipeline 的模式,只需要將邏輯處理放在 process_item(),scrapy 就會根據規則對資料進行處理。

當然,要想使我們寫的資料操作有效,別忘記了,在 settings.py 中進行配置

ITEM_PIPELINES = {

  'scrapy.pipelines.images.ImagesPipeline': 1,   'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,   'ArticleSpider.pipelines.JsonExporterPipeline':3, } 1 2 3 4 5 6 擴充套件五:將資料儲存到 MySQL 資料庫 前面介紹了將資料以 json 格式匯出到檔案,那麼將資料儲存到 MySQL 中,如何操作,相信大家已經差不多瞭然於胸了。這裡也介紹兩種方法,一種是通過 MySQLdb 的API來實現的資料庫存取操作,這種方法簡單,適合用與資料量不大的場合,如果資料量大,資料庫操作的速度跟不上資料解析的速度,就會造成資料擁堵。那麼使用第二種方法就更好,使用 twisted 框架提供的非同步操作方法,不會造成擁堵,速度更快。

既然是入 MySQL 資料庫,首先肯定是需要建立資料庫表了。表結構如下圖所示: 

上圖中有一個欄位的值,我沒有講述怎麼取,就是 front_img_path 這個值,大家在資料庫入庫的時候,直接用空置或者空字串填充即可。這個欄位是儲存圖片在本地儲存的路徑,這個需要在 ImagesPipe 的 item_completed(self, results, item, info) 方法中的 results 引數中獲取。

好了,資料庫表建立成功之後,下面就來將資料入庫了。

MySQLdb 的方法入庫 class MysqlPipeline(object):     def __init__(self):         # 連線資料庫         self.conn = MySQLdb.connect('192.168.0.101', 'spider', 'wuzhenyu', 'article_spider', charset="utf8", use_unicode=True)         self.cursor = self.conn.cursor()

    def process_item(self, item, spider):         insert_sql = """             insert into article(title, create_date, url, url_object_id, front_img_url, front_img_path, comment_nums,              fav_nums, vote_nums, tags, content) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s')         """ % (item["title"], item["create_date"], item["url"], item["object_id"],item["front_img_url"],                item["front_img_path"], item["comment_nums"], item["fav_nums"], item["vote_nums"], item["tags"],                item["content"])

        self.cursor.execute(insert_sql)         self.conn.commit()

    def spider_close(self, spider):         self.cursor.close()         self.conn.close() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 如果對 API 想了解的更多,就去閱讀 python MySQLdb 的相關API文件說明,當然,要想這個生效,首先得在 settings.py 檔案中將這個 pipeline 類加入 ITEM_PIPELINE 字典中

ITEM_PIPELINES = {

  'scrapy.pipelines.images.ImagesPipeline': 1,   'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,   'ArticleSpider.pipelines.JsonExporterPipeline':3,   'ArticleSpider.pipelines.MysqlPipeline': 4, } 1 2 3 4 5 6 7 通過 Twisted 框架提供的非同步方法入庫 class MysqlTwistedPipeline(object):     """     利用 Twisted API 實現非同步入庫 MySQL 的功能     Twisted 提供的是一個非同步的容器,MySQL 的操作還是使用的MySQLDB 的庫     """     def __init__(self, dbpool):         self.dbpool = dbpool

    @classmethod     def from_settings(cls, settings):         """         被 spider 呼叫,將 settings.py 傳遞進來,讀取我們配置的引數         模仿 images.py 原始碼中的 from_settings 函式的寫法         """         # 字典中的引數,要與 MySQLdb 中的connect 的引數相同         dbparams = dict(             host = settings["MYSQL_HOST"],             db = settings["MYSQL_DBNAME"],             user = settings["MYSQL_USER"],             passwd = settings["MYSQL_PASSWORD"],             charset = "utf8",             cursorclass = MySQLdb.cursors.DictCursor,             use_unicode = True         )

        # twisted 中的 adbapi 能夠將sql操作轉變成非同步操作         dbpool = adbapi.ConnectionPool("MySQLdb", **dbparams)         return cls(dbpool)

    def process_item(self, item, spider):         """         使用 twisted 將 mysql 操作程式設計非同步執行         """         query = self.dbpool.runInteraction(self.do_insert, item)         query.addErrback(self.handle_error) # handle exceptions

    def handle_error(self, failure):         """         處理非同步操作的異常         """         print(failure)

    def do_insert(self, cursor, item):         """         執行具體的操作,能夠自動 commit         """         print(item["create_date"])         insert_sql = """                     insert into article(title, create_date, url, url_object_id, front_img_url, front_img_path, comment_nums,                      fav_nums, vote_nums, tags, content) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s');                 """ % (item["title"], item["create_date"], item["url"], item["object_id"], item["front_img_url"],                        item["front_img_path"], item["comment_nums"], item["fav_nums"], item["vote_nums"], item["tags"],                        item["content"])

        # self.cursor.execute(insert_sql, (item["title"], item["create_date"], item["url"], item["object_id"],         #                                 item["front_img_url"], item["front_img_path"], item["comment_nums"],         #                                 item["fav_nums"], item["vote_nums"], item["tags"], item["content"]))         print(insert_sql)         cursor.execute(insert_sql) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 博主也是近一個多星期開始學習爬蟲的 scrapy 框架,對 Twisted 框架也不怎麼熟悉,上面的程式碼是一個例子,大家可以看下注釋,等以後瞭解更多會補充更多相關知識。

需要提到的是,上面定義的 from_settings(cls. settings) 這個類方法, scrapy 會從 settings.py  檔案中讀取配置進行載入,這裡將 MySQL 的一些配置資訊放在了 settings.py 檔案中,然後使用 from_settings 方法直接獲取,在 settings.py 中需要新增如下程式碼:

# MySQL params MYSQL_HOST = "" MYSQL_DBNAME = "article_spider" MYSQL_USER = "spider" MYSQL_PASSWORD = "" 本篇文章,主要以 scrapy 框架爬取伯樂線上文章為例,簡要介紹了 scrapy 爬取資料的一些方法,博主也是最近才開始學習爬蟲,有不對的地方還請大家能夠指正。

windows 中安裝環境與 Ubuntu 會有一些不一樣,而且如果使用的是 python3.x 版本,會要求 vc++ 的版本比較高,最好安裝的是 visual studio 2015 以上的版本。否則會很麻煩。

對於 python2.7版本,在windows中,可以安裝 VSForPython27.msi ,依賴的那些庫應該就不會再出錯了。 ---------------------  作者:貓步旅人  來源:CSDN  原文:https://blog.csdn.net/honglicu123/article/details/74906223  版權宣告:本文為博主原創文章,轉載請附上博文連結!