本文由 伯樂在線 - 木羊 翻譯,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
若是使用OSX或某種Linux,使用pip安裝Scrapy(激活命令行):web
1
|
$ pip install Scrapy
|
若是使用Windows的機器,你須要手動安裝一堆依賴庫(木羊吐槽:Win下也是有pip的po主你不要黑她,經測能夠用上面命令直接安裝成功)。請參考官方文檔詳細說明以及我建立的Youtube視頻。mongodb
一旦Scrapy安裝完畢,可在Python命令行中使用這個命令驗證:
數據庫
1
2
|
>>>
import
scrapy
>>>
|
若是沒有出錯,安裝就完成了。json
下一步,使用pip安裝PyMongo:api
1
|
$ pip install pymongo
|
如今能夠開始構建爬蟲了。dom
先建立一個新的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將從這些數據中獲取。接下來,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。
每當有一項返回,咱們想驗證數據,而後添加進一個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中下載完整的代碼。若是有問題請跟貼評論。謝謝閱讀!