Python下用Scrapy和MongoDB構建爬蟲系統(1)

本文由 伯樂在線 - 木羊 翻譯,xianhu 校稿。未經許可,禁止轉載!
英文出處:realpython.com。歡迎加入翻譯小組html

這篇文章將根據真實的兼職需求編寫一個爬蟲,用戶想要一個Python程序從Stack Overflow抓取數據,獲取新的問題(問題標題和URL)。抓取的數據應當存入MongoDB。值得注意的是,Stack Overflow已經提供了可用於讀取一樣數據的API。可是用戶想要一個爬蟲,那就給他一個爬蟲。python

像往常同樣,在開始任何抓取工做前,必定要先查看該網站的使用/服務條款,要尊重 robots.txt 文件。抓取行爲應該遵照道德,不要在很短期內發起大量請求,從而致使網站遭受泛洪攻擊。對待那些你要抓取的網站,要像對待本身的同樣。git

安裝

咱們須要Scrapy庫(v0.24.4),以及用於在MongoDB中存儲數據的PyMongo庫(v2.7.2)。一樣須要安裝MongoDB。github

Scrapy

若是使用OSX或某種Linux,使用pip安裝Scrapy(激活命令行):web

1
$ pip install Scrapy

若是使用Windows的機器,你須要手動安裝一堆依賴庫(木羊吐槽:Win下也是有pip的po主你不要黑她,經測能夠用上面命令直接安裝成功)。請參考官方文檔詳細說明以及我建立的Youtube視頻。mongodb

一旦Scrapy安裝完畢,可在Python命令行中使用這個命令驗證:
數據庫

1
2
>>> import scrapy
>>>

若是沒有出錯,安裝就完成了。json

PyMongo

下一步,使用pip安裝PyMongo:api

1
$ pip install pymongo

如今能夠開始構建爬蟲了。dom

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"&gt<h3&gt。繼續,像這樣更新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中下載完整的代碼。若是有問題請跟貼評論。謝謝閱讀!

相關文章
相關標籤/搜索