谢乾坤 | Kingname

给时光以生命。

故事背景

2014年12月28号开始,Gmail被伟大的墙从协议上封禁,POP3、SMTP、IAMP全部阵亡。于是不仅网页不能打开Gmail,连邮件客服端都不能使用Gmail收发邮件了。

Gmail在国内的用户相当的广泛,难道就真的不用了吗?当然不是。虽然使用VPN可以翻出长城,但是开着VPN做其他事情又不太方便。于是,一种Gmail的转发服务变得重要起来。

这篇文章将详细介绍如何使用亚马逊云AWS的免费主机EC2,配合Gmail的API来编写一个Gmail的转发程序。程序在设定时间内访问Gmail收件箱,发现新邮件以后,就通过另一个邮箱转发到国内邮箱中。每一次转发记录到一个日志文件中,并使用Flask搭建网站来,从而直观的检查接收发送记录。

AWS的免费主机EC2的申请不是本文的重点,网上有很多教程,故略去不讲。
Flask环境的搭建不是本文重点,网上有很多教程,故略去不讲。

本篇先讲解Gmail API的使用,下一篇讲解如何制作转发程序。

授权之路

既然要是用Gmail的API,那就要开通Gmail的授权。Google的官方英文教程请戳->Run a Gmail App in Python

打开Gmail API

访问https://console.developers.google.com/project,单击“建立档案”选项,新建一个项目。我这里新建的项目叫做“gmail”,如下图:

单击新建的档案“gmail”,在左侧点击“API和验证”,选择“API”,然后再右侧中间搜索框中输入Gmail,找到后打开。如下图:

然后点击左侧“凭证”,选择“建立新的用户端ID”

这个时候注意一定要选择第三项,才能正确生成json文件。选择第三项,并填写完一些信息后,做如下选择,并点击“建立用户端ID”

接下来,下载json文件。

验证机器

在服务器上新建ghelper文件夹:

1
2
mkdir ghelper
cd ghelper

然后安装Google API Python Client库。建议使用pip安装而不是easy_install,因为pip安装的库文件可以卸载,而easy_install安装的库文件不能卸载。

1
sudo pip install --upgrade google-api-python-client

为了使代码中的run.tools()能够正常执行,还需要安装gflags:

1
sudo pip install python-gflags

将json文件上传到AWS服务器上,我放在了~/wwwproject/ghelper目录下面,并且重命名为client_secret.json,这样代码就不需要进行修改了。同时在本目录下面新建ghelper_api.py文件,文件内容为官方指南中的验证机器的代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import httplib2

from apiclient.discovery import build
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import run

# Path to the client_secret.json file downloaded from the Developer Console
CLIENT_SECRET_FILE = 'client_secret.json'

# Check https://developers.google.com/gmail/api/auth/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/gmail.readonly'

# Location of the credentials storage file
STORAGE = Storage('gmail.storage')

# Start the OAuth flow to retrieve credentials
flow = flow_from_clientsecrets(CLIENT_SECRET_FILE, scope=OAUTH_SCOPE)
http = httplib2.Http()

# Try to retrieve credentials from storage or run the flow to generate them
credentials = STORAGE.get()
if credentials is None or credentials.invalid:
credentials = run(flow, STORAGE, http=http)

# Authorize the httplib2.Http object with our credentials
http = credentials.authorize(http)

# Build the Gmail service from discovery
gmail_service = build('gmail', 'v1', http=http)

# Retrieve a page of threads
threads = gmail_service.users().threads().list(userId='me').execute()

# Print ID for each thread
if threads['threads']:
for thread in threads['threads']:
print 'Thread ID: %s' % (thread['id'])

运行ghelper_api.py,进入Google验证阶段。

1
python ghelper_api.py

在红线处按回车键就可以进入输入模式。输入gmail和密码以后,移动光标到“Sign in”回车,然后进入如下页面:

输入你的信息,验证通过以后会让你进入开启浏览器的javascript功能。可是Linux服务器哪来的浏览器?这个时候按键盘的Ctrl + Z来取消。

继续输入:

1
python ghelper_api.py --noauth_local_webserver

