运行ggplot出现问题:no display name and no $DISPLAY environment variable

最近在用django做一个项目,关于数据可视化的。作图方面,之前用过百度的echart,这个是在前端直接调用的,这次想尝试在后台直接生成图,然后存在服务器上,之后前端再把图片显示出来就好了。

用的ggplot。

在windows下,用runserver测试,没问题。但是在服务器上,就跑不了了,出现了这样的提示:

tkinter.TclError: no display name and no $DISPLAY environment variable

查看了一下debug信息,发现貌似和Matplotlib有关,继续顺藤摸瓜。

找到了问题所在:http://matplotlib.org/faq/howto_faq.html#howto-webapp

官网的英文解释:

Many users report initial problems trying to use maptlotlib in web application servers, because by default matplotlib ships configured to work with a graphical user interface which may require an X11 connection. Since many barebones application servers do not have X11 enabled, you may get errors if you don’t configure matplotlib for use in these environments. Most importantly, you need to decide what kinds of images you want to generate (PNG, PDF, SVG) and configure the appropriate default backend. For 99% of users, this will be the Agg backend, which uses the C++ antigrain rendering engine to make nice PNGs

不想读完也没关系,大概说下怎么回事。

不同的系统有不同的用户图形接口,默认的接口在windows下跑是没有问题的,问题是我们很多的webapp都不在windows上跑,一般在linux上面,所以要更改它的默认配置,把模式更改成Agg。

纯代码解决方案

这也是大部分人在网上诸如stackoverflow的问答平台得到的解决方案,在引入pyplot、pylab之前,要先更改matplotlib的后端模式为”Agg”。直接贴代码吧!

# do this before importing pylab or pyplot
Import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot asplt

更改配置文件方案

就我的情况而言,上面那种解决方案似乎不适用,因为我是在django中使用的ggplot,它调用matplotlib的时机,我并不是很清楚。所以试了几次,都没有能把matplotlib.use('Agg')这句代码放在正确的位置上,因而也解决不了错误。

所以我转向了另一种解决方案,也是从官网启发而来。

好了,这里告诉我们,什么是backend:

http://matplotlib.org/faq/usage_faq.html#what-is-a-backend

然后这里告诉我们,怎么改配置文件:

http://matplotlib.org/users/customizing.html#customizing-matplotlib

看到下面这个地方:

matplotlibrc文件

没错,它的配置文件就是matplotlibrc,在哪里呢?不同系统不一样,我的系统是ubuntu,运行了命令whereis matlotlibrc,找到了。

matplotlibrc文件位置

编辑一下:

sudo vim /etc/matplotlibrc

找到backend这里,然后将其改成Agg,如下!

# The default backend; one of GTK GTKAgg GTKCairo GTK3Agg GTK3Cairo
# CocoaAgg MacOSX Qt4Agg Qt5Agg TkAgg WX WXAgg Agg Cairo GDK PS PDF SVG
# Template.
# You can also deploy your own backend outside of matplotlib by
# referring to the module name (which must be in the PYTHONPATH) as
# ‘module://my_backend’.
backend : Agg

保存,运行,错误消失。

java获取网页正文:WebCollector

WebCollector是一个无须配置、便于二次开发的JAVA爬虫框架(内核),它提供精简的的API,只需少量代码即可实现一个功能强大的爬虫。

Github上面更新到最新版本2.28,maven的中央仓库暂时只更新到2.09

2.09版本似乎没有把ContentExtractor集成进来,所以还是建议直接在github上先下载最新版本的jar,然后在buildpath里面引进来就行。

项目介绍:http://crawlscript.github.io/WebCollector/

项目github:https://github.com/CrawlScript/WebCollector

中文文档:https://github.com/CrawlScript/WebCollector/blob/master/README.zh-cn.md

来个简单的例子,对于中文url的支持,这个框架还是可以的。

import cn.edu.hfut.dmic.contentextractor.ContentExtractor;
import cn.edu.hfut.dmic.contentextractor.News;

public class WebExtraction {
    public static void main (String args[]) throws Exception{
        String url = "https://www.lookfor404.com/利用bootstrap的滚动监听+affix做一个侧导航landingpage/";
	String content = ContentExtractor.getContentByUrl(url);
	System.out.println("content:"+content);
	News news = ContentExtractor.getNewsByUrl(url);
	System.out.println("title:"+news.getTitle());
	System.out.println("content:"+news.getContent());
	}
}

输出结果如下:
webcollector例子输出结果

python获取网页正文:goose-extractor

做项目要用到提取网页的正文内容,于是在网上找找有什么好用的库。先测试一下,发现python就有现成的库,叫做goose-extractor。

事不宜迟,开始安装吧。

pip install goose-extractor

什么?出现问题了。

在安装依赖的lxml的时候,出现了以下的错误提示:

