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 版權宣告:本文為博主原創文章,轉載請附上博文連結!