会提示离线验证,如果仍然失败的话,就继续Ctrl+Z然后再输入上面的代码,很快就会让你离线验证:

复制他给出的网址,并在自己电脑上登录后,复制他给出的代码并粘贴回服务器上。验证通过。

使用API

打开API Reference,查看Gmail API的用法。

这里用Users.messages的list和get方法来演示API的使用。

先查看list的说明:

Lists the messages in the user’s mailbox.

列出邮箱里的信息。这里实际上列出来的是每一封邮件的id,于是,使用这个id,通过get就能获得邮件的内容。

通过查看list和get的使用范例:

list:
https://developers.google.com/gmail/api/v1/reference/users/messages/list
get:
https://developers.google.com/gmail/api/v1/reference/users/messages/get

构造出以下的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#-*-coding:utf-8 -*-
import httplib2

from apiclient.discovery import build
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import run
from apiclient import errors
import base64
import email


# Path to the client_secret.json file downloaded from the Developer Console
CLIENT_SECRET_FILE = 'client_secret.json'

# Check https://developers.google.com/gmail/api/auth/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/gmail.readonly'

# Location of the credentials storage file
STORAGE = Storage('gmail.storage')

# Start the OAuth flow to retrieve credentials
flow = flow_from_clientsecrets(CLIENT_SECRET_FILE, scope=OAUTH_SCOPE)
http = httplib2.Http()

# Try to retrieve credentials from storage or run the flow to generate them
credentials = STORAGE.get()
if credentials is None or credentials.invalid:
credentials = run(flow, STORAGE, http=http)

# Authorize the httplib2.Http object with our credentials
http = credentials.authorize(http)

# Build the Gmail service from discovery
gmail_service = build('gmail', 'v1', http=http)

# Retrieve a page of threads
# threads = gmail_service.users().threads().list(userId='me').execute()

# # Print ID for each thread
# if threads['threads']:
# for thread in threads['threads']:
# print 'Thread ID: %s' % (thread['id'])

def ListMessagesWithLabels(service, user_id, label_ids=[]):
"""List all Messages of the user's mailbox with label_ids applied.

Args:
service: Authorized Gmail API service instance.
user_id: User's email address. The special value "me"
can be used to indicate the authenticated user.
label_ids: Only return Messages with these labelIds applied.

Returns:
List of Messages that have all required Labels applied. Note that the
returned list contains Message IDs, you must use get with the
appropriate id to get the details of a Message.
"""
try:
response = service.users().messages().list(userId=user_id,
labelIds=label_ids).execute()
messages = []
if 'messages' in response:
messages.extend(response['messages'])

while 'nextPageToken' in response:
page_token = response['nextPageToken']
response = service.users().messages().list(userId=user_id,
labelIds=label_ids,
pageToken=page_token).execute()
messages.extend(response['messages'])

return messages
except errors.HttpError, error:
print 'An error occurred: %s' % error

def GetMessage(service, user_id, msg_id):
"""Get a Message with given ID.

Args:
service: Authorized Gmail API service instance.
user_id: User's email address. The special value "me"
can be used to indicate the authenticated user.
msg_id: The ID of the Message required.

Returns:
A Message.
"""
try:
message = service.users().messages().get(userId=user_id, id=msg_id).execute()

print 'Message snippet: %s' % message['snippet']

return message
except errors.HttpError, error:
print 'An error occurred: %s' % error

a = ListMessagesWithLabels(gmail_service,'me')[0]['id']
b = GetMessage(gmail_service,'me',a)
print b['snippet']
print b['payload']['headers'][3]['value']

通过观察GetMessage返回的数据,可以看到,返回的是一个字典dict,邮件的内容在key为snippet的里面。发件人在[‘payload’][‘headers’][3][‘value’]里面,如图:

代码在服务器上运行效果如图:

至此,Gmail API在AWS服务器上的部署完成。下一篇文章将会介绍如何使用Python轮询Gmail的收件箱,并在有新邮件的时候转发到国内邮箱。

在看《Dive into Python》的单元测试时,发现用作例子的“阿拉伯数字-罗马数字”的转换算法非常的巧妙,现在发上来和大家分享一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
romanNumeralMap = (('M',1000),
('CM',900),
('D',500),
('CD',400),
('C',100),
('XC',90),
('L',50),
('XL',40),
('X',10),
('IX',9),
('V',5),
('IV',4),
('I',1))
def toRoman(n):
result = ""
for numeral, integer in romanNumeralMap:
while n >= integer:
result += numeral
n -= integer
return result

def fromRoman(s):
result = 0
index = 0
for numeral, integer in romanNumeralMap:
while s[index:index+len(numeral)] == numeral:
result += integer
index += len(numeral)
return result

print toRoman(1356)
print fromRoman('MCMLXXII')

这个算法的聪明之处,就在于他通过一个romanNumeralMap,把罗马数字与阿拉伯数字里面的“边界值”做出一一对应。这个边界刚刚好是罗马数字组合之间的转换。例如,I,II,III都可以通过第一个边界值组合获得;V,VI,VII,VIII可以通过V和I的组合获得。而对于一些特殊的值,则直接列出来。例如IV。通过这个边界值的组合,就能实现所需求的转换。这就类似于在一些机读卡上,需要填写1到100的数字,他会使用0,1,2,4,7这样以来:

1
2
3
4
5
3 = 1 + 2;
5 = 4 + 1;
6 = 4 + 2;
8 = 7 + 1;
9 = 7 + 2.

首先看一下toRoman()函数,把阿拉伯数字转换成罗马数字。它使用Python连接字符串的操作符号 + 来使“边界值”连接到一起。例如用作例子的n = 1356,程序遍历romanNumeralMap,寻找n对应的罗马数字,如果找不到,那就找刚刚比n小一点的数字对应的罗马字符。遍历在能使n 在romanNumeralMap有对应值时结束。

找到刚刚比1356小的那个值对应的罗马数字,也就是1000,M
再继续找刚刚比n = 1356 - 1000 = 356小的数,也就是100,C;
又继续找比n = 356 - 100 = 256小的数,还是100,也就是C;
再找比n = 256 - 100 = 156小的数,仍然是100,C;
继续找比n = 156 - 100 = 56 小的数,50,L;
继续找比n = 56 - 50 = 6小的数,5,V;
继续找n = 6 - 5 = 1对于的数,1,I。 结束。

所以1356对应的值为MCCCLVI。 这样的操作很类似于在十进制里面,一个数字1356 = 1000 + 300 + 50 + 6,只是阿拉伯数字里面6是一个单独的符号,而罗马数字里面VI是个V + I的组合而已。

下面再说说fromRoman()函数,把罗马数字转换成阿拉伯数字。这个函数在理解上面可能比toRoman()稍稍要困难一点。

还是用例子来说明,MCMLXXII转换成阿拉伯数字。
其中如下代码

1
s[index:index+len(numeral)]

作用是把字符串s中,从第index位到第index+ len(numeral)位(不包含第index + len(numeral)位自身)的字符提取出来。比如:

>>> a = 'helloworld'
>>> print a[2:5]
llo

即s的第2,3,4位被取出。

回到对s = ‘MCMLXXII’的处理。

首先map中第一个罗马字符是M,只有一位,就把s 的第0位拿出来对比,发现s的第0位刚刚好是M,于是得到一个1000,index变为1,则之后从s的第一位开始。简单的说,相当于s 变成了s = 'CMLXXII'

接下来,经过一些无效的值以后,轮换到CM,发现CM为两位,就取出s的前两位,也就是CM,发现在s中刚刚好有CM,于是得到900. index再加2,则实际上s就相当于变成了LXXII

继续经过一些无效值以后,轮换到了L,发现s当前的1位为L,于是在map中有对应的值50.然后index加1,s相当于变成了XXII

接下来到了X,发现s当前的1位为X,在map中有对应的值10.然后index 再加1,s变成了XII

虽然这个时候人已经知道是12了,但是计算机还是不知道,于是继续一个X,s变为II

然后出现一个I,s变为I

终于程序找到了一个直接相等的值I,于是转换结束。

所以MCMLXXII对于的阿拉伯数字是1000+900+50+10+10+1+1 = 1972

这个方法,把一个罗马数字从高位开始逐次剥离最高位,从而渐渐的把数字缩小。

最近正在学习算法。因为越来越发现现在做的东西,如果仅仅实现功能的话,性能会出现瓶颈。希望我以后能写出更好的算法。

A totally amazing!!!

源代码请戳->https://github.com/kingname/MCC

实际上使用任何可以发送邮件的东西都可以。但是因为微信比较普及,所以就用微信的发送邮件功能做一个测试吧~~

文件结构

程序由两部分构成:

  • _config.ini为配置文件,用于配置主人邮箱,奴隶邮箱和手工添加需要执行的命令
  • auto.py为程序的主体文件,相关的实现代码均在里面

软件原理

本程序需要使用两个邮箱,我给他们取名字为【主人邮箱】和【奴隶邮箱】。建议奴隶邮箱使用小号。主人邮箱使用大号,我是使用的我的QQ邮箱作为主人邮箱,临时申请的一个新浪邮箱作为奴隶邮箱。目前奴隶邮箱使用新浪邮箱测试通过,其他邮箱未做测试。各位有兴趣的朋友可以测试一下并反馈给我,非常感谢~

本程序使用Python的poplib提供的函数,周期性读取奴隶邮箱最新的一封邮件,如果这封邮件是主人邮箱发送的,并且标题在_config.ini文件中有定义,则执行本标题定义的操作。

例如,_config.ini文件中有如下定义:

music=D:\backup\Music\Intro.mp3

主人邮箱发送一份邮件,标题为music,电脑就会调用默认播放器,播放D盘中的这个名叫Intro.mp3的音乐。如果这个Intro.mp3本身只有1秒钟,且没有内容,而音乐播放器设置为随机播放,就间接地实现了打开播放器随机播放音乐的目的。

目前程序可以实现两类功能:
运行命令与打开文件。

运行命令

其中运行命令的原理是:

1
os.system(command)

理论上任何在CMD命令提示符下可以执行的命令,在这里都可以执行。_config.ini中默认提供了两个样例,一个关闭计算机:

1
shutdown=shutdown -f -s -t 10 -c closing...

另一个是列出当前目录:

1
dir=dir

等号左侧为此命令的名字,也就是在邮件中可以发送的标题内容,等号右侧为命令本身。注意等号左右均不能有空格。

打开文件

打开文件的原理是:

1
win32api.ShellExecute(0, 'open', open_file, '','',1)

其中,open_file为文件在电脑中的位置。函数调用Windows的API来运行程序,效果和用鼠标双击相同。

运行流程

程序运行以后,先加载_config.ini,配置主人邮箱和奴隶邮箱,并确定扫描频率(time_limit)为多少秒检查一次邮箱。同时使用字典将命令的名称和命令本身添加到内存中。接下来的操作如下:

使用主人邮箱发送相应的命令名称以后,就能触发电脑的相关操作。

程序配置

打开_config.ini文件:

  • host填写奴隶邮箱的pop3服务器,例如新浪的pop3服务器为

      pop.sina.com
    
  • username为奴隶邮箱的邮箱号

  • password为奴隶邮箱的密码

  • boss_email为主人邮箱号

  • time_limit控制程序检查邮箱的评论,默认为300秒,也就是5分钟

  • <command>与</command>之间为命令区,此处可以使用任何能在CMD命令提示符中执行的命令格式为:

      名字=命令
    

注意=左右不能出现空格

  • <open_file></open_file>之间为可以打开的文件。任何在电脑上可以使用鼠标双击打开的程序、文件均可把其地址写在此处。格式为:

     名字=地址
    

注意=左右不能出现空格

编译程序

使用py2exe编译。进入代码目测,执行以下代码:

python mysetup.py py2exe

Todo

接下来的版本升级中

  • 会添加更多的操作进去
  • 开发图像界面,使配置更方便
  • 动态调整检查频率
  • 通过邮件的内容返回命令的执行状态
  • 通过邮件内容返回文件列表
  • 解决打开的文件功能在文件名和路径不能有汉字的bug

致谢

感谢知乎用户 @印如意fitz的启发与思路提供。

在Python的正则表达式中,有一个参数为re.S。它表示“.”(不包含外侧双引号,下同)的作用扩展到整个字符串,包括“\n”。看如下代码:

1
2
3
4
5
6
7
8
9
import re
a = '''asdfsafhellopass:
234455
worldafdsf
'''
b = re.findall('hello(.*?)world',a)
c = re.findall('hello(.*?)world',a,re.S)
print 'b is ' , b
print 'c is ' , c

运行结果如下:

1
2
b is  []
c is ['pass:\n\t234455\n\t']

正则表达式中,“.”的作用是匹配除“\n”以外的任何字符,也就是说,它是在一行中进行匹配。这里的“行”是以“\n”进行区分的。a字符串有每行的末尾有一个“\n”,不过它不可见。

如果不使用re.S参数,则只在每一行内进行匹配,如果一行没有,就换下一行重新开始,不会跨行。而使用re.S参数以后,正则表达式会将这个字符串作为一个整体,将“\n”当做一个普通的字符加入到这个字符串中,在整体中进行匹配。

在re.py库的介绍中有以下语句:

“.” Matches any character except a newline.

S DOTALL “.” matches any character at all, including the newline.

这里特别感谢评论中叫做Style的朋友指出了我的错误。

我在本科学的是Electric Engineering,大四找工作却当了软件工程师。这可以说是各种机缘巧合吧。

我接触编程是在初中,那个时候有同学在看《黑客X档案》,当时看到一篇文章说的一个万能密码:

'or''='

有一天在学校的网站论坛上测试了一下,发现真的登录了,而且是管理员的身份。虽然不知道是什么原理,但是觉得有点意思。

后来开始不间断的买《黑客X档案》,接触到了编程,VB,C语言,Perl还有汇编。我发现VB特别好懂,于是安装了一个Visual Basic 6.0开始写小程序。我写的最大的小程序就是一个网页浏览器了,当然是跟着书上写的。这些经历现在想想也挺丰富的,入侵网站,抓取肉鸡。不过这不是今天的重点,以后单独写一篇文章吧。

开始上大学了,第一学期就是C语言。周围无数的传言说C语言难得跟一坨屎一样,挂科一大半。直到现在,这四年间,每一年都能听到无数这样的话语。然而当年的C语言考试,我提前40分钟做完,不小心拿了一个满分。

那个时候用的是谭浩强的C语言,被人吐槽无数的版本。里面有些东西确实脑残:

b = a+++++a;

这样的问题确实没有什么意义,考试的题里面,概念题就能让你及格。剩下的改错题和编程题稍稍考一点水平,然而题目大多也是课程设计做过的。这一门课程,我的课程设计所有程序都是我自己凭脑袋想出来的,即便书上有现成代码的算法,我也是自己根据对算法的理解写出来的。而其他人有些照着书写代码,有些直接copy。这应该就是原因吧。

大学前三年,断断续续的搞了一些php, Java,LaTex 但是都坚持不长时间。这也和我三分钟的热情有关。一次偶然的机会,听到了一个语言,Python,网上搜索了一下,看到很多人说用它写爬虫很方便。那个时候我学到了一个词,爬虫。

今年三月份,Coursera上面开了一门课程,是Python入门。课程持续到5月份,每周课程都会有miniProject,一般来说是做小游戏。在完成每一个小项目的过程中,我发现我越来越喜欢Python。

5月份Python课程结束没多久,我收到教务处的通知,周五要提交创新项目的结题报告。我才反应我过来,我去年申请的一个创新训练项目还没有做。

这个项目是做一个选课辅助软件。当时申请表中,我表示准备用MFC与MSSQL制作这个软件和对应的数据库。可是,MSSQL安装好以后,我的电脑开机要5分钟。而MFC,倒腾了半个月,一点进展都没有,遂从此荒废。

时间只有一个星期,现学MFC是来不及了。这个时候我想到我是会Python的人,于是果断决定使用Python + MySQL来开发这个选课辅助软件。

一天半以后,软件连同图形界面全部做好了。那个时候,我觉得Python必定是神派来拯救我的天使。

后来辅导员做微信公众号,我提议我可以做一个爬虫,把教务处的通知扒取下来。那天以后,我正式开始做爬虫。

一开始是用Python的一个库urllib2的一个方法获取一个网页的全部代码,然互写入本地保存。这就是一个最简单的爬虫了。

接下来接触了正则表达式。于是一个真正意义的爬虫诞生了。那个时候我室友过生日,我给他写了一个扒取儿(cheng)童(ren)网站里面卖肉漫画的爬虫。他高兴得不得了。关于这个爬虫,有机会我应该会放出来吧。不过担心被查水表。

然后我觉得我有点了不起了,于是去淘宝开了一个店铺,专业定做Python爬虫。一个月都没有生意,直到有一天来了一个买家。他让我做百度贴吧的爬虫,于是我写了一个原始爬虫的demo给他,然后正式接手这个工作。他让他的一个下属教我Scrapy,这个爬虫框架让我的生产效率提升了一个数量级。接下来扒取各大视频网站,又指导我使用Scrapy配合Redis制作分布式爬虫,然后把数据存入MongoDB中,这样我的生产效率再一次提升了几个数量级。

这样我赚取到了给自己买iPad的钱。这也奠定了我学习软件方面技术的基本方法,项目驱动,现学现用。在后面接到的项目中,无论是爬虫的模拟登陆,打码,多代理,甚至是自然语言处理,多次证实了我这个方法的正确性。

从大学入学开始,我就决定要出国。可是最后TOEFL只差2分让我与资本主义国家失之交臂。一气之下决定考研。

大三下期和整个暑假都在准备考研,但是考研课程老师那种卖狗皮膏药十全大补丸的样子确实让我厌烦,考研数学题做起来也甚是恶心。后来因为一些私事,我放弃了考研,中秋节过后开始准备找工作。

那个时候,BAT和华为的提前批招聘都结束了。而我想都不敢想华为,因为印象中,华为是招硬件的,而且基本上都是研究生。我是学E.E的,但是我的项目经历只有学校的各种实验课做的东西。于是我抱着试一试的心态去应聘华为的软件工程师。

我不得不感谢Python,他让我几乎是毫无阻力的通过了大多数公司的面试,我的所有拿得出手的项目经历,全部都有Python的身影。最后我去了MTK而拒了华为,毕竟华为太累了。在两个月之前,我都不敢想我竟然有机会拒绝华为。

这样我就成了一个程序员。本科学的东西我想我还是有点用的,作为一个终身爱好也不错。毕竟搞硬件的要表白,场面炫目到秒杀码农几条街。

如果没有Python,我想我现在不知道在干嘛。他让我的人生从此不同。

Coney是一个非常漂亮的Hexo主题,作者的博客请戳->http://gengbiao.me/ 本博客的主题是基于Coney修改而成。

我个人比较喜欢侧边栏在左边,于是我希望能将右侧的侧边栏整体移动到左边去。大家对比我的博客首页和主题作者的博客首页就可以看出不同。

在修改之前,需要解释一下div标签。不讲定义,只举例子。大家注意这篇文章的配图。设想有这样一个场景,你要搬家,从成都搬到北京。你可以一件一件的把家里的东西搬出来,然后运到北京,再放进新的家。也可以,把你的整个房子连根撬起来,用超人或者热气球直接运送到北京去。这么做的好处是,你只需要搬房子,而房子里面的东西完全不受影响。div标签就可是实现这样的功能。

使用Chrome的审查元素功能,可以发现这个主题首页的正文部分的布局是这样的:

其中A,B分别是控制侧边栏开启和关闭的开关,A是openaside, B是closeaside,在侧边栏开启的时候,A是隐藏的。

当然实际的布局里还有其他div,但是当我只需要知道你有一个房子,房子里有几间房间的时候,我是没有必要去关心你卫生间里是马桶还是痰盂的。

那么现在目标就是,把asidepart,openaside这两个东西移动到左边,把main移动到右边。我这里没有说closeaside,不是笔误,而是因为closeaside是在asidepart里面的,因此移动asidepart的时候,closeaside跟着就移动了。

打开themes\coney\source\css_partial文件夹,发现有一个aside.styl文件,根据名字猜测,这个东西应该就是关于侧边栏的布局了。

