---
# System prepended metadata

title: 一个可以发微博的多功能脚本

---

一个可以发微博的多功能脚本
===

更新于2018年6月23日20:32:59，添加了一些新功能，详情：

https://github.com/786662216/weibo

注意发图片是这样的，传进去以二进制的方式打开的图片文件对象即可。常用的jpg，gif，png都支持，大小小于5mb

    pic = open('./pic/%s' % picture, 'rb')
    client.statuses.share.post(pic=pic)
    pic.close()

    当然也可以：
    with open('./pic/%s' % picture,'rb') as pic:
        client.statuses.share.post(pic=pic)

以下为原文

最近一直在学python，想练个手，于是弄了个发微博的脚本。新浪微博的API更新了也不把旧的删掉，新的还没有给代码示例 ，官方微博两天才回复，不爽

东西比较杂，比较重点的有

1. oAuth2.0协议
1. python的datetime、random模块
1. Linux的进程问题

其他的东西比较杂，到时候会提到

# 0x00 oAuth2.0

一个很常见的情景：如果一个用户需要两项服务：一项服务是图片在线存储服务A，另一个是图片在线打印服务B。服务A在甲网站上，服务B在乙网站上。这时候有两种传统解决方案：法一：用户可能先将待打印的图片从服务A上下载下来并上传到服务B上打印，这种方式安全但处理比较繁琐，效率低下；法二：用户将在服务A上注册的用户名与密码提供给服务B，服务B使用用户的帐号再去服务A处下载待打印的图片，这种方式效率是提高了，但是安全性大大降低了。oAuth协议就是为了解决类似问题的而诞生的。

新浪微博就是你的家。偶尔你会想让一些人（第三方应用）去你的家里帮你做一些事，或取点东西。你可以复制一把钥匙（用户名和密码）给他们，但这里有三个问题：

* 别人拿了钥匙后可以去所有的房间
* 别人拿到你的钥匙后也许会不小心丢到，甚至故意送到它人手里。这样你都不知到谁有你家用钥匙
* 过一段时间你也许会想要回自己的钥匙，但别人不还怎么办？

**OAuth 是高级钥匙:**

* 你可以配置不同权限的钥匙。有些只能进大厅（读取你的微博流）。有些钥匙可以进储藏柜（读取你的相片)
* 钥匙上带着指纹验证的（指纹 = appkey)。 收到钥匙的人只能自己用，不能转让
* 你可以远程废除之前发出的钥匙
* 相对来说, OAuth比给出用户名密码安全

链接：https://www.zhihu.com/question/19781476/answer/13158282

OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。微博开放接口的调用，如发微博、关注等，都是需要获取用户身份认证的。不仅仅是新浪微博，主流互联网产品的API授权都是用的oauth协议。


在微博里：client第三方应用客户端或服务器（其实是自己开发的，后面会说），用户就是用户（还是我自己），第二方应用服务器指的是微博的授权服务器，要请求资源所在的服务器就是微博API服务器

接下来我们细说一下其过程：

* 首先A网站要在B网站上注册一个应用。在这个应用创立好之后就已经规定A网站只能获得或者访问B网站中用户的那些功能和信息，同时B网站给了A网站两个字符串：Key、secret作为凭证

* 当用户在A网站上需要用到B网站的功能时，A网站会吧key给用户，用户带着key到了B网站的认证网站或应用，B网站会询问用户是否同意并在网页或者手机应用上显示出A网站的权限，比如说只能访问照片。用户如果不同意，那么结束授权过程；如果同意，则B网站会返回一个code，来证明用户已经同意，并且会记录下这次认证。

* 这时候A网站拿到code后，A网站把key、secret、code一起发送给B网站，换取最后的通行证access_token，之后A网站就可以拿着access_token去访问B网站中它需要的用户信息和服务了。当然，access_token是有时间限制的，之前AB网站也已经约定好了只能访问哪些信息，而且用户可以随时废掉这个access_token。这个这里secret的主要目的是code可以被任何人持有，用secret来证明我是A网站。