Microsoft\\Visual C++ for Python\\9.0\\VC\\Bin\\amd64\\cl.exe’ failedwith exitstatus 2

找不到好的解决方案,最后祭出大杀器。直接用编译好的。

先打开http://www.lfd.uci.edu/~gohlke/pythonlibs/

ctrl+F搜索lxml,然后下载它对应的whl文件,下载之后pip install xxx.whl即可安装!

安装完这个比较难缠的lxml之后,就方便了,重新pip install goose-extractor

等待安装成功。

使用非常简单,给出一个实例代码,抓取《https://www.lookfor404.com/简易python爬虫–爬取冷笑话/》的正文内容。

# -*- coding: utf-8 -*-
from goose import Goose
from goose.text import StopWordsChinese
url  = 'https://www.lookfor404.com/%e7%ae%80%e6%98%93python%e7%88%ac%e8%99%ab-%e7%88%ac%e5%8f%96%e5%86%b7%e7%ac%91%e8%af%9d/'
g = Goose({'stopwords_class': StopWordsChinese})
article = g.extract(url=url)
print article.cleaned_text

效果不错,截取一下控制台的输出:
goose-extractor提取网页正文结果

但是速度并不快,具体里面怎么实现的还没看,但是它要用到python的结巴分词,估计这个耗时久一点,另外还有一个问题,就是我<code>标签里面的内容,被过滤掉了,所以抓取技术文章,有点不合适,哈哈。

记录一下windows下panda的安装过程

搞大数据开发,就是和数据打交道,具体怎么处理数据,也需要深究,python有很多很好用的库,其中pandas是比较出名的。下面是简单的介绍:

Pandas是python的一个数据分析包,最初由AQR Capital Management于2008年4月开发,并于2009年底开源出来,目前由专注于Python数据包开发的PyData开发team继续开发和维护,属于PyData项目的一部分。Pandas最初被作为金融数据分析工具而开发出来,因此,pandas为时间序列分析提供了很好的支持。 Pandas的名称来自于面板数据(panel data)和python数据分析(data analysis)。panel data是经济学中关于多维数据集的一个术语,在Pandas中也提供了panel的数据类型。

官网:http://pandas.pydata.org/

我刚开始以为直接pip就能安装pandas了。事实证明我太天真了。下面还是记录一下我的安装过程吧。

利用pycharm直接安装pandas

pycharm的包管理工具挺好用的,直接可以在里面搜索到很多可以用的包,然后直接安装。安装pandas也不例外。

首先打开pycharm,直接在setting里面找到编译器的选项

pycharm中编译器设置

可以看到本地已经安装的包,点击右边的+号,就可以安装新的包了。想安装pandas,直接搜索pandas就是了,记得别少了s,是pandas

搜索后安装即可。pandas是依赖于别的包的,根据提示,如果还需要安装别的包,继续搜索安装就行。

在windows下用pip安装pandas

官网推荐的是直接使用Anoconda,它集成了pandas,可以直接使用。安装挺简单的,有windows下的安装包。

如果不想安装庞大的Anoconda,那就一步一步用pip来安装pandas。

如果直接pip install pandas,可能会出现一系列的问题,为了稳妥起见,我先把依赖包都一个个安装下来,最后再安装pandas,就没有问题了。

官网说pandas需要以下几个依赖:

setuptools
NumPy: 1.7.1 or higher
python-dateutil 1.5 or higher
pytz

一个个来。首先setuptools,这个和pip本身在新一点的python下是默认安装了的。我的python版本是2.7.11

然后pip install numpy

安装numpy

安装到一般,居然出错了。错误如下:

Microsoft Visual C++ 9.0 is required Unable to find vcvarsall.bat

原因:windows下使用pip安装包的时候需要机器装有vs2008,VS2012都不行,如果不想装VS2008的话,可以安装一个Micorsoft Visual C++ Compiler for Python 2.7的包。
下载地址:http://www.microsoft.com/en-us/download/details.aspx?id=44266

如果还是不行的话,就直接去官网找windows下的安装包。

官网:http://sourceforge.net/projects/numpy/files/

我找的1.9.2版本:http://sourceforge.net/projects/numpy/files/NumPy/1.9.2/

有windows下针对python2.7的安装包,文件名为numpy-1.9.2-win32-superpack-python2.7.exe

下载安装即可。

搞定了numpy,接下来安装python-dateutil,命令:pip install python-dateutil
很顺利。

然后安装pytz,命令:pip install pytz
也很顺利。

最后!直接安装pandas就行了,命令:pip install pandas

安装pandas

大功告成,真是折腾。

python爬虫–抓取ajax更新的内容

虽然很多情况下,直接对一个url发出请求,就能得到页面的源代码,但是我们还得考虑这么一种情况,就是有的网站为了用户体验,采用了ajax技术–即不刷新页面而改变页面内容,那么我们该怎么获取这些内容呢?

从本质出发,ajax技术也就是这么一个过程:利用js来发起一个post请求–然后接收返回的数据–js改变页面内容。在大多数情况下,我们都是点击一个按钮,然后页面就更新了。从原理上来看,点击某个按钮,就会发送一个post请求(当然还有滚动鼠标发送请求的),服务器再返回结果,再将结果直接更新到页面上。我们的思路是–用爬虫发送同样的post请求,从而获得返回来的数据。

简单点说,对于爬虫而言,在获取数据上,get和post并无太多异样,只是get的话,我们只需要对某个链接发送请求就行,而post,则需要一些表单内容。那么,我们怎么知道该post什么内容过去,又能获得什么响应呢?

以虎嗅网(http://www.huxiu.com/)为例,我用python写了一个爬取它利用ajax更新的数据。

我们打开它的首页,拉到最下面,发现有个“点击加载更多”的按钮,颜色很浅。

点击它,就会有新的内容出现。

虎嗅网底部

我们要做的,就是看看点击这个按钮会发生什么。

我这里以遨游浏览器为例,大多数浏览器(比如chrome,Firefox)应该都有类似的功能,在网页上,点击右键–审查元素,在弹出的窗口菜单栏点击“网络”一栏,在这里,我们可以监控一些get,post等行为。

刚开始是空的。

此时,回到网页上去,点击“点击加载更多”按钮。然后再回到开发者工具那里,会发现,出现了很多的请求:

点击加载更多

其中,有个请求的方法是POST,这就是我们要找的。点进去,可以看到http报头、预览、响应和cookies。

从中,我们可以获得一些头部信息,以及post的表单数据。

  • 头部信息

不同网站对头部信息的要求不同,有的不需要头部信息都能响应,有的则需要头部信息的一部分,虎嗅是不需要头部信息的。

  • 表单数据

从开发者工具里面看,虎嗅需要的表单数据有两个,一个是’huxiu_hash_code‘,这个是用来防止csrf的,一个是page,代表页数,我们把huxiu_hash_code复制下来。

构造一个请求过去,会得到一个json格式的响应,内容有msg,total_page,result和data,要的文章数据在data里面。我的程序把data里面的标题都提取出来,打印到屏幕下,下面贴上代码:

#-*- coding:utf-8 -*-
__author__ = '李鹏飞'
import urllib2
import urllib
import json
import re
url = 'http://www.huxiu.com/v2_action/article_list'
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
data = {'huxiu_hash_code':'322cf76eec54c4e275d7c8122028a3b2','page':2}
data = urllib.urlencode(data)
try:
    request = urllib2.Request(url=url,data=data)
    response = urllib2.urlopen(request)
    #解析json
    result = json.loads(response.read())
    print result['msg']
    content = result['data']
    #匹配文章标题
    pattern = re.compile('
<div class="mob-ctt">.*?target="_blank">(.*?)</a>',re.S)
    #虎嗅文章标题
    items = re.findall(pattern,content)
    for item in items:
        print item
except urllib2.URLError, e:
    if hasattr(e,"code"):
        print e.code
    if hasattr(e,"reason"):
        print e.reason

这样子的话,大部分网页就能抓了,管他是post还是get。

cloudera-quickstart-vm-集成了大数据平台的虚拟机镜像

前几天和朋友聊天,他告诉我有一个cloudera-quickstart,这是一个集成了大部分大数据组件的虚拟机镜像系统,反正就是–不用配置了,包括hadoop,hbase,hive,hue,spark,zookeeper等等,是cloudera定制的版本,还有cloudera的管理组件。我们只需要把镜像下下来,直接在visualbox或者vmvare里面运行就行。

于是我就自己尝试了一下vmvare的版本,不过出了问题,主要是内存不足。在这里也提醒一下想用quickstart-vm的朋友,你得保证你的电脑至少有8g的内存,配置越高越好,不然会很卡。

下面记录一下自己运行cloudera-quickstart-vm的vmvare版本

下载地址:http://www.cloudera.com/downloads.html

第一个就是quickstart-vm:

quickstart-vm下载

点击download下载。我下载的是vmvare的版本。PS:下载的时候可能会要求你登陆账号,注册一个就好。我得到的下载链接是:https://downloads.cloudera.com/demo_vm/vmware/cloudera-quickstart-vm-5.5.0-0-vmware.zip

整个zip大小4g多,本地用迅雷下载速度也快不到哪里去,于是我用百度网盘离线下载,秒下,然后再从百度云下到本地。

下载完成,解压。

打开vmvare,点击菜单栏上的文件–打开,找到刚才解压的路径,就能发现有一个vmx文件,打开它。

quickstart-vm虚拟机文件

打开之后,vmvare是这样的:

打开vmx文件的vmvare

先别急着运行,要先更改虚拟机的配置,你需要把这台虚拟机设置到至少8g内存(这意味着你的电脑需要至少需要8g内存,没有的话,会非常非常卡,而且一堆错误),处理器设置成至少两个。

vmvare内存和处理器的设置

这时候,可以启动这台虚拟机了。启动之后,它会自动启动火狐浏览器,打开一个初始页面:

quickstart初始界面

怎么进入管理界面呢?

运行桌面的cloudera express命令脚本即可。

如果出现以下错误,说明你的虚拟机没有设置到8g内存和两个处理器,请设置完毕再重新打开这个虚拟机。

quickstart错误

想强制启动的话,可以在终端运行以下命令:

sudo /home/cloudera/cloudera-manager --express --force

不出意外的话,启动成功了:

cloudera-quickstart管理组件启动成功

接下来可以去浏览器直接管理了。

打开内置的火狐,输入网址:http://quickstart.cloudera:7180

登录账号:cloudera
登录密码:cloudera

登陆之后,就可以看到管理界面了:

cloudera管理界面

可以看到,左边是整合的大数据工具,点击对应的下拉按钮,就可以启动,关闭,管理这些组件了。

PS:由于电脑内存太小,所以这个整合了所有单机版大数据工具的系统,我用的并不顺手,这里仅把过程记录下来,供大家参考。

PPS:还是乖乖搭环境去了

 

简易python爬虫–爬取冷笑话

今天闲来无事,就想接触一下爬虫的基本知识,搞大数据,没有数据是万万不可的,而数据的来源,有很大一部分是通过网络爬虫来抓取的。

关于爬虫,似乎python的框架会比较多,比如scrapy。不过刚开始接触,用python一些原生的库就可以完成一些简单的小爬虫程序了,就比如我刚刚写的冷笑话爬虫。

在这之前,得大概搞清楚爬虫的工作原理,其实也很简单。首先是对你的目标网站发出http请求,目标网站会返回一个html文档给你,也就是我们俗称的网页,原本,这些文档都是由一大堆标签构成的,但浏览器配合上css,js,就形成了我们看到的各种各样的网页。但我们的爬虫不需要这些外衣,我们只需要内容,所以爬虫接下来的工作是处理这个html文档,提取我们要的信息,然后继续爬取下一个网页,如此往复循环。

不多说,先上我写的一段python脚本,爬取<我们爱讲冷笑话>的随机页面,网址为:http://lengxiaohua.com/random,这个页面每次刷新都有20则不同的随机冷笑话,我的程序功能是:输入数字1到20看对应的笑话,输入new重新爬取另外20则新笑话,输入quit退出程序。代码如下:

#-*- coding:utf-8 -*-
__author__ = '李鹏飞'
import urllib2
import re

class randomJoke:

    #初始化方法
    def __init__(self):
        self.url = 'http://lengxiaohua.com/random'
        self.user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
        #初始化headers
        self.headers = { 'User-Agent' : self.user_agent }
        #笑话内容
        self.content = []

    #获取网页源代码
    def getSourceCode(self):
        try:
            request = urllib2.Request(url = self.url, headers=self.headers)
            response = urllib2.urlopen(request)
            sourceCode = response.read().decode('utf-8')
            return sourceCode
        except urllib2.URLError, e:
            if hasattr(e,"reason"):
                print u"网络错误...",e.reason
                return None

    #获取笑话
    def setContent(self):
        sourceCode = self.getSourceCode()
        if not sourceCode:
            print('获取网页内容失败~!')
            quit()
        pattern = re.compile(' <pre.*?js="joke_summary".*?"first_char">(.*?)</span>(.*?)</pre>

.*?class="user_info">.*?<a.*?>(.*?)</a>.*?

(.*?)

',re.S)
        items = re.findall(pattern,sourceCode)
        self.content = items
        print u"已经爬取源代码...正在解析源代码..."

    #返回笑话
    def getContent(self):
        return self.content

    #打印一则笑话
    def printAJoke(self,number):
        joke = self.content[number]
        print u"作者:%s" %(joke[2])
        print u'发表于:'+ joke[3]
        #item[0]和item[1]组成完整的内容
        print joke[0]+joke[1]

randomJoke = randomJoke()
notQuit = True
print u"你好,这里是随机笑话!"
print u"---------------------"
randomJoke.setContent()
print u"...."
print u"笑话池已经装满,20/20"
print u"输入1到20看笑话~~,输入quit退出,输入new重新爬取新笑话"
while notQuit:
    input = raw_input()
    if input == "quit" :
        print u"bye!"
        notQuit = False
    elif input == "new":
        randomJoke.setContent()  #重新抓取笑话内容
        print u"...."
        print u"笑话池已经装满,20/20"
        print u"输入1到20看笑话~~,输入quit退出,输入new重新爬取新笑话"
    else:
        input = int(input)
        randomJoke.printAJoke(input-1)
        print u"--------------------------------------------------"
        print u"输入1到20看笑话~~,输入quit退出,输入new重新爬取新笑话"
print u"您已经成功退出!"
quit()

保存这段脚本,运行,效果如下:

运行冷笑话爬虫

说明一下,有些笑话是带图片的,而我没有抓取图片,仅仅抓取了作者,发表日期,和内容。

下面对程序稍微进行一下解释:

引入了urllib2re两个模块,urllib2是用来发送http请求的,re是正则表达式的相关模块。

randomJoke是程序的主要部分,下面就所有的方法,进行简单的说明。

__init__方法

位置:9-15行

这个是初始化方法,定义了四个变量,url为目标网址;user_agent是我们发送http请求的时候伪造的头部的一个客户端信息;headers是头部信息,这里只把user_agent信息加进去就行了;content[]是一个列表,用来存放笑话内容。

getSourceCode方法

位置:18-27行

这个方法是用来获取网页的源代码的。在第20行,发起一个http请求,接着读取返回的源代码,并return源代码。

setContent方法

位置:30-44行

这个方法是比较关键的一个方法,主要是利用正则表达式匹配源代码,并把我们需要的内容爬取下来。第35-41行代码就是正则表达式的匹配模式,我们要抓取网页中的20个笑话,其中的.*?是匹配任意字符,多一个括号,即(.*?)表示匹配的内容是我们需要的内容,在这个片段当中,出现了4(.*?),因此,程序的第36行,item表示的是一个列表,这个列表的形式是这样的:[[[item1-1],[item1-2],[item1-3],[item1-4]],[…],[…],…[[item20-1],[item20-2],[item20-3],[item20-4]]]。即这个列表有20个元素(代表这个网页的20个笑话),每个元素又是一个列表–列表中有4个元素(代表爬取的4个内容,对应前面说的4个(.*?)。这里附上其中的一小部分网页源代码,对应着网页的html源代码,我们就能感受到这个简单的正则表达式的作用了:


<pre js="joke_summary"><span class="first_char">来</span>表哥家吃饭,小侄子一边看西游记一边手舞足蹈上蹦下跳学猴哥。
我哥:“傻儿子,你是孙悟空吗?”
侄子:“正是,你孙爷爷在此。”
打到现在还在哭。</pre>
</div>
<div class="para_info clearfix">
<div class="user_info">
                        <img src="http://joke-image.b0.upaiyun.com/img/user/cover/630347-299927_40x40.jpg" class="left corner4px" height="40" width="40"/>
                        <a href="/user/630347">离愁▍ Feast aw</a>
                        <p>15天前</p>

一共有20个类似的html片段,正则表达式的作用就是把笑话内容,作者和发表日期提取出来。

printAJoke方法

位置:51-56行

打印一则笑话,在这里我们更能感受到content[]的结构了,因为printAJoke这个方法需要传一个number参数进来,范围是1到20,我们就可以读取对应的笑话了,比如content[0]代表第一则笑话,和java中的数组类似。

53行以后就是程序运行的基本逻辑了,比较简单,就不多解释了。

就这样,一个简单的爬虫就完成了!

hbase的基本概念以及shell命令的基本用法【转载】

作为入门,这篇文章写得挺好的,在文章的后半部分,是hbase的shell的简单使用,我们可以通过shell来更好的了解一下hbase,实践出真知。

简介

HBase是一个分布式的、面向列的开源数据库,源于google的一篇论文《bigtable:一个结构化数据的分布式存储系统》。HBase是Google Bigtable的开源实现,它利用Hadoop HDFS作为其文件存储系统,利用Hadoop MapReduce来处理HBase中的海量数据,利用Zookeeper作为协同服务。

HBase的表结构

HBase以表的形式存储数据。表有行和列组成。列划分为若干个列族/列簇(column family)。

Row Key

column-family1

column-family2

column-family3

column1

column2

column1

column2

column3

column1

key1

t1:abc

t2:def

t4:lookfor404

t3:hello

t2:world

key2

t3:abc

t1:lipengfei

t4:java

t3:hello

t2:hadoop

t3:hbase

key3

t2:dfadfasd

t1:dfdasddsf

t2:hi

t1:fine

如上图所示,key1,key2,key3是三条记录的唯一的row key值,column-family1,column-family2,column-family3是三个列族,每个列族下又包括几列。比如column-family1这个列族下包括两列,名字是column1和column2,t1:abc,t2:def是由row key1和column-family1-column1唯一确定的一个单元cell。这个cell中有两个数据,abc和def。两个值的时间戳不一样,分别是t1,t2, hbase会返回最新时间的值给请求者。

这些名词的具体含义如下:

 (1)Row Key

与nosql数据库们一样,row key是用来检索记录的主键。访问hbase table中的行,只有三种方式:

          1.通过单个row key访问

          2.通过row key的range

          3.全表扫描

Row key行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。

存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)

注意:

字典序对int排序的结果是1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,9,91,92,93,94,95,96,97,98,99。要保持整形的自然序,行键必须用0作左填充。

行的一次读写是原子操作 (不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。

(2)列族 column family

hbase表中的每个列,都归属与某个列族。列族是表的chema的一部分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。例如courses:history , courses:math 都属于 courses 这个列族。

访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能帮助我们管理不同类型的应用:我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据)。

(3) 单元 Cell

HBase中通过row和columns确定的为一个存贮单元称为cell。由{row key, column( =<family> + <label>), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存贮。

(4) 时间戳 timestamp

每个cell都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。

为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。

HBase shell的基本用法

hbase提供了一个shell的终端给用户交互。通过执行 help get 可以看到命令的帮助信息。进入shell很简单,切换目录到hbase的bin下,运行命令:
./hbase shell
以网上的一个学生成绩表的例子来演示hbase的用法。

name

grade

course

math

art

zkb

5

97

87

baoniu

4

89

80

这里grade对于表来说是一个列,course对于表来说是一个列族,这个列族由两个列组成math和art,当然我们可以根据我们的需要在course中建立更多的列族,如computer,physics等相应的列添加入course列族。图中需要注意的是90这个值,列族下面的列也是可以没有名字的。

(1) 建立一个表格scores  具有两个列族grade 和courese

键入命令:
create 'scores','grade', 'course'

(2) 查看当前HBase中具有哪些表

键入命令:
list

(3) 查看表的构造

键入命令:
describe 'scores'

(4) 加入一行数据,行名称为zkb 列族为grade,列名为””(空),值为5

键入命令:
put 'scores','zkb','grade:','5'

(5) 给zkb这一行的数据的列族course添加一列<math,97>

键入命令:
put 'scores','zkb','course:math','97'

(6) 给zkb这一行的数据的列族course添加一列<art,87>

键入命令:
put'scores','zkb','course:art','87'

(7) 加入一行数据,行名称为baoniu 列族为grade,列名为””(空), 值为4

键入命令:
put'scores','baoniu','grade:','4'

(8) 给baoniu这一行的数据的列族course添加一列<math,89>

键入命令:
put'scores','baoniu','course:math','89'

(9) 给Jerry这一行的数据的列族course添加一列<art,80>

键入命令:
put'scores','baoniu','course:art','80'

(10) 查看scores表中zkb的相关数据

键入命令:
get'scores','zkb'

(11) 查看scores表中所有数据
注意:scan命令可以指定startrow,stoprow来scan多个row,例如:scan 'user_test',{COLUMNS =>'info:username',LIMIT =>10, STARTROW => 'test',STOPROW=>'test2'}

键入命令:
scan'scores'

(12) 查看scores表中所有数据courses列族的所有数据

键入命令:
scan'scores',{COLUMNS => 'course'}

(13) 删除scores表

键入命令:
disable'scores'
drop'scores'

总结下,hbase shell常用的操作命令有create,describe,disable,drop,list,scan,put,get,delete,deleteall,count,status等,通过help可以看到详细的用法。

转自http://blog.csdn.net/smcwwh/article/details/7468672,有些许修改,感谢原作者!

hbase的配置以及遇到的问题

这几天在熟悉学习hbase,记录一下配置过程,以及出现的问题。

首先,hbase是什么?先来一段摘抄:

HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System)所提供的分布式数据存储一样,HBase在Hadoop之上提供了类似于Bigtable的能力。HBase是Apache的Hadoop项目的子项目。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式。

反正这款数据库在大数据领域是比较出名的,nosql、面向列,都是它的特点。

在安装之前,我们首先最好查看一下hbase和hadoop对应的版本是否兼容。官方文档地址:http://hbase.apache.org/book.html

看到“4. Basic Prerequisites”,往下拉到hadoop那里,有一个表格,表明了hadoop和hbase的兼容情况:

HBase-0.94.x HBase-0.98.x (Support for Hadoop 1.1+ is deprecated.) HBase-1.0.x (Hadoop 1.x is NOT supported) HBase-1.1.x HBase-1.2.x

Hadoop-1.0.x

X

X

X

X

X

Hadoop-1.1.x

S

NT

X

X

X

Hadoop-0.23.x

S

X

X

X

X

Hadoop-2.0.x-alpha

NT

X

X

X

X

Hadoop-2.1.0-beta

NT

X

X

X

X

Hadoop-2.2.0

NT

S

NT

NT

NT

Hadoop-2.3.x

NT

S

NT

NT

NT

Hadoop-2.4.x

NT

S

S

S

S

Hadoop-2.5.x

NT

S

S

S

S

Hadoop-2.6.0

X

X

X

X

X

Hadoop-2.6.1+

NT

NT

NT

NT

S

Hadoop-2.7.0

X

X

X

X

X

Hadoop-2.7.1+

NT

NT

NT

NT

S

我下载的是hbase-0.98.16.1-hadoop2-bin.tar.gz。

下面讲一下配置:

分布式配置

解压文件:
tar -zxvf  hbase-0.98.16.1-hadoop2-bin.tar.gz -C /env/

切换到 hbase目录下的conf目录,编辑hbase-env.sh
vim hbase-env.sh
修改以下两行,并把原有的#去掉,第一行换上你jdk的目录,第二行表示使用hbase自带的zookeeper。

export JAVA_HOME=/usr/java/jdk1.7.0_79/
export HBASE_MANAGES_ZK=true

再修改hbase-site.xml
vim hbase-site.xml
改为以下内容,其中的主机名不要搞错,要在/etc/hosts里面有相应的映射。

<configuration>

  <property>
    <name>hbase.rootdir</name>
    <value>hdfs://main:49002/hbase</value>
    <description>The directory shared byRegionServers.
    </description>
  </property>

  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>

    <property>
      <name>hbase.zookeeper.property.clientPort</name>
      <value>2222</value>
    </property>

    <property>
      <name>hbase.zookeeper.quorum</name>
      <value>main,slave1,slave2</value>
    </property>

    <property>
      <name>hbase.zookeeper.property.dataDir</name>
      <value>/home/hadoop/zookeeper</value>
    </property>

</configuration>

修改regionservers,我把slave1和slave2加进去
vim regionservers
内容修改为

slave1
slave2

一个节点占一行,保存。
把配置好的hbase复制到另外两台主机上

scp -r /env/hbase-0.98.16.1-hadoop2/ root@slave1:/env/
scp -r /env/hbase-0.98.16.1-hadoop2/ root@slave2:/env/

先启动hadoop的hdfs,然后再启动hbase即可。
在bin目录下,输入以下命令
./start-hbase.sh

不过,我是在虚拟机上弄的分布式环境,在运行hbase的shell的时候,HMaster老是自动退出,查看日志,看到是zookeeper的错误,错误如下:

zookeeper.ClientCnxn: Opening socket connection to server main/192.168.254.100:2299. Will not attempt to authenticate using SASL (unknown error)
2016-01-11 05:23:12,267 WARN [main-SendThread(main:2299)] zookeeper.ClientCnxn: Session 0x0 for server null, unexpected error, closing socket connection and attempting reconnect
java.net.ConnectException: Connection refused

有点郁闷,找了好多解决方法,都不行,后来转念一想,既然是想熟悉一下hbase,运行一下它的shell,就不搞那么复杂了,重新配个单机版,单机版的配置非常简单。

单机版配置

和分布式配置的步骤基本一致,只需要把hbase-site.xml改为简易版的就行,内容如下:

<configuration>
  <property>
    <name>hbase.rootdir</name>
    <value>/home/hbase</value>
    <description>The directory shared byRegionServers.
    </description>
  </property>
</configuration>

改为单机版之后,首先还是得运行hadoop的hdfs,然后再运行./start-hbase.sh,此时jps一下,看到hadoop的namenode启动了,hbase的Hmaster也启动了,此时再运行./hbase shell就正常了。下一篇转一篇文章,学习一下如何直接在shell操作数据库。

啊啊啊赶在12点断网之前。

hadoop官方mapreduce简单例子-WordMean

上一篇写了MapReduce的经典例子–WordCount,为了进一步理解和熟悉一下MapReduce这个框架,这次再来看看官方给出的另外一个例子:WordMean

WordMean,Mean我们都知道,是平均数的意思。所以很显然,这个程序是用来统计单词的平均字符数的。

先来跑一下。

在《centos+虚拟机配置hadoop2.5.2-mapreduce-wordcount例子》里面,我已经在hdfs里面新建了一个input文件夹,并且在里面放置了一个test.txt,内容如下:

test.txt的内容
事实上,无论是WordCount还是WordMean,都不仅仅是只处理一个文件,为了验证这一点,我在跑WordMean之前,建多一个文件。

先在linux下新建一个test2.txt

vim /home/txt/test2.txt

内容如下:
test2.txt的内容

将其复制到hdfs下的input文件夹内:

hadoop fs -put /home/txt/text2.txt input

准备工作完毕,现在跑一下程序。仍然是这个jar文件:/hadoop/share/hadoop/mapreduce/sources/hadoop-mapreduce-examples-2.5.2-sources.jar

输出我们放在名为wordmean-output的文件夹下(命令行的最后一个参数),具体运行命令如下:

hadoop jar share/hadoop/mapreduce/sources/hadoop-mapreduce-examples-2.5.2-sources.jar org.apache.hadoop.examples.WordMean input wordmean-output

运行过程中,控制台最后几行信息输出如下:

Bytes Read=173
File Output Format Counters
Bytes Written=20
The mean is: 5.653846153846154

最后一行显示,the mean is 5.653846153846154,即单词的平均字符数。

我手工统计了一下,一共有26个单词,合计147个字符,147/26,确实是这个数。为了进一步确认,到wordmean-output那里看一下输出结果。

hadoop fs -cat wordmean-output/part-r-00000

结果如下,看来我手工也没数错哈哈:
WORDMEAN输出结果

跑完程序了,把源代码贴一下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

import com.google.common.base.Charsets;

public class WordMean extends Configured implements Tool {

  private double mean = 0;

  private final static Text COUNT = new Text("count");
  private final static Text LENGTH = new Text("length");
  private final static LongWritable ONE = new LongWritable(1);

  
  public static class WordMeanMapper extends
      Mapper<Object, Text, Text, LongWritable> {

    private LongWritable wordLen = new LongWritable();

    
    public void map(Object key, Text value, Context context)
        throws IOException, InterruptedException {
      StringTokenizer itr = new StringTokenizer(value.toString());
      while (itr.hasMoreTokens()) {
        String string = itr.nextToken();
        this.wordLen.set(string.length());
        context.write(LENGTH, this.wordLen);
        context.write(COUNT, ONE);
      }
    }
  }


  public static class WordMeanReducer extends
      Reducer<Text, LongWritable, Text, LongWritable> {

    private LongWritable sum = new LongWritable();

    
    public void reduce(Text key, Iterable<LongWritable> values, Context context)
        throws IOException, InterruptedException {

      int theSum = 0;
      for (LongWritable val : values) {
        theSum += val.get();
      }
      sum.set(theSum);
      context.write(key, sum);
    }
  }

  private double readAndCalcMean(Path path, Configuration conf)
      throws IOException {
    FileSystem fs = FileSystem.get(conf);
    Path file = new Path(path, "part-r-00000");

    if (!fs.exists(file))
      throw new IOException("Output not found!");

    BufferedReader br = null;

    // average = total sum / number of elements;
    try {
      br = new BufferedReader(new InputStreamReader(fs.open(file), Charsets.UTF_8));

      long count = 0;
      long length = 0;

      String line;
      while ((line = br.readLine()) != null) {
        StringTokenizer st = new StringTokenizer(line);

        // grab type
        String type = st.nextToken();

        // differentiate
        if (type.equals(COUNT.toString())) {
          String countLit = st.nextToken();
          count = Long.parseLong(countLit);
        } else if (type.equals(LENGTH.toString())) {
          String lengthLit = st.nextToken();
          length = Long.parseLong(lengthLit);
        }
      }

      double theMean = (((double) length) / ((double) count));
      System.out.println("The mean is: " + theMean);
      return theMean;
    } finally {
      if (br != null) {
        br.close();
      }
    }
  }

  public static void main(String[] args) throws Exception {
    ToolRunner.run(new Configuration(), new WordMean(), args);
  }

  @Override
  public int run(String[] args) throws Exception {
    if (args.length != 2) {
      System.err.println("Usage: wordmean <in> <out>");
      return 0;
    }

    Configuration conf = getConf();

    @SuppressWarnings("deprecation")
    Job job = new Job(conf, "word mean");
    job.setJarByClass(WordMean.class);
    job.setMapperClass(WordMeanMapper.class);
    job.setCombinerClass(WordMeanReducer.class);
    job.setReducerClass(WordMeanReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(LongWritable.class);
    FileInputFormat.addInputPath(job, new Path(args[0]));
    Path outputpath = new Path(args[1]);
    FileOutputFormat.setOutputPath(job, outputpath);
    boolean result = job.waitForCompletion(true);
    mean = readAndCalcMean(outputpath, conf);

    return (result ? 0 : 1);
  }

  /**
   * Only valuable after run() called.
   * 
   * @return Returns the mean value.
   */
  public double getMean() {
    return mean;
  }
}

 

简单分析一下,大概的过程如下:

map过程

看第43和44行,可以知道map输出了两个键值对,一个是<“length”,单词的长度>,一个是<“count”,1>即单词的个数。

reduce过程

代码56至66行。对已经处理好的键值对进行最终处理,分别处理<“length”,<单词的长度>>,和<“count”,<1,1,1,1,1…>>,做的是同样的处理–累加。

readAndCalcMean方法

第68行到111行,主要是读取输出的文件,计算平均数。如上文所示,文件里面的内容如下:

WORDMEAN输出结果

所以这个方法,就是用来读取26和147两个数字,作除法,然后输出到屏幕。具体代码写的很清楚,就不细读了。