摘要:本文介紹了Scrapy的基礎爬取流程,也是最重要的部分
Scrapy的爬取流程
Scrapy的爬取流程可以概括為一個方程式:UR2IM,其含義如下圖所示
URL:Scrapy的運行就從那個你想要爬取的網站地址開始,當你想要驗證用xpath或其他解析器來解析這個網頁時,可以使用Scrapy shell工具來進行分析,譬如
$ scrapy shell http://web:9312/properties/property_000000.html
現在你就可以開始驗證了
Request和Response:在上面使用Scrapy shell的過程中可以發現,只要我們輸入了一個URL,它就可以自動發送一個GET請求並獲取返回結果。request是一個把url封裝好的對象,response則是一個把網頁返回結果封裝好的對象,response.body的值是網頁的源代碼,response.url是網頁的url地址,還有更多相關的屬性
Items:我們要爬取一個網頁的時候並不是只把源代碼下載下來就完事了,還需要提取網頁中的相關信息,譬如網頁的標題,網頁的發布時間等等內容,而這些內容使用面向對象的技術,封裝成一個Item對象,然后從網頁中提取信息來填充這個Item
新建Scrapy工程
首先新建一個名為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上下載
編寫爬蟲
定義item
編輯items.py文件,在該文件中定義的item並不是一定要在每一個spider中填充,也不是要全部同時使用的,你可以隨意添加字段,並且在任何時候填充。
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()
定義spider
在項目的根目錄下根據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上
ItemLoader
對於上面混亂且難看的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十一個用來爬取特定網頁的類,包括了如何執行爬取動作(譬如如何跟蹤超鏈接)和如何從頁面中提取信息。換句話說,spider就是你用來定義對某個特定網站的爬取動作的工具,他的爬取循環類似於這樣:
1、 首先要將你指定的初始URL封裝成Request對象,並且要指定在網頁返回該請求的內容后應該用哪個函數來處理網頁的內容。
默認情況下,會調用start_requests()函數,對start_urls中的URL分別生成一個Request對象,並且指定parse()函數作為回調函數(回調函數指的是callback變量指定的函數)
2、 在回調函數中,可以處理response變量,然后返回一個已經提取好數據的字典或者是一個Item對象,或者是Request對象(在這個Request對象中,也可以指定一個回調函數,同樣地,處理完這個Request之后生成的response就會傳送到回調函數中處理)
3、 在回調函數中,也可以提取網頁內容,通常使用Selector(也可以使用BeautifulSoup,lxml或者其他你熟悉的機制)來生成包含了解析數據的item
4、 最后,這些從spider中返回的item通常會存入到數據庫中,或者寫入到文件中
即使上述流程適用於大部分的spider,但是仍然有不同的spider運行不同的默認流程,更多的信息就查閱這里: