可可老师24小时热门分享(2016.10.13)

本文介绍了包括 Python、Java、Haskell等在内的一系列编程语言的深度学习库。
Python
C++
Java
JavaScript
Lua
Julia
Lisp
Haskell
.NET
R
毕设大概是大学四年里最坑爹之一的事情了,毕竟一旦选题不好,就很容易浪费一年的时间做一个并没有什么卵用,又不能学到什么东西的鸡肋项目。所幸,鄙人所在的硬件专业,指导老师并不懂软件,他只是想要一个农业物联网的监测系统,能提供给我的就是一个Oracle 11d数据库,带着一个物联网系统运行一年所保存的传感器数据…That’s all。然后,因为他不懂软件,所以他显然以结果为导向,只要我交出一个移动客户端和一个服务端,并不会关心我在其中用了多少坑爹的新技术。
那还说什么?上!我以强烈的恶搞精神,决定采用业界最新最坑爹最有可能烂尾的技术,组成一个 Geek 大杂烩,幻想未来那个接手我工作的师兄的一脸懵逼,我露出了邪恶的笑容,一切只为了满足自己的上新欲。
全部代码在 GPL 许可证下开源:
由于数据库是学校实验室所有,所以不能放出数据以供运行,万分抱歉~。理论上应该有一份文档,但事实上太懒,不知道什么时候会填坑~。
OK,上图说明技术框架。

该物联网监测系统整体上可分为三层:数据库层,服务器层和客户端层。
数据库层除了原有的Oracle 11d数据库以外,还额外增加了一个Redis数据库。之所以增加第二个数据库,原因为:
服务器层,采用 Node.js 的 Express 框架作为客户端的 API 后台。因为 Node.js 的单线程异步并发结构使之可以轻松实现较高的 QPS,所以非常适合 API 后端这一特点。其框架设计和主要功能如下图所示:
像网关层:鉴权模块这么装逼的说法,本质也就是app.use(jwt({secret: config.jwt_secret}).unless({path: ['/signin']}));
一行而已。因为是直接从毕业论文里拿下来的图,毕业论文都这尿性你们懂的,所以一些故弄玄虚敬请谅解。
客户端层绝大部分是 React Native 代码,但是监控数据的图表生成这一块功能(如下图),由于 React Native 目前没有开源的成熟实现;试图通过 Native 代码来画图表,需要实现一个 Native 和 React Native 互相嵌套的架构,又面临一些可能的困难;故而最终选择了内嵌一个 html 页面,前端代码采用百度的 Echarts 框架来绘制图表。最终的结构就是大部分 React Native + 少部分 Html5 的客户端结构。
另外就是采用了 Redux 来统一应用的事件分发和 UI 数据管理了。可以说,React Native 若能留名青史,Redux 必定是不可或缺的一大原因。这一点我们后文再述。
服务端接口表:

服务端程序的编写过程中,往往涉及到了大量的异步操作,如数据库读取,网络请求,JSON解析等等。而这些异步操作,又往往会因为具体的业务场景的要求,而需要保持一定的执行顺序。此外,还需要保证代码的可读性,显然此时一味嵌套回调函数,只会使我们陷入代码几乎不可读的回调地狱(Callback Hell)中。最后,由于JavaScript单线程的执行环境的特性,我们还需要避免指定不必要的执行顺序,以免降低了程序的运行性能。因此,我在项目中使用Promise模式来处理多异步的逻辑过程。如下代码所示:
function renderGraph(req, res, filtereds) {
var x = [];
var ys = [];
var titles = [];
filtereds[0].forEach(function(row) {
x.push(getLocalTime(row.RECTIME));
});
filtereds.forEach(function(filtered){
if (filtered[0] == undefined)
// even if at least one of multi query was succeed
// fast-fail is essential for secure
throw new Error('数据库返回结果为空');
var y = [];
filtered.forEach(function(row) {
y.push(row.ANALOGYVALUE);
});
ys.push(y);
titles.push(filtered[0].DEVICENAME + ': ' + filtered[0].DEVICECODE);
});
res.render('graph', {
titles: titles,
dataX: x,
dataY: ys,
height: req.query.height == undefined ? 200 : req.query.height,
width: req.query.width == undefined ? 300 : req.query.width,
});
}
function resFilter(resolve, reject, connection, resultSet, numRows, filtered) {
resultSet.getRows(
numRows,
function (err, rows)
{
if (err) {
console.log(err.message);
reject(err);
} else if (rows.length == 0) {
resolve(filtered);
process.nextTick(function() {
oracle.releaseConnection(connection);
});
} else if (rows.length > 0) {
filtered.push(rows[0]);
resFilter(resolve, reject, connection, resultSet, numRows, filtered);
}
}
);
}
function createQuerySingleDeviceDataPromise(req, res, device_id, start_time, end_time) {
return oracle.getConnection()
.then(function(connection) {
return oracle.execute(
"SELECT\
DEVICE.DEVICEID,\
DEVICECODE,\
DEVICENAME,\
UNIT,\
ANALOGYVALUE,\
DEVICEHISTROY.RECTIME\
FROM\
DEVICE INNER JOIN DEVICEHISTROY\
ON\
DEVICE.DEVICEID = DEVICEHISTROY.DEVICEID\
WHERE\
DEVICE.DEVICEID = :device_id\
AND DEVICEHISTROY.RECTIME\
BETWEEN :start_time AND :end_time\
ORDER\
BY RECTIME",
[
device_id,
start_time,
end_time
],
{
outFormat: oracle.OBJECT,
resultSet: true
},
connection
)
.then(function(results) {
var filtered = [];
var filterGap = Math.floor(
(end_time - start_time) / (120 * 100)
);
return new Promise(function(resolve, reject) {
resFilter(resolve, reject,
connection, results.resultSet, filterGap, filtered);
});
})
.catch(function(err) {
res.status(500).json({
status: 'error',
message: err.message
});
process.nextTick(function() {
oracle.releaseConnection(connection);
});
});
});
}
function secureCheck(req, res) {
let qry = req.query;
if (
qry.device_ids == undefined
|| qry.start_time == undefined
|| qry.end_time == undefined
) {
throw new Error('device_ids或start_time或end_time参数为undefined');
}
if (req.query.end_time < req.query.start_time) {
throw new Error('终止时间小于起始时间');
}
};
router.get('/', function(req, res, next) {
try {
var device_ids;
var queryPromises = [];
secureCheck(req, res);
device_ids = req.query.device_ids.toString().split(';');
for(let i=0; i<device_ids.length; i++) {
queryPromises.push(createQuerySingleDeviceDataPromise(
req, res, device_ids[i], req.query.start_time, req.query.end_time));
};
Promise.all(queryPromises)
.then(function(filtereds) {
renderGraph(req, res, filtereds);
}).catch(function(err) {
res.status(500).json({
status: 'error',
message: err.message
});
})
} catch(err) {
res.status(500).json({
status: 'error',
message: err.message
});
}
});
这是生成指定N个传感器在一段时间内的折线图的逻辑。显然,剖析业务可知,我们需要在数据库中查询N次传感器,获得N个值对象数组,然后才能去用N组数据渲染出图表的HTML页面。 可以看到,外部核心的Promise控制的流程只集中于下面的几行之中:Promise.all(queryPromises()).then(renderGraph()).catch()
。即,只有获取完N个传感器的数值之后,才会去渲染图表的HTML页面,但是这N个传感器的获取过程却又是并发进行的,由Promise.all()来实现的,合理地利用了有限的机器性能资源。
然而,推入queryPromises数组中的每个Promise对象,又构成了自己的一条Promise逻辑链,只有这些子Promise逻辑链被处理完了,才可以说整个all()函数都被执行完了。子Promise逻辑链大致地可以总结为以下形式:
function() {
return new Promise().then().catch();
}
其中的难点在于:
由此我们可以看到,没有无缘无故的高性能。Node.js 的高并发的优良表现,是用异步编程的高复杂度换来的。当然,你也可以选择不要编程复杂度,即不采用 Promise,Asnyc 等等异步编程模式,任由代码沦入回调地狱之中,那么这时候的代价就是维护复杂度了。其中取舍,见仁见智。
客户端主要功能如下表所示:

接下来简单介绍下几个主要页面。可以发现 iOS 明显比 Android 要来的漂亮,因为只对 iOS 做了视觉上的细调,直接迁移到 Android 上,就会由于屏幕显示的色差问题,显得非常粗糙。所以,对于跨平台的 React Native App 来说,做两套色值配置文件,以供两个平台使用,还是很有必要的。

上图即是土壤墒情底栏的当前数据页面,分别在Android和iOS上的显示效果,默认展示所有当前的传感器的数值,可以通过选择传感器种类或监测站编号进行筛选,两个条件可以分别设置,选定后再点击查找,即向服务器发起请求,得到数据后刷新页面。由于React Native 的组件化设计,刷新将只刷新下侧的DashBoard部分,且,若有上次已经渲染过的MonitorView,则会复用他们,不再重复渲染,从而实现了降低CPU占用的性能优化。MonitorView,即每一个传感器的展示小方块,自上至下依次展示了传感器种类,传感器编号,当前的传感器数值以及该传感器显示数值的单位。MonitorView和Dashboard均被抽象为一个一般化,可复用的组件,使之能够被利用在气象信息、病虫害监测之中,提升了开发效率,降低了代码的重复率。

上图是土壤墒情界面的历史数据界面,分别在Android和iOS上的展示效果,默认不会显示数据,直到输入了传感器种类和监测站编号,选择了年月日时间后,再点击查找,才会得到结果并显示出来。该界面并非如同当前数据界面一样,Android和iOS代码完全共用。原因在于选择月日和选择时间的控件,Android和iOS系统有各自的控件,它们也被封装为React Native中不同的控件,因此,两条绿色的选择时间的按钮,被封装为HistoricalDateSelectPad,分别放在componentIOS和componentAndroid文件夹中。界面下侧的数据监测板,即代码中的Dashboard,是复用当前数据中的Dashboard。

上图是土壤墒情界面的图表生成界面,分别在Android和iOS上的展示效果。时间选择界面,查找按钮,选择框,均可复用前两个界面的代码,因此无需多提。值得说的是,生成的折线图,事实上是通过内嵌的WebView来显示一个网页的。图表网页的生成,则依靠的百度Echarts 第三方库,然后服务端提供了一个预先写好的前端模板,Express框架填入需要的数据,最后下发到移动客户端上,渲染生成图表。图表支持了多曲线的删减,手指选取查看具体数据点,放大缩小等功能。

上图则是实际项目应用中的Redux相关文件的结构。stores中存放全局数据store相关的实现。
actions中则存放根据模块切割开的各类action生成函数集合。在 Redux 中,改变 State 只能通过 action。并且,每一个 action 都必须是 Javascript Plain Object。事实上,创建 action 对象很少用这种每次直接声明对象的方式,更多地是通过一个创建函数。这个函数被称为Action Creator。
reducers中存放许多reducer的实现,其中RootReducer是根文件,它负责把其他reducer拼接为一整个reducer,而reducer就是根据 action 的语义来完成 State 变更的函数。Reducer 的执行是同步的。在给定 initState 以及一系列的 actions,无论在什么时间,重复执行多少次 Reducer,都应该得到相同的 newState。
测试工具:OS X Activity Monitor(http_load)

测试工具:Xcode 7.3

测试工具:Android Studio 1.2.0


React Native 尽管在开发上具有这样那样的坑,但是因其天生的跨平台,和优于 Html5的移动性能表现,使得他在写一些不太复杂的 App 的时候,开发速度非常快,自带两倍 buff。
At Meguro.es #4 on June 21th, 2016, I talked about drawing animated chart on React Native. The talk was about the things I learned through developing an tiny app, Compare. It’s a super simple app to compare temperatures.
Before creating it, I had no idea about what temperatures on weather forecast, like 15 degrees Celsius, were actually like. I remember what yesterday was like, but not the numbers. Typical weather forecast apps shows only future temperatures without past records. Thanks to The Dark Sky Forecast API, the app fetches both of past records and future forecasts, and show them together.
The app’s source code is on GitHub:
There might have been some charting libraries to draw similar charts, but I like to write things from scratch. I like to reinvent the wheel especially when it’s a side project. Thanks to that, I found a way to animate smooth paths with the Animated
library.
If I have to add something to the slides:
LayoutAnimation
at Justin Poliachik’s React Native’s LayoutAnimation is Awesome, which is a great post too.By JC on Saturday 22 October 2016, 17:42 – Hacking – Permalink
Two years ago, I started playing around with cheap 433MHz plugs that can be found almost everywhere. At that time, I got several from different brands, from the well known Chacon Di-O plugs, to the most obscure chinese/no-name ones, and my goal was to reverse engineer as much protocols as possible. I compiled the result into a little tool I called rf-ctrl (now available on my GitHub), and forgot about it. However, this summer, I needed to find a solution to remotely control my electric heaters (not because I was cold obviously, but because I had the time to do it), and thought it was time to dig up rf-ctrl with a bit of polishing (a Web UI called Home-RF).
Let’s first talk about OOK a little bit. Most of the cheap 433MHz plugs (but also chimes, rolling shutter controllers, thermometers …) use the ON/OFF Keying or OOK modulation. The idea is that data are sent in binary form, by alternatively enabling and disabling the transmitter, thus the carrier frequency. I found mainly two ways of doing so:
Most of the plugs I found use this scheme, and this is the kind of modulation that rf-ctrl implements. This technique, which could be seen as some kind of Manchester code, allows the receiver to easily recover the clock and sync, since the carrier frequency cannot be enabled or disabled longer than a particular amount of time. The timings for a 0 are often inverted compared to those of a 1, for instance, 160µs-ON/420µs-OFF represents a 0 with the OTAX protocol, while 420µs-ON/160µs-OFF represents a 1. However, this is not systematic, and some protocols use totally different timings, for instance 260µs-ON/260µs-OFF for a 0 and 260µs-ON/1300µs-OFF for a 1 with the Di-O protocol. The data part of the frame is sometimes encapsulated between a starting marker and an ending marker. These markers are also represented with an ON/OFF transition, but with different timings. The whole frame is then repeated a specific number of times, with a delay between the frames that can also be assimilated to either a starting marker without ON state, or an ending one with a long OFF state. Last thing to note is the transition order which is often ON/OFF, but can be OFF/ON as well.
This is actually the “real” low-level way of doing OOK things. You can even describe the previous one that way by choosing a bit-rate (1/Tb) high enough to represent the previous ON/OFF transitions by a succession of ones and zeros that will match the timings. This kind of coding is rather found in high-end devices, like old car keys and more secure plugs/rolling shutters. It was not compatible with the HE853 dongle I had at that time, and thus is not supported by rf-ctrl. However I played with it at a point in order to control the rolling shutters and plugs from the Somfy brand, and to test TI’s CC110x transceiver, but that’s not the purpose of this post.
To replicate a protocol, one must understand two things. The OOK timings (physical characteristics) is the first one and the easiest, while the actual data format of the frame will be the second one.
The easiest way to capture a frame is to use a 1$ 433MHz (433,92Mhz actually) receiver connected to either an oscilloscope or a digital analyzer. You will get something like this (Sumtech protocol):
But if you do not have this kind of receiver but have an oscilloscope laying around, you can also use a simple wire of around 17 cm (= 3×10^8/(4x433x10^6) = lambda/4) connected to one of the inputs ! You will get something like this, which is enough to understand the underlaying timings (Idk protocol this time):
Thanks to this, you can measure the expected timings and the number of times the frame needs to be repeated. It’s time to start writing down zeros and ones on a sheet of paper.
Now, what remains is the actual data to send. Most of the time, a frame consists of a remote ID which is the ID of the remote that sends the frame, a device ID which is just the number of the button pressed on the remote, an action, like ON or OFF, which is most likely 1 or 0, some kind of checksum, and some fixed values. In some cases there are additional values that change every time a button is pressed. They are called rolling codes, and are found in brands like Somfy. This kind of codes are often harder to reverse, but the cheap plugs do not use that. Finally, some protocols add a simple obfuscation layer on top of the frame, like a XOR for instance.
To understand a protocol, the best method remains to gather as much frames as possible, while writing down what generated them. The first step is to determine if two frames generated by pushing the same button are indeed the same. It will most likely be the case, but if not, you need to find out which part of the frame changes. It can be a simple counter, or something more clever. Remember that if there is some kind of encryption/obfuscation, the whole frame can change because of a simple counter. Anyway, you need to scratch your head and find the solution by comparing as much frames as possible.
Assuming all frames generated by one button are the same, the next thing to do is to change one parameter at a time, and look at the result to identify the different fields. For instance, press the ON and OFF button of the same plug number, on the same remote, and compare the resulting frames. Only a small part of it should change, part that you can now identify as the action field.
Then press the ON button for another plug, and compare to the ON button for the first plug. Check that 1) the action field remains the same, 2) something else changed. This something else is probably the device ID. You can then try to open the remote, and look for some kind of multi-switch or jumpers. You will not necessarily find something in all remotes since some will have their ID stored in an Eeprom or something like that, but if you do find something, try to change it and check the generated frames. This will most likely help to find the remote ID.
If you see a part of the frame that seems to change only when something else changes, then you might just have identified a checksum. Try to find how these bits can be computed from the other ones. It can be a for instance a simple sum, or a XOR. Repeat the procedure until you are convinced that all those fields behave as assumed.
Now, keep in mind this is just a generic description of a 433MHz device. Some will not fit the mold and might have, for instance, more or less fields. The frame format can even be completely different.
Once the frame format understood, it’s time to test ! For this you will need a 433MHz transmitter. I first used this HE853 USB dongle, which works fine with a regular PC, but I found out it was easier to just use this 1$ transmitter connected to a Raspberry PI, a TP-Link TL-WR703N router, or any device that offers GPIOs. And this is where rf-ctrl comes in handy. It uses a back-end/front-end (transmitter driver/protocol driver) logic allowing to implement new protocols easily. Here is how to do so:
OBJECTS
variabletiming_config
structure with the values you measured (values are expected in µs)int (*format_cmd)(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command);
function which is supposed to generate the final frame in the pre-allocated *data
array of data_len
bytes from the remote ID, the device ID and the command.rf_protocol_driver
structure with a short and long name for the protocol, the pointer to the format_cmd()
function and to the timing_config
structure, the max allowed remote and device IDs, and the actual parameters this protocol needs (most likely PARAM_REMOTE_ID | PARAM_DEVICE_ID | PARAM_COMMAND
)That’s all ! You should be able to build rf-ctrl and control your plug with it. If it does not work, do not hesitate to check the generated signal with your oscilloscope or digital analyzer.
Let’s get back to the main topic. To control my heaters, I thought I would buy plugs from one of the brands I already reversed, and went to buy the “auchan” ones. Unfortunately, they were still selling 433MHz plugs under the same name, but the underlaying supplier had clearly changed. I decided to buy three of them anyway, but knew I would have to reverse yet another protocol, with the risk it might have used some kind of rolling codes… Hopefully, it did not, and was pretty straightforward to understand. For your information it’s the protocol I called “auchan2”.
Now regarding the actual setup, I used the well known TP-Link TL-WR703N router running OpenWrt and a 1$ 433MHz transmitter (again, like this one) connected, through a 2V -> 5V level shifter, to the GPIO 7 of the router. I wrote the needed Makefile to build rf-ctrl as an OpenWrt package, and also created a kernel driver that generates the proper OOK signal on GPIO 7 once fed with the correct timings and data. This driver, called ook-gpio, is directly provided as an OpenWrt package on my GitHub. Since the WR703N does not have much free space, I chose to build a special firmware for it with everything in it, removing what was useless. Once the firmware flashed, I verified that I was indeed able to control my heaters. But to do that remotely, I had to connect trough SSH and use my command line tool, which looked like something that could have been improved. So I made a little Web UI called Home-RF, which is a little shell script that allows to control rf-ctrl by generating a web page with configurable presets. It looks like this:
The idea is that you can add presets for devices like plugs, rolling shutters or chimes, and they will be displayed like a remote. As a bonus, It also supports WakeOnLan compatible computers (usingetherwake). There is a simple preset editor included in the interface, as well as an advanced panel that allows to manually control rf-ctrl or etherwake. Home-RF will be nicely displayed on a PC, as well as on mobile phones. It is available here on my GitHub, and can be built as an OpenWrt package.
At that point, I rebuilt a firmware with Home-RF inside, and flashed it. I’m using a VPN at home, so I do not care about authentication directly in Home-RF. However, if you plan to use it remotely, do not forget to add some kind of access control on top of it (htaccess, SSH, VPN…) !
In order to build your own RF gateway, you will need:
The provided instructions assume you are working on a PC running Linux.
The schematic for the level shifter is the following:
– Solder the MOSFET and the resistor to match the schematic above
– Use one pad of the PCB as Ground, and solder three wires on Output, +5V Transmitter, and GND Transmitter
– Either solder the 3 pins connector to the other end of the wires, or solder the RF transmitter directly (remove the male pins of the transmitter if any)
– Open the WR703N router, and look for the four signals below:
– Solder one end of four wires on these signals, and the other end to the level shifter previously made
– Solder the 17,3 cm long wire to the antenna pad of the transmitter and put Kapton everywhere to prevent any short-circuit (I tried without antenna at first, that’s why it is missing on my picture)
– Put the board back in its casing, use its reset hole to get the antenna out of it (you will have to bend the antenna to do so, so make sure it does not push the reset button), and close it
You should get something like this:
I attached a prebuilt Barrier Breaker (14.07) OpenWrt firmware with all the tools in it, but it is funnier to build it yourself:
– Create your root folder for the build, for instance my-gateway:
$ mkdir my-gateway
– Go to that folder, and checkout a Barrier Breaker OpenWrt tree (I did not try Chaos Calmer, so let me know if it works):
$ cd my-gateway
$ git clone -b barrier_breaker git://github.com/openwrt/openwrt.git
– Checkout rf-ctrl, Home-RF and ook-gpio:
$ git clone https://github.com/jcrona/rf-ctrl.git
$ git clone https://github.com/jcrona/home-rf.git
$ git clone https://github.com/jcrona/ook-gpio.git
– Create the packages folders in OpenWrt:
$ mkdir -p openwrt/package/utils/home-rf/files
$ mkdir -p openwrt/package/utils/rf-ctrl/src
$ mkdir -p openwrt/package/kernel/ook-gpio
– Copy the packages content:
$ cp -a home-rf/www openwrt/package/utils/home-rf/files/
$ cp home-rf/OpenWrt/Makefile openwrt/package/utils/home-rf/
$ cp rf-ctrl/* openwrt/package/utils/rf-ctrl/src/
$ cp rf-ctrl/OpenWrt/Makefile openwrt/package/utils/rf-ctrl/
$ cp -a ook-gpio/* openwrt/package/kernel/ook-gpio/
– Update external feeds in OpenWrt and add etherwake to the build system:
$ cd openwrt
$ ./scripts/feeds update -a
$ ./scripts/feeds install etherwake
– Download the attached home-rf_openwrt.config into the my-gateway folder, and use it:
$ cp ../home-rf_openwrt.config .config
$ make oldconfig
– Build the OpenWrt firmware
$ make
– You should have your firmware ready in my-gateway/openwrt/bin/ar71xx/.
If you have any issue buidling the mac80211 package, it might be because the build system failed to clone the linux-firmware Git. In that case, download the linux-firmware-2014-06-04-7f388b4885cf64d6b7833612052d20d4197af96f.tar.bz2 archive from here, copy it into the my-gateway/openwrt/dl/ folder, and restart the build.
Now, you need to flash your WR703N router. If you never flashed OpenWrt before on your router, use openwrt-ar71xx-generic-tl-wr703n-v1-squashfs-factory.bin as explained here. Otherwise, use openwrt-ar71xx-generic-tl-wr703n-v1-squashfs-sysupgrade.bin with the sysupgrade tool.
At that point, you should have your router up and running. You still need to configure it like a regular OpenWrt router, as explained here. You can, for instance, configure it in WiFi station mode, so that you can find the best place to reach all your 433MHz devices.
Once properly configured, open a browser and go to http://<your_router_ip>/home-rf. If everything went well, you will get the Home-RF interface ready to be configured !
So now I’m able to control my electric heaters from my phone for around 20$, and I hope you will be able to do the same with your own 433MHz devices. All the discussed tools are available on myGitHub. I will be happy to extend the list of supported protocols in rf-ctrl, so feel free to add more.
If you want to play around, try the “scan” mode of rf-ctrl ! It allows to send all possible frames within a range of given remote IDs, device IDs, and protocols.
That’s all for now !
Posted 2015/02/13. Last updated 2015/02/17.
I recently bought a DVB-T dongle containing the Realtek RTL2832U and Raphael Micro R820T chips with the intent to use it as a Software-Defined Radio (SDR) receiver. These dongles are incredible because for about $10, you can tune in to frequencies between 24 and 1766MHz and listen to a wide range of devices and signals, provided you have a proper antenna (and a down-/up- converter if you want to listen outside of this range). The device, pictured below, is truly very simple: the back consists solely of a couple lines that could probably not be routed on the top layer of the PCB.
As a first project, I decided to look into the 433MHz frequency, as others have also successfully done (see here, here, and here for instance), but decided to focus on the methodology and the tools available, rather than recovering a specific device’s key, since I didn’t have one lying around. This post describes the manual process I followed with existing tools, as well as a basic MATLAB script that I wrote interfacing with the RTL device which automates the binary signal recovery process.
UPDATE: There is some good discussion of this post going on at Hackaday, RTL-SDR, and Reddit, which also contain a few more pointers for this kind of thing. My response to some of the points raised can be found here. A good alternative to MATLAB which I had not considered is Octave, which apparently interfaces well with GNU Radio.
As mentioned above, I did not have a device transmitting at 433MHz, so instead I used a typical cheap MX-FS-03V RF transmitter (pictured below) bought off of EBay, connected to an Arduino Uno. I used the rc-switch library, which appears to be pretty popular, with a lot of forks on GitHub. My code‘s loop simply calls mySwitch.send("010010100101")
followed by a delay of 1 second and makes no other calls to the library besides enabling transmission on the appropriate Arduino pin.
The goal of the project was to uncover the details of the protocol (and the value transmitted) before looking at the library code to verify it. To this end, I installed SDR# to visualize and record the signal, as well as Audacity to inspect the produced WAV file. I additionally installed the rtl-sdr and rtl_433 libraries which contain command-line utilities for automation (Windows binaries can be found here and here).
Having programmed the Arduino and left it to constantly transmit, my first step was to fire up SDR# to visually inspect the signal. The figures below show SDR#’s spectrum analyzer and waterfall graphs centered at 433MHz. The spectrum analyzer shows a consistent noise level across frequencies when the transmitter is silent, and also indicates a few DC bias spikes. Moreover, the waterfall illustrates that the transmitter output is not filtered and produces noise/energy across many unwanted frequencies. [UPDATE: Per a suggestion here, reducing the gain helps remove the aliases, but does not entirely eliminate them.]
This can be seen even more clearly below, when a transmission is occurring, where we can also identify that the strongest signal is actually at 434MHz.
After selecting the frequency, I recorded 10 seconds of the signal which came out as an astonishingly large 110MB WAV file! Opening up the recording on Audacity, as shown below, we can identify 10 seemingly identical, equally spaced transmissions 1 second apart, with the exception of the 8th one.
We ignore the anomaly for now (as a closer inspection indicates it is simply truncated, but otherwise the same as other transmissions), and focus on an individual section:
Once more we find 10 identical transmissions within each section, so zooming further we can clearly identify the modulation as a type of on-off keying (OOK) where 0s are short HIGH bursts followed by long periods of silence, and 1s are long HIGH bursts followed by small periods of silence.
Note of course that the encoding could be reversed, but it is reasonable to assume that it is not (and our knowledge of what is being transmitted tells us we are right!): the signal appears to be 0100101001010
. This is indeed what we transmitted, but there is a spurious 0 at the end. Though this could be a checksum, flipping the last bit or removing it does not alter the value, hence we can assume it is simply an End-of-Message (EOM) value. Looking at the individual signals for 0 and 1, we see that the pulse length for a 0 is 350μs long, and it is 3 times as long for a 1.
Looking at the setup code, we see that the pulse length is indeed 350μs long, and each message is repeated 10 times, each of which is followed by a sync message. Moreover, for the default protocol, a 0 is represented as 1 HIGH, 3 LOWs, while a 1 is the reverse. Success!
Even though rtl_433 readily decodes this message for us, when I found out that MATLAB has a package for RTL-SDR (which needs the Communications System Toolbox), I thought I’d try it out. As a first step, I tried the spectrum analyzer example, just to ensure that everything works. 433.989MHz gave the strongest signal, and behaves as expected both during silence and transmission:
The data is output in I/Q format with values between -1 and 1, but I did not want to write a demodulator, so I instead took the real part, corresponding to the in-phase component, which proves to be sufficient for our purposes. [UPDATE: An alternative is taking the modulus of the complex value. This has the added benefit of not needing the Hilbert transform below, asthis comment mentions. I can confirm that setting rdata = abs(data);
and binary(smoothed >= high_thres) = 1;
in the code works without further changes.] As can be seen in the figure below and left, the output is very noisy, so I immediately applied a Savitzky-Golay filter, which was chosen to be cubic for data frames of length 41, as in the MATLAB example. As the picture below and to the right shows, the filtering is very effective.
Having reduced the noise, the next step was to calculate the envelope of the signal, which in MATLAB is implemented by taking the modulus of the Hilbert transform, as also explainedhere. The figures below show what that looks like for the overall signal, as well as for a specific transmission of our 10 bits. As can be seen, during the transmission the envelope fluctuates a bit, but is most frequently above 1. When the transmission is not occurring, the value remains below 0.1, but this is not pictured here.
The conversion to a binary signal is straightforward: if the magnitude of the above quantity is above 0.5, the signal is considered to be at a logical HIGH, and if it is below 0.5 it is a logical LOW. Zooming into one of the transmissions shows us that the digital pulse produced is as expected, without noise:
The basic idea to automatically detect whether a signal is a 0 or 1 is simple: count the number of consecutive samples that were HIGH, and if they are close to the transmission pulse length of a 0 or a 1, print that value! There were a few intricacies in debouncing (where the code basically skips over a few LOWs in between HIGHs) and in setting the appropriate thresholds for what counts as “close enough”, but in the end the code was able to accurately recover all transmitted bits. That said, I expect that changes to the parameters will need to be made for other hardware, depending on factors such as the antennas and power of transmission.
RTL-SDR definitely opens up many possibilities. Even though this post was a “toy example”, it has real-world implications as plenty of devices operate freely at 433MHz and other frequencies, as explained in the introduction. Although MATLAB is not always easy to work with, it has tremendous capabilities, and the fact that it interfaces with the dongle is a great feature.
I believe that the RTL-SDR community would greatly benefit from more open-source projects using MATLAB, so I have made my code availabe on GitHub, if you would like to try it out for yourself. As mentioned above, it might need some tweaking based on your hardware, but I hope such changes will be minimal. If you have any comments or improvements, feel free tocontact me!
My initial plan was to use GNU Radio on my new Raspberry Pi 2, but despite its extra processing power, I found that it could not adequately do signal processing, even for FM frequencies, and often underflowed. If you are interested in going down that route, you might want to look at this post containing installation instructions, and gqrx as a *nix alternative to SDR# (it’sgqrx-sdr
under the repositories). Also take a look at this forum discussion if you get a BadMatch
error, and at this post detailing how to approach the analysis using GNU Radio. Finally, if you, like me, don’t have an Ethernet plug available, but have an Android phone that can tether (even if it is using Wi-Fi), connect it to your Pi’s USB, set the connection mode to “Media” and follow the instructions here!
[导读] “大数据时代”,数据为王!无论是数据挖掘还是目前大热的深度学习领域都离不开“大数据”。大公司们一般会有自己的数据,但对于创业公司或是高校老师、学生来说,“Where can I get large datasets open to the public?”是不得不面对的一个问题。
刘念宏:清华大学微电子系在读硕士研究生,清华大学“大数据硕士”,现任清华大学学生大数据协会会长。 主要研究方向:深度学习图像检测。 联系方式: lnh15@mails.tsinghua.edu.cn。
付睿:清华大学自动化系在读硕士研究生,清华大学“大数据硕士”,前任清华大学学生大数据协会会长。 主要研究方向:智能交通。 联系方式:freefor_ever@163.com。
有的厂商将Mirai命名为蠕虫不是很贴切,Mirai利用类似蠕虫的方式感染(与传统蠕虫感染方式不同),但实际上是一款僵尸程序。因而称之为Mirai僵尸蠕虫更为准确,后文主要以僵尸称呼。
2016年9月30日,黑客Anna-senpai公开发布Mirai僵尸源码。其公布源码的目的一则是发现有关机构正在清理其掌控的僵尸设备;二则是为了让更多的黑客使用该僵尸进行扩散,掩人耳目,隐藏自己的踪迹。
2016年10月21日,美国东海岸地区遭受大面积网络瘫痪,其原因为美国域名解析服务提供商Dyn公司当天受到强力的DDoS攻击所致。Dyn公司称此次DDoS攻击涉及千万级别的IP地址(攻击中UDP/DNS攻击源IP几乎皆为伪造IP,因此此数量不代表僵尸数量),其中部分重要的攻击来源于IOT设备,攻击活动从上午7:00(美国东部时间)开始,直到下午1:00才得以缓解,黑客发动了三次大规模攻击,但是第三次攻击被缓解未对网络访问造成明显影响。
此次攻击是一次跨越多个攻击向量以及互联网位置的复杂攻击,Flashpoint与Akamai的分析确认攻击流量的来源之一是感染了Mirai僵尸的设备,因为部分离散攻击IP地址来自Mirai僵尸网络。
Mirai僵尸在黑客Anna-senpai公布源码后,被黑客利用并快速的形成了大量的僵尸网络,其中部分黑客参与了此次攻击,目前不排除黑客Anna-senpai也参与了本次攻击,其拥有大概30万-40万的Mirai僵尸肉鸡。
启明星辰ADLab分析发现,Mirai僵尸借鉴了QBOT的部分技术,并在扫描技术、感染技术等方面做了优化,大大提升了感染速度。
此次针对Dyn域名服务器的攻击让古老的DDoS技术再一次震撼了互联网,其中最引人注目是物联网僵尸网络的参与,物联网概念流行了近7年,大量的智能设备正不断地接入互联网,其安全脆弱性、封闭性等特点成为黑客争相夺取的资源。目前已经存在大量针对物联网的僵尸网络,如QBOT、Luabot、Bashlight、Zollard、Remaiten、KTN-RM等等,并且越来越多的传统僵尸也开始加入到这个物联网行列中。
通过启明星辰ADLab的调查分析,Mirai僵尸网络有两次攻击史,其中一次是针对安全新闻工作者Brian Krebs的网站,攻击流量达到665Gbps。
另一次是针对法国网站主机OVH的攻击,其攻击流量达到1.1Tbps,打破了DDoS攻击流量历史记录。
(1)2016年8月31日,逆向分析人员在malwaremustdie博客上公布mirai僵尸程序详细逆向分析报告,此举公布的C&C惹怒黑客Anna-senpai。
(2)2016年9月20日,著名的安全新闻工作者Brian Krebs的网站KrebsOnSecurity.com受到大规模的DDoS攻击,其攻击峰值达到665Gbps,Brian Krebs推测此次攻击由Mirai僵尸发动。
(3)2016年9月20日,Mirai针对法国网站主机OVH的攻击突破DDoS攻击记录,其攻击量达到1.1Tpbs,最大达到1.5Tpbs
(4)2016年9月30日,Anna-senpai在hackforums论坛公布Mirai源码,并且嘲笑之前逆向分析人员的错误分析。
(5)2016年10月21日,美国域名服务商Dyn遭受大规模DDoS攻击,其中重要的攻击源确认来自于Mirai僵尸。
在2016年10月初,Imperva Incapsula的研究人员通过调查到的49,657个感染设备源分析发现,其中主要感染设备有CCTV摄像头、DVRs以及路由器。根据这些调查的设备IP地址发现其感染范围跨越了164个国家或地区,其中感染量最多的是越南、巴西、美国、中国大陆和墨西哥。
直到2016年10月26日,我们通过Mirai特征搜索shodan发现,当前全球感染Mirai的设备已经超过100万台,其中美国感染设备有418,592台,中国大陆有145,778台,澳大利亚94,912台,日本和中国香港分别为47,198和44,386台。
在该地图中颜色越深,代表感染的设备越多,可以看出感染Mirai最多的几个国家有美国、中国和澳大利亚。
Mirai源码是2016年9月30日由黑客Anna-senpai在论坛上公布,其公布在github上的源码被star了2538次,被fork了1371次。
Mirai通过扫描网络中的Telnet等服务来进行传播,实际受感染的设备bot并不充当感染角色,其感染通过黑客配置服务来实施,这个服务被称为Load。黑客的另外一个服务器C&C服务主要用于下发控制指令,对目标实施攻击。
(1)黑客服务端实施感染,而非僵尸自己实施感染。
(2)采用高级SYN扫描,扫描速度提升30倍以上,提高了感染速度。
(3)强制清除其他主流的IOT僵尸程序,干掉竞争对手,独占资源。比如清除QBOT、Zollard、Remaiten Bot、anime Bot以及其他僵尸。
(4)一旦通过Telnet服务进入,便强制关闭Telnet服务,以及其他入口如:SSH和web入口,并且占用服务端口防止这些服务复活。
(5)过滤掉通用电气公司、惠普公司、美国国家邮政局、国防部等公司和机构的IP,防止无效感染。
(6)独特的GRE协议洪水攻击,加大了攻击力度。
上图简单显示了Mirai僵尸的感染过程,与普通僵尸感染不同的是,其感染端是通过黑客服务端实施的,而不是靠bot来实施感染。
受感染的设备端的 bot程序通过随机策略扫描互联网上的设备,并会将成功猜解的设备用户名、密码、IP地址,端口信息以一定格式上传给sanListen,sanLiten解析这些信息后交由Load模块来处理,Load通过这些信息来登录相关设备对设备实施感染,感染方式有echo方式、wget方式和tftp方式。这三种方式都会向目标设备推送一个具有下载功能的微型模块,这个模块被传给目标设备后,命名为dvrHelper。最后,dvrHelper远程下载bot执行,bot再次实施Telnet扫描并进行密猜解,由此周而复始的在网络中扩散。这种感染方式是极为有效的,Anna-senpai曾经每秒会得到500个成功爆破的结果。
bot是mirai僵尸的攻击模块,其主要实现对网络服务设备(扫描过程不只针对IOT设备,只要开启Telnet服务的网络设备均不会放过)的Telnet服务的扫描并尝试进行暴力破解,其会将成功破解的设备ip地址、端口、用户名、密码等信息发送给黑客配置的服务器。并且同时接收C&C服务器的控制命令对目标发动攻击。
由于Mirai的攻击目标主要设计来针对IOT设备,因此其无法将自身写入到设备固件中,只能存在于内存中。所以一旦设备重启,Mirai的bot程序就会消失。为了防止设备重启,Mirai向看门狗发送控制码0×80045704来禁用看门狗功能。
通常在嵌入式设备中,固件会实现一种叫看门狗(watchdog)的功能,有一个进程会不断的向看门狗进程发送一个字节数据,这个过程叫喂狗。如果喂狗过程结束,那么设备就会重启,因此为了防止设备重启,Mirai关闭了看门狗功能。这种技术常常被广泛应用于嵌入式设备的攻击中,比如曾经的海康威视漏洞(CVE-2014-4880)攻击代码中就采用过这种防重启技术。
这里有个小插曲,2016年8月31日,一位逆向分析人员将此代码判定错误,认为这是为了做延时而用,黑客Anna-senpai在Hackforums论坛公布源码时嘲笑并斥责了该逆向分析人员的错误。
Mirai为了防止进程名被暴露,在一定程度上做了隐藏,虽然这种隐藏并不能起到很好的作用。Mirai的具体做法是将字符串进行了随机化。
Mirai同大多数恶意代码一样,需要一种互斥机制防止同一个设备多个实例运行。但Mirai采用的手段有所不同,其通过开启48101端口来防止多个实例运行,具体做法是通过绑定和监听此端口,如果失败,便会关闭已经开启此端口的进程确保只有一个实例运行。这个特点是检测网络设备中是否存在Mirai的最高效的检测方法。
Mirai有一个特点就是具有排他性,设备一旦感染,其会通过端口来关闭Telnet(23)、SSH(22,编译时可选删除项)、HTTP(80,编译时可选删除项)服务并且会阻止这些服务进行重启,其主要实现方法是通过kill强制关闭这三个服务进程,并强行占用这些服务开启时所需要的端口。此举Mirai既可以防止设备被其他恶意软件感染,也可以防止安全人员从外部访问该设备,提高Mirai的取证难度。此功能实现在killer.c文件中。
Telnet服务的重绑定实现如下图,SSH和HTTP服务采用类似的方式实现。
SSH服务的重绑定实现:
HTTP服务的重绑定实现:
通过对实际样本的分析我们发现,大部分黑客并没有对SSH和HTTP进行重绑定操作,绝大部分都只针对于Telnet服务进行了重绑定。
Mirai会通过一种 memory scraping的技术干掉设备中的其他恶意软件,其具体做法是搜索内存中是否存在QBOT特征、UPX特征、Zollard蠕虫特征、Remaiten bot特征来干掉对手,以达到独占资源的目的。
此外,Mirai如果发现anime恶意软件,同样也会强行干掉它。
Mirai僵尸随机扫描网络中IOT设备的Telnet服务并通过预植的用户名密码进行暴力破解,然后将扫描得到的设备IP地址、端口、设备处理器架构等信息回传给Load服务器。这里要注意的是,Mirai的随机扫描是有一个过滤条件的,其中比较有意思就是他会过滤掉通用电气公司、惠普公司、美国国家邮政局、国防部等公司和机构的IP地址。
Mirai僵尸中内置有60余个用户名和密码,其中内置的用户名和密码是加密处理过的,加密算法是通过简单的单字节多次异或实现,其密钥为0xDEADBEEF, 解密密钥为0xEFBEADDE。
Mirai使用高级SYN扫描技术对网络中的设备进行扫描破解,其速度较僵尸程序QBOT所采用的扫描技术快80倍,资源消耗减少至少达20倍。因此具备强大的扫描感染能力,黑客在收集肉鸡过程中,曾经每秒可新增500个IOT设备。
Telnet服务扫描实现如下:
当Mirai扫描到Telnet服务时,会连接Telnet并进行暴力登录尝试。Mirai首先会使用内置的用户名和密码尝试登录,之后通过发送一系列命令来判定登录成功与否。如果成功则试图进行一些操作,比如开启shell等操作,其发送的命令被初始化在一个Table中,如下表所示:
命令操作类型 | Index | 有效 | 功能描述 |
---|---|---|---|
TABLE_SCAN_CB_DOMAIN | 18 | yes | domain to connect to |
TABLE_SCAN_CB_PORT | 19 | yes | Port to connect to |
TABLE_SCAN_SHELL | 20 | yes | ‘shell’ to enable shell access |
TABLE_SCAN_ENABLE | 21 | yes | ‘enable’ to enable shell access |
TABLE_SCAN_SYSTEM | 22 | yes | ‘system’ to enable shell access |
TABLE_SCAN_SH | 23 | yes | ‘sh’ to enable shell access |
TABLE_SCAN_QUERY | 24 | yes | echo hex string to verify login |
TABLE_SCAN_RESP | 25 | yes | utf8 version of query string |
TABLE_SCAN_NCORRECT | 26 | yes | ‘ncorrect’ to fast-check for invalid password |
TABLE_SCAN_PS | 27 | no | “/bin/busybox ps” |
TABLE_SCAN_KILL_9 | 28 | no | “/bin/busybox kill -9 “ |
以上表格中只有TABLE_SCAN_PS和TABLE_SCAN_KILL_9进行了初始化而未对目标设备进行预执行操作。从20到26的操作均是在发送用户名和密码后的登录验证操作。其中TABLE_SCAN_CB_DOMAIN和TABLE_SCAN_CB_PORT为黑客配置的Load服务器,该服务器用于获取有效的Telnet扫描结果,扫描结果中包含IP地址、端口、Telnet用户名和密码等信息。发送信息的格式如下:
zero(1个字节) | IP地址(4bytes) | 端口(2bytes) | 用户名长度(4bytes) | 用户名(muti-bytes) | 密码长度(4bytes) | 密码(muti-bytes) |
---|
Mirai的攻击类型包含UDP攻击、TCP攻击、HTTP攻击以及新型的GRE攻击。其中,GRE攻击就是著名安全新闻工作者Brian Krebs的网站KrebsOnSecurity.com遭受的主力攻击形式,攻击的初始化代码如下:
C&C会被初始化在一张表中,当Mirai回连C&C时,会从表中取出C&C进行连接。
连接C&C成功后,Mirai会进行上线,其上线过程非常简单,自身简单向C&C发送4个字节的0。
接下来会等候C&C的控制命令,伺机对目标发动攻击。对于接受控制命令处做了一些处理,比如首先会进行试读来做预处理(控制指令长度判定等等),最后才会接受完整的控制命令。
当接受到控制命令后,Mirai对控制命令做解析并且执行。控制命令格式如下:
type Attackstruct {
Durationuint32
Typeuint8
Targetsmap[uint32]uint8 //Prefix/netmask
Flagsmap[uint8]string // key=value
}
其中,前4个字节为攻击时长,接下来的4个字节为攻击类型(攻击ID),然后是攻击目标,攻击目标格式如下:
目标数(4个字节) | IP地址(4个字节) | MASK(一个字节) | IP地址(4个字节) | MASK(一个字节) | IP地址….MASK… |
---|
最后是Flags,Flag是一系列的键值对数据,结构类似于攻击目标的格式。下面列出Mirai僵尸网络攻击功能列表。
攻击类型(32位) | 类型值 | 攻击函数 |
---|---|---|
ATK_VEC_UDP | 0 | attack_udp_generic |
ATK_VEC_VSE | 1 | attack_udp_vse |
ATK_VEC_DNS | 2 | attack_udp_dns |
ATK_VEC_UDP_PLAIN | 9 | attack_udp_plain |
ATK_VEC_SYN | 3 | attack_tcp_syn |
ATK_VEC_ACK | 4 | attack_tcp_ack |
ATK_VEC_STOMP | 5 | attack_tcp_stomp |
ATK_VEC_GREIP | 6 | attack_gre_ip |
ATK_VEC_GREETH | 7 | attack_gre_eth |
ATK_VEC_PROXY | 8 | attack_app_proxy(已经被取消) |
ATK_VEC_HTTP | 10 | attack_app_http |
这其中的GRE攻击也就是9月20日安全新闻工作者Brian Krebs攻击事件的主力攻击类型。
ScanListen主要用于处理bot扫描得到的设备信息(ip、端口、用户名、密码),并将其转化为如下格式后输入给Load处理。
Load模块的主要功能是处理scanListen的输入并将其解析后针对每个设备实施感染。其感染实现方法如下:
(1)首先通过Telnet登陆目标设备。
(2)登陆成功后,尝试运行命令/bin/busybox ps来确认是否可以执行busybox命令。
(3)远程执行/bin/busybox cat /proc/mounts;用于发现可读写的目录。
(4)如果发现可用于读写的文件目录,进入该目录并将/bin/echo拷贝到该目录,文件更名为dvrHelpler,并开启所有用户的读写执行权限。
(5)接下来通过执行命令”/bin/busybox cat /bin/echo\r\n”来获取当前设备架构信息。
(6)如果获取架构信息成功,样本试图通过三种方式对设备进行感染,这三种方式分别为echo方式、wget方式、tftp方式。
(7)接下来通过Telnet远程执行下放的程序。
(8)最后远程删除bot程序。
僵尸网络已成为全球面临的共同问题,其攻击不同于其他以窃密、远控为主的恶意代码,其通过掌握着的巨型僵尸网络可以在任何时候对任何目标发动DDoS攻击。僵尸的感染对象已经从服务器、PC、智能手机,扩展向摄像头、路由器、家居安防系统、智能电视、智能穿戴设备,甚至是婴儿监视器,任何互联网连接的设备都可能成为一个潜在的目标。而一般用户是很难注意到被感染的状况的。Mirai僵尸由于源码的开放可能正在迅速的扩散,其攻击的流量特征也可能快速变化而难以监测。由于受感染目标多以IOT设备为主,所有的密码均固化在固件中,因此即便重启后Mirai从内存中消失也无法杜绝二次感染,并且隐藏在这种嵌入式设备中是极难判定其是否受到恶意感染。
(1)如果感染Mirai,请重启设备,并且请求设备厂商更新固件剔除Telnet服务。
(2)不必要联网的设备尽量不要接入到互联网中。
(3)通过端口扫描工具探测自己的设备是否开启了SSH (22), Telnet (23)、 HTTP/HTTPS (80/443)服务,如果开启,请通知技术人员禁用SSH和Telnet服务,条件允许的话也可关闭HTTP./HTTPS服务(防止类似攻击利用Web对设备进行感染)。
Networked hard drives are super convenient. You can access files no matter what computer you’re on — and even remotely.
But they’re expensive. Unless you use the Raspberry Pi.
If you happen to have a few of hard drives laying around you can put them to good use with a Raspberry Pi by creating your own, very cheap NAS setup. My current setup is two 4TB hard drives and one 128GB hard drive, connected to my network and accessible from anywhere using the Raspberry Pi.
Here’s how.
For starters, you need an external storage drive, such as an HDD, SSD or a flash drive.
You also need a Raspberry Pi. Models 1 and 2 work just fine for this application but you will get a little better support from the Raspberry Pi 3. With the Pi 3, you’re still limited to USB 2.0 and 100Mbps via Ethernet. However, I was able to power one external HDD with a Pi 3, while the Pi 2 Model B could not supply enough power to the same HDD.
In my Raspberry Pi NAS, I currently have one powered 4TB HDD, one non-powered 4TB HDD and a 128GB flash drive mounted without issue. To use a Pi 1 or 2 with this, you may want to consider using a powered USB hub for your external drives or using a HDD that requires external power.
Additionally, you need a microSD card — 8GB is recommended — and the OpenMediaVault OS image, which you can download here.
To install the operating system, we will use the same method used for installing any OS without NOOBS. In short:
More detailed installation instructions can be found here for both Windows and Mac. Just substitute the Raspbian image with OpenMediaVault.
After the image has been written to the SD card, connect peripherals to the Raspberry Pi. For the first boot, you need a keyboard, monitor and a local network connection via Ethernet. Next, connect power to the Raspberry Pi and let it complete the initial boot process.
Once that is finished, use the default web interface credentials to sign in. (By default, the username isadmin and the password is openmediavault.) This will provide you with the IP address of the Raspberry Pi. After you have that, you will no longer need a keyboard and monitor connected to the Pi.
Connect your storage drives to the Raspberry Pi and open a web browser on a computer on the same network. Enter the IP address into the address bar of the browser and press return. Enter the same login credentials again ( admin for the username and openmediavault for the password) and you will be taken to the web interface for your installation of OpenMediaVault.
The first thing you will want to do to get your NAS online is to mount your external drives. Click File Systems in the navigation menu to the left under Storage.
Locate your storage drives, which will be listed under the Device column as something like /dev/sda1 or/dev/sdc2. Click one drive to select it and click Mount. After a few seconds have passed, click Apply in the upper right corner to confirm the action.
Repeat this step to mount any additional drives.
Next, you will need to create a shared folder so that the drives can be accessed by other devices on the network. To do this:
Finally, to access these folders and drives from an external computer on the network, you need to enable SMB/CFIS.
Click SMB/CFIS under Services in the left navigation pane and click the toggle button beside Enable. Click Save and Apply to confirm the changes.
Next, click on the Shares tab near the top of the window. Click Add, select one of the folders you created in the dropdown menu beside Shared folder and click Save. Repeat this step for shared folders you created.
Now that your NAS is up and running, you need to map those drives from another computer to see them. This process is different for Windows and Mac, but should only take a few seconds.
To access a networked drive on Windows, open File Explorer and click This PC. Select the Computer tab and click Map network drive.
In the dropdown menu beside Drive choose an unused drive letter. In the Folder field, input the path to the network drive. By default, it should look something like \\RASPBERRYPI\[folder name]. (For instance, one of my folders is HDD, so the folder path is \\RASPBERRYPI\HDD). Click Finish and enter the login credentials. By default, the username is pi and the password is raspberry. If you change or forgot the login for the user, you can reset it or create a new user and password in the web interface under User in Access Rights Management.
To open a networked folder in OS X, open Finder and press Command + K. In the window that appears, type smb://raspberrypi or smb://[IP address] and click Connect. In the next window, highlight the volumes you want to mount and click OK.
You should now be able to see and access those drives within Finder or File Explorer and move files on or off the networked drives.
There are tons of settings to tweak inside OpenMediaVault, including the ability to reboot the NAS remotely, setting the date and time, power management, a plugin manager and much, much more. But if all you need is a network storage solution, you’ll never need to dig any deeper.
昨天晚上,产品教父张小龙在WXG(微信事业群)领导力大会上的讲话又一次刷爆了互联网人的朋友圈。谈到敏捷开发的时候,张小龙直言:
我们今天可以想一些与众不同的点子,然后我们可以很快就看到效果,因为我们可以很快把它上线了,然后可以去验证,如果不对就下线,如果还有改进余地,下个星期再去改它。这是一个能够持续实现你的想法的过程。
其实这种敏捷开发的方法由来已久,并且被Google、Facebook等硅谷企业广泛应用。它已经形成了一套完整的方法论,总结起来就是“MVP”和“精益分析”两个概念。
MVP是最简化可实行产品(Minimum Viable Product)的简称。最简化可实行产品是以尽可能低的成本展现产品的核心概念,用最快、最简的方式建立一个可用的产品原型,用这个原型表达出你产品最终想要的效果,然后通过迭代来完善细节。
图1:MVP的产品迭代策略
假如你的产品愿景是一种高级出行工具,比如小轿车。传统的产品设计思路是一步一步,从车轮、车轱辘、外壳、动力装置、内部装饰一个流程一个流程做起,最后得到一个完善的产品。而MVP的思路,我们可能会先做一个小滑板车或者自行车,看看用户对出行工具的认可程度。如果用户认可我们的产品概念,我们可以接下去生产更加高级、完善的摩托车、甚至小轿车。
传统产品迭代思路成本高、速度慢、风险大,花高成本做出来的产品用户可能不认可;MVP策略的优点在于试错成本低、速度快、风险低,能满足产品快速迭代的需求。
埃里克·莱斯,硅谷著名的企业家和作家,最先提出来“精益创业”的概念。精益创业的理念涵括精益客户开发、精益软件开发和精益生产实践三大部分,这是一个快速和高效开发产品和服务的框架。
图2:”构建-衡量-学习“的精益分析框架
精益创业的本质是精益分析,核心是“构建-衡量-学习”循环。张小龙的敏捷开发想法和这个方法论不谋而合:首先是你有一个想法或者灵感,然后通过MVP策略产品快速上线;产品上线后,通过数据来衡量用户的表现,如果好的话就保持、继续优化,不好的下线反思。通过这种循环,产品快速迭代、用户的需求得到更好的满足。
在MVP开发过程或者精益分析整个框架中,最重要的莫属于衡量(measure)这个环节。如何选择合理的指标来衡量MVP的效果?如何通过合理工具来监测用户行为数据、优化产品?这都是敏捷开发需要解决的问题。
在精益化运营的今天,产品、市场、运营如何通过敏捷开发来提升效率,这是一个所有互联网人的需要面对的命题。
任何产品,信息量达到一个量级的时候就会出现信息查找困难的情况。一个装满文件的Mac笔记本,想找一个文档却不知道放在哪个文件夹了,这个时候就可以使用【Spotlight搜索】进行全局搜索。这样的好处,一个是能找到所有相关的文件,另一方面是节省时间。
图3:借鉴Mac笔记本的Spotlight搜索功能
某数据分析产品内含单图、看板、漏斗、分群等十多个功能模块,如何通过一次检索找到需要的图表就非常重要。因为这样可以避免个功能之间来回跳转,节省检索时间、提高效率。
1. 提出想法、快速构建
工程师提出做一个搜索工具的想法,方便用户在整个系统内进行全局搜索。产品经理根据用户反馈设定使用产品,把功能点进行优先级排序,确定MVP(最简化可实行产品)–––部分功能点击后直接进入详情页,降低操作成本。
2.产品落地、快速上线
工程师开始进行搜索库的建设,打通各个模块里的图表存储做为搜索库,匹配拼音/空格等多种搜索方式。设计师负责界面样式的设计,把信息直观地表现出来。
图4:GrowingIO的全局检索功能
3.数据验证、快速迭代
短短 1 天半的时间里,全局搜索产品快速迭代了三次。从只能把汉字作为关键字,到可以直接用拼音进行搜索,再把关键词和模块自动分类 ,提高整个搜索工具的检索速度。
在这次MVP实践中,快速组建团队、合理分工、快速上线验证起到了重要作用。团队越来越大的时候,我们需要跨部门更加直接的沟通、需要直接在白板前讨论的效率。这个【全局搜索】开发案例是一个非常好的MVP案例。
商业环境变幻莫测,如何在竞争激烈的市场中赶超对手,这个时候快速、正确的决策尤为重要。
图5:GrowingIO分钟级别的实时数据监测
某在线教育产品正在探索获取用户的新方式,依次尝试了免费视频课程、赠送电子教材、热门话题在线讨论等多种方法,但是效果都一般般。某日该企业市场部门举行了一次免费在线直播课程,直播结束后的5分钟,该社区平台的流量比往日均值上涨了8倍,获取了大量新用户。实时监测到的数据极大启发了市场人员,直播流量会成为营销的新一轮增长点。
某互联网金融企业在SEM方面有大量的推广,但是他的CAC(获客成本)只有行业均值的一半,这到底是怎样做到的呢?在投放的初期,他们也面临着如何优化渠道、关键词等问题。在没有任何指导数据的基础上,他们对市场上的主流搜索引擎、相关关键词进行了一轮短时间的“地毯式轰炸”。
图6:GrowingIO监测到的广告效果
通过前期的地毯式投放,该企业获得了各个渠道、各个关键词的转化数据。通过数据分析,市场人员选出来转化效果最佳的两个渠道,进行大规模投放,并且在后期不断优化。通过这种方式该企业以非常低的成本获得了大量的新用户。
产品越来越多,同质化竞争越来越严重。产品要想在激烈的竞争中拔得头筹,除了产品设计,运营也是非常重要的一个因素。
某技术社区花了大量精力获取新用户,但是新用户的流失率一直居高不下,次周留存率才11%。一个偶然机会,一个新用户反馈给社区运营,说他们的精选文章非常好,他每天都看。
图7:GrowingIO留存图
受到这个启发,运营想既然精选文章能吸引新用户留下来,那是不是可以向新用户多多推荐精选内容呢?于是他们挑选了20篇优质文章拼凑成了一本PDF电子书,每位新注册用户都能收到这本电子书。数据显示,点击了这本电子书的用户次周留存率提升到了43%,这样的数据让社区运营人员异常兴奋。
图8:电子书MVP迭代过程
通过对读过电子书新用户的访谈,他们发现之前仓促准备的电子书内容不是适合所有人、而且内容深度不一,给很多新用户造成困恼。在接下来的几周时间里,该社区准备了前端、中端、后端、测试4个技术方向电子书,并且根据用户的工作时间推荐初级、中级、高级三个版本。一个月后,新版电子书向新用户推送,数据显示点击过电子书的新用户次周留存率再一次提升到了56%。
精益不是廉价或者小规模的代名词,精益意味着破除浪费、低效率并且快速行动起来,它适用于任何组织。MVP不仅是改善产品的方式,更是现实业务的监测器。
谈及之前所做的事,用张小龙自己的话说,“一个非常平庸的团队用了一些非常平庸的方法去做出来一个非常平庸的产品,而且是不知不觉的!”那么,如今的你为何不放手一搏。搭建一个志同道合的团队,尝试一下敏捷开发的方式,在不断试错中做出一个个惊艳的功能!
你,是不是也想试试?
Deep Learning is a sub-field of Machine Learning that has its own peculiar ways of doing things. Here are 10 lessons that we’ve uncovered while building Deep Learning systems. These lessons are a bit general, although they do focus on applying Deep Learning in a area that involves structured and unstructured data.
The one tried and true way to improve accuracy is to have more networks perform the inferencing and combining the results. In fact, techniques like DropOut is a means of creating “Implicit Ensembles” were multiple subsets of superimposed networks cooperate using shared weights.
2. Seek Problems where Labeled Data is Abundant
The current state of Deep Learning is that it works well only in a supervised context. The rule of thumb is around 1,000 samples per rule. So if you are given a problem where you don’t have enough data to train with, try considering an intermediate problem that does have more data and then run a simpler algorithm with the results from the intermediate problem.
3. Search for ways to Synthesize Data
Not all data is nicely curated and labeled for machine learning. Many times you have data that are weakly tagged. If you can join data from disparate sources to achieve a weakly labeled set, then this approach works surprisingly well. The most well known example is Word2Vec where you train for word understanding based on the words that happen to be in proximity with other words.
4. Leverage Pre-trained Networks
One of the spectacular capabilities of Deep Learning networks is that bootstrapping from an existing pre-trained network and using it to train into a new domain works surprisingly well.
5. Don’t forget to Augment Data
Data usually have meaning that a human may be aware of that a machine can likely never discover. One simple example is a time feature. From the perspective of a human the day of the week, whether this is a holiday or not or the time of the day may be important attributes, however a Deep Learning system may never be able to surface that if all its given are seconds since Unix epoch.
6. Explore Different Regularizations
L1 and L2 regularizations are not the only regularizations that are out there. Explore the different kinds and perhaps look at different regularizations per layer.
7. Embrace Randomness
There are multiple techniques to initialize your network prior to training. In fact, you can get very far just training the last layer of a network with the previous layers being mostly random. Consider using this technique to speed up you Hyper-tuning explorations.
8. End-to-End Deep Learning is a Hail Mary Play
A lot of researchers love to explore end-to-end deep learning research. Unfortunately, the most effective use of Deep Learning has been to couple it with out techniques. AlphaGo would not have been successful if Monte Carlo Tree Search was not employed. If you want to make an impact in the Academic community then End-to-end Deep Learning might be your gamble. However in a time constrained industrial environment that demands predictable results, then you best be more pragmatic.
9. Resist the Urge to Distribute
If you can, try to avoid using multiple machines (with the exception of hyper-parameter tuning). Training on a single machine is the most cost effective way to proceed.
10. Convolution Networks work pretty well even beyond Images
Convolution Networks are clearly the most successful kind of network in the Deep Learning space. However, ConvNets are not only for Images, you can use them for other kinds of features (i.e. Voice, time series, text).
That’s all I have for now. There certainly a lot more other lessons. Let me know if you stumble on others.
You can find more details of these individual lessons athttp://www.deeplearningpatterns.com
Originally published at blog.alluviate.com.
Posted by T.L on October 29, 2016
大数据社会下数据就是黄金,新浪微博作为一个国内网络社交早就意识到这一点,本着资本家和商人的心态给你提供的开放API接口只可以获得少量无关紧要的数据(想要数据,money来换),对比国外Twitte等社交平台会提供一些数据接口供研究人员获取大量研究数据。那我们GEEK的口号是,凡是网上能显示数据的朕兼“可取”(v_v…为什么加个引号呢,因为虽然出于技术角度是都可取得,但出于道德方面考虑也要尊重数据作者的规约)。
本文基于python以新浪微博为数据平台,从数据采集、关键字提取、数据存储三个角度,用最简单的策略来挖掘我们的“黄金”。
有爬虫基础的人可以直接跳过数据采集部分看“上海租房”话题挖掘实战项目,项目地址https://github.com/luzhijun/weiboSA(目前已更新豆瓣小组爬取)。
使用python是因为代码简洁,虽然计算比java和c慢很多,但数据采集时间开销大部分是IO部分的,你愿意每次用java或者c写效率也提高不到哪去。
数据采集基本用爬虫机器人,原理谁都会,google就是靠他发家致富走上人生巅峰的。下面介绍常用来做爬虫的几个库。
怎样抓网页呢?其实就是根据URL来获取它的网页信息,虽然我们在浏览器中看到的是一幅幅优美的画面,但是其实是由浏览器解释才呈现出来的,实质它是一段HTML代码,加 JS、CSS,如果把网页比作一个人,那么HTML便是他的骨架,JS便是他的肌肉,CSS便是它的衣服。所以最重要的部分是存在于HTML中的,下面我们就写个例子来扒一个网页下来。
import urllib2
response = urllib2.urlopen("http://www.baidu.com")
print response.read()
结果就和在Chrome等浏览器中右键查看源码一样的内容,urllib2是python内置库,简化了httplib的用法(urllib2.urlopen相当于Java中的HttpURLConnection)。有2那肯定有urllib啊,urllib2可以接受一个Request类的实例来设置URL请求的headers,但urllib仅可以接受URL。这意味着,你不可以伪装你的User Agent字符串等。urllib2在python3.x中被改为urllib.request。 接下来用urllib2伪装iphone 6浏览,模拟浏览器发送GET请求。
req = request.Request('http://www.douban.com/')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
with request.urlopen(req) as f:
print('Status:', f.status, f.reason)
print('Data:', f.read().decode('utf-8'))
结果会返回移动版的源码信息
...
<link rel="apple-touch-icon-precomposed" href="https://gss0.bdstatic.com/5bd1bjqh_Q23odCf/static/wiseindex/img/screen_icon.png"/>
<meta name="format-detection" content="telephone=no"/>
...
如果想要以post方式提交,只要在Request中附加data字段就可以,下面附加用户名密码登录新浪博客。
#我们模拟一个微博登录,先读取登录的邮箱和口令,然后按照weibo.cn的登录页的格式以username=xxx&password=xxx的编码传入:
from urllib import parse
print('Login to weibo.cn...')
email = input('Email: ')
passwd = input('Password: ')
login_data = parse.urlencode([
('username', email),
('password', passwd),
('entry', 'weibo'),
('client_id', ''),
('savestate', '1'),
('ec', ''),
('pagerefer', 'https://passport.weibo.cn/signin/welcome?entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F')
])
req = request.Request('https://passport.weibo.cn/sso/login')
req.add_header('Origin', 'https://passport.weibo.cn')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
req.add_header('Referer', 'https://passport.weibo.cn/signin/login?entry=mweibo&res=wel&wm=3349&r=http%3A%2F%2Fm.weibo.cn%2F')
with request.urlopen(req, data=login_data.encode('utf-8')) as f:
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s: %s' % (k, v))
print('Data:', f.read().decode('utf-8'))
其中Origin和referer字段是反“反盗链”,就是检查你发送请求的header里面,referer站点是不是他自己。
爬虫被封的一个依据就是重复IP,因此可以为爬虫设置不同代理IP。此外有些网站需要cookie才能查看,所谓Cookie,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。比如说有些网站需要登录后才能访问某个页面,在登录之前,你想抓取某个页面内容是不允许的。那么我们可以利用Urllib2库保存我们登录的Cookie,然后再抓取其他页面就达到目的了。
cookielib模块的主要作用是提供可存储cookie的对象,以便于与urllib2模块配合使用来访问Internet资源。Cookielib模块非常强大,我们可以利用本模块的CookieJar类的对象来捕获cookie并在后续连接请求时重新发送,比如可以实现模拟登录功能。该模块主要的对象有CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。
它们的关系:CookieJar–派生->FileCookieJar –派生–>MozillaCookieJar和LWPCookieJar
from urllib import request
from http.cookiejar import CookieJar
cookie=CookieJar()
cookie_support= request.HTTPCookieProcessor(cookie)#cookie处理器
opener = request.build_opener(cookie_support)
opener.open('http://www.baidu.com')
for item in cookie:
print(item.name,':',item.value)
结果: >BAIDUID : E4DECD4AF63915B9AFF5AC28951A3DAA:FG=1
BIDUPSID : E4DECD4AF63915B9AFF5AC28951A3DAA
H_PS_PSSID : 1437_18241_17944_21079_18559_21454_21406_21377_21191_21321
PSTM : 1477631558
BDSVRTM : 0
BD_HOME : 0
这里使用默认的CookieJar 对象,如果要将Cookie保存起来,可以使用FileCookieJar类和其子类中的save方法,加载就用load方法。
写脚本从指定网站抓取数据的时候,免不了会被网站屏蔽IP。所以呢,就需要有一些IP代理。随便在网上找了一个提供免费IP的网站西刺做IP抓取。观察可以发现有我们需要的信息的页面url有下面的规律:www.xicidaili.com/nn/+页码。可是你如果直接通过get方法访问的话你会发现会出现500错误。原因其实出在这个规律下的url虽然都是get方法获得数据,但都有cookie认证,另外还有反外链等,下面例子用来获得西刺的cookie。
headers=[('User-Agent','Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25'),
('Host','www.xicidaili.com'),
('Referer','http://www.xicidaili.com/n')]
def getCookie()
cookie=CookieJar()
cookie_support= request.HTTPCookieProcessor(cookie)#cookie处理器
opener = request.build_opener(cookie_support)
opener.addheaders=headers
opener.open('http://www.xicidaili.com/')
return cookie
有了cookie就可以爬了,爬的内容怎么处理呢,介绍个SB工具—— BeautifulSoup。
BeautifulSoup翻译叫鸡汤,现在版本是4.5.1,简称BS4,倒过来叫4SB,不过抓数据一点都不SB。提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时,Beautiful Soup就不能自动识别编码方式了。然后,你仅仅需要说明一下原始编码方式就可以了。 关于BS的介绍和用法官方文档很详细,下面给几个”Web scraping with python”1中的例子看下BS是否好喝,可以和文档对照看。 首先你得安装了BS,然后爬取http://www.pythonscraping.com/pages/page3.html中的图片来小试牛刀。
import re
from urllib import request
from bs4 import BeautifulSoup
html=request.urlopen("http://www.pythonscraping.com/pages/page3.html")
bs=BeautifulSoup(html,"lxml")
#打印所有图片地址
for pic in bs.find_all('img',{'src':re.compile(".*\.jpg$")}):
print(pic['src'])
结果: >../img/gifts/logo.jpg
>../img/gifts/img1.jpg
>../img/gifts/img2.jpg
>../img/gifts/img3.jpg
>../img/gifts/img4.jpg
>../img/gifts/img6.jpg
接上文,我们把西刺的高匿代理ip爬出来放到本地proxy.txt。
cookie=getCookie()
# get the proxy
with open('proxy.txt', 'w') as f:
for page in range(1,101):
if page%50==0:#每50页更新下cookie
cookie=getCookie()
url = 'http://www.xicidaili.com/nn/%s' %page
cookie_support= request.HTTPCookieProcessor(cookie)
opener = request.build_opener(cookie_support)
request.install_opener(opener)
req = request.Request(url,headers=dict(headers))
content = request.urlopen(req)
soup = BeautifulSoup(content,"lxml")
trs = soup.find('table',id="ip_list").findAll('tr')
for tr in trs[1:]:
tds = tr.findAll('td')
ip = tds[1].text.strip()
port = tds[2].text.strip()
protocol = tds[5].text.strip().
f.write('%s://%s:%s\n' % (protocol, ip, port))
结果十五秒爬了1万条数据(与电脑环境有关),说明1页正好100条,而总页数超过1000页,也就是记录数超过10w条,如果固定用同一个cookie肯定不安全(谁会有空翻看1000页数据。。。),因此设置每爬50页更新下cookie。 有了代理地址,不一定能保证有效,可能就被封杀了,因此使用思路是把代理地址存入哈希表,验证无效的删除(看状态码),重新在表中取新的记录。 代理地址使用方式如下:
...
proxy_handler = request.ProxyHandler({'http': '123.165.121.126:81'}) #http://www.xicidaili.com/nn/2 随便找个
opener = request.build_opener(proxy_handler,cookie_handler ...各种其他handle)
...
另外推荐个神器,crawlera ,基本满足各种需要。
假如真要爬1000页,需要花150秒?好吧,好像也不多,但我要说的是可以多进程或者异步处理。多进程很好做,注意以手动维护一个HttpConnection的池,然后每次抓取时从连接池里面选连接进行连接即可(每秒几百个连接正常的有理智的服务器一定会封禁你的)。python的异步处理用到了Twisted库,却远没有同是异步模式的nodejs火,算是python中的巨型框架了,想想python的巨型框架活的不久,感兴趣的推荐看下《Twisted网络编程必备》2。关于单线程、多线程、异步有张图推荐看下。
写爬虫还要考虑其他很多问题,授权验证、连接池、数据处理、js处理等,这里有个经典爬虫框架:Scrapy,目前支持python3,支持分布式, 使用 Twisted来处理网络通讯,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求。
Scrapy的入门学习参见学习Scrapy入门,对应中文文档几小时内可以快速掌握。另外国内某大神开发了个WebUI的Pyspider,具有以下特性:
从内容上讲,两者具有功能差不多,包括以上3,5,6。不同是Scrapy原生不支持js渲染,需要单独下载scrapy-splash,而PyScrapy内置支持scrapyjs;PySpider内置 pyquery选择器,Scrapy有XPath和CSS选择器,这两个大家可能更熟一点;此外,Scrapy全部命令行操作,Pyscrapy有较好的WebUI;还有,scrapy对千万级URL去重支持很好,采用布隆过滤来做,而Spider用的是数据库来去重?最后,PySpider更加容易调试,scrapy默认的debug模式信息量太大,warn模式信息量太少,由于异步框架出错后是不会停掉其他任务的,也就是出错了还会接着跑。。。从整体上来说,pyspider比scrapy简单,并且pyspider可以在线提供爬虫服务,也就是所说的SaaS,想要做个简单的爬虫推荐使用它,但自定义程度相对scrapy低,社区人数和文档都没有scrapy强,但scrapy要学习的相关知识也较多,故而完成一个爬虫的时间较长。
因为比较喜欢有完整文档的支持,所以后面主要用Scrapy,简要说下Scrapy运行流程。
根据scrapy文档描述,要防止scrapy被禁用,主要有以下几个策略。
由于Google cache基于你懂的原因不可用,其余都可以利用,Crawlera的分布式下载,我们可以在下次用一篇专门的文章进行讲解。下面主要从动态随机设置user agent、禁用cookies、设置延迟下载和使用代理IP这几个方式入手。
自定义中间件
Scrapy下载器通过中间件控制的,要实现代理IP、user agent切换可以自定义个中间件。 在项目下创建(如何创建项目,使用scrapy start yourProject命令,参考文档)好项目后,在里面找到setting.py文件,先把agents和代理ip放到setting.py中(代理ip较少情况下这样做,较多的话还是放到数据库中去,方便管理),设置中间件名字MyCustomSpiderMiddleware和优先级。
USER_AGENTS = [
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
]
PROXIES = [
{'ip_port': '111.11.228.75:80', 'user_pass': ''},
{'ip_port': '120.198.243.22:80', 'user_pass': ''},
{'ip_port': '111.8.60.9:8123', 'user_pass': ''},
{'ip_port': '101.71.27.120:80', 'user_pass': ''},
{'ip_port': '122.96.59.104:80', 'user_pass': ''},
{'ip_port': '122.224.249.122:8088', 'user_pass': ''},
]
# 禁用cookoe (enabled by default)
COOKIES_ENABLED = False
#设置下载延迟
DOWNLOAD_DELAY = 1
# 下载中间件
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
'weiboZ.middlewares.MyCustomDownloaderMiddleware': 543,
}
middlewares/MyCustomDownloaderMiddleware.py
import random
import base64
from settings import PROXIES
class RandomUserAgent(object):
"""Randomly rotate user agents based on a list of predefined ones"""
def __init__(self, agents):
self.agents = agents
@classmethod
def from_crawler(cls, crawler):
return cls(crawler.settings.getlist('USER_AGENTS'))
def process_request(self, request, spider):
#随机选个agent
request.headers.setdefault('User-Agent', random.choice(self.agents))
class ProxyMiddleware(object):
def process_request(self, request, spider):
proxy = random.choice(PROXIES)
if proxy['user_pass'] is not None:
request.meta['proxy'] = "http://%s" % proxy['ip_port']
encoded_user_pass = base64.encodestring(proxy['user_pass'])
request.headers['Proxy-Authorization'] = 'Basic ' + encoded_user_pass
else:
request.meta['proxy'] = "http://%s" % proxy['ip_port']
当你准备爬某个网站的时候,首先应该先看下该网站有没有robots.txt。robots.txt是1994年出现的,也称为机器人排除标准(Robots Exclusion Standard),网站管理员不想某些内容被爬到的时候可以再该文件中注明。robots.txt虽然有主流的语法格式,但是各大企业标准不一,没有别人可以阻止你创建自己版本的robots.txt,但这些robots.txt不应该因为不符合主流而不被遵守。一般文件字段包含:User-agent,Allow,Disallow分别代表搜索机器人允许看和不许看的内容。
之前看新闻说今年4月大众点评把百度给告了,请求法院判令两被告停止不正当竞争行为,共同赔偿汉涛公司经济损失9000万元和为制止侵权行为支出的45万余元,并刊登公告、澄清事实消除不良影响。有用百度地图的应该知道这个(最近百度高德开撕,又在黑百度了~~~),定位完毕会显示附近商家和点评信息,来看下大众点评网的robots.txt. 光看
User-agent: *
…
Disallow: /shop//rank_p
…
就知道不允许任何企业和个人爬他家的商店评分数据,更何况其他更具有价值的数据呢,数据是黄金,要求赔偿9000万元对百度来说不算多,但百度回应内容大众点评网的robots协议面向百度等搜索引擎开放,百度地图抓取大众点评网的内容正是在robots.txt允许的情况下。通常业内习惯上没有被不允许的就是允许的,也就是说网站的关键信息可以帮助SEO优化的这个不能被禁止哟,不然你就没头条了,看人家竞争对手爱帮网倒是单独被列出来全面封杀了,因为其实力太弱,没有商业合作价值。就算这样我也没看出允许百度抓点评的用户评论数据,难道说点评网之前没robots.txt?人家不傻!百度挖了人家数据还叫嚣着遵守Robots协议,(其实他完全可以偷偷摸摸抓了数据自己私下研究,却要直接在百度地图上显示出来,这是要把数据价值榨干啊,够霸道)好比把人打了顿理直气壮地说你瞅啥一样,太野蛮了。。。
说多了,来看下新浪微博的Robots协议。明确规定了Sitemap: http://weibo.com/sitemap.xml 中列出的内容不允许被百度、360、谷歌、搜狗、微软必应、好搜、神马查看,后面还注明了Disallow: User-agent: * Disallow: /,也就是说前面是单独列出的,理论上这些数据不允许任何机构和个人爬取。这些是啥数据呢,movie和music数据,那你放心好了,微博文本数据可以爬了,但人家也不傻,可以显示的微博信息是有限制的,不可能所有数据库的数据都显示出来。
在58、赶集、链家上找过房子的人都为中介苦恼,所谓的行业规矩令人做呕,这些人不生产社会价值却担当了新世纪的买办角色,好在通过微博也可以找房,而且绝大部分是个人房源。
以上海找房子为例,微博搜索框输入@上海租房 就可以的到如下页面 >http://s.weibo.com/weibo/%2540%25E4%25B8%258A%25E6%25B5%25B7%25E7%25A7%259F%25E6%2588%25BF?topnav=1&wvr=6&b=1
还是不错的,然后看下源码发现并没有html数据,显然是AJAX异步了,Scrapy要爬的话还得安装scrapy-splash改下配置用splash解析js内容,而且要看下一页必须登录状态才可以,那要在header里面添加cookie,可以登录后chrome F12 开发工具查看,但你敢保证拿包含自己的账号的cookie去做爬虫发现了不被封?其实这里可以显示的数据最多1000条,按最新的1000条显示,何必大费周章去搞那么复杂呢,可以用移动版的微博搜下嘛,点击。
用开发者工具看下网络请求数据状况,搜索包含名字‘page’ 请求消息头,可以发现规律:
左边Name列凡是内容页下拉引起ajax加载新页,新页内容以json格式返回;右边字段末尾page=?部分,代表传递第几页的内容,?最大到100,和电脑版最多看50页一样有数据限制。
json内容如下:
ok,能显示的数据都在里面,而且还是json格式,都不用选择器了,这个要比电脑版简单多了。
并不是所有json字段的数据都有用,这里只选取有用的字段,总的原则是按需抽取。可以看下项目中定义的Items.py。
微博内容id 对应字段放数据库中将有唯一约束,防止重复微博。选择mblogid作为唯一id,而千万不是itemid,经测试发现itemid只代表当天微博的槽位,比如限制浏览10条数据,就有1~10个槽位,而itemid就代表这10个槽位标签,并不代表微博内容id。另外mblog字段下还有个id属性,估计和mblogid一样的效果,有兴趣可以试试。
发布时间代表信息的实效,json里面有两个字段表示,一个是时间戳created_timestamp,另一个是显示出来的真实时间数据,这里取真实数据方便直接提取显示,但后期存储的时候需要统一转换为标准时间格式。
评论数、转发数、点赞数和时效结合可以用来综合评估微博信息价值(时间越靠后这三个数字越能评价信息价值)。
用户名、粉丝数、说说数可以用来检验用户是否有价值用户,或者是机器人。
后期处理需要提取求/租信息的关键词,包含价格、几号线、行政区划、信息是求租还是出租。
项目中定义的pipelines.py文件是scrapy管道处理类,也就是主要的后期数据处理类。其中一个是JsonPipeline类,直接将数据打印到json文件中,这个前期可以用来调试爬虫效果。另一个是MongoPipeline类,用来保存后期处理后的数据。在setting文件中ITEM_PIPELINES属性可以设置具体采用哪个管道处理类。
后期处理主要任务是提取关键字,如何从微博信息中爬取地理位置、价格?这里采用双数组Trie树
Trie树是搜索树的一种,来自英文单词”Retrieval”的简写,可以建立有效的数据检索组织结构,是中文匹配分词算法中词典的一种常见实现。它本质上是一个确定的有限状态自动机(DFA),每个节点代表自动机的一个状态。在词典中这此状态包括“词前缀”,“已成词”等。前面文章讲了下其原理,可以查看。
采用Trie树搜索最多经过n次匹配即可完成一次查找(即最坏是0(n)),而与词库中词条的数目无关,缺点是空间空闲率高,它是中文匹配分词算法中词典的一种常见实现。
双数组Trie(doublearrayTrie,DAT)是trie树的一个简单而有效的实现(日本人发明的),由两个整数数组构成,一个是base[],另一个是check[]。双数组Trie树是Trie树的一种变形,是在保证Trie树检索速度的前提下,提高空间利用率而提出的一种数据结构.其本质是一个确定有限状态自动机(DeterministicFiniteAutomaton,DFA),每个节点代表自动机的一个状态,根据变量的不同,进行状态转移,当到达结束状态或者无法转移时完成查询.DAT采用两个线性数组(base和check)对Trie树保存,base和check数组拥有一致的下标,即DFA中的每一个状态,也即Trie树中所说的节点,base数组用于确定状态的转移,check数组用于检验转移的正确性,检验该状态是否存在34。
在比较用于正向最大匹配分词的速度方面,DAT分词平均速度为936kB/s5(2006年),项目用到github上一日本人的python版的DAT,其查询速度可以达到 2.755M/s,查询速度和分词速度基本是差不多的,这三倍的差距应该是做了优化的。
词典的收集是比较麻烦,没有现成的,项目中搜集了上海地铁、街道、行政区、乡镇等信息,其中价格信息范围是从600~9000,可识别二千、二千二、两千一等中文价格,后面微博上看到有人用1.2k做价格的,暂时没加入,自己可以加入词条后重新运行下makeData.py文件即可收录。
判断信息是租房还是求房也是根据关键字,当信息中出现[“求租”, “想租”,”求到”,”求从”, “要租”, “寻租”,”寻找”, “找新房子”, “找房子”, “找房”, “寻房”, “求房”, “想找”, “希望房”]信息就标注为求房,否则标注为租房。
此外项目还收集了三千多个楼盘信息,由于有些楼盘信息容易混淆真实语境,比如‘峰会’(真不懂怎么会有这楼盘名)、‘艺品’与信息‘文艺品味’、‘黄兴’、‘金铭’与人名冲突等等。有想根据楼盘查询信息的同学可以把makeData.py中第5、51行注释取消运行下这个文件。
关于时间处理,微博挖到的时间有几种类型:
需要统一转化,使用DataUtil类处理。其中mongodb使用的是ISO时间,比北京时间早8小时,而pymongo中的datetime.datetime 数据并不会按时区处理,因此手动减少8小时后存储。同样从mongoDB中取出的时间要转化为当地时间。
> d=new Date()
> d
ISODate("2016-10-29T06:59:49.461Z")
> d.toLocaleDateString()
10/29/2016
其实就这点数据放哪个数据库都无所谓,但假如这个数据量很大,就要好好考虑数据存储了。
数据库的比较就好比java、c#、python、Go等的骂战一样,没有最好的,只有最适合场景的。oracle、mysql都学过,nosql中学过hbase和mongodb,就我而言单从7个角度比较:
对于现在这个场景,爬虫在前端爬数据,管道层在那边处理数据后写数据,而这些数据具有时效性,也就是说只会去读一部分数据,相对来说,这就对写的要求较高。此外,这个场景就一个表,不涉及多表关联、约束等,复杂查询可以说没有,需要功能较少。另外网络数据不能保证一致性和可靠性,只要高可用性(HA)即可,Nosql可以设置副本机制达到高可用性,mysql虽然也可以做到成本稍高,将来可扩展角度也不适合。因此这个场景最适合的是Nosql。
Cassandra HBase和MongoDb性能比较此文详细比较了三种主流Nosql数据库,最终项目选择Mongodb,就在于MongoDB适合做读写分离场景中的读取场景,并且其用js开发的,对json插入支持特别好。什么时候mongodb是较坏的选择呢,参考WHY MONGODB IS A BAD CHOICE FOR STORING OUR SCRAPED DATA
python的mongodbSDK包叫pymongo,十分钟看个教程就会了,这个业务场景为了加快查询,需要对价格、行政区、发布时间创建索引,其中价格、行政区由于是数组形式所以是多键索引,索引属性是稀疏的,即不允许空值。此外对这条微博的mblog_id加个唯一索引。索引在初始运行时创建,之后除非手动删除数据库后运行,否则不会再创建。
为保证每次插入的数据都是最新的,插入前应比较数据的发布时间与数据库中的最新时间,如果是早的说明已经爬过的,不需要插入。
关于mongodb的使用文档,点这里。
将项目git到本地后,请先确保以下环境已经安装:
执行下面命令:
mongod
cd weiboSA
scrapy crawl mblogSpider
可选参数: > scrapy crawl mblogSpider -a num= -a new_url=
➜ weiboZ git:(master) ✗ scrapy crawl mblogSpider -a num=10 -a new_url="http://m.weibo.cn/page/pageJson\?containerid\=\&containerid\=100103type%3D1%26q%3D%E6%B5%A6%E4%B8%9C%E7%A7%9F%E6%88%BF\&type\=all\&queryVal\=%E6%B5%A6%E4%B8%9C%E7%A7%9F%E6%88%BF\&luicode\=10000011\&lfid\=100103type%3D%26q%3D%E4%B8%8A%E6%B5%B7%E6%97%A0%E4%B8%AD%E4%BB%8B%E7%A7%9F%E6%88%BF\&title\=%E6%B5%A6%E4%B8%9C%E7%A7%9F%E6%88%BF\&v_p\=11\&ext\=\&fid\=100103type%3D1%26q%3D%E6%B5%A6%E4%B8%9C%E7%A7%9F%E6%88%BF\&uicode\=10000011\&next_cursor\=\&page\="
2016-10-29 14:41:11 [root] WARNING: 生成MongoPipeline对象
2016-10-29 14:41:11 [root] WARNING: 开始spider
2016-10-29 14:41:11 [root] WARNING: 允许插入数据的时间大于2016-10-29 14:15:05.875000
2016-10-29 14:41:13 [root] WARNING: do page1.
2016-10-29 14:41:13 [root] WARNING: do other pages.
2016-10-29 14:41:13 [root] ERROR: 编号为:E91f233Ds的数据插入异常
2016-10-29 14:41:13 [root] ERROR: 编号为:Ef4ri5bC6的数据插入异常
2016-10-29 14:41:13 [root] ERROR: 编号为:Ef3UNqMmV的数据插入异常
2016-10-29 14:41:13 [root] ERROR: 编号为:Ef3stkA8a的数据插入异常
2016-10-29 14:41:13 [root] ERROR: 编号为:Ef3pzmJ6i的数据插入异常
2016-10-29 14:41:13 [root] ERROR: 编号为:Ef1OBtvQr的数据插入异常
2016-10-29 14:41:13 [root] ERROR: 编号为:Ef03Lj54z的数据插入异常
2016-10-29 14:41:13 [root] ERROR: 编号为:EeYLU2GQd的数据插入异常
2016-10-29 14:41:13 [root] ERROR: 编号为:EeYlBv7bn的数据插入异常
2016-10-29 14:41:13 [root] ERROR: 编号为:EeXkop2vu的数据插入异常
2016-10-29 14:41:15 [root] WARNING: 结束spider
更改日志显示级别请在setting.py中修改LOG_LEVEL,介意采用项目默认的WARNNING,否则信息会很多。
查询示例
查询当前时区的2016-10-20至今有在9号线附近租房房租不高于2000的信息。
db.house.find(
{
created_at:{$gt:new Date('2016-10-20T00:00:00')},
$or:
[
{price:{$lte:2000}},
{price:[]}
],
admin:'9号线',
tag:true
},
{
_id:0,
text:1,
created_at:1,
scheme:1
}
).hint('created_at_-1').pretty()
{
"text" : "房子在大上海国际花园,漕宝路1555弄,距9号线合川路地铁站步行5分钟,距徐家汇站只有4站,现在转租大床,有独立卫生间,公共厨房,房租2400,平摊下来1200,有一女室友,室友宜家上班,限女生,没有物业费,包网络,水电自理@上海租房无中介 @上海租房无中介 @上海租房 @上海租房无中介联盟",
"scheme" : "http://m.weibo.cn/1641537045/EetVm3WBV?",
"created_at" : ISODate("2016-10-25T09:18:00Z")
}
{
"text" : "#上海租房##上海出租#9号线松江泗泾地铁站金地自在城,12层,步行、公交或小区班车直达地铁站。精装,品牌家具家电,主卧1800RMB/月;公寓门禁出入,房东直租,电话:13816835869,或QQ:36804408。@上海租房 @互助租房 @房天下上海租房 @上海租房无中介 @应届毕业生上海租房",
"scheme" : "http://m.weibo.cn/1641537045/Een8cAoy8?",
"created_at" : ISODate("2016-10-24T16:00:00Z")
}
{
"text" : "#上海租房# 个人离开上海:转租地铁9号线朝南主卧带大阳台,离地铁站两分钟!设备齐全,交通方便,随时入住。具体信息看图片~@上海租房 @上海租房无中介联盟 @魔都租房 帮转谢谢!",
"scheme" : "http://m.weibo.cn/1641537045/EdRpfuKuH?",
"created_at" : ISODate("2016-10-21T07:14:00Z")
}
{
"text" : "9号线桂林路 离地铁站8分钟 招女生室友哦 @上海租房 @上海租房无中介联盟 上海·南京西路",
"scheme" : "http://m.weibo.cn/1641537045/EdJ2U8Kv3?",
"created_at" : ISODate("2016-10-20T09:57:00Z")
}
relay
and redux
together in one componentreact-redux-provide
.react-redux-provide
. Demonstrates truly universal rendering with replication and queries.provide-router
.position: sticky
document
modification for React applicationslinter
packagereact-hot-api
to add hot reloading to React.createClass
and all classes with a render
method.A curated list of awesome iOS frameworks, libraries, tutorials, Xcode plugins, components and much more.
The list is divided into categories such as Frameworks, Components, Testing and others, open source projects, free and paid services. There is no pre-established order of items in each category, the order is for contribution. If you want to contribute, please read the guide.
Projects in Swift will be marked with :large_orange_diamond:, Swift Extensions will be marked with [e] and for Apple Watch projects. Feel free to add your project.
Awesome-iOS is an amazing list for people who need a certain feature on their app, so the best ways to use are:
print
in development and NSLog
in production. Support colourful and formatted output. :large_orange_diamond:Also see push notifications
Most of these are paid services, some have free tiers.
stringWithFormat:
for the sophisticated hacker set@IBDesignable
iOS controls, which have useful @IBInspectable
properties (border width and color, corner radius and much more) :large_orange_diamond:NSDate
, NSCalendar
, and NSDateComponents
. :large_orange_diamond:NSDate
, NSCalendar
, NSDateComponents
, NSDateFormatter
) management :large_orange_diamond:LibYAML
.⌘ +
/ ⌘ -
.Other amazingly awesome lists can be found in the
Distributed under the MIT license. See LICENSE for more information.
In this month, we‘ve compared nearly 1,600 JavaScript articles to pick the Top 10 (0.63% chance).
The good news is the scope of JavaScript development is getting ever greater that allows you to build almost anything. The bad news is you have a lot, a lot more things to learn to become an effective JavaScript developer today.
“I’m just going to move back to the backend. I just can’t handle these many changes and versions and editions and compilers and transpilers. The JavaScript community is insane if it thinks anyone can keep up with this...” — How it feels to learn JavaScript in 2016 (Rank 1)
Mybridge AI ranks the best articles for professionals. Hopefully this condensed list will help you avoid poor quality articles, and read and learn more productively in the area of JavaScript.
This post is specific to general JavaScript programming. For those who looking for React.JS, Angular 2.0, Python, Machine Learning, CSS, Swift… Visit the publication.
Step-by-step tutorial to build a modern JavaScript stack from scratch [5750 stars on Github] Courtesy of Jonathan Verrecchia
5 things you can do with Yarn: Yarn is a new package manager for JavaScript by Facebook. Learn how to use Yarn to increase your productivity. Courtesy ofProsper Otemuyiwa and Auth0
Practical ES6: A practical dive into ES6 and maintainable JavaScript modules [832 stars on Github]
Overview of JavaScript ES6 features (a.k.a ECMAScript 6 and ES2015+). Courtesy of Adrian Mejia
What I learned from writing six functions that all did the same thing. Courtesy of Jackson Bates and Free Code Camp
How to make a compiler with JavaScript. Courtesy of Mariko Kosaka
ES6 For Everyone: The Best Way To Learn Modern JavaScript.Courtesy of Wes Bos
[5,120 recommends]
80 JavaScript Interview Questions and Answers
.
.
[663 stars on Github]
Sat, Sep 3, 2016
Did you see the successfully launch of a really cheap ARM board for $9 only – the C.H.I.P. computer? It has an ARMv7 CPU with 512 MByte of main memory, 4 GByte flash memory as disk storage and is equipped with onboard WiFi and bluetooth as well.
With these awesome features built-in it would be really a great device to run Docker containers if only the recent Linux kernel 4.4 has the correct modules included, but it doesn’t – what a bummer!
But with spending a lot of time in building a custom Linux kernel and tweaking & testing I was finally able to install the latest Docker Engine for ARM on the C.H.I.P. — and as a result you can easily follow this tutorial and within a few minutes only you can run your first Docker container on this cute ARM board…
Preparing your operating system and your Linux kernel to be able to run the Docker Engine efficiently can be a hard thing and can consume a lot of labor time.
Fortunately in this tutorial I’ll show you the basic steps to get Docker running on the $9 C.H.I.P. computer, so every normal user should be able to do it on her own within a short time only – even without the need being an expert in this area. And if you’re in a hurry you can skip most of the tutorial and go straight ahead to theLessons learned - TL;DR
section and install Docker with just two single commands.
Use a Chrome browser and flash the latest firmware and OS on your C.H.I.P. computer. For detailed instructions go to the appropriate web site at http://flash.getchip.com/.
To run Docker on the C.H.I.P. we’re using the OS image for Debian Headless 4.4
, which is a server installation without any GUI and thus it’s quite smaller is size, so we do have more space left for running apps and Docker containers.
Pro Tip: You can even see all the detailed log messages while flashing via an UART console cable:
...
Starting download of 6291508 bytes
................................................
downloading of 6291508 bytes finished
Flashing sparse image on partition UBI at offset 0x26800000 (ID: 10)
start 0x9a00 blkcnt 0x180 partition 0x400 size 0x7fc00
Writing at offset 0x26800000
New offset 0x27400000
........ wrote 384 blocks to 'UBI'
*****************[ FLASHING DONE ]*****************
Once the C.H.I.P. is successfully flashed you can connect it directly with an USB cable to a Mac or Linux machine. The C.H.I.P. is getting power over the USB cable and connects via an USB serial console driver, so you can easily connect to.
Let’s see if we can find the booted C.H.I.P. on the USB wire:
ls -al /dev/cu.usb*
crw-rw-rw- 1 root wheel 20, 159 Sep 3 16:52 /dev/cu.usbmodem141113
Note 1: you have to wait a few minutes until the device can be detected as the C.H.I.P. has to be fully booted.
Note 2: it’s strongly recommended to use a powered USB hub, otherwise you’ll hit some power problems and the C.H.I.P. can’t access or can immediately shuts off
Now we can connect to the ARM device via the screen
utility:
sudo screen /dev/cu.usbmodem141113
Alternatively, and this is my preferred way, you can attach an UART console cable (e.g. from AdaFruit) which is typically shown as a device on the Mac like /dev/cu.usbserial
. With this setup you can even watch the complete boot logs of the C.H.I.P. computer and you are able to see all early boot messages from U-Boot and from loading and starting the Linux kernel. This gives you all details in case there are any problems and issues with a homegrown kernel.
sudo screen /dev/cu.usbserial 115200
Once you get to the login message, you can use username root
and password chip
to login:
Debian GNU/Linux 8 chip ttyS0
chip login: root
Password:
Linux chip 4.4.11-ntc #1 SMP Sat May 28 00:27:07 UTC 2016 armv7l
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@chip:~#
Following the instruction here http://docs.getchip.com/chip.html#wifi-connection you can list all the available WiFi networks and then connect the C.H.I.P. to your preferred network.
nmcli device wifi list
* SSID MODE CHAN RATE SIGNAL BARS SECURITY
HITRON-FEE0 Infra 11 54 Mbit/s 67 ▂▄▆_ WPA2
WLAN-R46VFR Infra 1 54 Mbit/s 65 ▂▄▆_ WPA2
My ASUS Infra 6 54 Mbit/s 64 ▂▄▆_ WPA2
WLAN-718297 Infra 1 54 Mbit/s 59 ▂▄▆_ WPA2
WLAN-MCQYPS Infra 1 54 Mbit/s 30 ▂___ WPA2
Telekom_FON Infra 1 54 Mbit/s 27 ▂___ --
* SSID MODE CHAN RATE SIGNAL BARS SECURITY
Connect to the WiFi station with the SSID mySSID
and password myPASSWORD
, please insert you own SSID and PASSWORD. In this example I’m using the SSID WLAN-R46VFR
:
nmcli device wifi connect 'WLAN-R46VFR' password '**********' ifname wlan0
Once you are connected you can see the ‘*’ in front of your connected WiFi network:
nmcli device wifi list
* SSID MODE CHAN RATE SIGNAL BARS SECURITY
HITRON-FEE0 Infra 11 54 Mbit/s 67 ▂▄▆_ WPA2
My ASUS Infra 6 54 Mbit/s 64 ▂▄▆_ WPA2
WLAN-718297 Infra 1 54 Mbit/s 59 ▂▄▆_ WPA2
WLAN-MCQYPS Infra 1 54 Mbit/s 30 ▂___ WPA2
Telekom_FON Infra 1 54 Mbit/s 27 ▂___ --
* WLAN-R46VFR Infra 1 54 Mbit/s 100 ▂▄▆█ WPA2
* SSID MODE CHAN RATE SIGNAL BARS SECURITY
And the C.H.I.P. should have got an IP address from the DHCP server:
ifconfig wlan0
wlan0 Link encap:Ethernet HWaddr cc:79:cf:20:6d:d8
inet addr:192.168.2.112 Bcast:192.168.2.255 Mask:255.255.255.0
inet6 addr: fe80::ce79:cfff:fe20:6dd8/64 Scope:Link
inet6 addr: 2003:86:8c18:1a37:ce79:cfff:fe20:6dd8/64 Scope:Global
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:119 errors:0 dropped:1 overruns:0 frame:0
TX packets:102 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:24656 (24.0 KiB) TX bytes:16973 (16.5 KiB)
Now we’re connected to the network and can access the internet and the C.H.I.P. can be reached from our Mac or Linux machine.
Here we have to use the same username root
and password chip
to login via SSH:
ssh-add
ssh-keygen -R 192.168.2.112
ssh-copy-id root@192.168.2.112
Finally we can login to the C.H.I.P. computer via SSH:
ssh root@192.168.2.112
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Jan 1 00:32:25 1970
-bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
root@chip:~#
As a first step we’d like to check the current Linux kernel version and operating system.
Kernel version:
uname -a
Linux chip 4.4.11-ntc #1 SMP Sat May 28 00:27:07 UTC 2016 armv7l GNU/Linux
Operating system:
cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 8 (jessie)"
NAME="Debian GNU/Linux"
VERSION_ID="8"
VERSION="8 (jessie)"
ID=debian
HOME_URL="http://www.debian.org/"
SUPPORT_URL="http://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
BUILD_ID=Wed Jun 1 05:34:36 UTC 2016
VARIANT="Debian on C.H.I.P"
VARIANT_ID=SERVER
In order to install Docker I’ve prepared a complete installation script which can be downloaded and executed in a single command line. I’ve you’re interested into the details you should check the script at GitHub.
# install Docker
curl -sSL https://github.com/DieterReuter/arm-docker-fixes/raw/master/002-fix-install-docker-on-chip-computer/apply-fix-002.sh | bash
At the end of running the install script we’ll see some errors occurred and the start of the Docker Engine has failed.
...
Errors were encountered while processing:
docker-engine
E: Sub-process /usr/bin/dpkg returned an error code (1)
This is OK for now as it just indicates the default Linux kernel isn’t able to run Docker on the C.H.I.P. and we have to build and install a custom Linux kernel which has all the necessary kernel settings for Docker enabled.
If you’re interested in analyzing these errors in more detail you can run the command systemctl status docker.service
and you’ll get more detailed log messages from systemd
.
root@chip:~# systemctl status docker.service -l
● docker.service - Docker Application Container Engine
Loaded: loaded (/etc/systemd/system/docker.service; enabled)
Active: failed (Result: exit-code) since Sat 2016-09-03 13:20:49 UTC; 2min 23s ago
Docs: https://docs.docker.com
Main PID: 10840 (code=exited, status=1/FAILURE)
Sep 03 13:20:48 chip dockerd[10840]: time="2016-09-03T13:20:48.580271961Z" level=info msg="libcontainerd: new containerd process, pid: 10848"
Sep 03 13:20:49 chip dockerd[10840]: time="2016-09-03T13:20:49.652832502Z" level=error msg="'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded."
Sep 03 13:20:49 chip dockerd[10840]: time="2016-09-03T13:20:49.656854332Z" level=fatal msg="Error starting daemon: error initializing graphdriver: driver not supported"
Sep 03 13:20:49 chip systemd[1]: docker.service: main process exited, code=exited, status=1/FAILURE
Sep 03 13:20:49 chip systemd[1]: Failed to start Docker Application Container Engine.
Sep 03 13:20:49 chip systemd[1]: Unit docker.service entered failed state.
Sep 03 13:20:50 chip systemd[1]: [/etc/systemd/system/docker.service:24] Unknown lvalue 'Delegate' in section 'Service'
Sep 03 13:20:52 chip systemd[1]: [/etc/systemd/system/docker.service:24] Unknown lvalue 'Delegate' in section 'Service'
Sep 03 13:20:53 chip systemd[1]: [/etc/systemd/system/docker.service:24] Unknown lvalue 'Delegate' in section 'Service'
Sep 03 13:20:54 chip systemd[1]: [/etc/systemd/system/docker.service:24] Unknown lvalue 'Delegate' in section 'Service'
In order to keep this tutorial short and easy to follow, I’d like to use an already prepared custom kernel which has nearly all the possible kernel modules and settings enabled to run the Docker Engine in an optimized way on the C.H.I.P. computer.
Therefore we only have to install our new Linux kernel and have to reboot the system to activate it.
# install custom Linux Kernel and reboot
curl -sSL https://github.com/hypriot/binary-downloads/releases/download/chip-kernel-4.4.11/4.4.11-hypriotos.tar.bz2 | tar xvfj - -C /
reboot
After rebooting we’re going to check the kernel version again:
uname -a
Linux chip 4.4.11-hypriotos #1 SMP Mon Aug 29 19:18:49 UTC 2016 armv7l GNU/Linux
Check the Docker client version:
docker -v
Docker version 1.12.1, build 23cf638
Check the Docker server version:
docker version
Client:
Version: 1.12.1
API version: 1.24
Go version: go1.6.3
Git commit: 23cf638
Built: Thu Aug 18 05:31:15 2016
OS/Arch: linux/arm
Server:
Version: 1.12.1
API version: 1.24
Go version: go1.6.3
Git commit: 23cf638
Built: Thu Aug 18 05:31:15 2016
OS/Arch: linux/arm
Getting the detailed informations about the Docker Engine:
docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 1.12.1
Storage Driver: overlay
Backing Filesystem: <unknown>
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: null host bridge overlay
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Security Options:
Kernel Version: 4.4.11-hypriotos
Operating System: Debian GNU/Linux 8 (jessie)
OSType: linux
Architecture: armv7l
CPUs: 1
Total Memory: 491 MiB
Name: chip
ID: SSJ5:7OTQ:BCAZ:4MDL:VEW6:VKND:3J6W:UI3O:UTBB:7H5V:LQ4W:ABRP
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Insecure Registries:
127.0.0.0/8
Finally we could see, the latest Docker Engine v1.12.1 is now installed and is successfully running.
As a last step we’d like to start a first Docker container, a small web server.
docker run -d -p 80:80 hypriot/rpi-busybox-httpd
Unable to find image 'hypriot/rpi-busybox-httpd:latest' locally
latest: Pulling from hypriot/rpi-busybox-httpd
c74a9c6a645f: Pull complete
6f1938f6d8ae: Pull complete
e1347d4747a6: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:c00342f952d97628bf5dda457d3b409c37df687c859df82b9424f61264f54cd1
Status: Downloaded newer image for hypriot/rpi-busybox-httpd:latest
fec2773baaec570ba8b6e00296dfd11b4b4768d1b51e574d851968b9225b9d22
Now start your web browser and point it to the website from our Docker container.
open http://192.168.2.112
Additional tip:
After installing some packages via apt-get
it’s a good idea to clean the APT cache from time to time and save disk space.
root@chip:~# apt-get clean
root@chip:~# df -h
Filesystem Size Used Avail Use% Mounted on
ubi0:rootfs 3.7G 373M 3.3G 11% /
devtmpfs 213M 0 213M 0% /dev
tmpfs 246M 0 246M 0% /dev/shm
tmpfs 246M 6.7M 239M 3% /run
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 246M 0 246M 0% /sys/fs/cgroup
tmpfs 50M 0 50M 0% /run/user/0
Currently the C.H.I.P. isn’t able to run Docker out-of-the-box, but it just needs to install a custom built Linux kernel to prepare this awesome ARM board to run Docker easily. And now we’re able to install the officially built Docker Engine from the Docker project’s APT repository.
TL;DR
these are the only commands you need to install Docker
# install Docker
curl -sSL https://github.com/DieterReuter/arm-docker-fixes/raw/master/002-fix-install-docker-on-chip-computer/apply-fix-002.sh | bash
# install custom Linux Kernel and reboot
curl -sSL https://github.com/hypriot/binary-downloads/releases/download/chip-kernel-4.4.11/4.4.11-hypriotos.tar.bz2 | tar xvfj - -C /
reboot
And the best thing is, according to this tweet, the developers at @NextThingCo have already started to include all the required kernel settings into the standard OS images. So we can expect that the Docker Engine can be installed in the future even without tweaking the Linux kernel.
As I told you at the beginning of this tutorial, these are just the basic steps for a normal user to install and use Docker on the C.H.I.P. computer. But if you’re interested in all the technical details behind the scene, how to check and analyze your Linux kernel and how to optimize it for running Docker efficiently, then please drop me a comment or tweet me and I’ll write even more about all the technical details so you can follow the path along on to an expert level too. With these skills you then should be able to install Docker on any Linux-based ARM device.
As always use the comments below to give us feedback and share it on Twitter or Facebook.
Please send us your feedback on our Gitter channel or tweet your thoughts and ideas on this project at@HypriotTweets.
我们偶尔听到的声音还是在说“银发经济”,在说“一亿的广场舞大妈”,进入养老行业的互联网人摩拳擦掌,似乎这是唯一还未被挖掘的万亿规模蓝海市场。
而当我们在说养老行业和养老创业,究竟是怎样的?从今天起,我将尽可能真实全面的梳理呈现中国养老行业的种种,这当中有我在“陪爸妈”踩过的坑,也有行业前辈的经验分享,以及数千位老人的沟通反馈,希望能对愿意投入养老事业的创业者们有所帮助。
创业维艰,在每一个你拼命闯荡的时候,你的爸妈正在老去,而我们也终有衰老的那一天——因此特别希望能有更多人一起来参与讨论和分享养老的话题,等你来:)
根据国家统计局数据,截至2015年底中国60岁以上人口达到2.22亿,占总人口16.1%,65岁以上人口1.44亿,占总人口10.5%。全国老龄委指出,中国60岁以上老年人口2033年前后将翻番到4亿,到2050年左右将达到全国人口的1/3。
根据联合国相关数据,中国在2000年进入老龄化社会(65岁+人口超过总人口的7%),预计进入老龄社会(65岁+>14%)为2025年前后,相比发达国家50-70年的进程,预计中国与日本类似仅需25年。
预测2014-2050年间,中国养老产业规模将从4.1万亿元增长到106万亿元左右,GDP占比8%增至33%左右,中国将成为全球老龄产业市场潜力最大的国家,但银发经济的背后是未富先老和未备先老的事实,老人的收入更多以家庭储蓄和应急为主。
根据相关政策,9074或9064是主要的养老策略,共性是90%为居家养老,6%-7%是社区养老,3-4%为机构养老。2015年起,国资背景的金融、保险、地产等公司开始布局或收购养老项目,而2016年起亲和源、汇晨、人寿堂先后被收购,则代表着前期5年的跑马圈地画上句号,千人规模,运营亏钱,依然值钱。
而2013年起,以北京市为例,政府兴建郊区养老院,以及城区内的日间照料中心;但从2015年的政府报告来看,郊区养老院的床位空置率高达48%,日间照料中心超过66%已经关闭,即便在有高额补贴的情况下,依然无法持续经营;到了2016年,相关政策导向已经是“社区养老驿站+照料中心”的公建民营,显然这当中事业和产业的界限并不清晰。
生活状态上,空巢、独居和高龄老人成为庞大群体,空巢老人1亿+,独居和高龄老人都分别在2000万+;身体状态上,失能/半失能老人为4063万(占老人总数的18.3%),按老人护工比1:4计,对应的护工缺口高达千万,即便是到岗的养老职业相关人才,到岗第一年的离职率就高达30%以上。
目前60-70岁的低龄老人占55%;其中60-65岁达7814万占35%,该群体具有旺盛的养老需求和较强的消费能力。一方面全国老年人旅游人数已占总人数的20%,平均4次/年/人,另一方面是低价团的欺诈和强制购物;一方面是老人使用移动互联网抢红包聊微信不亦乐乎,另一方面是移动支付的使用率仅在5-10%之间。我们看到传统线下服务的升级与老人对价格的敏感仍是典型矛盾,社群+电商的移动互联网化也还和老人很远。
是的,在我们一直强调养老是万亿市场的时候,更应该冷静下来去看待中国式养老的冰火交融。政策永远都是锦上添花,回到老人用户的需求本身,我们该思考些什么?
首先我们来看现在的老年人用户群体是怎样的:按60岁以上定义,现在的老人都是1956年前出生,成长于中国经济较为困难时期,省吃俭用是一辈子的习惯,首先观念上就是节省为主。
再来看这些老人退休后的收入主要是靠养老金,平均值从2005年的平均714元到2015年的2200+元,自2008年连续保持8年涨幅10%以上。考虑职工平均工资增长率、物价涨幅、企业单位和事业单位养老金并轨等因素,2016年的养老金涨幅降为6.5%,也就是说老人的平均退休工资在2350+元/月。
虽然养老金的绝对水平在不断提高且涨幅跑赢CPI,但老人的首选仍是储蓄以备不时之需。一方面虽然医保体系在不断升级,但因身体机能退化/意外事件造成需要长期照料的情况,每个月的退休金将立刻入不敷出(最基础的家政阿姨照料费用是3500-4500元/月);另一方面受中国传统观念影响,加之房产市场的不断升温,老人在条件允许时也会选择攒钱购买房产,更多是为80、90后的子女提供支持。
因此,有限的养老金将主要用于自身的养老和应急照料,以及家庭/子女的住房支持等……为应对未来不确定的大额支出,储蓄行为存在较强的预防性动机,对应老人的可支配收入就会显得不足。
这当中显而易见,已失能/失智老人的长期介护/照料是强刚需,是老人储蓄金/子女赡养资金的重要流向之一,是养老创业最直观可见的机会点;此外,分时陪护和智能硬件是补充和辅助:
线下存量形态——家政公司、养老机构
创业公司——上门照料(二毛照护-自营;云家政-平台);社区养老(爱照护)
支付形态——老人+子女,出发点是减轻长期照料压力,为老人提供更专业服务
背景数据:截至2015年底,全国2.22亿60岁以上老人中,其中4000万失能老人
观点1:服务人员的供给将长期限制该类企业的发展规模和速度
家政(阿姨)住家24小时照护目前仍是主流观念和选择,但需要注意的是,从事养老照料的家政人员典型画像是45-55岁、女性、文化低,这个行业在过去的10年平均工资涨幅仅为200-250%(即从1500-1800元/月到3500-4000元/月),是所有家政工种中涨幅最低的,也是强度最大最辛苦的。考虑到该群体学习能力有限,也正在面临返乡照顾老小及自身养老的问题,因此未来的5-10年一定会面临一线城市的养老看护阿姨荒(月嫂/育儿嫂 会相反呈井喷),这将直接影响到线下存量和创业公司的供给能力。
想要解决服务人员供给问题,一方面是对接“医护卫养”相关大专院校,定向培养年轻化的服务人员,甚至是采用委培方式解决就业问题;另一方面,加强服务人员的专业技能培训,明确职业发展上升空间,使其单位时间收入提升也是留存的重要途径。
观点2:线下养老服务实体首先考虑成本结构,再去+互联网
对于该方向,首先要具备线下实体作为支撑,需要同时具备“老人长期照料、服务人员培训、增强用户信任”的功能。这当中对成本支出起关键影响的是“房租+护理人员”。因此理想的成本结构是房间内本身就有需要照料的老人居住,护理人员1对4-5位老人可以保证较好的服务质量,那么老人的床位费(住宿、饮食、护理等)就需要覆盖上述成本,保证实体店自给自足。之后+互联网,可以连接老人家属,并为更多社区周边的老人服务。
以“爱照护”为例,自2009年起承接上海市政府长者照护之家的运营工作(老人床位收入+政府补贴),并通过SaaS系统+智能硬件的组合,进一步提升了护理人员一对多的照料效率,改善了老人的服务体验(+互联网,提升效率降低成本),是相对比较讨巧的组合形态。
相对来说,二毛照护定位专业服务人员提供的居家养老照料,包括居室清洁、护老保姆、专业护理、病后康复。从商业模式来说,其一对一的上门照料成本结构并不合理——自营的上门服务人员经过培训确实带来了更好的服务水准,同时带来的工资比市场价高20%左右(约5000-6000元),定位于相对中高端的客户并由子女买单。足够多的订单并进行抽佣可以满足实体店的租金成本——按其官网的线下店来看月租金约在10000元,若按每个订单抽佣20%计,则需要10个长期上门照料订单,这就意味着至少需要10位专业优质护工储备,这就又回到了观点1中的服务供给匮乏问题。
而对于家政门店来说,本身家政的房租低、月嫂/育儿嫂/长期工等抽佣比例高、多工作的服务供给也多,即便少量老人的订单不赚钱,整体成本也是合理的。此时平台帮助家政公司+互联网,就能获取更多的订单,并对门店有服务质量的控制。
观点3:养老护理行业的服务升级仍处于萌芽阶段
早在2004年左右,以青松护理为代表的养老护理服务升级品牌就已诞生,随后在2010年前后慈爱嘉引进任爱华体系进入中国,同期夕悦等合资公司也开始养老服务的布局。但走到2016年的今天,150-300元/小时的客制化康复上门服务并没有被市场所广泛接受。
一方面原因是前面提到的,过去10年中老人群体的支付能力比现在还要弱;另一方面是对于老人照料的常规路径就是请家政阿姨,市场对于该服务的定义已经固化在“24小时住家看护,为此家庭支出<4000元/月”,看护的概念也停留在不要出事吃饱穿暖的水准,虽然照料水平并不专业,但不到135元/天的“性价比”远高于150-300元/小时,因此家庭用户很难选择升级服务。
这样的情况预计将在2020年后开始逐渐转变,主要愿意是70后人群正式步入50岁,60后人群开始退休,以及80/90后的经济积累和消费观念转变。
线下存量形态——家政阿姨、医院护工、全科/专科医生
创业公司——陪诊(e陪诊)、关联陪护(优护家)
支付形态——一般为子女,出发点是减轻短期照料压力,为老人提供更专业服务
背景数据:
75.2%的老年人患有慢性疾病,其中高血压、心脏病/冠心病、颈/腰椎病、关节炎、糖尿病和类风湿等是城乡老年人患病比例较高的五类慢性疾病。
观点1:细分垂直市场有可为,长期跟踪价值高于临时陪护
个人认为单纯的点对点陪护价值并不高,其中典型的行为是陪诊——供给端是共享护士的闲散时间,护士在该场景下提供的价值是全程陪伴和建议提醒,可以节省需求端子女无暇照顾老人的问题,也让老人在排队拿药等跑腿工作上解放出来。
这当中的第一个问题是,供给端并没有提供物超所值的刚性价值:护士工作的动力是200-300元/半天的增量收入,对于能走能动的老人来说,一次陪诊要花掉月养老金的10%-15%是不现实的,而对于子女来说,如果老人情况确实严重,一定会亲自或让信任的亲戚来全程陪同。
第二个问题是,单纯的半天陪诊,并不能带来对于老人身体情况的改善:老年人的慢病需要长期跟踪和综合治疗,最常见的情况是在换季/症状更迭等节点,花1-2个半天去三甲医院确诊病症,然后每周/双周去社区医院取药(医保报销比例更高 + 距离更近不用排队)。考虑到分级诊疗中社区首诊制度试点、医保用药品类逐步与三甲医院统一、社区医院与周边三甲医院的医联体和转诊机制等,虽然还需要时间逐步推进,但老人看病的省时省心程度会越来越高。
对比来看,长期对于老人典型慢病的的介入跟踪是更有价值的,例如优护家的模式是将医院内+家中的护理,通过移动互联网连接并分析形成老人专属的护理档案。举例来说,老人住院7-15天中主要以治疗/观察为主,回到家后可能会出现病情反复或实现康复,这两个阶段的体征/状态/陪护需求都有所不同,通过数据化的跟踪分析会对老人的“延续护理”起到重要作用。
线下存量形态——血压仪、血糖仪
创业公司——防走失(360手环)、监测状态(e伴)、慢病跟踪(如糖护士、六六脑)
支付形态——子女或政府,出发点是降低管理/照料风险
背景数据:
2016年10月9日重阳节发布的《中国老年人走失状况白皮书》数据显示,中国每年走失老人约50万人,平均每天有1370个走失老人案例发生,其中60%+的走失老人配偶不在身边,72%的老人大多都出现记忆力障碍情况,经医院确诊的老年痴呆症患者占到总比例的25%;接受过救助的走失老人中,约有25%会出现再次走失。
观点1:硬件仅提供辅助作用,人的因素不可替代(亲人陪伴+医生介入+社会化救援)
很多人都应该看过《贫嘴张大民的幸福生活》和《嘿老头!》两部相隔18年的电视剧,当中老妈和老爸因老年痴呆带来的家庭困扰,让很多人看着揪心却又无可奈何。那么互联网和科技近20年的进步,到底能不能改变这样的状态?
首先来看防走失手环:
从京东搜索结果来看,相比品类繁多的儿童定位手表/手环类产品,老人定位的产品数量仅为儿童的29%。目前较知名的是360手环,作为北京市老龄委防走失的政府采购供应商,从2016年7月起为10000名北京市户籍60周岁+ 有记忆障碍、认知障碍或已确诊老年痴呆的失智老年人登记,并在30天内在社区属地免费发放手环。
首先手环类的老人产品可选数量少、老人对于移动智能设备的接受度低,为此付出购买力的意愿度低(200-400元均价),更重要的是一旦出现紧急情况(如跌倒、走失等),需要的是属地化的迅速响应和救援,这意味着在应急服务上需要足够的专业人员,覆盖范围也要足够大,这些工作谁来承载?此类产品更适合于由政府牵头采购,由社区志愿者属地跟踪服务,而并不适合大规模推广。
再来看状态监测:
根据e伴的数据,平均子女每天访问e伴的微信应用2次,浏览7.5个页面,全天平均停留时长14分49秒,用六年时间专注于打磨产品在养老行业中非常可贵。老人只要将设备佩戴在腰间,连续监测动作,分析行为活动,自动识别风险状况,及时提醒通知家人和服务机构。
类似于分时陪护中提到的观点,长期跟踪更具竞争力,只要能做到动作/状态识别的稳定和精准,并通过关爱宝奖金激励子女和老人的佩戴与关注,这样的连续性数据对于医疗和保险机构来说同样具有高价值。除了紧急情况的服务承载问题外,该类产品面临的挑战还在于如何更快速的获取用户,已看到的手段也包括与街道合作进行政府采购,以及与机构和企业渠道对接,虽然1099元/年的软硬件价格并不贵,但只要最终是面向C端老人/子女的推广,都会面临信任建立慢和使用粘性弱的挑战。
最后看慢病跟踪:
脑部衰老/病变导致老人记忆障碍,尽早发现症状需要通过子女观察和医生指导,而此前的预防行为和后续的康复行为成为有效的切入点。例如针对脑部预防/康复的六六脑(纯软件,机构/医院使用),及针对糖尿病管理/康复的糖护士/糖大夫等(家庭使用),都是在慢病中进行管理和干预的典型产品。从业务形态来说,ToB的机构对接比ToC用户业务的想象空间更大,这就要求团队的技术实力较高,属于技术创新基础上的模式创新(互联网+智能硬件)。
综上,在人工智能还远远不能代替现有服务人员的情况下,垂直领域(如慢病管理/干预)的技术创新将成为机会与壁垒;鉴于老年疾病的多样性和混合性,专业医生的配合与介入(如数据分析)必不可少。
结束语:在中国养老整体市场的现状下,老人可支配的收入有限,护理/康复相关的领域是可见的直接刚性需求,也已经有不少的创业公司在此领域探索。这当中一定还有没能关注到的好公司好模式,或是个人判断上有漏洞的地方,欢迎大家加我微信(everbluesun)一同讨论。
本文为系列文章,下一期我们要一起聊的问题是:
老人的存量收入有限,有办法激活老人收入的增量吗?
这当中的创业机会又有哪些?
之前黎阿姨和大家一起讨论了第1-2个问题——主要阐述的观点是:在中国养老产业和事业界限尚未清晰的情况下,老人有限的养老金收入将主要用于自身的养老和应急照料,因而显性的强刚需——“失能/失智老人的照料陪护”,就成为养老行业创业的切入点之一。
今天我们要继续讨论:
老人的存量收入有限,有办法激活老人收入的增量吗?
提升收入的方法是开源和节流,我们依次进行梳理:
首先来看政策方面的变化——之前我们提到,养老金的12连涨增加了老人的绝对收入,但由于房产和应急等需求,支配能力并没有明显增强。2016年7月13日人力资源和社会保障部部长尹蔚民表示,延迟退休预计将在2022年进行试点,这将带来的是整体养老金池子的压力相对减小,这里更多的是政策性的内容,与养老市场本身的创业机会关联不大;同样的,开放二胎刚刚过去一周年,无论现在效果如何,其对于养老的长远影响也要到20年后,在此这两点我们都不做讨论。
然后来看金融体系的变化——“以房养老”和“长期护理险”:
是指将房屋抵押给有资质的银行、保险公司等机构,每个月从该机构取得贷款作为养老金,老人继续在原房屋居住,去世后则用该住房归还贷款。初衷是把最主要的家庭财富(即房产——2015年中国家庭的人均财富中,其净值的占比达到65.61%)变成“活钱”,既保证老年人实现居家养老,又能增加养老收入。
来看下以房养老在中国的落地情况:
2014年7月初,我国“以房养老”保险在北京、上海、广州、武汉四地进行试点;
2015年3月,作为中国首款“以房养老”政策性保险产品,幸福人寿“房来宝”上市销售;
2016年6月底,签订投保意向书的客户有60户,78人——无子女老人占到40%,参保人平均年龄70.5岁;其中32户家庭已领取保险金,以5000-10000元/月居多,平均养老金8465元/月,最高的是一位上海老人为19003元;
2016年9月,《关于2016年深化经济体制改革重点工作的意见》提出,推进住房反向抵押养老保险试点。保监会将在现有四个试点城市的基础上,选择经济条件较好、房地产市场较为规范、当地政府支持的城市和地区纳入试点范围,扩大业务经营区域(例如南京、青岛等)。
可以看到15个月的试运营数据并不成功,原因是什么?有哪些问题需要解决?
虽然以房养老并不能在短期内增加老人收入,但随之产生的问题中,我们可以看到第二点健康/慢病管理 和 第五点围绕老人需求的垂直服务,是养老行业的创业机会。我们认为能够让老人和家庭少花钱也能享受到好的服务,这本身也是以节流的方式提升老人可支配收入。
观点1:慢病/健康管理更适合“服务+硬件”
互联网慢病管理的突破点在于数据收集+互动体验,但直接盘活C端用户的难度并不小,尤其是在移动互联网和智能硬件使用能力有限的老人群体上尤为突出。以阿里健康父母关怀计划为例,首先需要串联社区医院的全科医生首诊服务,再结合智能硬件厂商的相关设备收集并由医生观察分析数据,然后通过运营策略(如购买费用50%-70%左右的现金返还)激励用户养成使用习惯,甚至是在测量数据正常的情况下奖励赠予保险。
无论是IT技术公司通过给医院提供SaaS系统获取门诊体检人群、还是通过机构/医生在线下与病患接触向线上导流,再或是将智能硬件与第三方平台做捆绑,获取和留存用户都是该领域持续性的挑战。整体来说,慢病管理的用户群体意识、数据连续性价值都还属于成长初期,能够有成体系的服务支撑,会比单纯的硬件切入要更具价值。
图1:服务是基础 硬件是辅助
观点2:优质服务可由老人互助,时间银行仍是概念
在本文的后续部分将会重点分析“广场舞和家政服务”,这证明老人之间的互助行为已经可以作为优质服务的一类供给形态,老人群体天然的相互理解,能够让相关技能的分享效率更高更具价值。而在丹麦等发达国家,本身也有老人与年轻人混合居住的社区,年轻人只要达成每周固定时长的陪伴/照料服务,就可以减免房租;在美国著名的众筹网站Kickstarter上,筹款10W+美元的“Present Perfect”,也是将幼儿园的孩子定期与养老院的老人聚在一起交流沟通,产生了非常好的互助效果。
但在中国的大环境下,想用时间银行的概念仍是相对遥远的概念。具体的困难主要是在服务周期、服务项目、兑换方式等多方面。类似的形态是无偿献血机制,但献血的血型、用血的触发机制等相对标准化,也已经多年实践形成了相关的法律规定,相对来说完全非标的养老服务就很难采用此方式落地。
在2016年7月的人社部《关于开展长期护理保险制度试点的指导意见》中明确提及,将在首批包括上海、广州、青岛等15个城市,用1-2年探索为长期失能人员基本生活照料和医疗护理,提供保障的社会保险制度,力争在2020年前,基本形成适应我国社会的长期护理保险制度政策框架。2016年10月24日,以中国泛海控股集团有限公司为主体组成的财团,以27亿美元现金收购纽交所上市公司Genworth金融集团(全美第一大长期护理保险公司)的全部已发行股份。
中国的养老保险是对退休后基本生活给予的经济保障,所以养老金常被视作退休工资,并未实现真正意义的老有所“养”。以试点最早的青岛为例,与德国长期护理保险类似,采取“跟随医疗保险”原则,即将医疗保险参保人作为长期医疗护理保险制度的参保人;在受益方式上,青岛借鉴了日本模式,仅提供服务,不支持现金给付。但目前青岛面临的问题是由于护理服务分级和失能依赖程度评估工具/机构的缺失,由商业保险公司经办的保险金给付无法与护理服务和失能水平联动。
观点3:长期护理险对陪护类公司利好;适老化居家改造未来可期
按照国家试点的要求,长期护理保险基金支付水平总体控制在70%左右,这意味着家庭/老人只需承担30%的护理费用,按3500-4000元/月的平均照护成本看,将降低至1167-1333元/月,相当于2016年职工平均养老金的50%-60%,这将意味着有更多家庭可以支付得起陪护/照料服务,与保险公司/政府部门共同探索和制定护理分级和失能评估体系,将助力该领域创业公司的发展;而参照日本来看,适老化的居家改造也属于介护险范围内,若该领域开放的话,麦麦养老(智能养老,现阶段与高端机构合作)也将迎来更大的ToC市场。
对于活力老人,身体精神状态都比较好,对于当中一部分愿意参与到社会工作中的人群,相对标准化的技能分享就是很好的方式,首先来看“食住行”的基础领域,我们可以看到这些案例:
先来看“食”——“回家吃饭”曾在2015年7月接受采访时表示,以北京为例有上千家厨参与,其中70%-80%的厨师是老年人。对比来看,我们近期在建外SOHO区域范围测试来看,最新的数据是周边5公里内,有137位家厨,其中60后36位(占26.3%),50后16位(占11.7%),总体老人厨师的比例为38%,应该说这依然是很高的比例。需要注意的是,2016年3月北京食品药监局的一纸批文,会让共享吃饭模式后续的发展受到影响。
对于“住”——Airbnb 在2015年7月的数据显示,全球有10%的房东超过60岁,其56%已退休,49%依靠固定退休收入生活。在经济收入上,老年房东群体平均接待房客约60天/年,平均收入约 6000 美元/年,这些收入足以支持老人购买各种必需品或外出旅行;在社会认同感上,老年房东有74%是独居或只与一位同伴居住,来往的客人带来了新鲜的世界,老人们更加用心的提供服务,获得了高于其他房东群体7.5%的五星好评。
2016年9月,Airbnb与中国老龄委旗下的华寿之家合作,介绍如何使用分享经济帮助提升生活质量,并利用现有平台帮主老人更好的参与社会活动和文化交流。
在“行”上——滴滴出行 在2016年4月的《移动出行与司机就业报告》中提到,60后的专快车司机占整体的9%、顺风车4%、代驾1%,2016年11月1日网约车新规实施,我们也将关注其带来的影响;Uber美国在2015年1月,已有25%的Uber司机年龄超过50岁,3%本身就是退休人群,此外美国退休协会机构Life Reimagined和美国退休人士协会(AARP)都与Uber持续合作,为平台累计招募近1000名老司机。
观点1:老有所乐>老有所为,要和时间做朋友
“顺便”做点“自给自足”的事,获得乐趣,顺手赚点儿零花钱,老人们才会乐于分享:
举例来说,本来就喜欢做饭,也要每天吃三顿饭,顺带手帮周围邻居/上班族也做饭,老头子买菜、老太太烧好、老头子再送过去,看着上班的孩子就跟自家儿子闺女似的,还能锻炼锻炼身体,这本身就是一种心理上的补偿;同样的,做司机是开了一辈子车了,出门为了解闷,不和家里老太太吵架:);房子住了很久,世界那么大,我没法出去看,那就叫有趣的陌生人来给我家讲吧……
再举例来说,让大妈们不去跳广场舞了,而是拿着手机等着周围3-5公里跑腿的活儿,甭管是洗衣O2O的取送衣服、还是电商/生鲜产品的配送,这本身就不是一件乐趣为先的事情,不是顺便要去做的,更不是自身有需求的事,一旦受束缚(随机性强、时间不可控),自然就不愿意干了。
因此,看起来活力老人群体的供给非常充足,但与年轻人群的共享形态不同,老人并不是最为看重单位时间内的收入提升,而是优先选择能让社会认同感和自身愉悦度更高的方式。
观点2:移动互联网渗透率 + 共享经济发展程度,中国还有很长的路
根据2016年8月皮尤和普华永道的相关数据来看,美国55岁+老人参与共享经济的比例为18%、72%的即将退休者想要继续工作、74%的65岁+人群拥有移动设备,这些都是美国老人再就业的重要支撑。
对应的,2016年8月摩根大通的调研报告显示(26万美国人,2012.10-2015.9长期跟踪):成年人参与到在线平台经济的比例从0.1%提升至4.7%;其中约0.9%的美国老年人(40万+)成为平台的服务供给方。在类似Uber或TaskRabbit的服务平台中,老年人获得了平台收益中的28%;而在eBay或Airbnb之类的交易平台中,获取收益为11.5%。
图2:美国老人参与共享经济“再就业”的基础条件成熟
可以看到,活力老人的“再就业”+“共享”是养老创业中,相对更容易管理和形成规模效应的突破点,政策相对宽松、市场更开放、老人观念新,是国外同类业务领先中国市场的原因;而在中国的“共享”发展理念、“互联网+”、以及50/60后老人的意识转变都刚刚起步,预计相关领域在接下来的5年内是市场的快速成长期。
接下来,我们再来看老人在分享更专业技能上产生的机会——从广场舞→家政→教育,这是三个现阶段50岁+老年人群参与度较高的领域。专业技能要求越来越高,Ta们会不会变成移动互联网的新人群?能否与平台一起成长获得收益增量?
2年来总融资额已超1亿人民币的广场舞市场,成为了老年市场互联网创业的热点,相比照料陪护来说,这个领域更加快乐也更加具有想象空间。关于几家已拿到融资的创业公司我们会稍晚去做单独分析,现在我们更想探讨的是,参与广场舞是否带来了收入增加?到底为老人生活带来了怎样的改变?
我们选取两家典型的广场舞创业公司来看,线下活动+线上社群的“舞动时代”(类似的还有99广场舞、就爱广场舞)、和线上视频的“糖豆广场舞”。
观点1:广场舞网红成线上必争之地,收益可观,竞争激烈
广场舞本身具备强内容属性,因此邀请签约优质广场舞团队+PGC模式定期输出教学视频,就成为拉新和留存用户的重要手段——线上属性更强的糖豆在此领域全面领先,274个推荐广场舞团队、1小时为单位的视频更新、15-30万次播放的单个热门视频……领先于行业10倍级的数据,对应的是优质团队的长期入驻和高频产出。
根据2016年10月糖豆广场舞B轮融资的相关数据,250万日活中对应的274个明星团队,粗略估算(按每个明星团队每天上传1个视频计),相当于有1/10000的线上人群,是有能力、有意愿与平台签约并作为KOL网络老师获得收益增量;这当中可参照的行业典型包括:参加过《中国梦想秀》的广场舞达人毕刚、央视相关栏目的舞蹈老师杨艺(现99广场舞联合创始人),他们在服装、课程、商演等实现了最早期的尝试,根据媒体相关报道,其个人形态的商演课程等可以达到月收入2-3万元左右,成为广场舞相关公司的艺术顾问可获得月收入1-2万元,通过淘宝客等引流至服装网销甚至可以带来4-5万元的月收入。相对来说,在糖豆上的团队仍以纯广场舞内容产出为主,对应的收益主要是平台签约费用和线下商演费用,但已经是流量变现的先行者。
从目前的数据来看,糖豆从工具到社区的过渡已经比较成功,经过领舞到爱好者的工具自然增长,现在已在养生/健身/美容等领域形成线上社群。正在尝试的线上广告是线上社群流量变现的可能性,我们也将持续关注2017年糖豆在更多领域和形式的变现可能性,以及为用户带来的收益增量。
类似的,微博上的王德顺、花甲背包客,以及B站红人局座张召忠等,也都是更早期互联网+老年网红的典型,通过品牌代言/节目录制等都实现了名利双收,相比广场舞来说,这类老人自身的技能/故事/既有影响力都更强。
观点2:线下社群更具变现能力,让更多人一起享受红利
根据2016年8月舞动时代Pre-A融资公布的数据,其在全国拥有100多个市会长,1700个区县会长,城市合伙人数量已覆盖全国70%的地区;值得注意的是,其3月营业额已突破千万人民币,其中中老年定制游占到了营收的60%,由会长发起的线下团购占到了30%,周边产品占10%。从其App中的微店来看,确实在新疆/西藏/内蒙/云贵/江浙等地的旅游均有较好的销售,旅游+网红老师教学+广场舞交流/比赛的融合是其亮点。
与平台一起赚钱享受变现红利,应该是更合理的共赢方式——这批意见领袖,本身在传统线下就已经存在团购保健品/养生食品的形态;未来随着各类广场舞App在健康养生、旅游产品和金融理财产品等流量变现上的延展,将可能产生达人分销(三级以内)等收益分成形态。
图3 基于大妈社群的强信任 达人分销将带来相关领域的收益增量
可见的收益分成来源包括:根据淘宝2015年12月发布的中国消费趋势报告,舞林大妈的标配是437元的广场舞套装;而2015年5月CBNData公布的数据显示,45岁+中国女性在(跨境)保健品和保健器材成交额3年来均保持两位数增长,尤其是保健品类,增幅在2015年仍达到30%;此外,房地产商、银行类公司与广场舞团队的线下合作也为大妈们带来了可观的直接收入增量。
观点3:广场舞App做的是年轻大妈们的生意
我们来看参与广场舞的人群诉求是什么?健康(锻炼身体)+ 快乐(排解寂寞)+ 成就(获得认同)。那么广场舞App在这几点提供的价值是:更高效的学习优质编舞、更高效的认识靠谱舞友、更高效的进行广泛传播。
以2016年10月糖豆广场舞B轮融资的公开信息来看,250万日活+90%移动端用户,可以证明用户确实愿意通过App满足需求。94%的用户为2-5线城镇女性(典型画像是已婚已育,生活状态接近中老年),以30-55岁(即1961-1986年25年间出生的人群)为主,也就是说真正55岁+的退休女性并不是广场舞App的主要人群;再来看广场舞网红的年龄普遍在35-45岁间,从各广场舞App实际体验来看,至少90%的明星用户头像是在中青年人群,也不是50岁+的老龄人群。
也就是说,广场舞App的核心活跃用户并不是传统意义上50岁+人群,按照一门生意来看的话,消费能力更强的应该是70-80后人群。在App完成广场舞本身的价值之后,“提升在某个生活领域的效率”是其进一步的自然延展——对于年轻大妈们的功能应该是“优质内容筛选器”,背后的连接关键点是“信任”(例如健康、理财等本身是重决策的产品)。相对来说,年轻大妈们更容易接受移动互联网/电商的形式,也愿意作为意见领袖进行商品背书以实现收益分成;而50岁+的大妈们更倾向于线下的面对面社群连接以及购物/分红方式。
除广场舞外,我们也看到近期上线的专门针对中老年人的友瓣直播,从实际体验看参与人数还很有限,仍处于种子用户运营阶段,在这里不做讨论。
我们接着来分析50岁以上的家政从业人员在互联网相关平台的情况:
我们选取“钟点工”和“老人护理”作为两个最典型的业务进行观察——钟点工是家庭用户最容易决策并体验(价格低至20元/小时起),老人护理则是相对服务周期最长(7-15天术后康复,或1-5年的长期陪护照料)。
钟点工:
首先来看钟点工,由于“互联网+家政”对于阿姨自身的接单数量、服务标准化要求、移动互联网使用要求都相对较高,整体服务供给年轻化已是趋势。因此50岁+的阿姨在e家洁和阿姨帮上仅占8%-10%,58到家几乎没有;而主打“轻管家”概念的好慷在线和小马管家,则以员工制(有社保)吸引更年轻(23-45岁)人群投入家政服务升级,但体验升级的背后是优质供给端的相对缺失。
再来看“家政+互联网”的阿姨来了和无忧保姆,在7300+的阿姨数据中,50岁+占比22.2%,两家的模式均是自营/加盟线下门店,然后将阿姨数据进行互联网化和标准化,本身更多是对门店管理和平台人员的互联网要求更高,因此阿姨的老龄化比例是互联网家政平台的2-3倍,这也与线下传统门店形态基本一致,对应带来的问题是阿姨本身的服务质量参差不齐。
对比全球最大的家政服务平台Care.com,在其Housekeeping类目上可以看到,平均薪资是14.50美元/小时(相当于98元/小时),平台的服务供给共2186人,抽样1000人,50+占比18.2%。这些50岁+的叔叔阿姨们普遍具备5-10年经验,约80%+是寻找兼职,普遍收入是在10-15美元/小时。其中甚至有70岁的两位老人(最高年龄77岁),可以看到Ta们的简介中更多的是对生活的乐趣。
老人护理:
再来看老人护理,阿姨来了和无忧保姆共26801位阿姨可选,其中50+的阿姨占比47.5%。服务供给侧虽然绝对数量较多,但接近50%的老龄化程度,带来的是专业能力和文化层次与用户心理预期不可弥补(年龄大对应学习能力非常有限)的差距。而Ta们正在面临返乡照顾老小及自身养老的问题,因此未来的5-10年一定会面临一线城市的养老看护阿姨荒,这将直接影响到线下存量和创业公司的供给能力。
而在Care.com的Senior Care类目上,平均薪资是13.75美元/小时(相当于93元/小时),平台的服务供给共3855人,抽样1000人,50+占比24%。该人群中约40%具备10-20年经验,约80%+是寻找兼职,普遍收入是在20-25美元/小时。70岁+老人共5位,很惊讶于70岁的老人还在照顾80-90岁的长者,也有人从26岁开始至今连续40年从事老人相关职业。Ta们在职业资质、实践经验、行业深度上都有着很强的积累,并保有持续的热情,以及对老人照料/护理发自内心的喜爱。
图4 年轻老人照顾长者 寻找分时服务需求
观点1:辛苦谋生>发挥余热,职业化带来巨大差异
明显的对比可见,在中国从事家庭生活服务的人员还是以40岁+的非一线城市女性为主,3500-4500元/月的收入尚可基本保障自身家庭的运转。但相比美国的服务供给者,专业性(拥有护理/医疗相关证书、本科以上学历)、经验积累(10-20年垂直领域的服务经验)、互联网意识(对接平台、自我描述、积累口碑)等,都是制约这一代中国阿姨们持续发展的问题。中国家政互联网平台已经历3年的发展,可以看到一部分优秀阿姨已经朝着标准化职业化的方向改进,对于现阶段40-50岁的主力人群来说,10年后她们就是50岁+的服务者,我们有理由相信她们能更好的提供可持续的优质服务,但前提是要解决自身的保障(如社保),这就意味着平台本身有足够的用户端流量,给到阿姨足够的订单和收入增量。
观点2:智能化陪护是未来趋势
北京时间2016年6月30日, Google Capital首次对上市公司进行投资——Care.com获得4635万美元融资后,Google正式成为Care.com的第一大股东。
根据相关报告,2014年美国人在护理服务上的总花费是2800亿美元,包括孩子、成年人、老年人的日常护理、家政、家教及家庭宠物护理,美国有4400万个家庭有上述方面的需求。Care.com于2014年1月上市,是全球最大的家政服务平台,目前拥有1840万个注册用户,包括1030万个家庭和810万个服务人员,横跨16个国家。对于服务人员而言,Care.com为他们提供了有价值的且灵活的全职和兼职机会。在2015年,Care.com上59%的需求是兼职护理服务,其余41%是寻求全职护理。
Alphabet旗下DeepMind团队开发的人工智能AlphaGO和无人驾驶汽车家喻户晓,而人工智能与家政服务的结合想象空间更大。Alphabet在语音助手+智能家居、医疗保健+数字健康的投资布局上,都可能在未来颠覆现有的家庭生活/陪护照料服务的体验。
而在中国,扫地机器人只用了2年时间进入民用化,智能陪护机器人也已亮相并成为关注热点,包括科大讯飞语义识别输入法的民用化,这都代表着技术正在改变生活服务的形态。我们的判断是人力的作用会越来越少,更趋向于年轻人的智能化工具/平台管理,而类似于Care.com上15年以上经验的护理人员,Ta们的专业技能和经验累积,将会反哺AI。也许10年后,50岁+的大龄阿姨们将少之又少,人工+技术结合的升级将会一定程度上解决照料人员匮乏的问题。
对于中国的教育行业,具备30年教龄的老教师至少已经50-55岁,基本进入退休年龄(法定女性55岁、男性60岁)。因此我们相对粗略的将30年教龄作为标签,来看50+的老教师们都去哪儿了?
先来看注重习惯养成和基础教育的小学1-6年级,根据教育部官方数据统计,2011年全国46-55岁的小学教师占比24.18%,56+为6.02%;那么从2016年-2020年预估来看,接近退休/已退休的老师占比将达到30.2%(约169万)。一般来说,退休小学老师的去处是线下的托管班/培训班,考虑到托管类属地性强且尚无较大规模的互联网创业公司,因此该领域的50+再就业暂未列入观察范围内。
我们再来看更注重成绩提升的初高中教育,这当中我们选取教育O2O中典型(融资额度、发展阶段、业务数据等)“跟谁学”和“疯狂老师”来进行分析(以北京区域数据为例):
跟谁学北京共1000名教师,其中50岁+(准)退休老师占2.6%。这26名教师中,约20%的老师有较为明显的收入增量(1000+元/月)。其中以最高收入的刘老师为例,课程总购买为12689人次,其中免费11741人次(占92.5%),付费为948人次(7.5%);付费课程总收入为43212.5元,人均客单价为45.6元;对应到24个月的入驻平台时间,收入增量约为1800元/月。
可以看到50岁+的(准)退休老师,通过互联网获取收入增量的人群比例较低;按收入1000元/月为平均水平计,相比(北京)平均退休工资3000-5000元可实现20%-30%的增量,这对于普通的退休老师来说属于中规中矩。考虑到大部分课程都是以线上方式执行(公开课+1对1线上),时间占用非常少,这样的方式更容易被老师接受。
疯狂老师则主打挑选前30%的优秀老师,北京总量共150人,其中50+(准)退休老师为2.7%(共4人)。按周授课时长曲线看,老师平均授课3.3小时/周,按客单价400-500元/小时,则每周的收入为1320-1650元,月收入增量5280-6600元,这笔收入对于老教师来说相对价值比较高。相对来说,疯狂老师的客单价高是由于线下属性更强(线下小班 1老师对2-6学生),对于老师来说可能会面临时间和地点的双重协调,相对来说参与度会受到影响。
观点1:因热爱而投入,收入并非核心因素,移动互联网成拦路虎
本身存量的老教师们就活跃在线下的各类民营教育机构中,并被机构/中介方“大幅”剥削(50%+的收入被机构扣除),解放老教师们的生产力(去中介化)存在较大可能与机会,且存量供给市场的口碑传播速度会很快。但需要注意的是,老教师们并不是以赚钱为核心诉求,而是多年来与学生们的感情和对教育行业的喜爱。
从上面数据看到,50岁+老教师的互联网渗透率约在2.6%,其主要的原因并非意愿和收入问题——举例来说,我的爸爸就是40年+教龄的语文老师,在2015年注册跟谁学时,一方面是上传多项资质证书对于50岁+的老教师来说有不小的困难;之后的线上课程设置、公开课录制、师生互动等,在移动互联网使用习惯尚未形成时都有着较大挑战,最终他又回到了线下机构以及朋友之间推荐的家教工作。我们预判在未来的5年里,50岁+的老教师们仍以线下机构活跃为主,而70后的教师将在2020年进入50岁后,成为互联网教育的重要组成力量。
结束语:在老人如何增加收入的问题上,随着老人主动的技能分享意愿度逐步提升、相关政策的逐步开放,个人借助平台成为网红或优质服务供给将是理想的路径,也是创业公司的切入机会,这显然是一场持久战。有任何想法或疑问,都欢迎大家加我微信(everbluesun)一同讨论。
(注:所有App相关数据均为2016年10月底实际测试所得)
下一期我们要一起聊的问题是:
老人的收入提升了,钱都花去哪里了?这当中的创业机会又有哪些?
参考阅读:
Silicon Labs, the leader in energy-friendly solutions for a smarter, more connected world, has been constantly making silicon, software and tools to help engineers transform industries and improve lives since 1996.
Silicon Labs has just launched its newest development platform, The Thunderboard Sense Kit. Thunderboard Sense is a small and feature packed development platform for battery operated IoT applications. It is partnered with a mobile app that seamlessly connects Thunderboard Sense to a real time cloud database.
The mobile app enables a quick proof of concept of cloud connected sensors. The multi-protocol radio combined with a broad selection of on-board sensors, make the Thunderboard Sense an excellent platform to develop and prototype a wide range of battery powered IoT applications.
The 30 mm x 45 mm board includes these energy-friendly
components:
Onboard sensors measure data and transmit it wirelessly to the cloud. Thunderboard Sense comes with Silicon Labs’ ready-to-use cloud-connected IoT mobile apps, to collect and view real-time sensor data for cloud-based analytics and business intelligence.
“We’ve designed Thunderboard Sense to inspire developers to create innovative, end-to-end IoT solutions from sensor nodes to the cloud,” said Raman Sharma, Director of Silicon Labs’ IoT Developer Experience. “Thunderboard Sense helps developers make sense of everything in the IoT. They can move quickly from proof of concept to end product and develop a wide range of wireless sensing applications that leverage best-in-class cloud analytics software and business intelligence platforms.”
Check out the official intro video by Raman Sharma
To start using Thunderboard Sense you have to place your CR2032 battery in the right polarity, install the mobile app from Google Play or Apple store, find your board listed on the main screen of the app, and then you will be ready to explore the Thunderboard demos and start your own project! You can program Thunderboard Sense using the USB Micro-B cable and onboard J-Link debugger. You do not need RF design expertise to develop wireless sensor node applications.
Thunderboard Sense kit is available for $36 and you can buy it from here. All hardware, software and design files will be open and accessible for developers. You can visit Silicon Labs Github to download Thunderboard mobile app and cloud software source code.
In this month, we‘ve compared nearly 1,300 React.JS articles to pick the Top 10 (0.77%).
Mybridge AI ranks the best articles for professionals. Hopefully this condensed list will help read and learn more productively in the area of React.JS.
This post is specific to React and React Native.
For those who looking for JavaScript, Angular 2.0, Python, Machine Learning, CSS, Swift… Visit the publication.
Progressive Web Apps with React.js [Part I]: Introduction. Courtesy of Addy Osmani at Google
……………………………………[Part II]
Redux Step by Step: A Simple and Robust Workflow for Real Life Apps. Courtesy of Tal Kol and Hackernoon
Implementation Notes: A detailed advanced level tutorial on how React really works. Courtesy of Dan Abramov at Facebook
ARc: A progressive React starter kit based on the Atomic Design methodology. Courtesy of Diego Haz and Brad Frost
Styled Components: Visual primitives for React and React Native. Use the best bits of ES6 and CSS to style your apps [747 stars on Github]
N1: An extensible desktop mail app built with React.
[20955 stars on Github]
.
Redux VCR: Record and replay user sessions in real time with React
[390 stars on Github]
.
.
React for Beginners. Courtesy ofWes Bos
[8,014 recommends]
.
Build iOS and Android App from scratch with React Native and Redux
[3,853 recommends, 4.8/5 rating]