开场图:喜欢数大于1000的用户分布:横轴为总字数,纵轴为总喜欢数(对数坐标轴);点的颜色越红,关注者越多,颜色数值为关注者数量的开平方;点的形状越大,文章数量越多。
爬取到的入口用户数据-密码:coyu / 代码github-jianshuUser

入口用户的定义与获取
简书首页右下角有推荐作者的入口,点击查看全部跳转到如下网页:

我们可以获取当前所有的推荐作者,算作第0层(约两百多位);然后去爬取所有推荐作者所关注的作者,算作第1层(去重后约1w多):

这时数据量还是略小,再爬取一层,即第一层作者所关注的作者,为第2层(总条目为56w多,去重后约12w多)。
(总共用时34分钟。全部代码在文后。爬虫使用python的requests +BeautifulSoup + 简单线程池。数据分析与动态图采用pandas + seaborn + 简单美观的plotly)
至此,算作简书入口用户。在用户处理时,再按一定的喜欢数筛选。
这里的入口用户定义为本人主观定义,主要重心放在优质内容生产者上,通过关注链获取,(有一个问题:对同一个用户网页版简书最多只能获取900个他关注的人,不过影响不大),欢迎讨论其他更好易行的定义方式。
感兴趣的同学可以下载数据看看自己是否在这三层之中,这也是我的出发点。
数据可视化与简单分析
按喜欢数排序的头部用户:

将用户按喜欢数排序后选取喜欢数大于100的用户画柱状图,可以看出喜欢数的差异十分巨大,头部作者拥有大量的喜欢数:

然而大多数作者之间并没有太明显的字数差异:

文章数差异也不明显:

在被关注数量上还是和喜欢数有比较强的相关性的:

而大家的关注人数就相对随意了,但是前面的密度还是大些:

而通过爬取层级可以看出0级即推荐作者(最前面的白缝隙)大多位于最前列,1级即推荐作者的关注作者分布也相对靠前

喜欢数大于100的用户分布,横轴为总字数,纵轴为总喜欢数;点的颜色越红,关注者越多,颜色数值为关注者数量的开平方;点的形状越大,文章数量越多:

喜欢数大于100的用户分布,横轴为总字数,纵轴为总喜欢数;颜色越往红入口层级越前;点的形状越大,关注者越多:

普通坐标轴,喜欢数大于100的用户分布,横轴为总字数,纵轴为总喜欢数,颜色代表关注者多寡:

分布密度与相关性:


- 喜欢数大于1k的所有用户数为 --> 3593
- 喜欢数大于10k的所有用户数为 --> 235
- 粉丝大于1k的所有用户数为 --> 1238
- 粉丝大于10k的所有用户数为 --> 85
其他