打开aside.styl文件,发现这其实就是一个css文件。首先看到前面几段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//button
.openaside
display none
position fixed
right 7.5%
top 260px
a
display block
color color-white
border 1px solid color-white
border-radius 5px
background color-theme
padding 0.2em 0.55em
&::before
font-family font-icon-family
font-smoothing()
content "\f0c9"

.closeaside
display none
a
color color-theme
&:hover
color color-blue
&::before
font-family font-icon-family
font-smoothing()
content "\f0c9"
@media tablet
display block
position absolute
right 25px
top 22px

//sidebar
#asidepart
background color-section
margin 1em 0 0
padding 0.5em 2% 1em
@media tablet
position relative
float left
width 18%
margin 2em 0 0 3%

Coney作者的命名方式非常好,于是我们很容易就看到了需要的内容。
在.openaside下面,看到一个

1
right 7.5%

当我们隐藏了侧边栏以后,看到的小方块,就是箭头指向的这个

如果我把这个right改成left会有什么效果呢?不妨一试

看起来有点效果了。那么继续看下面的.closeaside

1
right 25px

尝试把right修改为left,对比一下前后效果:

修改前:

修改后:

这个小图标在侧边栏里面的相对位置改变了,这就像是你从家里的卧室走到了厨房。你并不关心房子外面是不是奥特曼在打小怪兽。你只是换了一个房间。

再往下走,发现#asidepart下面有个

1
2
position relative
float left

我想把侧边栏从右边移动到左边,那这里应该有个right才对,为什么它竟然是left?而且position的意思是位置,relative是相对,也就是说是相对位置?很奇怪,先不动它。

再来找找main在哪里。在themes\coney\source\css_partial下面的index.styl里发现了它。

1
2
3
4
5
6
7
8
9
#main
margin 1em 0 0
line-height line-height+0.3
@media tablet
margin 2em 0 0
width 75%
position relative
float left
transition margin 0.5s ease-out

这里发现了一个left,既然我想把main从左边移动到右边,那就尝试把这个left改成right,然后测试一下效果。

哈哈,成了!

等等,别高兴的太早,点一下关闭侧边栏的按钮试一试。

是不是感觉怪怪的?对比一下作者的博客效果

隐藏侧边栏以后,文章列表应该向右移动,那我把侧边栏移动到左边并隐藏以后,文章列表应该向左移动才对啊!可是它为什么死在那里了?

经过询问作者,我得知这个文章列表的移动的相关javascript代码在after_footer.ejs文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var m = $('#main'),
a = $('#asidepart'),
c = $('.closeaside'),
o = $('.openaside');
...........

c.click(function(){
a.addClass('fadeOut').css('display', 'none');
o.css('display', 'block').addClass('fadeIn');
m.addClass('moveMain');
});
o.click(function(){
o.css('display', 'none').removeClass('beforeFadeIn');
a.css('display', 'block').removeClass('fadeOut').addClass('fadeIn');
m.removeClass('moveMain');
});

虽然我不会javascript,但是根据这段代码,猜一下意思:

当关闭侧边栏(closeaside)按钮被点击,就添加一个叫做moveMain的东西;
当打开侧边栏(openaside)的按钮被点击,就移除这个叫做moveMain的东西。

觉得moveMain很可疑,而且move移动,Main就是文章列表的div的名字,这个解释天衣无缝啊。

我在themes\coney\source\css_partial文件夹下面的index.styl中找到了这个moveMain

1
2
.moveMain
margin-left 10% !important

网上搜索了一下,发现margin-left的意思是:

margin-left 属性设置元素的左外边距。

就是距离左侧10%的距离,假设原来的距离小于10%,当我点了关闭侧边栏以后,为了让main距离左侧的距离达到10%,不就正好的是向右移动吗?

既然这个解释天衣无缝,那就把left改成right试一试效果。

这样看起来似乎已经达到效果了,但是感觉有些地方不太和谐。

侧边栏距离文章列表太近了。

这个时候就要用到Chrome的强大功能了,启动审查元素功能,定位到侧边栏

注意右侧

1
margin 2em 0 0 3%

margin是边界的意思,那么右边四个数据应该就是控制侧边栏上下左右距离的了。尝试修改,发现吧3%改成0的时候,出现了我希望的效果

