|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +# @Author: LoveNight |
| 4 | +# @Date: 2015-11-23 17:56:34 |
| 5 | +# @Last Modified by: LoveNight |
| 6 | +# @Last Modified time: 2015-11-25 13:23:07 |
| 7 | + |
| 8 | +import configparser |
| 9 | +import sys |
| 10 | +import os |
| 11 | +import requests |
| 12 | +import json |
| 13 | +import re |
| 14 | +import base64 |
| 15 | +from datetime import datetime |
| 16 | +import time |
| 17 | + |
| 18 | + |
| 19 | +class WeiboUtil(object): |
| 20 | + |
| 21 | + """新浪微博脚本 |
| 22 | +
|
| 23 | + 需要把App Key和Access Token写入同目录下的config.ini。格式如下: |
| 24 | + [Account] |
| 25 | + appKey = XXXXXXXXXXX |
| 26 | + accessToken = XXXXXXXXXXXXXXX |
| 27 | + cookie = 没有可以不填 |
| 28 | +
|
| 29 | + 其中: |
| 30 | + app_key:登录后在「我的应用」里可以找到 |
| 31 | + Access Token 登录后访问:http://open.weibo.com/tools/console?uri=statuses/update&httpmethod=POST&key1=status&value1=%E5%BE%AE%E5%8D%9A%E6%9D%A5%E8%87%AAAPI%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7 |
| 32 | + """ |
| 33 | + configFile = "config.ini" |
| 34 | + headers = { |
| 35 | + "accept-encoding": "gzip, deflate, sdch", |
| 36 | + "Upgrade-Insecure-Requests": "1", |
| 37 | + "user-agent": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36", |
| 38 | + } |
| 39 | + postData = { |
| 40 | + "entry": "sso", |
| 41 | + "gateway": "1", |
| 42 | + "from": "null", |
| 43 | + "savestate": "30", |
| 44 | + "useticket": "0", |
| 45 | + "pagerefer": "", |
| 46 | + "vsnf": "1", |
| 47 | + "su": "base64编码后的用户名", |
| 48 | + "service": "sso", |
| 49 | + "sp": "密码明文", |
| 50 | + "sr": "1440*900", |
| 51 | + "encoding": "UTF-8", |
| 52 | + "cdult": "3", |
| 53 | + "domain": "sina.com.cn", |
| 54 | + "prelt": "0", |
| 55 | + "returntype": "TEXT", |
| 56 | + } |
| 57 | + |
| 58 | + # GET 请求 |
| 59 | + loginURL = r'https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.15)' |
| 60 | + timeLineURL = r'https://api.weibo.com/2/statuses/friends_timeline.json?&since_id={since_id}&max_id={max_id}&count={count}&page={page}&feature={feature}' |
| 61 | + friendsURL = r'https://api.weibo.com/2/friendships/friends.json?&uid={uid}&count={count}&cursor={cursor}&trim_status={trim_status}' |
| 62 | + friendsIdsUrl = r'https://api.weibo.com/2/friendships/friends/ids.json?&uid={uid}&count={count}&cursor={cursor}' |
| 63 | + currentUserUidUrl = r'https://api.weibo.com/2/account/get_uid.json?' |
| 64 | + bilateralTimelineURL = r'https://api.weibo.com/2/statuses/bilateral_timeline.json?&since_id={since_id}&max_id={max_id}&count={count}&page={page}&feature={feature}' |
| 65 | + |
| 66 | + # POST请求 |
| 67 | + updateURL = r'https://api.weibo.com/2/statuses/update.json' |
| 68 | + uploadURL = r'https://upload.api.weibo.com/2/statuses/upload.json' |
| 69 | + commentURL = r'https://api.weibo.com/2/comments/create.json' |
| 70 | + |
| 71 | + def __init__(self, auth = "access_token"): |
| 72 | + config = configparser.ConfigParser() |
| 73 | + config.read(os.path.join(sys.path[0], WeiboUtil.configFile)) |
| 74 | + self.app_key = config.get("Account", "appKey") |
| 75 | + self.access_token = config.get("Account", "accessToken") |
| 76 | + self.cookie = config.get("Account", "cookie") |
| 77 | + self.auth = "&access_token=" + self.access_token |
| 78 | + self.timeline_since_id = 0 # 标识微博TimeLine读取到了哪一条 |
| 79 | + self.bilateral_timeline_since_id = 0 |
| 80 | + self.session = requests.Session() |
| 81 | + if self.cookie: |
| 82 | + WeiboUtil.headers["cookie"] = cookie |
| 83 | + self.session.headers = WeiboUtil.headers |
| 84 | + |
| 85 | + def login(self, username, password): |
| 86 | + """登录微博""" |
| 87 | + self.auth = "&source=" + self.app_key |
| 88 | + self.username = username |
| 89 | + self.password = password |
| 90 | + self.username = base64.b64encode( |
| 91 | + self.username.encode('utf-8')).decode('utf-8') |
| 92 | + WeiboUtil.postData["su"] = self.username |
| 93 | + WeiboUtil.postData["sp"] = self.password |
| 94 | + res = self.session.post(WeiboUtil.loginURL, data=WeiboUtil.postData) |
| 95 | + jsonStr = res.content.decode('gbk') |
| 96 | + info = json.loads(jsonStr) |
| 97 | + if info["retcode"] == "0": |
| 98 | + print("登录成功") |
| 99 | + # 把cookies添加到headers中,必须写这一步,否则后面调用API失败 |
| 100 | + cookies = self.session.cookies.get_dict() |
| 101 | + cookies = [key + "=" + value for key, value in cookies.items()] |
| 102 | + cookies = "; ".join(cookies) |
| 103 | + self.session.headers["cookie"] = cookies |
| 104 | + else: |
| 105 | + print("登录失败,原因: %s" % info["reason"]) |
| 106 | + |
| 107 | + # 一般用不着 |
| 108 | + def _getCurrentUID(self): |
| 109 | + """获取当前登录用户的UID |
| 110 | +
|
| 111 | + UID API:http://open.weibo.com/wiki/2/account/get_uid |
| 112 | + """ |
| 113 | + url = WeiboUtil.currentUserUidUrl.format(access_token=self.access_token) + self.auth |
| 114 | + jsonStr = self.session.get(url).text |
| 115 | + data = self._getJson(url) |
| 116 | + self.uid = data["uid"] |
| 117 | + |
| 118 | + def _getJson(self, url, data=None, files = None, post=False): |
| 119 | + """发送HTTP请求并将返回的JSON字符串格式化""" |
| 120 | + if post: |
| 121 | + jsonStr = self.session.post(url, data=data, files=files).text |
| 122 | + else: |
| 123 | + jsonStr = self.session.get(url).text |
| 124 | + return json.loads(jsonStr) |
| 125 | + |
| 126 | + def update(self, text): |
| 127 | + """发微博,pic为图片网址或本地绝对路径 |
| 128 | +
|
| 129 | + update API: http://open.weibo.com/wiki/2/statuses/update |
| 130 | + upload API: http://open.weibo.com/wiki/2/statuses/upload |
| 131 | + source string 采用OAuth授权方式不需要此参数,其他授权方式为必填参数,数值为应用的AppKey。 |
| 132 | + access_token string 采用OAuth授权方式为必填参数,其他授权方式不需要此参数,OAuth授权后获得。 |
| 133 | + status string 必填。要发布的微博文本内容,必须做URLencode,内容不超过140个汉字。 |
| 134 | + visible int 微博的可见性,0:所有人能看,1:仅自己可见,2:密友可见,3:指定分组可见,默认为0。 |
| 135 | + list_id string 微博的保护投递指定分组ID,只有当visible参数为3时生效且必选。 |
| 136 | + lat float 纬度,有效范围:-90.0到+90.0,+表示北纬,默认为0.0。 |
| 137 | + long float 经度,有效范围:-180.0到+180.0,+表示东经,默认为0.0。 |
| 138 | + annotations string 元数据,主要是为了方便第三方应用记录一些适合于自己使用的信息,每条微博可以包含一个或者多个元数据,必须以json字串的形式提交,字串长度不超过512个字符,具体内容可以自定。 |
| 139 | + rip string 开发者上报的操作用户真实IP,形如:211.156.0.1。 |
| 140 | +
|
| 141 | + upload API 多一个字段:pic binary 要上传的图片,仅支持JPEG、GIF、PNG格式,图片大小小于5M。 |
| 142 | + """ |
| 143 | + postData = { |
| 144 | + "access_token":self.access_token, |
| 145 | + "status":text, |
| 146 | + } |
| 147 | + url = WeiboUtil.updateURL |
| 148 | + data = self._getJson(url, data=postData, post = True) |
| 149 | + status = Status() |
| 150 | + status.fromDict(data) |
| 151 | + return status |
| 152 | + |
| 153 | + |
| 154 | + def comment(self, text, weiboID, comment_ori=0): |
| 155 | + """评论微博,每小时只能发十五条 |
| 156 | +
|
| 157 | + comment API : http://open.weibo.com/wiki/2/comments/create |
| 158 | + comment string 必填。评论内容,必须做URLencode,内容不超过140个汉字。 |
| 159 | + id int64 必填需要评论的微博ID。 |
| 160 | + comment_ori int 当评论转发微博时,是否评论给原微博,0:否、1:是,默认为0。 |
| 161 | + rip string 开发者上报的操作用户真实IP,形如:211.156.0.1。 |
| 162 | + """ |
| 163 | + postData = { |
| 164 | + "access_token":self.access_token, |
| 165 | + "comment":text, |
| 166 | + "id":weiboID |
| 167 | + } |
| 168 | + data = self._getJson(WeiboUtil.commentURL, data=postData, post=True) |
| 169 | + print(data) |
| 170 | + print("="*50) |
| 171 | + # comment = Comment() |
| 172 | + # comment.fromDict(data) |
| 173 | + # return comment |
| 174 | + |
| 175 | + |
| 176 | + def getTimeline(self, max_id=0, count=100, page=1, feature=0): |
| 177 | + """读取时间线上的微博 |
| 178 | +
|
| 179 | + TimeLine API:http://open.weibo.com/wiki/2/statuses/friends_timeline |
| 180 | + since_id 则返回ID比since_id大的微博(即比since_id时间晚的微博),默认为0。 |
| 181 | + max_id 则返回ID小于或等于max_id的微博,默认为0。 |
| 182 | + count 单页返回的记录条数,最大不超过100,默认为20。 |
| 183 | + page 返回结果的页码,默认为1。 |
| 184 | + feature 过滤类型ID,0:全部、1:原创、2:图片、3:视频、4:音乐,默认为0。 |
| 185 | + """ |
| 186 | + url = WeiboUtil.timeLineURL.format(app_key=self.app_key, since_id=self.timeline_since_id, max_id=max_id, count=count, page=page, feature=feature) + self.auth |
| 187 | + # print(url) |
| 188 | + weiboInfo = self._getJson(url) |
| 189 | + self.timeline_since_id = weiboInfo["since_id"] |
| 190 | + max_id = weiboInfo["max_id"] |
| 191 | + weiboPosts = weiboInfo["statuses"] |
| 192 | + # print(len(statuses)) |
| 193 | + statuses = [] |
| 194 | + for post in weiboPosts: |
| 195 | + status = Status() |
| 196 | + status.fromDict(post) |
| 197 | + statuses.append(status) |
| 198 | + return statuses |
| 199 | + |
| 200 | + |
| 201 | + def getBilateralTimeline(self, max_id=0, count=100, page=1, feature=0): |
| 202 | + """获取互相关注好友的时间线 |
| 203 | +
|
| 204 | + bilateral_timeline API: http://open.weibo.com/wiki/2/statuses/bilateral_timeline |
| 205 | + since_id int64 若指定此参数,则返回ID比since_id大的微博(即比since_id时间晚的微博),默认为0。 |
| 206 | + max_id int64 若指定此参数,则返回ID小于或等于max_id的微博,默认为0。 |
| 207 | + count int 单页返回的记录条数,最大不超过100,默认为20。 |
| 208 | + page int 返回结果的页码,默认为1。 |
| 209 | + feature int 过滤类型ID,0:全部、1:原创、2:图片、3:视频、4:音乐,默认为0。 |
| 210 | + """ |
| 211 | + url = WeiboUtil.bilateralTimelineURL.format(app_key=self.app_key, since_id=self.bilateral_timeline_since_id, max_id=max_id, count=count, page=page, feature=feature) + self.auth |
| 212 | + # print(url) |
| 213 | + data = self._getJson(url) |
| 214 | + try: |
| 215 | + weiboPosts = data["statuses"] |
| 216 | + self.bilateral_timeline_since_id = data["since_id"] |
| 217 | + statuses = [] |
| 218 | + for post in weiboPosts: |
| 219 | + status = Status() |
| 220 | + status.fromDict(post) |
| 221 | + statuses.append(status) |
| 222 | + return statuses |
| 223 | + except Exception as e: |
| 224 | + msg = str(e)+"\n"+str(data) |
| 225 | + return msg |
| 226 | + |
| 227 | + |
| 228 | + def printLimit(self): |
| 229 | + url = r'https://api.weibo.com/2/account/rate_limit_status.json?'+self.auth |
| 230 | + data = self._getJson(url) |
| 231 | + commit_limit_remaining=data["api_rate_limits"][1]["remaining_hits"] |
| 232 | + print("剩余评论数", commit_limit_remaining) |
| 233 | + |
| 234 | + |
| 235 | + |
| 236 | +class Status(object): |
| 237 | + """单条微博类""" |
| 238 | + |
| 239 | + visible_type = { |
| 240 | + 0:"普通微博", 1:"私密微博", 3:"指定分组微博", 4:"密友微博" |
| 241 | + } |
| 242 | + def __init__(self): |
| 243 | + pass |
| 244 | + |
| 245 | + def fromJsonStr(self, jsonStr): |
| 246 | + """从字符串中提取信息""" |
| 247 | + data = json.loads(jsonStr) |
| 248 | + self.fromDict(data) |
| 249 | + |
| 250 | + def fromDict(self, data): |
| 251 | + self.created_at = data["created_at"] |
| 252 | + self.id = data["id"] |
| 253 | + self.text = data["text"] # 微博内容 |
| 254 | + self.source = data["source"] |
| 255 | + self.favorited = data["favorited"] |
| 256 | + self.truncated = data["truncated"] |
| 257 | + userJson = data["user"] |
| 258 | + self.user = User() |
| 259 | + self.user.fromDict(userJson) # 作者 |
| 260 | + self.reposts_count = data["reposts_count"] # 转发数 |
| 261 | + self.comments_count = data["comments_count"] |
| 262 | + self.attitudes_count = data["attitudes_count"] # 表态数 |
| 263 | + self.visible = data["visible"]["type"] # 可见性 |
| 264 | + # print(self.visible) |
| 265 | + self.visible = Status.visible_type.get(self.visible) |
| 266 | + |
| 267 | + |
| 268 | +class User(object): |
| 269 | + """微博用户类 |
| 270 | +
|
| 271 | + Weibo User API:http://open.weibo.com/wiki/%E5%B8%B8%E8%A7%81%E8%BF%94%E5%9B%9E%E5%AF%B9%E8%B1%A1%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84#.E7.94.A8.E6.88.B7.EF.BC.88user.EF.BC.89 |
| 272 | + """ |
| 273 | + def __init__(self): |
| 274 | + pass |
| 275 | + |
| 276 | + def fromJsonStr(self, jsonStr): |
| 277 | + """从字符串中提取信息""" |
| 278 | + data = json.loads(jsonStr) |
| 279 | + self.fromDict(data) |
| 280 | + |
| 281 | + def fromDict(self, data): |
| 282 | + self.uid = data["id"] |
| 283 | + self.screen_name = data["screen_name"] |
| 284 | + self.name = data["name"] |
| 285 | + self.provinceID = data["province"] |
| 286 | + self.cityID = data["city"] |
| 287 | + self.location = data["location"] |
| 288 | + self.description = data["description"] |
| 289 | + self.blogURL = data["url"] |
| 290 | + self.profile_image_url = data["profile_image_url"] # 头像,50 * 50 |
| 291 | + self.avatar_large = data["avatar_large"] # 头像,180 * 180 |
| 292 | + self.avatar_hd = data["avatar_hd"] # 头像,原图 |
| 293 | + self.gender = data["gender"] # 性别,m:男、f:女、n:未知 |
| 294 | + self.followers_count = data["followers_count"] |
| 295 | + self.friends_count = data["friends_count"] # 关注数 |
| 296 | + self.statuses_count = data["statuses_count"] # 微博数 |
| 297 | + self.favourites_count = data["favourites_count"] |
| 298 | + self.created_at = data["created_at"] |
| 299 | + self.allow_all_act_msg = data["allow_all_act_msg"] # 是否允许所有人给我发私信 |
| 300 | + self.allow_all_comment = data["allow_all_comment"] # 是否允许所有人评论微博 |
| 301 | + self.verified = data["verified"] # 是否大V |
| 302 | + self.following = data["following"] # 当前登录用户是否关注对方 |
| 303 | + self.follow_me = data["follow_me"] # 是否关注当前登录用户 |
| 304 | + self.online_status = data["online_status"] # 在线状态 |
| 305 | + self.bi_followers_count = data["bi_followers_count"] # 互粉数 |
| 306 | + |
| 307 | +class Comment(object): |
| 308 | + """单条评论的类""" |
| 309 | + def __init__(self): |
| 310 | + pass |
| 311 | + |
| 312 | + def fromJsonStr(self, jsonStr): |
| 313 | + data = json.loads(jsonStr) |
| 314 | + self.fromDict(data) |
| 315 | + |
| 316 | + def fromDict(self, data): |
| 317 | + self.created_at = data["created_at"] |
| 318 | + self.id = data["id"] |
| 319 | + self.text = data["text"] |
| 320 | + self.source = data["source"] |
| 321 | + self.mid = data["mid"] |
| 322 | + self.status = Status() |
| 323 | + self.status.fromDict(data["status"]) |
| 324 | + self.user = User() |
| 325 | + self.user.fromDict(data["user"]) |
| 326 | + |
| 327 | + |
| 328 | +if __name__ == '__main__': |
| 329 | + # 使用前先根据WeiboUtil的说明文字配置好config.ini |
| 330 | + |
| 331 | + u = WeiboUtil() |
| 332 | + # print(u.access_token) |
| 333 | + # u.printLimit() |
| 334 | + |
| 335 | + # 默认使用access_token验证,不需要用户名密码。 |
| 336 | + |
| 337 | + # 如果access_token验证失败,改用app_key,此时需要用户名密码登陆 |
| 338 | + # u.login("用户名", "密码") |
| 339 | + |
| 340 | + # # 读取Timeline上的一百条最新微博 |
| 341 | + # statuses = u.getTimeline() |
| 342 | + |
| 343 | + # # 读取互相关注好友的最新微博,最多一百条 |
| 344 | + # statuses = u.getBilateralTimeline() |
| 345 | + # print(statuses) |
| 346 | + # for status in statuses: |
| 347 | + # print(status.user.name) # 作者 |
| 348 | + # print(status.text) # 微博内容,更多属性请查看Status和User类 |
| 349 | + # print("=" * 50) |
| 350 | + |
| 351 | + # # ---------------评论其中的第一条微博------------------------ |
| 352 | + # weiboID = statuses[0].id |
| 353 | + # print(weiboID) |
| 354 | + # print("评论的微博:", statuses[0].text) |
| 355 | + # print("用户:", statuses[0].user.name) |
| 356 | + # for _ in range(100): |
| 357 | + # comment = u.comment("小金微博机器人测试 " + str(_) + " " + str(datetime.now()), weiboID) |
| 358 | + |
| 359 | + |
| 360 | + # # 发一条新的纯文本微博 |
| 361 | + # text = "小金微博机器人测试" |
| 362 | + # status = u.update(text) # 返回单条微博对象 |
| 363 | + # print(status) |
| 364 | + |
| 365 | + # 发一条带图片的微博,还没调试成功 |
| 366 | + # 相关资料http://hbprotoss.github.io/posts/multipartform-datade-shi-xian.html |
| 367 | + |
| 368 | + |
0 commit comments