在上一篇中,我們實現了一個基本網路爬蟲,它可以從StackOverflow上下載最新的問題,並將它們儲存在MongoDB資料庫中。在本文中,我們將對其擴充套件,使它能夠爬取每個網頁底部的分頁連結,並從每一頁中下載問題(包含問題標題和URL)。
在你開始任何爬取工作之前,檢查目標網站的使用條款並遵守robots.txt檔案。同時,做爬取練習時遵守道德,不要在短時間內向某個網站發起大量請求。像對待自己的網站一樣對待任何你將爬取的網站。
開始
有兩種可能的方法來接著從上次我們停下的地方繼續進行。
第一個方法是,擴充套件我們現有的網路爬蟲,通過利用一個xpath表示式從”parse_item”方法裡的響應中提取每個下一頁連結,並通過回撥同一個parse_item方法產生一個請求物件。利用這種方法,爬蟲會自動生成針對我們指定的連結的新請求,你可以在Scrapy文件這裡找到更多有關該方法的資訊。
另一個更簡單的方法是,使用一個不同型別的爬蟲—CrawlSpider(連結)。這是基本Spider的一個擴充套件版本,它剛好滿足我們的要求。
CrawlSpider
我們將使用與上一篇教程中相同的爬蟲專案,所以如果你需要的話可以從repo上獲取這些程式碼。
建立樣板
在“stack”目錄中,首先由crawl模板生成爬蟲樣板。
1 2 3 |
$ scrapy genspider stack_crawler stackoverflow.com -t crawl Created spider 'stack_crawler' using template 'crawl' in module: stack.spiders.stack_crawler |
Scrapy專案現在看起來應該像這樣:
1 2 3 4 5 6 7 8 9 10 |
├── scrapy.cfg └── stack ├── __init__.py ├── items.py ├── pipelines.py ├── settings.py └── spiders ├── __init__.py ├── stack_crawler.py └── stack_spider.py |
stack_crawler.py檔案內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# -*- coding: utf-8 -*- import scrapy from scrapy.contrib.linkextractors import LinkExtractor from scrapy.contrib.spiders import CrawlSpider, Rule from stack.items import StackItem class StackCrawlerSpider(CrawlSpider): name = 'stack_crawler' allowed_domains = ['stackoverflow.com'] start_urls = ['http://www.stackoverflow.com/'] rules = ( Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), ) def parse_item(self, response): i = StackItem() #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract() #i['name'] = response.xpath('//div[@id="name"]').extract() #i['description'] = response.xpath('//div[@id="description"]').extract() return i |
我們只需要對這個樣板做一些更新。
更新“start_urls”列表
首先,新增問題的第一個頁面連結到start_urls列表:
1 2 3 |
start_urls = [ 'http://stackoverflow.com/questions?pagesize=50&sort=newest' ] |
更新“rules”列表
接下來,我們需要新增一個正規表示式到“rules”屬性中,以此告訴爬蟲在哪裡可以找到下一個頁面連結:
1 2 3 4 |
rules = [ Rule(LinkExtractor(allow=r'questions?page=[0-9]&sort=newest'), callback='parse_item', follow=True) ] |
現在爬蟲能根據那些連結自動請求新的頁面,並將響應傳遞給“parse_item”方法,以此來提取問題和對應的標題。
如果你仔細檢視的話,可以發現這個正規表示式限制了它只能爬取前9個網頁,因為在這個demo中,我們不想爬取所有的176234個網頁。
更新“parse_item”方法
現在我們只需編寫如何使用xpath解析網頁,這一點我們已經在上一篇教程中實現過了,所以直接複製過來。
1 2 3 4 5 6 7 8 9 10 |
def parse_item(self, response): questions = response.xpath('//div[@class="summary"]/h3') for question in questions: item = StackItem() item['url'] = question.xpath( 'a[@class="question-hyperlink"]/@href').extract()[0] item['title'] = question.xpath( 'a[@class="question-hyperlink"]/text()').extract()[0] yield item |
這就是為爬蟲提供的解析程式碼,但是現在先不要啟動它。
新增一個下載延遲
我們需要通過在settings.py檔案中設定一個下載延遲來善待StackOverflow(和任何其他網站)。
1 |
DOWNLOAD_DELAY = 5 |
這告訴爬蟲需要在每兩個發出的新請求之間等待5秒鐘。你也很有必要做這樣的限制,因為如果你不這麼做的話,StackOverflow將會限制你的訪問流量,如果你繼續不加限制地爬取該網站,那麼你的IP將會被禁止。所有,友好點—要像對待自己的網站一樣對待任何你爬取的網站。
現在只剩下一件事要考慮—儲存資料。
MongoDB
上次我們僅僅下載了50個問題,但是因為這次我們要爬取更多的資料,所有我們希望避免向資料庫中新增重複的問題。為了實現這一點,我們可以使用一個MongoDB的 upsert方法,它意味著如果一個問題已經存在資料庫中,我們將更新它的標題;否則我們將新問題插入資料庫中。
修改我們前面定義的MongoDBPipeline:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class MongoDBPipeline(object): def __init__(self): connection = pymongo.Connection( settings['MONGODB_SERVER'], settings['MONGODB_PORT'] ) db = connection[settings['MONGODB_DB']] self.collection = db[settings['MONGODB_COLLECTION']] def process_item(self, item, spider): for data in item: if not data: raise DropItem("Missing data!") self.collection.update({'url': item['url']}, dict(item), upsert=True) log.msg("Question added to MongoDB database!", level=log.DEBUG, spider=spider) return item |
為簡單起見,我們沒有優化查詢,也沒有處理索引值,因為這不是一個生產環境。
測試
啟動爬蟲!
1 |
$ scrapy crawl questions |
現在你可以坐下來,看著你的資料庫漸漸充滿資料。
結論
你可以從Github庫下載整個原始碼,也可以在下面評論或提問。