0%

Golang学习(一)那篇博客中,我们提到了由于inode被耗尽导致的磁盘存储空间不足的报错,现在我们就来深入了解下inode。


什么是inode?

理解inode,要从文件储存说起。
文件储存在硬盘上,硬盘的最小存储单位叫做”扇区”(Sector)。每个扇区储存512字节(相当于0.5KB)。
操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。这种由多个扇区组成的”块”,是文件存取的最小单位。”块”的大小,最常见的是4KB,即连续八个 sector组成一个 block。
文件数据都储存在”块”中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为”索引节点”。

inode记录了什么信息?

inode包含文件的元信息,具体来说有以下内容:

  • 文件的字节数
  • 文件拥有者的User ID
  • 文件的Group ID
  • 文件的读、写、执行权限
  • 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。
  • 链接数,即有多少文件名指向这个inode
  • 文件数据block的位置

可以用stat命令,查看某个文件的inode信息:stat example.txt

总之,除了文件名以外的所有文件信息,都存在inode之中。

inode的大小

inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。
每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。

查看每个硬盘分区的inode总数和已经使用的数量,可以使用df命令。df -i

查看每个inode节点的大小,可以用如下命令sudo dumpe2fs -h /dev/vdb | grep "inode size"

由于每个文件都必须有一个inode,因此有可能发生inode已经用光,但是硬盘还未存满的情况。这时,就无法在硬盘上创建新文件。

inode号码

每个inode都有一个号码,操作系统用inode号码来识别不同的文件。
这里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。
对于系统来说,文件名只是inode号码便于识别的别称或者绰号。
表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。
使用ls -i命令,可以看到文件名对应的inode号码:ls -i example.txt

目录文件

Unix/Linux系统中,目录(directory)也是一种文件。打开目录,实际上就是打开目录文件。
目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。
ls命令只列出目录文件中的所有文件名:ls /etc
ls -i命令列出整个目录文件,即文件名和inode号码:ls -i /etc
如果要查看文件的详细信息,就必须根据inode号码,访问inode节点,读取信息。ls -l命令列出文件的详细信息:ls -l /etc

硬链接

一般情况下,文件名和inode号码是”一一对应”关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为”硬链接”(hard link)。
ln命令可以创建硬链接:ln 源文件 目标文件

运行上面这条命令以后,源文件与目标文件的inode号码相同,都指向同一个inode。inode信息中有一项叫做”链接数”,记录指向该inode的文件名总数,这时就会增加1。反过来,删除一个文件名,就会使得inode节点中的”链接数”减1。当这个值减到0,表明没有文件名指向这个inode,系统就会回收这个inode号码,以及其所对应block区域。
这里顺便说一下目录文件的”链接数”。创建目录时,默认会生成两个目录项:”.”和”..”。前者的inode号码就是当前目录的inode号码,等同于当前目录的”硬链接”;后者的inode号码就是当前目录的父目录的inode号码,等同于父目录的”硬链接”。所以,任何一个目录的”硬链接”总数,总是等于2加上它的子目录总数(含隐藏目录),这里的2是父目录对其的“硬链接”和当前目录下的“.硬链接”。

软链接

除了硬链接以外,还有一种特殊情况。文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的”软链接”(soft link)或者”符号链接(symbolic link)。
这意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错:”No such file or directory”。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode”链接数”不会因此发生变化。ln -s 命令可以创建软链接。ln -s 源文文件或目录 目标文件或目录

inode的特殊作用

由于inode号码与文件名分离,这种机制导致了一些Unix/Linux系统特有的现象。
​ 1. 有时,文件名包含特殊字符,无法正常删除。这时,直接删除inode节点,就能起到删除文件的作用。
​ 2. 移动文件或重命名文件,只是改变文件名,不影响inode号码。
​ 3. 打开一个文件以后,系统就以inode号码来识别这个文件,不再考虑文件名。因此,通常来说,系统无法从inode号码得知文件名。
第3点使得软件更新变得简单,可以在不关闭软件的情况下进行更新,不需要重启。因为系统通过inode号码,识别运行中的文件,不通过文件名。更新的时候,新版文件以同样的文件名,生成一个新的inode,不会影响到运行中的文件。等到下一次运行这个软件的时候,文件名就自动指向新版文件,旧版文件的inode则被回收。

实际问题

问题现象

在一台配置较低的Linux服务器(内存、硬盘比较小)的/data分区内创建文件时,系统提示磁盘空间不足,用df -h命令查看了一下磁盘使用情况,发现/data分区只使用了66%,还有12G的剩余空间,按理说不会出现这种问题。后来用df -i查看了一下/data分区的索引节点(inode),发现已经用满(IUsed=100%),导致系统无法创建新目录和文件。

问题原因

/data/dstFilePath目录中存在数量非常多的小字节文件,占用的Block不多,但是占用了大量的inode。

解决方案

方案1. 删除/data/dstFilePath目录中的部分文件,释放出/data分区的一部分inode。
方案2. 用软连接将空闲分区/中的newdstFilePath目录连接到/data/dstFilePath,使用/分区的inode来缓解/data分区inode不足的问题:ln -s /newdstFilePath /data/dstFilePath

参考《Linux的inode的理解》


2017年12月24日 于 南京
Email
GitHub

闲扯

去北京参加Top100学习到现在一个多月过去了,时间过得好快,转眼2017年只剩下最后一周了,不由得感叹时间就像指缝的流沙。
再加上最近中年程序员不堪重负的各种新闻,感觉自己距离那一天也没多远了,在此之前努力提高自己的姿势水平吧,要没时间了。

在Top100听到了很多讲师提到了Golang,结合之前看过的左耳朵耗子的文章《GO语言、DOCKER 和新技术》,决定要学习了解下Golang了。
最近半个月断断续续看了Golang的一些教程《GO 语言简介(上)— 语法》《GO 语言简介(下)— 特性》、无闻老师的《Go 编程基础》,只是对Golang有了一点点初步的了解。
学习一门语言最重要的还是要撸代码啊,要动手写个小工具试试手。
恰巧昨天加班时帮同事写了个小工具,感觉用Python的性能不够好,而且想要高性能还要依赖gevent这种第三方库,不方便部署,于是想到用Golang可以来试下,写完编译下给同事用就好啦!昨晚回到家吃过饭陪女朋友玩了会,从十点开始撸代码到凌晨一点,终于写出一个小demo,特此记录下年轻人的第一个Golang小程序。


正文

工具需求是有一个样例zip包,zip包里面有一些bcp数据、xml数据、bjson数据、图片、视频什么的。
zip包的命名规范如下:
​ AAA-BBB-1514090969-CCC_DDD_21234.zip
我们要将zip包的1514090969【绝对秒数】与21234【随机序列】进行替换,随机生成大量的zip包副本,发送到某个ETL输入目录,暂且不管zip包里面的内容(其实zip包内的文件也要随机生成)。
之前同事用Shell写的,没看他代码怎么实现的,不过肯定有问题,一小时才生成了三万个样例zip包,远远达不到压测的要求。
于是我花了十几分钟调用Python的gevent模块帮他重新用Python写了一遍,性能瞬间爆炸。
但是部署gevent比较麻烦(公司和客户都是内网环境,pip是没法用的,只能手动安装所需的第三方包,而且公司大部分操作系统还是Redhat AS6U3,默认的Python是2.6版本,各种不方便),就想到了如果用Golang写一遍是否性能很好,编译后提供二进制文件直接运行就可以了,于是就有了下面的代码。

代码

1
/*
2
 * User: toddlerya
3
 * Date: 2017/12/23
4
 * ds接入模块加压工具
5
 */
6
7
package main
8
9
import (
10
	"flag"
11
	"fmt"
12
	"io"
13
	"math/rand"
14
	"os"
15
	"strconv"
16
	"strings"
17
	"time"
18
)
19
20
func judgeExists(name string) bool {
21
	if _, err := os.Stat(name); err != nil {
22
		if os.IsNotExist(err) {
23
			return false
24
		}
25
	}
26
	return true
27
}
28
29
func copyFile(src, des string) (w int64, err error) {
30
	srcFile, err := os.Open(src)
31
	if err != nil {
32
		fmt.Println(err)
33
	}
34
	defer srcFile.Close()
35
36
	desFile, err := os.Create(des)
37
	if err != nil {
38
		fmt.Println(err)
39
	}
40
	defer desFile.Close()
41
42
	return io.Copy(desFile, srcFile)
43
}
44
45
func generateRandomNumber(start int, end int) int {
46
	if end < 0 || start < 0 || (end-start) <= 0 {
47
		fmt.Println("[-] 随机数起始值[start]必须大于等于0, 截至值[end]必须大于起始值!")
48
		fmt.Printf("[-] 请检查配置是否正确: start=%v, end=%v\n", start, end)
49
		panic("随机数参数错误")
50
	}
51
	rand.Seed(time.Now().UnixNano())
52
	num := rand.Intn((end - start)) + start
53
	return num
54
}
55
56
func main() {
57
	srcFilePath := flag.String("s", "/home", "输入原始文件路径")
58
	dstFilePath := flag.String("d", "/tmp", "目标输出路径")
59
	renameFormat := flag.String("f", "abc-{random1}-456_780_{random2}.zip", "参数原始文件替换格式")
60
	random1Start := flag.Int("r1s", 100, "随机参数1的起始值")
61
	random1End := flag.Int("r1e", 999, "随机参数1的截止值")
62
	random2Start := flag.Int("r2s", 100000, "随机参数2的起始值")
63
	random2End := flag.Int("r2e", 999999, "随机参数2的截止值")
64
	generateFileNumber := flag.Int("g", 10000, "需要生成的文件个数")
65
	// taskQueueSize := flag.Int("t", 100, "每次任务队列生成文件个数")
66
	// interval := flag.Int("i", 3, "任务运行间隔[单位秒]")
67
	flag.Parse()
68
	fmt.Printf("[srcFilePath]          :%s\n", *srcFilePath)
69
	fmt.Printf("[dstFilePath]          :%s\n", *dstFilePath)
70
	fmt.Printf("[renameFormat]         :%s\n", *renameFormat)
71
	fmt.Printf("[random1Start]         :%v\n", *random1Start)
72
	fmt.Printf("[random1End]           :%v\n", *random1End)
73
	fmt.Printf("[random2Start]         :%v\n", *random2Start)
74
	fmt.Printf("[random2End]           :%v\n", *random2End)
75
	fmt.Printf("[generateFileNumber]   :%v\n", *generateFileNumber)
76
	// fmt.Printf("[taskQueueSize]        :%v\n", *taskQueueSize)
77
	// fmt.Printf("[interval](second)     :%v\n", *interval)
78
	fmt.Println("=======================================")
79
	if judgeExists(*srcFilePath) {
80
		fmt.Println("[+] 开始随机生成目标数据, 请注意", *dstFilePath, "目录是否有数据生成\t", "计划生成", *generateFileNumber, "个文件Orz")
81
		count := 0
82
		for {
83
			if count >= *generateFileNumber {
84
				fmt.Println("[+] 已经完成目标: 共计生成", *generateFileNumber, "个文件")
85
				return
86
			}
87
			random1Val := generateRandomNumber(*random1Start, *random1End)
88
			random2Val := generateRandomNumber(*random2Start, *random2End)
89
			temp := strings.Replace(*renameFormat, "{random1}", strconv.Itoa(random1Val), -1)
90
			newFileName := strings.Replace(temp, "{random2}", strconv.Itoa(random2Val), -1)
91
			dstJoinList := []string{*dstFilePath, newFileName}
92
			newDstFilePath := strings.Join(dstJoinList, "/")
93
			fmt.Println("[*] 输出随机生成的文件: ", newDstFilePath)
94
			copyFile(*srcFilePath, newDstFilePath)
95
			count++
96
		}
97
	} else {
98
		fmt.Println("[-] 原始文件不存在, 请检查: ", *srcFilePath)
99
	}
100
}

使用效果如下

测试运行

异常处理

遗留BUG

但是在阿里云的测试中发现了程序的BUG,df看磁盘剩余空间还有很多,但是df -i查看发现磁盘Inode被耗尽了导致无法复制文件,可是是程序并没有停止复制,异常退出,这个BUG以后再修改:

后续

因为还没学到Golang的并发,所以这个demo只是简单的循环执行,后续学会了并发再来更新T_T


2017年12月24日 于 南京
Email
GitHub

今天我们继续来搞Coding数据~

从官方给出的数据我们知道目前Coding大概有50万注册用户

然后我们还能看到冒泡广场有很多热门用户

那么这些用户之间的关系是什么样子滴?
哪些用户是高冷吸粉狂人?
哪些用户是社交达人?
哪些用户是万年潜水独行侠?
50万Coding小伙伴的社交网络核心是哪位?
是否真的有50万用户?

带着这些疑问,我们开始撸代码!
先把项目地址贴下:https://github.com/toddlerya/AnalyzeCoding

爬虫算法分析设计:

如何获取全部用户信息

通过分析Coding网站, 我们发现有3个地方可以获取到用户数据信息:

  • 冒泡广场: 每一条冒泡都有发布人,有些还有点赞人打赏人评论人

  • 热门用户:冒泡广场首页右侧有20个热门用户,这些用户是当前最活跃的用户,这些用户拥有比较多的粉丝和朋友。

  • 个人主页:用户的个人主页有一个标签页:关注,这里可以看到此用户关注了哪些人,被哪些人关注。

现在就是开脑洞的时候啦!

用户之间的关系分为三种:

1
A关注B: A-->B  
2
A被B关注:A<--B  
3
A与B互相关注:A<-->B

这就是个有向图嘛!

六度分隔理论:
1967年,哈佛大学的心理学教授Stanley Milgram(1933-1984)想要描绘一个连结人与社区的人际连系网。做过一次连锁信实验,结果发现了“六度分隔”现象。简单地说:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过六个人你就能够认识任何一个陌生人。”

思路渐渐清晰了,我们可以采用深度优先算法(Depth-First Search,简称DFS)
从A出发,找到A所有的好友(A1,A2,A3)记录为{A: [A1, A2, A3]}
从A1出发,找到A1所有的好友(A,A2-1,A3-1)记录为{A1: [A, A2-1, A3-1]}
从A2-1出发,找到A2-1所有的好友(A2-1-1)记录为{A2-1: [A2-1-1]}
从A3-1出发,找到A3-1所有的好友(A3-1-1)记录为{A3-1: [A3-1-1]}
从A2出发,找到A2所有的好友(A2-1)记录为{A2: [A2-1]}
……循环递归……
直到Z,Z同学没有任何好友,结束本次遍历爬取。
爬取过程中要注意,已经爬取过的人要跳过,否则会陷入无限循环中。

相关API

  • 当前热门用户:https://coding.net/api/tweet/best_user
  • 用户的朋友们:https://coding.net/api/user/friends/{用户全局唯一代号}?page=1&pageSize=20
  • 用户的粉丝们:https://coding.net/api/user/followers/{用户全局唯一代号}?page=1&pageSize=20
  • 用户个人信息详情:https://coding.net/api/user/key/{用户全局唯一代号}

代码设计实现

数据库设计

为了程序轻便,数据便于分享,我们决定使用Sqlite数据库。
上述分析过程中发送,用户的登录帐号是全局唯一的,不会重复的,我们以此字段作为主键且不允许为空,
还可以通过上述API获取用户的朋友、朋友的个数、用户的粉丝、粉丝的个数、用户详细信息,因此用户社交关系表设计如下:

1
CREATE TABLE IF NOT EXISTS coding_all_user (
2
       global_key VARCHAR PRIMARY KEY NOT NULL,
3
       friends_count INTEGER,
4
       friends VARCHAR,
5
       followers_count INTEGER,
6
       followers VARCHAR
7
     )

用户个人信息详情表设计如下:

1
CREATE TABLE IF NOT EXISTS coding_user_info (
2
        global_key VARCHAR PRIMARY KEY NOT NULL,
3
        user_name VARCHAR,
4
        name_pinyin VARCHAR,
5
        sex VARCHAR,
6
        slogan VARCHAR,
7
        company VARCHAR,
8
        job VARCHAR,
9
        tags VARCHAR,
10
        skills VARCHAR,
11
        website VARCHAR,
12
        introduction VARCHAR,
13
        avatar VARCHAR,
14
        school VARCHAR,
15
        follows_count VARCHAR,
16
        fans_count INTEGER,
17
        tweets_count INTEGER,
18
        vip VARCHAR,
19
        created_at VARCHAR,
20
        last_logined_at VARCHAR,
21
        last_activity_at VARCHAR
22
        )

爬虫设计

这里要注意有一个特殊的用户coding,此用户为官方帐号,此用户的friendsfollowers两个API数据为空,遇到此用户要进行跳过。

根节点设置

第一次爬取,将当前热门用户作为起始根节点,后续取数据库中已经存储的用户与当前热门用户比较,若当前热门用户有未录入数据库的,则以当前热门用户作为根节点,否则以数据库中的用户作为根节点启动爬虫。

遍历用户爬虫(crawl_all_user.py)执行逻辑

爬取每一个节点的好友、粉丝,入库(coding_all_user)更新(因为用户的好友和粉丝会变化)、若其好友未录入数据库,则加入下一轮递归抓取的任务列表,否则结束此轮任务。

用户详细信息爬虫(crawl_user_info.py)逻辑

遍历用户爬虫录入的coding_all_user表作为输入数据,遍历库中的每一个用户,通过用户个人信息详情API来获取用户信息录入coding_user_info

遇到的问题:

1. python递归深度报错

  • 报错详情:
    1
    --RuntimeError: maximum recursion depth exceeded
  • 问题原因:
    python默认的递归深度是1000,因此当递归深度超过999的时,就会引发这样的一个异常。
    从Coding的官方公布数据了解到,Coding目前有50万用户,所以当程序爬取到第1000个人以后肯定就报错崩溃啦!
  • 解决方案:
    粗暴的将默认递归深度调大点
    1
    import sys   
    2
    sys.setrecursionlimit(1000000) #例如这里设置为一百万
    经过尝试我们发现Python可以递归的最大深度为2147483647
    大于此深度,设置时会报错:
    1
    OverflowError: signed integer is greater than maximum  # Linux
    2
    OverflowError: Python int too large to convert to C long  # Windows
    但是!但是!这不是正确的解决方案,正确的解决方案应该是优化代码,使用生成器+循环来解决问题!

部分代码

crawl_all_user.py
crawl_user_info.py

爬取的数据库文件

analyzecoding.db


2017年10月29日 于 南京
Email
GitHub

关键词: Python、wordcloud、jieba、matplotlib、词云、分词

前几天的召开的十九大,习近平讲了三小时的三万字工作报告究竟讲了些什么内容呢,我们用Python来一次数据分析看看究竟讲了哪些内容。
主要思路:

  • 通过jieba分词对工作报告进行切词,清洗,词频统计。
  • 通过wordcloud对切词统计结果进行可视化展示。

jieba分词利器

特点

  • 支持三种分词模式:
    • 精确模式,试图将句子最精确地切开,适合文本分析;
    • 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
    • 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。

jieba项目地址:https://github.com/fxsjy/jieba

遇到的问题以及解决办法:

1. 无法匹配最新的词汇

我们采用精确模式进行分词,但是遇到一些词汇在jieba的默认词库没有,所以要根据十九大进行一些定制词库,加载到jieba词库:

1
import jieba
2
cpc_dict_path = u'user_dict/cpc_dictionary.txt'
3
jieba.load_userdict(cpc_dict_path)  # 加载针对全国人民代表大会的分词词典

2. 匹配到了各种符号、空格

切词后统计词频发现有很多标点符号、空格,这些内容我们可以使用正则匹配法进行过滤,u'[\u4e00-\u9fa5]+'匹配所有中文字符,舍弃未命中内容:

1
import re
2
goal_word = ''.join(re.findall(u'[\u4e00-\u9fa5]+', seg)).strip()  # 过滤所有非中文字符内容

3. 匹配到了很多停词

切词后统计词频发现有很多停词,例如:“的”、“和”、“而且”……
这种问题肯定不止我遇到了,所以直接去找前人整理好的停词词库即可,通过匹配停词来进行过滤:

1
stop_words_path = u'user_dict/stopword.txt'
2
with open(stop_words_path) as sf:
3
    st_content = sf.readlines()
4
stop_words = [line.strip().decode('utf-8') for line in st_content]  # 将读取的数据都转为unicode处理
5
if len(goal_word) != 0 and not stop_words.__contains__(goal_word):
6
    ......

wordcloud词云神器

使用wordcloud生成词云,支持进行各种个性化设置,很好很强大。
项目地址:https://github.com/amueller/word_cloud

遇到的问题及解决办法:

1. wordcloud默认不支持显示中文

不进行处理,直接使用wordcloud绘制词云,显示效果如下,中文都是小方框:

善用搜索引擎,查到问题原因根本在于wordcloud的默认字体不支持中文
解决方案基本分为两种:

个人认为方案2更好一些,提高了代码的可移植性,同时避免了升级wordcloud库导致代码失效的风险。

1
import os
2
font = os.path.abspath('assets/msyh.ttf')
3
wc = WordCloud(collocations=False, font_path=font, width=3600, height=3600, margin=2)

设置好字体后显示效果如下,已经基本实现了我们的目标:


项目地址:AnalyzeNPC
核心代码如下:

1
#!/usr/bin/env python
2
# -*- coding:utf-8 -*-
3
# author: toddler
4
5
import jieba
6
from collections import Counter
7
import re
8
from wordcloud import WordCloud
9
import matplotlib.pyplot as plt
10
11
12
def cut_analyze(input_file):
13
    """
14
    :param input_file: 输入带切词分析的文本路径
15
    :return: (list1, list2) list1切词处理后的列表结果, list2输出切词处理排序后的词频结果, 列表-元祖嵌套结果
16
    """
17
    cpc_dict_path = u'user_dict/cpc_dictionary.txt'
18
    stop_words_path = u'user_dict/stopword.txt'
19
20
    with open(input_file) as f:
21
        content = f.read()
22
23
    with open(stop_words_path) as sf:
24
        st_content = sf.readlines()
25
26
    jieba.load_userdict(cpc_dict_path)  # 加载针对全国人民代表大会的分词词典
27
28
    stop_words = [line.strip().decode('utf-8') for line in st_content]  # 将读取的数据都转为unicode处理
29
30
    seg_list = jieba.cut(content, cut_all=False)  # 精确模式
31
32
    filter_seg_list = list()
33
34
    for seg in seg_list:
35
        goal_word = ''.join(re.findall(u'[\u4e00-\u9fa5]+', seg)).strip()  # 过滤所有非中文字符内容
36
        if len(goal_word) != 0 and not stop_words.__contains__(goal_word):  # 过滤分词结果中的停词内容
37
            # filter_seg_list.append(goal_word.encode('utf-8'))  # 将unicode的文本转为utf-8保存到列表以备后续处理
38
            filter_seg_list.append(goal_word)
39
40
    seg_counter_all = Counter(filter_seg_list).most_common()  # 对切词结果按照词频排序
41
42
    # for item in seg_counter_all:
43
    #     print "词语: {0} - 频数: {1}".format(item[0].encode('utf-8'), item[1])
44
45
    return filter_seg_list, seg_counter_all
46
47
48
def main():
49
    input_file_path = u'input_file/nighteen-cpc.txt'
50
    cut_data, sort_data = cut_analyze(input_file=input_file_path)
51
    font = r'E:\Codes\National_Congress_of_ CPC\assets\msyh.ttf'
52
    wc = WordCloud(collocations=False, font_path=font, width=3600, height=3600, margin=2)
53
    wc.generate_from_frequencies(dict(sort_data))
54
    plt.figure()
55
    plt.imshow(wc)
56
    plt.axis('off')
57
    plt.show()
58
59
60
if __name__ == '__main__':
61
    main()

2017年10月22日 于 南京
Email
GitHub

项目地址: https://github.com/toddlerya/learn_scrapy

I. Coding的冒泡广场

CODING 是国内专业的一站式云端软件服务平台,Coding.net 为开发者提供了免费的基础服务,包括但不限于 Git 代码托管,项目管理,Pages 服务,代码质量管理。您可以在 Coding.net 一站完成代码及代码质量,项目及项目人员的管理,Coding.net 让开发变得前所未有的敏捷和简单。

其中Coding有一个冒泡的社交功能– 冒泡广场,比较像微博,会有很多程序员的日常吐槽,分享等,比如这样:

这里面的数据属性非常丰富,每一条冒泡都具有如下属性:

1. 爬虫分析

爬虫的基本原则是能使用API接口,绝不解析html页面,恰巧这个网站的API很好用。

  • 对这个网站的API进行了基本的分析:html https://coding.net/api/tweet/public_tweets?size=20&sort=time&filter=true&last_time=1504876265000

  • 分析发现只需提交size和filter参数即可:

    • filter=true为只获取精华冒泡,false为获取全量,当然是全量啦!
    • size为最近发表的多少条冒泡信息
  • 因此最终的get 请求为: https://coding.net/api/tweet/public_tweets?size=20&filter=false

2. 这里有两个注意事项

  1. 网站的rebots.txt设置了规则,禁止爬取Disallow: /api/*,我们只是做个小实验,不进行大规模的爬取,因此需要修改下Scrapy项目的settings.py配置(不守规矩~):
    1
    # Obey robots.txt rules
    2
    ROBOTSTXT_OBEY = False
  2. size参数如果太大会导致HTTP访问超时,Scrapy报错中止,需要在请求发起时修改Request.meta的参数,见官方文档:DOWNLOAD_TIMEOUT
    1
    [scrapy.downloadermiddlewares.retry] DEBUG: Retrying <GET https://coding.net/api/tweet/public_tweets?size=100000&filter=false> (failed 1 times): 504 Gateway Time-out

2017年09月11日 于 南京
Email
GitHub

去年申请的免费aws上个月底到期了(ss梯子没有了T_T),这个月搞了个半年免费的阿里云VPS。
国内的VPS网速果然好快,决定好好利用起来~

正好最近在学Scrapy,感觉用vim写代码,不能自动补全好难受,在公司的内网环境用vim不好折腾插件也就罢了,自己的服务器还是要搞的顺手点,磨刀不误砍柴工嘛。

为了一劳永逸,决定开个坑,维护自己的vim配置,以后换个环境就能开箱使用啦!

先上个项目地址:https://github.com/toddlerya/awesome-vim/

自动补全主要用了jedi-vim插件,这插件太给力了,自动补全方法,还能提示参数,查看文档。
还有一部分配置参考了k-vim-server,这位同学的vim配置很给力,他还有一个完全版的vim插件配置k-vim,大家可以去看看~

使用效果如下:

部署步骤:

1. 备份你的vimrc配置(如果有的话)

1
cp ~/.vimrc ~/.vimrc_bak

2. 安装Vundle

1
git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim

3. 安装jedi-vim

1
pip install jedi-vim

4. 下载并设置vimrc

1
git clone https://github.com/toddlerya/awesome-vim.git && ln -s awesome-vim/vimrc ~/.vimrc

5. 安装Vundle插件

打开vim,运行命令

1
:PluginInstall

等显示Done后,退出vim就好啦,时间长短看网速,耐心等待。
此过程会安装这几个插件:

1
davidhalter/jedi-vim
2
tmhedberg/SimpylFold
3
vim-scripts/indentpython.vim
4
jnurmine/Zenburn
5
Lokaltog/powerline

6. 到此就完成啦,享受生活吧!


2017年09月8日 于 南京
Email
GitHub

2017年的5月1日,三天假期,闭门造了个轮子

写这个工具的目的是为了解决工作问题。 个人工作生产环境无法连接互联网,也没有自建的yum源等,手头又有很多服务器需要进行监控,使用现有的开源方案安装部署是个问题, 各种依赖组件包需要挨个安装,很麻烦,所以想找一款依赖较少部署简单的分布式服务器资源监控工具,找来找去没找到,索性自己动手写一个。 我的本职工作是测试,所以就用最熟悉的Python来写吧,第一次写web应用,先做出来再边学边优化吧。

工具分为客户端和服务端两部分: 服务端使用了bottle来作为web框架,echarts来渲染生成图表; 客户端使用Python原生类库采集服务器资源,客户端采集数据部分代码参考了pyDash

效果如下

项目链接链接

NebulaSolarDash


2017年05月10日 于 南京
Email
GitHub