项目地址在 Github

人生活在社区里,对一个常使用微博的人来说,微博记录和反映了他在一段时间内所接触的信息,思考的问题和表达的观点,是值得研究的。如果这个研究对象是一个优秀的人,这里面的价值可能比想象要大。

很显然,要做成收集整理一个人所有的微博 这件事,首先是收集,其次是整理。

收集主要想到有三种方式:

  • 找现成工具(无趣,暂不考虑)。
  • https://m.weibo.cn/u/14015127xxx 页面一直按END 键,然后页面会不断异步发送请求以增加页面内容,直到全部内容都被获取。
  • 可以看到第二种方法中的请求是向https://m.weibo.cn/api/container/getIndex?type=uid&value=1401527xxx&containerid=1076031401527xxx&page=1 发送GET请求,只需迭代page的参数即可得到所有数据。

方法二实践

用如下简单脚本模拟按键行为,泡杯茶观察成果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import win32api
import time
print time.asctime()
cnt=0
while 1:
	cnt+=1
	win32api.keybd_event(35, 0, 0, 0) #35 stands for "END" key; 0 means hold down
 	win32api.keybd_event(35, 0, 1, 0) # 1 means hold up 
	print cnt,
	time.sleep(2)
print time.asctime()

发现按了几十下页面就开始不变化了,观察请求发现都是发给page=50的,想必是做了限制,最多获取50条记录,暂告失败。

方法三实践

要用这个方法首先最好知道总共有多少page,用二分法手动测,很快就能发现目标用户共有1542个page的记录,然后写个脚本dump下这些响应,保存成json文件,以供后面处理即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from requests import * 
from time import *
import json
print asctime()
url="https://m.weibo.cn/api/container/getIndex?type=uid&value=1401527xxx&containerid=1076031401527xxx&page="
for i in range(1543,1,-1):
	u=url+str(i)
	f=open("result/%d.json"%i,'w+')
	f.write(get(u).content)
	f.close()
	sleep(2)
	if i%50==0:
		sleep(3)
print asctime()

处理json

分析响应结构,提取关键信息,构造文件汇总即可。代码见 GitHub

 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
#coding:utf8
# Github 项目已删,成果暂存 https://findneo.github.io/p/tkposts.html
import json
import re

def parse_cards_to_html(data_cards):
	"""
	微博API
	(https://m.weibo.cn/api/container/getIndex?type=uid&value=1401527553&containerid=1076031401527553&page=1)
	返回的一个字典,形如
	{	ok: 1,
	data: 
	{	cardlistInfo: {...},
		cards: 
		[	{	card_type: 9,	
				itemid: foo,	
				scheme: post链接,
				mblog: {created_at: post发表时间,	...,
						text: post内容,		...,
						retweeted_status: {created_at: repost发表时间,...,text: repost内容,...,}
						},
				show_type: 0
			},
			{...},...
		],
		showAppTips: 0,
		scheme: foo
	}
}
	本函数的功能是将cards中每一个表示post的card里的时间和内容,以及repost的时间和内容(如果有的话),提取出来,
	构造成HTML语句。
	"""
	ret=""
	for card in data_cards:
		if card["card_type"]==9: 
			# card_type==9 说明当前card是一个post
			is_repost=0
			post=card["mblog"] 
			post_time=post["created_at"]
			post_text=post["text"]
			ret0="<p><code><br />%s</code><br />%s</p>"%(post_time,post_text)
			if "retweeted_status" in post.keys():
				# 当前card中有retweeted_status键说明这是一个repost
				is_repost=1
				repost=post["retweeted_status"]
				repost_time=repost["created_at"]
				repost_text=repost["text"]
				ret1="\n\t<blockquote><p><code>%s</code><br />%s</p></blockquote>"%(repost_time,repost_text)
			ret=ret+ret0+ret1 if is_repost else ret+ret0
			ret=ret+"<hr />\n"
	return ret


def comment_img_in_html(file_name):
	"""
	因为教主post中时有图片,15000+条post里的图片数量相当可观(而且很多加载不出来),
	为了提高加载速度,这个函数将成品中的img标签全部注释掉。
	"""
	with open(file_name) as old_f,open("noimg_%s"%file_name,"w+") as new_f:
		for line in old_f.readlines():
			res=re.sub('(<img.*?>)',r"<!--\1-->",line)
			new_f.write(res)

def main():
	with open("tkposts.html","w+") as f:
		head='<html><head><style type="text/css">html{background:#f2f2f2;font-size:16px;font-family:Monaco}</style><title>TK POSTS</title></head><body>\n'
		f.write(head.encode('utf8'))
		for i in xrange(1,1543):
			data = json.load(open("%d.json"%i))
			if data["ok"]!=1:
				print "%d.json no data"%i
				continue
			msg_list=data["data"]["cards"]
			msg_html=parse_cards_to_html(msg_list)
			f.write(msg_html.encode('utf8'))
		tail="</body></html>"
		f.write(tail.encode('utf8'))
	comment_img_in_html("tkposts.html")

if __name__ == '__main__':
	"""
	截至 2018/3/8 下午一时许,教主在新浪微博上共发表约13831条状态,
	其中约6157条是转发自己或其他人的状态并评论。
	第一条发布在2010-05-21,内容是
	“刚看到的:2007年德国《自然科学》杂志的一篇文章指出,
	最近感染过弓形虫的女性怀孕生男孩的概率为60.8%。也就是说,
	感染弓形虫有助于生儿子——不过,不知道是需要在感染状态才行,还是治好了也行。 ”,
	可以看到风格很早就形成了,幽默感,批判性,科学性具存,这就是让人乐于追随学习的地方。
	"""
	main()

收获

  • 了解了正则表达式的backreferencelazy mode
  • 使用 json.load(file) 将json文件转换成字典。
  • 数据编码问题可能导致写文件报错,可用f.write(data.encode('utf8'))
  • with open("f1") as f1,open("f2") as f2: 打开多个文件。
  • 每一个人作为数字公民的部分可能最后并不能留下太多痕迹,而且这些痕迹可能非常脆弱。当然,话说回来,这同线下生活也是相似的。