（当然也有一些弱点，比如这里存在着一个容易被CRSF的弱点，以后总结CRSF时再提）

# 0x01微博API调用

我们是通过第三方应用调用API来对自己的微博进行操作，所以认证过程和上述的一样，只不过这个第三方应用和微博账号都是我们自己的。

1、首先申请新浪微博的第三方应用（微博开放平台里申请），并且在应用信息的测试信息里把自己的账号关联到测试账号里。同样的，如果你有多个账号想发送微博或者进行其他操作的话，也可以馆两道测试账号中去，最多十五个。再多就要审核应用了，很麻烦。

2、申请完第三方应用后他会给出APP_KEY和APP_SECTET两个字符串，把他们保存好。在应用信息的高级信息中编辑OAuth2.0的授权设置，更改授权回调页为https://api.weibo.com/oauth2/default.html，其他的我没试过，理论上应该都可以。主要目的就是拿到code。还要设置安全域名，安全域名在应用信息->基本信息->应用基本信息里编辑，发送的微博里一定要带这个域名下的一个URL,或者就是设置的URL本身也行，记得加上协议http(s)

3、安装python的微博SDK：sinaweibopy，由廖雪峰大神提供。直接pip install sinaweibopy就可以，直接从网站上下载也可以。

4.获取code：

    from weibo import APIClient

    APP_KEY = 'XXXXXXXXX'  # app key
    APP_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'  # app secret
    CALLBACK_URL = 'https://api.weibo.com/oauth2/default.html'  # callback url

    client = APIClient(app_key=APP_KEY, app_secret=APP_SECRET, redirect_uri=CALLBACK_URL)
    print client.get_anthorize_url()


访问打印出的URL，会重定向到会跳地址，地址栏后会有code参数，把code的值记下来

5、获取access_token：

    r = client.request_access_token(code) #注意加引号，这是字符串
    access_token = r.access_token 
    expires_in = r.expires_in #这个是有效期，单位是秒。有效期大概为3、4年
    client.set_access_token(access_token, expires_in) #绑定access_token


6、直接使用API发送微博：

    


当然有很多API，都是通过client来调用的

# 0x02 python的datetime、random模块

现在可以使用API来对我的微博账号进行一系列的操作了，我想实现的是每天半夜三点发一条微博，而且每天微博的内容不能重复，这就要用到python关于时间和随机数的库了。

**datetime模块**

time类在模块中，而且和time模块有几乎相同的功能，所以没有用到time模块，暂且不提。

date类
静态方法和字段

    date.max、date.min：date对象所能表示的最大、最小日期；
    date.resolution：date对象表示日期的最小单位。这里是天。
    date.today()：返回一个表示当前本地日期的date对象；
    date.fromtimestamp(timestamp)：根据给定的时间戮，返回一个date对象；


**方法和属性**

    datetime.date(year, month, day)
    d1 = date(2011,06,03)#date对象
    d1.year、date.month、date.day：年、月、日；
    d1.replace(year, month, day)：生成一个新的日期对象，用参数指定的年，月，日代替原有对象中的属性。（原有对象仍保持不变）
    d1.timetuple()：返回日期对应的time.struct_time对象；
    d1.weekday()：返回weekday，如果是星期一，返回0；如果是星期2，返回1，以此类推；
    d1.isoweekday()：返回weekday，如果是星期一，返回1；如果是星期2，返回2，以此类推；
    d1.isocalendar()：返回格式如(year，month，day)的元组；
    d1.isoformat()：返回格式如'YYYY-MM-DD’的字符串；
    d1.strftime(fmt)：和time模块format相同。


**datetime类**

datetime相当于date和time结合起来。
datetime.datetime (year, month, day[ , hour[ , minute[ , second[ , microsecond[ , tzinfo] ] ] ] ] )


**静态方法和字段**

    datetime.today()：返回一个表示当前本地时间的datetime对象；
    datetime.now([tz])：返回一个表示当前本地时间的datetime对象，如果提供了参数tz，则获取tz参数所指时区的本地时间；
    datetime.utcnow()：返回一个当前utc时间的datetime对象；#格林威治时间
    datetime.fromtimestamp(timestamp[, tz])：根据时间戮创建一个datetime对象，参数tz指定时区信息；
    datetime.utcfromtimestamp(timestamp)：根据时间戮创建一个datetime对象；
    datetime.combine(date, time)：根据date和time，创建一个datetime对象；
    datetime.strptime(date_string, format)：将格式字符串转换为datetime对象；


**方法和属性**

    dt=datetime.now()#datetime对象
    dt.year、month、day、hour、minute、second、microsecond、tzinfo：
    dt.date()：获取date对象；
    dt.time()：获取time对象；
    dt. replace ([ year[ , month[ , day[ , hour[ , minute[ , second[ , microsecond[ , tzinfo] ] ] ] ] ] ] ])：
    dt. timetuple ()
    dt. utctimetuple ()
    dt. toordinal ()
    dt. weekday ()
    dt. isocalendar ()
    dt. isoformat ([ sep] )
    dt. ctime ()：返回一个日期时间的C格式字符串，等效于time.ctime(time.mktime(dt.timetuple()))；
    dt. strftime (format)

**timedelta类，时间加减**

    使用timedelta可以很方便的在日期上做天days，小时hour，分钟，秒，毫秒，微妙的时间计算，如果要计算月份则需要另外的办法。
    dt1 = dt + timedelta(days=-1)#昨天
    dt2 = dt - timedelta(days=1)#昨天
    dt3 = dt + timedelta(days=1)#明天
    delta_obj = dt3-dt
    print type(delta_obj),delta_obj#<type 'datetime.timedelta'> 1 day, 0:00:00
    print delta_obj.days ,delta_obj.total_seconds()#1 86400.0

**random模块**
    random.uniform()用于生成一个指定范围内的随机浮点数，两个参数其中一个是上限，一个是下限。上下限没有顺序 
    random.randint(a,b)用于生成一个指定范围内的整数。其中参数a是下限，参数b是上限。上下限是有顺序的，反了会报错 
    random.choice()参数是个元素全为字符串的列表，也可以是一个字符串。如果传入一个列表，从其中随机选取一个元素并返回；如果传入了一个字符串，则随机返回一个字符 
    random.sample()选取特定数量的字符；两个参数，第一个是个元素全为字符的列表，第二个参数是要选取的个数 
    random.shuffle()洗牌函数。顾名思义，接受一个列表，将其元素的顺序打乱并返回这个新列表

# 0x03 源代码

在这里我把各个文件各个函数拆开直接合在一起了，亲测可用，注释也写的还算明白（不