- 简单算出当前推荐作者数:稍微修改下请求的网址:
http://www.jianshu.com/recommendations/users?page=22&per_page=10
可以算出目前的推荐作者数量为(22-1)*10+1即211个:
代码
# -------------------数据库 (------------------- #
# http://5izxy.cn/2016/03/28/MongoDB/
# http://api.mongodb.com/python/current/faq.html
# mongodb可视化选用 RoboMongo # db.getCollection('users_7_9').find({}).count()
from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.jianshu
coll = db.users_7_9
# -------------------数据库 )------------------- #
import requests
# from requests.compat import urljoin
import pandas as pd
from fake_useragent import UserAgent
from bs4 import BeautifulSoup
import time
start = time.ctime()
from multiprocessing.dummy import Pool
pool = Pool(30)
host = 'http://www.jianshu.com'
ua = UserAgent()
headers = {'User-Agent': ua.chrome}
# ---------------------收集当前所有推荐作者-------------------------
def get_recommend_users():
url = host + '/recommendations/users?page=1&per_page=1000'
res = requests.get(url, headers=headers)
soup = BeautifulSoup(res.text, 'lxml')
recommend_users_id = [
i.find('a')['href'].split('/')[-1]
for i in soup.select(".wrap")
]
print("共得到 %s 名推荐作者" % len(recommend_users_id))
return recommend_users_id
def get_info_from_single_user(id):
url = host + '/u/{0}'.format(id) # url_user_single = 'http://www.jianshu.com/u/ddae1fe6d804'
res = requests.get(url, headers=headers)
soup = BeautifulSoup(res.text, 'lxml')
name = soup.select_one('.title .name').text.strip()
print('Done %s' % name)
info = soup.select(".info li")
return {
'id': id,
'name': name,
'following': info[0].find('p').text, # 'following_url': urljoin(host, info[0].find('a')['href']),
'followers': info[1].find('p').text, # 'followers_url': urljoin(host, info[1].find('a')['href']),
'articles': info[2].find('p').text,
'words': info[3].find('p').text,
'likes': info[4].find('p').text,
'order': 0,
}
def get_recommend_users_info():
recommend_users_id = get_recommend_users()
full_info = pool.map(
get_info_from_single_user, recommend_users_id)
return full_info
recommend_users_info = get_recommend_users_info()
# ---------------------得到推荐作者的关注用户-------------------------
from retry import retry
# pip install retry
# https://pypi.python.org/pypi/retry
@retry(Exception, delay=1, backoff=2, tries=2)
def get_followers_or_following(id, order=1, state='following'):
# 无论网页还是程序访问都是最多900个,像简叔这样关注超2000个的无法全部获取
url = host + '/users/{id}/{state}?page=%s'.format(id=id, state=state)
users = []
num = 1
while 1:
res = requests.get(url%num, headers=headers)
soup = BeautifulSoup(res.text, 'lxml')
follow_users = soup.select(".user-list .info")
if not follow_users:
break
for i in follow_users:
spans = i.findAll('span')
des = i.findAll('div')[-1].text.split(' ')
href = i.find('a')['href']
users.append({
'id': href.split('/')[-1],
'name': i.find('a').text.strip(),
'following': spans[0].text.split(' ')[-1],
'followers': spans[1].text.split(' ')[-1],
'articles': spans[2].text.split(' ')[-1],
'words': des[7],
'likes': des[9],
'order': order,
})
# print(num)
num += 1
return users
# from threading import Lock
# lock = Lock()
# users = [] # https://stackoverflow.com/questions/6319207/are-lists-thread-safe
# with lock:
# users.extend(new_users)
errors = []
def get_next_layer_users_through_following(info, store=False):
try:
new_users = get_followers_or_following(
info['id'], order=info['order']+1)
if store and new_users:
coll.insert_many(new_users)
print('--- ', info['name'])
return new_users
except:
errors.append(info)
print('error', info['id'], info['name'])
return []
second_layer_users = pool.map(
get_next_layer_users_through_following, recommend_users_info)
# [[dict1, dict2], [dict3, dict4], ...] --> [dict1, dict2, dict3, dict4, ...]
print('第二层 即推荐作者的关注 完成')
from itertools import chain
users = [i for i in chain(*second_layer_users, recommend_users_info)]
users = pd.DataFrame(users)
users.drop_duplicates('id', inplace=True, keep='last')
from functools import partial
third_layer_users = pool.map(
partial(get_next_layer_users_through_following, store=True),
users[users['order']>0].to_dict('records'))
end = time.ctime()
print('start', start, '; end', end)
print('出错数为', len(errors))
# --------------------处理出错的连接 (------------------------ #
if len(errors) != 0:
errors_ = errors
errors = []
repair = pool.map(
partial(get_next_layer_users_through_following, store=True),
errors_)
print('再次出错数为', len(errors))
# --------------------处理出错的连接 )------------------------ #
pool.close()
pool.join()
# -------------------- 数据处理 -------------------------------
more_users = [i for i in chain(*third_layer_users)]
more_users = pd.DataFrame(more_users) # 564620
del more_users['_id'] # 因为插入数据库所以会存在_id列
more_users.drop_duplicates('id', inplace=True) # 128306
all_users = pd.concat([users, more_users])
all_users.drop_duplicates('id', inplace=True, keep='first')
# 清洗后存入新的数据库
coll = db.users_clean
coll.insert_many(all_users.to_dict('records'))
# 读取使用
# cursor = coll.find({}, {'_id': False})
# document = [d for d in cursor]
# all_users = pd.DataFrame(document)#, dtype=int
columns = ['articles', 'followers', 'following', 'likes', 'words', 'order']
all_users[columns] = all_users[columns].astype('int')
exception_users = all_users[ all_users['likes']<0 ]
# 去除没有喜欢数的用户并排序
all_users = all_users[ all_users['likes']>0 ].sort_values(
['likes', 'followers'], ascending=False
).reset_index(drop=True)
# 查看我的信息
me = all_users[all_users['name'].str.contains('treelake')]
all_users[all_users['likes'] > me['likes'].item()]
print('喜欢数大于1000的所有用户数为 --> ',
len(all_users[all_users['likes']>1000]))
# 利用matplotlib库可视化喜欢数
import matplotlib.pyplot as plt
all_users['likes'].plot.line()
plt.show()
# 利用sns美化matplotlib可视化喜欢数密度分布
import seaborn as sns
sns.distplot(all_users[all_users['likes']<10000]['likes'])
sns.plt.show()
sns.distplot(all_users[all_users['likes']>10000]['likes'])
sns.plt.show()
# 查看相关性
sns.jointplot(data=all_users, x='words', y='likes', kind='reg', color='g')
sns.jointplot(data=all_users, x='articles', y='likes', kind='reg', color='g')
sns.plt.show()
# 使用动态可视化库 Plotly
import plotly
import plotly.figure_factory as ff
import plotly.graph_objs as go
# 画表格
table = ff.create_table(all_users[:100])
plotly.offline.plot(table, filename='jianshu_user_table1.html')
# 柱状图
filtered = all_users[all_users['likes']>100]
for d, t in zip(
['likes', 'words', 'articles', 'followers', 'following', 'order'],
['喜欢数', '总字数', '文章数', '关注ta的人数', '被ta关注的人数', '爬取层级']):
fig = {
'data': [go.Bar(x=filtered.name,
y=filtered[d])],
'layout': {'yaxis': {'title': t}},
}
plotly.offline.plot(fig, filename='basic_bar_%s.html'%d, show_link=False)
# 散点图
trace1 = go.Scatter(
x = filtered.words,
y = filtered.likes,
mode='markers',
marker=dict(
size='16',
color=filtered.followers.apply(pd.np.sqrt), #set color equal to a variable
colorscale='Viridis',
# Greys, YlGnBu, Greens, YlOrRd, Bluered, RdBu, Reds, Blues,
# Picnic, Rainbow, Portland, Jet, Hot, Blackbody, Earth, Electric, Viridis
showscale=True,
),
text=filtered.name,
)
data = [trace1]
plotly.offline.plot(data, filename='scatter-plot-with-colorscale.html', show_link=False)
# 点大小不一, 对数坐标轴的散点图
trace1 = go.Scatter(
x = filtered.words,
y = filtered.likes,
mode='markers',
marker=dict(
size=filtered.articles.apply(pd.np.sqrt), # 3/(filtered.order + 1),
color=filtered.followers.apply(pd.np.sqrt), # set color equal to a variable
colorscale='Rainbow',
showscale=True,
),
text=filtered.name,
)
fig = {
'data': [trace1],
'layout': {
'xaxis': {'title': '总字数', 'type': 'log'},
'yaxis': {'title': "总喜欢数", 'type': 'log'},
},
}
plotly.offline.plot(fig, filename='scatter-plot-with-colorscale22.html', show_link=False)
网友评论