一个可以发微博的多功能脚本 === 更新于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( ̄▽ ̄)d ', u'o(^▽^)o']), date[0], date[1], DateMday, DateHour, date[4], date[5], random.choice([u'w(゚Д゚)w', u'O(≧口≦)O', 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了,导致第一天没发送成功