coding:utf-8
#注意每个微博API调用成功后会返回一个很长的列表，具体参数含义参考微博开放平台

    from weibo import APIClient
    import time #可忽略
    import datetime
    import random

    APP_KEY = 'XXXXXXXX'  # app key
    APP_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'  # app secret
    CALLBACK_URL = 'https://api.weibo.com/oauth2/default.html'  # callback url

    access_token = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    expires_in = 1660225135
    client = APIClient(app_key=APP_KEY, app_secret=APP_SECRET, redirect_uri=CALLBACK_URL)
    client.set_access_token(access_token, expires_in)

    schedtime = datetime.datetime(2017,8,15,3,3,3)  # 要执行的时间
    Frequency = datetime.timedelta(days=1)  # 频率，这个函数的参数有days,months,seconds,years,minutes都是字面意思,也可以为负

    while True:
        time.sleep(1)  # 循环执行的速度太快，虽然只有一秒但也会执行很多次,所以要等1s

    TimeSecound = time.time() #这个确实没啥用
    date = time.localtime(TimeSecound)  # 现在的具体时间
    DateMday = date[2]
    DateHour = date[3]

    str = u'''咚~咚~咚~我是老佘百分百的机器人%s,现在是%d年%d月%d日%d时%d分%d秒,
    现在我只有报时功能哦~  http://www.google.com 404喽%s''' % (random.choice(
        [u'(๑•̀ㅂ•́)و✧', u'ヾ(≧▽≦*)o ', u'o(*≧▽≦)ツ ', u'(o゜▽゜)o☆', u'<(￣︶￣)>', u'o(*￣▽￣*)o ', u'(｡･∀･)ﾉﾞ', u'ヾ(≧∇≦*)ゝ',
         u'Hi~ o(*￣▽￣*)ブ ', u'(≧∀≦)ゞ', u'ε(*´･∀･｀)зﾞ', u'(～￣▽￣)～ ', u'︿(￣︶￣)︿', u'(/≧▽≦)/', u'(ﾉ*･ω･)ﾉ',
         u'o(〃\'▽\'〃)o ', u'o(￣▽￣)ｄ ', u'o(^▽^)o']), date[0], date[1], DateMday, DateHour, date[4], date[5],
                                                    random.choice([u'w(ﾟДﾟ)w', u'Ｏ(≧口≦)Ｏ', u'Σ(｀д′*ノ)ノ', u'ヽ(*。>Д<)o゜', u'┻━┻︵╰(‵□′)╯︵┻━┻', u'φ(-ω-*)', u'(σ｀д′)σ', u'(#`O′)', u'( >ρ < ”)', u'o(一︿一+)o', u'(#`O′) ', u'(＞﹏＜)', u'(；′⌒`) ', u'（；´д｀）ゞ', u'Σ( ° △ °|||)︴', u'(lll￢ω￢)', u'…（⊙＿⊙；）…', u'_〆(´Д｀ ) ', u'Σ(っ °Д °;)っ', u'(・-・*)', u'(°ー°〃) ', u'(((φ(◎ロ◎;)φ)))', u'o((⊙﹏⊙))o.', u'ヽ(*。>Д<)o゜ ', ]))
    now = datetime.datetime.now()  # 返回值里面有微秒可把我坑惨了
    if now.date() == schedtime.date():  # 先判断日期相不相同
        if (now.hour == schedtime.hour) and (now.minute == schedtime.minute) and (now.second == schedtime.second):  # 在判断时间相不相同，主要是为了避开微秒的比较
            client.statuses.share.post(status=str)  # 这是有返回值的
            schedtime = schedtime + Frequency
            print now, u"sent successfully"
        else:
            pass
    else:
        pass

# 0x04 Linux下执行

现在只要送到阿里云里运行就可以了

python SendWeiboAt3 >weibo.log &

最后&符号就是表示后台运行，运行输出的结果存在weibo.log文件里。但是不要直接关闭ssh客户端，要用exit退出。如果直接关闭ssh客户端则linux会关闭掉所有bash进程

现在的问题是我如何在Linux下运行多个python进程。再直接用python解释器直接运行的话上一个运行的脚本就会关闭换成这个新的脚本，能从PID号看出不是一个进程了。目前来说也用不到，以后再说吧

￥ 0x05 总结

解决时间比较长的问题：
1、API更新我不知道
2、新API发送的格式里要带URL，而且是必须是自己设置的安全域名下的URL3、datetime.datetime.now()还会返回微秒：datetime.datetime(2017, 8, 26, 23, 54, 43, 950000)最后那个参数，我设置的执行时间datetime.datetime(2017,8,15,3,3,3)里没有微秒，if比较的时候结果永远是False。即使加上微秒也没用，python的时间观念精确不到一秒的10^-6（用while尝试一下，发现微秒没那么精确，和操作系统本身处理速度有关），解决方法看代码
4、在Ubuntu下运行时直接退出xshell了，导致第一天没发送成功