這篇文章將根據真實的兼職需求編寫一個爬蟲,使用者想要一個Python程式從Stack Overflow抓取資料,獲取新的問題(問題標題和URL)。抓取的資料應當存入MongoDB。值得注意的是,Stack Overflow已經提供了可用於讀取同樣資料的API。但是使用者想要一個爬蟲,那就給他一個爬蟲。
像往常一樣,在開始任何抓取工作前,一定要先檢視該網站的使用/服務條款,要尊重 robots.txt 檔案。抓取行為應該遵守道德,不要在很短時間內發起大量請求,從而導致網站遭受泛洪攻擊。對待那些你要抓取的網站,要像對待自己的一樣。
安裝
我們需要Scrapy庫(v0.24.4),以及用於在MongoDB中儲存資料的PyMongo庫(v2.7.2)。同樣需要安裝MongoDB。
Scrapy
如果使用OSX或某種Linux,使用pip安裝Scrapy(啟用命令列):
1 |
$ pip install Scrapy |
如果使用Windows的機器,你需要手動安裝一堆依賴庫(木羊吐槽:Win下也是有pip的po主你不要黑她,經測可以用上面命令直接安裝成功)。請參考官方文件詳細說明以及我建立的Youtube視訊。
一旦Scrapy安裝完畢,可在Python命令列中使用這個命令驗證:
1 2 |
>>> import scrapy >>> |
如果沒有出錯,安裝就完成了。
PyMongo
下一步,使用pip安裝PyMongo:
1 |
$ pip install pymongo |
現在可以開始構建爬蟲了。
Scrapy工程
先建立一個新的Scrapy工程:
1 |
$ scrapy startproject stack |
這條命令建立了許多檔案和資料夾,其中包含一套有助於你快速開始的基本模板:
1 2 3 4 5 6 7 8 |
├── scrapy.cfg └── stack ├── __init__.py ├── items.py ├── pipelines.py ├── settings.py └── spiders └── __init__.py |
提取資料
items.py檔案用於定義儲存“容器”,用來儲存將要抓取的資料。
StackItem()類繼承自Item (文件),主要包含一些Scrapy已經為我們建立好的預定義物件:
1 2 3 4 5 6 |
import scrapy class StackItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() pass |
新增一些想要收集的項。使用者想要每條問題的標題和URL。那麼,照這樣更新items.py:
1 2 3 4 5 |
from scrapy.item import Item, Field class StackItem(Item): title = Field() url = Field() |
建立蜘蛛
在“spiders”目錄下建立一個名為stack_spider.py的檔案。這裡是見證奇蹟發生的地方—-比如在這裡告訴Scrapy怎麼去找到我們想要的指定資料。正如你想的那樣,對於每一個獨立的網頁,stack_spider.py都是不同的。
我們從定義一個類開始,這個類繼承Scrapy的Spider,並新增一些必須的屬性:
1 2 3 4 5 6 7 8 9 |
from scrapy import Spider class StackSpider(Spider): name = "stack" allowed_domains = ["stackoverflow.com"] start_urls = [ "http://stackoverflow.com/questions?pagesize=50&sort=newest", ] |
最初一些變數的含義很容易理解(文件):
- 定義蜘蛛的名字。
allowed_domains
包含構成許可域的基礎URL,供蜘蛛去爬。start_urls
是一個URL列表,蜘蛛從這裡開始爬。蜘蛛從start_urls中的URL下載資料,所有後續的URL將從這些資料中獲取。
XPath選擇器
接下來,Scrapy使用XPath選擇器在一個網站上提取資料。也就是說,我們可以通過一個給定的XPath選擇HTML資料的特定部分。正如Scrapy所稱,“XPath是一種選擇XML節點的語言,也可以用於HTML。”
使用Chrome的開發者工具,可以很容易找到一個特定的Xpath。簡單地檢查一個特定的HTML元素,複製XPath,然後修改(如有需要)。
開發者工具同時為使用者提供在JavaScript控制檯測試XPath選擇器的功能,使用$x,如$x("//img")
:
繼續,通過定義的XPath告訴Scrapy去哪裡尋找資訊。在Chrom中導航至Stack Overflow網址,尋找XPath選擇器。
右鍵點選第一條問題,選擇“插入元素”:
現在從<div class="summary">, //*[@id="question-summary-27624141"]/div[2]
中抓取XPath,然後在JavaScript控制檯測試它:
也許你會說,這隻選擇了一條問題。現在需要改變XPath去抓取所有的問題。有什麼想法?很簡單://div[@class="summary"]/h3
。
什麼意思呢?本質上,這條XPath是說:抓取<div>
的子樹中所有這一類<h3>
元素的總集。在JavaScript控制檯中測試XPath。
請注意我們不會使用Chrome開發者工具的實際輸出。在大多數案例中,這些輸出僅僅是一個參考,便於直接找到能用的XPath。
現在更新stack_spider.py指令碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from scrapy import Spider from scrapy.selector import Selector class StackSpider(Spider): name = "stack" allowed_domains = ["stackoverflow.com"] start_urls = [ "http://stackoverflow.com/questions?pagesize=50&sort=newest", ] def parse(self, response): questions = Selector(response).xpath('//div[@class="summary"]/h3') |
提取資料
我們仍然需要解析和抓取想要的資料,它符合<div class="summary"><h3>
。繼續,像這樣更新stack_spider.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from scrapy import Spider from scrapy.selector import Selector from stack.items import StackItem class StackSpider(Spider): name = "stack" allowed_domains = ["stackoverflow.com"] start_urls = [ "http://stackoverflow.com/questions?pagesize=50&sort=newest", ] def parse(self, response): questions = Selector(response).xpath('//div[@class="summary"]/h3') for question in questions: item = StackItem() item['title'] = question.xpath( 'a[@class="question-hyperlink"]/text()').extract()[0] item['url'] = question.xpath( 'a[@class="question-hyperlink"]/@href').extract()[0] yield item |
我們將遍歷問題,從抓取的資料中分配標題和URL的值。一定要利用Chrome開發者工具的JavaScript控制檯測試XPath的選擇器,例如$x('//div[@class="summary"]/h3/a[@class="question-hyperlink"]/text()')
和 $x('//div[@class="summary"]/h3/a[@class="question-hyperlink"]/@href')
。
測試
準備好第一次測試了嗎?只要簡單地在“stack”目錄中執行下面命令:
1 |
$ scrapy crawl stack |
隨著Scrapy堆疊跟蹤,你應該看到50條問題的標題和URL輸出。你可以用下面這條小命令輸出一個JSON檔案:
1 |
$ scrapy crawl stack -o items.json -t json |
我們已經基於要尋找的資料實現了爬蟲。現在需要將抓取的資料存入MongoDB。
在MongoDB中儲存資料
每當有一項返回,我們想驗證資料,然後新增進一個Mongo集合。
第一步是建立一個我們計劃用來儲存所有抓取資料的資料庫。開啟settings.py,指定管道然後加入資料庫設定:
1 2 3 4 5 6 |
ITEM_PIPELINES = ['stack.pipelines.MongoDBPipeline', ] MONGODB_SERVER = "localhost" MONGODB_PORT = 27017 MONGODB_DB = "stackoverflow" MONGODB_COLLECTION = "questions" |
管道管理
我們建立了爬蟲去抓取和解析HTML,而且已經設定了資料庫配置。現在要在pipelines.py中通過一個管道連線兩個部分。
連線資料庫
首先,讓我們定義一個函式去連線資料庫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import pymongo from scrapy.conf import settings 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']] |
這裡,我們建立一個類,MongoDBPipeline(),我們有一個建構函式初始化類,它定義Mongo的設定然後連線資料庫。
處理資料
下一步,我們需要定義一個函式去處理被解析的資料:
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 |
import pymongo from scrapy.conf import settings from scrapy.exceptions import DropItem from scrapy import log 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): valid = True for data in item: if not data: valid = False raise DropItem("Missing {0}!".format(data)) if valid: self.collection.insert(dict(item)) log.msg("Question added to MongoDB database!", level=log.DEBUG, spider=spider) return item |
我們建立一個資料庫連線,解包資料,然後將它存入資料庫。現在再測試一次!
測試
再次,在“stack”目錄下執行下面命令:
1 |
$ scrapy crawl stack |
萬歲!我們已經成功將我們爬下了的資料存入資料庫:
總結
這是一個用Scrapy爬取網頁的簡單示例。真實兼職工作需要能跟蹤分頁連結的指令碼,用CrawlSpider(文件)抓取每一個頁面,非常容易實現。自己動手實現下,在下面Github倉庫連結中寫一個評論,快速檢視程式碼。需要幫助?從這個指令碼開始,它已經很接近完成了。然後檢視第二部分,它包含完整的解決方案。
你可以從Github repo中下載完整的程式碼。如果有問題請跟貼評論。謝謝閱讀!
新年快樂