本文由 伯樂在線 - 木羊 翻譯,xianhu 校稿。未經許可,禁止轉載!
英文出處:realpython.com。歡迎加入翻譯小組。
這篇文章將根據真實的兼職需求編寫一個爬蟲,用戶想要一個Python程序從Stack Overflow抓取數據,獲取新的問題(問題標題和URL)。抓取的數據應當存入MongoDB。值得注意的是,Stack Overflow已經提供了可用于讀取同樣數據的API。但是用戶想要一個爬蟲,那就給他一個爬蟲。
像往常一樣,在開始任何抓取工作前,一定要先查看該網站的使用/服務條款,要尊重 robots.txt 文件。抓取行為應該遵守道德,不要在很短時間內發起大量請求,從而導致網站遭受泛洪攻擊。對待那些你要抓取的網站,要像對待自己的一樣。
我們需要Scrapy庫(v0.24.4),以及用于在MongoDB中存儲數據的PyMongo庫(v2.7.2)。同樣需要安裝MongoDB。
如果使用OSX或某種linux,使用pip安裝Scrapy(激活命令行):
1 | $ pip install Scrapy |
如果使用Windows的機器,你需要手動安裝一堆依賴庫(木羊吐槽:Win下也是有pip的po主你不要黑她,經測可以用上面命令直接安裝成功)。請參考官方文檔詳細說明以及我創建的Youtube視頻。
一旦Scrapy安裝完畢,可在Python命令行中使用這個命令驗證:
1 2 | >>> import scrapy>>> |
如果沒有出錯,安裝就完成了。
下一步,使用pip安裝PyMongo:
1 | $ pip install pymongo |
現在可以開始構建爬蟲了。
先創建一個新的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 scrapyclass 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, Fieldclass 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 Spiderclass 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將從這些數據中獲取。接下來,Scrapy使用XPath選擇器在一個網站上提取數據。也就是說,我們可以通過一個給定的XPath選擇HTML數據的特定部分。正如Scrapy所稱,“XPath是一種選擇xml節點的語言,也可以用于HTML。”
使用Chrome的開發者工具,可以很容易找到一個特定的Xpath。簡單地檢查一個特定的HTML元素,復制XPath,然后修改(如有需要)。

開發者工具同時為用戶提供在javaScript控制臺測試XPath選擇器的功能,使用$x,如$x("http://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 Spiderfrom scrapy.selector import Selectorclass 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 Spiderfrom scrapy.selector import Selectorfrom stack.items import StackItemclass 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。
每當有一項返回,我們想驗證數據,然后添加進一個Mongo集合。
第一步是創建一個我們計劃用來保存所有抓取數據的數據庫。打開settings.py,指定管道然后加入數據庫設置:
1 2 3 4 5 6 | ITEM_PIPELINES = ['stack.pipelines.MongoDBPipeline', ]MONGODB_SERVER = "localhost"MONGODB_PORT = 27017MONGODB_DB = "stackoverflow"MONGODB_COLLECTION = "questions" |
管道管理
我們建立了爬蟲去抓取和解析HTML,而且已經設置了數據庫配置。現在要在pipelines.py中通過一個管道連接兩個部分。
連接數據庫
首先,讓我們定義一個函數去連接數據庫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import pymongofrom scrapy.conf import settingsclass 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 pymongofrom scrapy.conf import settingsfrom scrapy.exceptions import DropItemfrom scrapy import logclass 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中下載完整的代碼。如果有問題請跟貼評論。謝謝閱讀!
新聞熱點
疑難解答