摘要:本文介紹了Scrapy的基礎爬取流程,也是最重要的部分html
Scrapy的爬取流程能夠歸納爲一個方程式:UR2IM,其含義以下圖所示python
URL:Scrapy的運行就從那個你想要爬取的網站地址開始,當你想要驗證用xpath或其餘解析器來解析這個網頁時,可使用Scrapy shell工具來進行分析,譬如git
$ scrapy shell http://web:9312/properties/property_000000.html
如今你就能夠開始驗證了github
Request和Response:在上面使用Scrapy shell的過程當中能夠發現,只要咱們輸入了一個URL,它就能夠自動發送一個GET請求並獲取返回結果。request是一個把url封裝好的對象,response則是一個把網頁返回結果封裝好的對象,response.body的值是網頁的源代碼,response.url是網頁的url地址,還有更多相關的屬性web
Items:咱們要爬取一個網頁的時候並非只把源代碼下載下來就完事了,還須要提取網頁中的相關信息,譬如網頁的標題,網頁的發佈時間等等內容,而這些內容使用面嚮對象的技術,封裝成一個Item對象,而後從網頁中提取信息來填充這個Itemshell
首先新建一個名爲properties的Scrapy工程數據庫
$ scrapy startproject properties $ cd properties $ tree . ├── properties │ ├── __init__.py │ ├── items.py │ ├── pipelines.py │ ├── settings.py │ └── spiders │ └── __init__.py └── scrapy.cfg 2 directories, 6 files
注意,本系列文章的源代碼均可以從github上下載express
編輯items.py文件,在該文件中定義的item並非必定要在每個spider中填充,也不是要所有同時使用的,你能夠隨意添加字段,而且在任什麼時候候填充。json
from scrapy.item import Item, Field class PropertiesItem(Item): # Primary fields title = Field() price = Field() description = Field() address = Field() image_urls = Field() # Calculated fields,這些字段須要運算後才獲得,後面的文章會解析,暫時不用管 images = Field() location = Field() # Housekeeping fields,這些字段用來在調試時顯示相關信息 url = Field() project = Field() spider = Field() server = Field() date = Field()
在項目的根目錄下根據basic模板建立一個名爲basic的spider,後面的web指的是spider的可運行的域名數組
scrapy genspider –t basic basic web
固然能夠本身手寫一個spider,可是從模板裏建立能夠省去很多的時間和減小出錯機率,查看其餘模板的命令:
scrapy genspider -l
由模板建立的basic.py文件的代碼以下
# -*- coding: utf-8 -*- import scrapy class BasicSpider(scrapy.Spider): name = "basic" allowed_domains = ["web"] start_urls = ( 'http://www.web/', ) def parse(self, response): pass
把抓取到的網頁存入item中(文件名:basic.py)
import scrapy from properties.items import PropertiesItem class BasicSpider(scrapy.Spider): name = "basic" allowed_domains = ["web"] start_urls = ( 'http://web:9312/properties/property_000000.html', ) def parse(self, response): item = PropertiesItem() item['title'] = response.xpath( '//*[@itemprop="name"][1]/text()').extract() item['price'] = response.xpath( '//*[@itemprop="price"][1]/text()').re('[.0-9]+') item['description'] = response.xpath( '//*[@itemprop="description"][1]/text()').extract() item['address'] = response.xpath( '//*[@itemtype="http://schema.org/' 'Place"][1]/text()').extract() item['image_urls'] = response.xpath( '//*[@itemprop="image"][1]/@src').extract() return item
啓動爬蟲後,看到控制檯輸出以下信息,說明爬取成功
$scrapy crawl basic DEBUG: Scraped from <200 http://...000.html> {'address': [u'Angel, London'], 'description': [u'website ... offered'], 'image_urls': [u'../images/i01.jpg'], 'price': [u'334.39'], 'title': [u'set unique family well']}
將上面的輸出保持到各類文件中:
scrapy crawl basic –o item.json scrapy crawl basic –o item.jl #json格式的文件會把整個json對象保存在一個巨大的數組裏,意味着若是你要保存的數據量有1GB,那麼在你解析這些數據以前,就必須用1GB的內存來保存整個文件。而jl格式會在每一行上放一個json對象,因此讀取起來效率會更高 scrapy crawl basic –o item.xml scrapy crawl basic –o item.csv scrapy crawl basic –o ftp://user:pass@ftp.scrapybook.com/items.json 直接保存到ftp上
對於上面混亂且難看的parse函數,可使用Item Loader來處理,而且Item Loader提供更多的功能(http://doc.scrapy.org/en/latest/topics/loaders.html),最重要的是可以對經過xpath提取出來的信息進行處理,譬如去掉空格和替換字符等,而後將清洗後的數據再寫入item中。對數據的清洗是經過processor來實現的,很經常使用的一個processor就是MapCompose()函數,該函數將python函數或者lambda表達式做爲參數(參數個數無限制),而後按順序執行這些函數來產生最終的結果。譬如MapCompose(unicode.strip, float)首先將xpath提取的信息去掉空格,再將其轉換爲float格式
basic.py源代碼文件:
修改上面的basic.py文件,使得代碼更加簡潔和一目瞭然
import datetime import urlparse import socket import scrapy from scrapy.loader.processors import MapCompose, Join from scrapy.loader import ItemLoader from properties.items import PropertiesItem class BasicSpider(scrapy.Spider): name = "basic" allowed_domains = ["web"] # Start on a property page start_urls = ( 'http://web:9312/properties/property_000000.html', ) def parse(self, response): """ This function parses a property page. @url http://web:9312/properties/property_000000.html @returns items 1 @scrapes title price description address image_urls @scrapes url project spider server date """ # Create the loader using the response l = ItemLoader(item=PropertiesItem(), response=response) # Load fields using XPath expressions l.add_xpath('title', '//*[@itemprop="name"][1]/text()', MapCompose(unicode.strip, unicode.title)) l.add_xpath('price', './/*[@itemprop="price"][1]/text()', MapCompose(lambda i: i.replace(',', ''), float), re='[,.0-9]+') l.add_xpath('description', '//*[@itemprop="description"][1]/text()', MapCompose(unicode.strip), Join()) l.add_xpath('address', '//*[@itemtype="http://schema.org/Place"][1]/text()', MapCompose(unicode.strip)) l.add_xpath('image_urls', '//*[@itemprop="image"][1]/@src', MapCompose(lambda i: urlparse.urljoin(response.url, i))) # Housekeeping fields 能夠經過add_value函數直接向item填充數據 l.add_value('url', response.url) l.add_value('project', self.settings.get('BOT_NAME')) l.add_value('spider', self.name) l.add_value('server', socket.gethostname()) l.add_value('date', datetime.datetime.now()) return l.load_item()
注意在上面的parse函數中有這樣的一段註釋
""" This function parses a property page. @url http://web:9312/properties/property_000000.html @returns items 1 @scrapes title price description address image_urls @scrapes url project spider server date """
這些以@開頭的稱爲contract,相似於單元測試,假如你在一個月前寫了這個spider,如今想測試這個spider是否仍然正確運行,就可使用這些contract。上面的這些contract的意思是:檢查該url,你應該獲得一個包含了下面列出來的字段的item
運行scrapy check來檢查contract
$ scrapy check basic ---------------------------------------------------------------- Ran 3 contracts in 1.640s OK
若是spider的代碼出錯了,或者xpath表達式已通過期了(網站發生了更新),那麼就會獲得測試失敗的結果,雖然出錯信息並不詳盡,但這是最快的檢查手段
在上面用模板定義的spider十一個用來爬取特定網頁的類,包括瞭如何執行爬取動做(譬如如何跟蹤超連接)和如何從頁面中提取信息。換句話說,spider就是你用來定義對某個特定網站的爬取動做的工具,他的爬取循環相似於這樣:
一、 首先要將你指定的初始URL封裝成Request對象,而且要指定在網頁返回該請求的內容後應該用哪一個函數來處理網頁的內容。
默認狀況下,會調用start_requests()函數,對start_urls中的URL分別生成一個Request對象,而且指定parse()函數做爲回調函數(回調函數指的是callback變量指定的函數)
二、 在回調函數中,能夠處理response變量,而後返回一個已經提取好數據的字典或者是一個Item對象,或者是Request對象(在這個Request對象中,也能夠指定一個回調函數,一樣地,處理完這個Request以後生成的response就會傳送到回調函數中處理)
三、 在回調函數中,也能夠提取網頁內容,一般使用Selector(也可使用BeautifulSoup,lxml或者其餘你熟悉的機制)來生成包含了解析數據的item
四、 最後,這些從spider中返回的item一般會存入到數據庫中,或者寫入到文件中
即便上述流程適用於大部分的spider,可是仍然有不一樣的spider運行不一樣的默認流程,更多的信息就查閱這裏: