Python下用Scrapy和MongoDB構建爬蟲系統(1)
這篇文章將根據真實的兼職需求編寫一個爬蟲,使用者想要一個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(啟用命令列):
Python1 | $pip install Scrapy |
如果使用Windows的機器,你需要手動安裝一堆依賴庫(木羊吐槽:Win下也是有pip的po主你不要黑她,經測可以用上面命令直接安裝成功)。請參考官方文件詳細說明以及我建立的Youtube視訊。
一旦Scrapy安裝完畢,可在Python命令列中使用這個命令驗證:
12 | >>>importscrapy>>> |
如果沒有出錯,安裝就完成了。
PyMongo
下一步,使用pip安裝PyMongo:
Python1 | $pip install pymongo |
現在可以開始構建爬蟲了。
Scrapy工程
先建立一個新的Scrapy工程:
Python1 | $scrapy startproject stack |
這條命令建立了許多檔案和資料夾,其中包含一套有助於你快速開始的基本模板:
Python12345678 | ├──scrapy.cfg└──stack├──__init__.py├──items.py├──pipelines.py├──settings.py└──spiders└──__init__.py |
提取資料
items.py檔案用於定義儲存“容器”,用來儲存將要抓取的資料。
StackItem()類繼承自Item (文件),主要包含一些Scrapy已經為我們建立好的預定義物件:
Python123456 | importscrapyclassStackItem(scrapy.Item):# define the fields for your item here like:# name = scrapy.Field()pass |
新增一些想要收集的項。使用者想要每條問題的標題和URL。那麼,照這樣更新items.py:
Python12345 | fromscrapy.item importItem,FieldclassStackItem(Item):title=Field()url=Field() |
建立蜘蛛
在“spiders”目錄下建立一個名為stack_spider.py的檔案。這裡是見證奇蹟發生的地方—-比如在這裡告訴Scrapy怎麼去找到我們想要的指定資料。正如你想的那樣,對於每一個獨立的網頁,stack_spider.py都是不同的。
我們從定義一個類開始,這個類繼承Scrapy的Spider,並新增一些必須的屬性:
Python123456789 | fromscrapy importSpiderclassStackSpider(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指令碼:
Python12345678910111213 | fromscrapy importSpiderfromscrapy.selector importSelectorclassStackSpider(Spider):name="stack"allowed_domains=["stackoverflow.com"]start_urls=["http://stackoverflow.com/questions?pagesize=50&sort=newest",]defparse(self,response):questions=Selector(response).xpath('//div[@class="summary"]/h3') |
提取資料
我們仍然需要解析和抓取想要的資料,它符合<div class="summary"><h3>
。繼續,像這樣更新stack_spider.py:
1234567891011121314151617181920212223 | fromscrapy importSpiderfromscrapy.selector importSelectorfromstack.items importStackItemclassStackSpider(Spider):name="stack"allowed_domains=["stackoverflow.com"]start_urls=["http://stackoverflow.com/questions?pagesize=50&sort=newest",]defparse(self,response):questions=Selector(response).xpath('//div[@class="summary"]/h3')forquestion inquestions:item=StackItem()item['title']=question.xpath('a[@class="question-hyperlink"]/text()').extract()[0]item['url']=question.xpath('a[@class="question-hyperlink"]/@href').extract()[0]yielditem |
我們將遍歷問題,從抓取的資料中分配標題和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”目錄中執行下面命令:
Python1 | $scrapy crawl stack |
隨著Scrapy堆疊跟蹤,你應該看到50條問題的標題和URL輸出。你可以用下面這條小命令輸出一個JSON檔案:
Python1 | $scrapy crawl stack-oitems.json-tjson |
我們已經基於要尋找的資料實現了爬蟲。現在需要將抓取的資料存入MongoDB。
在MongoDB中儲存資料
每當有一項返回,我們想驗證資料,然後新增進一個Mongo集合。
第一步是建立一個我們計劃用來儲存所有抓取資料的資料庫。開啟settings.py,指定管道然後加入資料庫設定:
123456 | ITEM_PIPELINES=['stack.pipelines.MongoDBPipeline', ]MONGODB_SERVER="localhost"MONGODB_PORT=27017MONGODB_DB="stackoverflow"MONGODB_COLLECTION="questions" |
管道管理
我們建立了爬蟲去抓取和解析HTML,而且已經設定了資料庫配置。現在要在pipelines.py中通過一個管道連線兩個部分。
連線資料庫
首先,讓我們定義一個函式去連線資料庫:
Python1234567891011121314 | importpymongofromscrapy.conf importsettingsclassMongoDBPipeline(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的設定然後連線資料庫。
處理資料
下一步,我們需要定義一個函式去處理被解析的資料:
Python12345678910111213141516171819202122232425262728 | importpymongofromscrapy.conf importsettingsfromscrapy.exceptionsimportDropItemfromscrapy importlogclassMongoDBPipeline(object):def__init__(self):connection=pymongo.Connection(settings['MONGODB_SERVER'],settings['MONGODB_PORT'])db=connection[settings['MONGODB_DB']]self.collection=db[settings['MONGODB_COLLECTION']]defprocess_item(self,item,spider):valid=Truefordata initem:ifnotdata:valid=FalseraiseDropItem("Missing {0}!".format(data))ifvalid:self.collection.insert(dict(item)) |