这个数据是在aside.styl中,修改以后生成页面,完美实现了需求的功能。你在首页或者这个页面都可以看到最终效果。

需要说明的是,虽然我写了这么长一篇,但是我实际上是没有CSS基础的,都是自己摸索,所以你也可以做出自己的主题。

既然选择了Hexo,也就选择了折腾。

生命不息,折腾不止。

Coney是一个非常优秀的Hexo主题,作者的网站http://gengbiao.me/然而在使用的过程中,我发现作者有一些地方没有说清。因此在这里写一些我使用中发现的问题和解决方法。

标签

作者并未说明多个标签之间应该以何种方式分隔,我尝试使用逗号,空格以及方括号均不能正确分隔标签。后来尝试使用一种笨的方法:

1
2
3
tags: 标签一
tags: 标签二
tags:标签三

还有一种聪明的办法,就是:

1
tags: [标签一,标签二,标签三]

标题

如图,标题显示异常


出现这种问题,是由于title中加了双引号,去掉双引号以后正常。但是不知道这个双引号是怎么加上去的。

页面

模板只有两个页面,如果想添加更多页面,就像这个博客上面一样,可以
在博客根目录下面使用git bash输入:

1
hexo n page About

这条命令新建了一个“关于”页面,在source下会生成一个About文件夹,文件夹里面有一个index.md 通过编辑index.md就可以编写“关于”页面。如图:

接下来要如何将这个页面添加到博客上面呢?

可以修改themes/coney/_config.yml

1
2
3
4
5
6
7
   ##### Menu
menu:
首页: /
归档: /archives
分类: /categories
标签: /tags
关于: /About

如图是我修改以后的效果:

最后实际显示效果如下:

空格

也许是Hexo优化方面的问题,空格在文件修改的过程中非常重要。还是以上面的添加页面为例:

1
2
3
4
5
6
7
   ##### Menu
menu:
首页: /
归档: /archives
分类: /categories
标签: /tags
关于:/About #这里少了一个空格

如果写成这样,在使用

1
hexo g

生成静态文件的过程中,就会出错,如下图:

因此,在修改文件的过程中,一定不能漏掉空格。

评论

Coney使用多说的评论系统。主题作者对多说系统提供的官方代码做了一些修改,从而导致了一个隐藏的bug。

在themes\coney\layout_partial下的after_footer.ejs文件的112行代码:

1
var duoshuoQuery = {short_name:"<%= theme.duoshuo.short_name %>"};

主题作者的本意是通过theme.duoshuo.short_name 调用 _config.yml中的short_name。作者的名字是英文,因此不会出问题,但是当short_name的值为中文时,会导致整个评论框消失。

然而,coney作者告诉我,short_name仅仅是一个标记,在评论中并不会显示出来,因此只要随便写个英文上去就好了。

在使用Python做爬虫的过程中,经常遇到字符编码出问题的情况。

1
UnicodeEncodeError: 'ascii' codec can't encode character u'\u6211' in position 0: ordinal not in range(128)

针对这种情况,网上已经有很多原理性的分析了,我在此就不一一列举。然而,我相信很多人,即便看完原理以后也不知道怎么解决。

我自己琢磨出一种快速解决的方法:

1
2
3
4
5
6
def get_page_sourse(url):
req = urllib2.Request(url)
for key in headers:
req.add_header(key,headers[key])
content = urllib2.urlopen(req).read()
return content

这个函数使用urllib2扒取了一个网页的源代码。并返回了这个源代码。当我们使用文件操作准备把content的内容写到一个txt文件中时,就会出现编码错误。

在这种情况下,如何快速的排除问题?

只需要将最后一行改成下面这样就可以了。

1
return content.decode('A','ingore').encode('B','ignore')

这里,A和B分别可以使gbk, gb2312,utf-8,它们排列组合一下,一共只有6种组合方式

例如:

1
2
3
return content.decode('gbk','ingore').encode('utf-8','ignore')
return content.decode('utf-8','ingore').encode('gb2312','ignore')
...

我使用这种方式,解决了所有的我遇到的Python的编码错误。虽然是一个一个的试,但是也用不了2分钟就能搞定。

至于背后原理什么的,程序跑通了以后再慢慢研究吧。

0%