爬取知乎top50,数据清洗与pd2csv格式

爬取知乎top50

方法

关于数据清洗的方法

快速入门 | Pandas 中文 (pypandas.cn)

pd.nan为pd的缺失值对象。

  • 增加一行包含缺失值的列 df['employee']=np.nan

  • 检查非缺失值数据 pd.notnull()

    检查缺失值数据 pd.isnull()

    检查缺失值是否含有 pd.values.any()

    检查缺失值数量 pd.isnull().sum()

    检查所有缺失值的数量 pd.isnull().sum().sum()

  • 舍弃缺失值 pd.dorpna()

    舍弃所有字段都缺失的行 pd.dropna(how='all')

    舍弃所有字段都缺失的列 pd.dropna(how='all',axis=1)

    舍弃超过两行缺失值的行 pd.dropna(thresh=2)

  • 零填充缺失值 pd.fillna(0)

    平均值填充缺失值 pd.fillna(df['col'].mean())

    使用内插法填充缺失值 pd.interpolate()

以上为常见的方法,可以在手册中搜索以上关键字,查找更多方法和详细参数。

代码

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# 如果想运行这份代码,请将headers参数里的cookie值换成自己的知乎登陆cookie。
import requests
import pandas as pd
from bs4 import BeautifulSoup
import re
import json
import datetime


def get_url():
"""获取top50的link"""
top_list=[]
url_list=[]
headers = {
'cookie': '在此替换 你的知乎登陆cookie'
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.55'
}
home_url = 'https://www.zhihu.com/hot'
res = requests.get(url=home_url, headers=headers)
soup = BeautifulSoup(res.text, 'html.parser')
for section in soup.find_all("section"):
top=int(section.find("div",attrs={"class":"HotItem-rank"}).string)
top_list.append(top)
url=section.find("a",attrs={"target":"_blank","rel":"noopener noreferrer","data-za-not-track-link":"true"})["href"]
url_list.append(url)
return top_list, url_list


def save_section(link_list):
"""获取每一节的内容"""
data_list=[]
headers = {
'cookie': '在此替换 你的知乎登陆cookie'
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.55',
}
for top,url in zip(link_list[0],link_list[1]):
data = {
'top': top,
'url': url,
'question': None,
'recommend': None,
'star': None,
'watcher': None,
'date': None,
}
print(url)
print(top)
res = requests.get(url=url, headers=headers)
soup = BeautifulSoup(res.text, 'html.parser')
try:
data['question']=str(soup.find('h1').string)
print(data['question'])
except:
data['question']=None
try:
data['recommend']=soup.find('button',attrs={'type':"button","class":"Button GoodQuestionAction-commonBtn Button--plain Button--withIcon Button--withLabel"}).contents[-1]
print(data['recommend'])
data['recommend']=int(re.sub(r'好问题','',data['recommend']).strip())
print(data['recommend'])
except:
data['recommend']=None
try:
NumberBoard=soup.find_all('strong', attrs={'class': 'NumberBoard-itemValue'})
data['star']=int(str(NumberBoard[0].string).replace(',',''))
print('关注者',data['star'])
except:
data['star']=None
try:
NumberBoard = soup.find_all('strong', attrs={'class': 'NumberBoard-itemValue'})
data['watcher'] = int(str(NumberBoard[1].string).replace(',',''))
print('浏览者',data['watcher'])
except:
data['watcher']=None
try:
res_time = requests.get(url=url+'/log', headers=headers)
soup_time=BeautifulSoup(res_time.text, 'html.parser')
data['date']=str(soup_time.find('time').string)
print(data['date'])
except:
data['date']=None
data_list.append(data)
with open('hot_list.json','w',encoding='utf-8') as f:
f.write(json.dumps(data_list,indent=4,ensure_ascii=False, separators=(', ', ': ')))
data_tab=pd.DataFrame(data_list)
# 这样写会将默认index写入csv
data_tab.to_csv('hot_list.csv', encoding='utf_8_sig') # utf-8


def tab_clean():
with open('hot_list.json', 'r', encoding='utf-8') as f:
hot_list = json.loads(f.read())
data_tab = pd.DataFrame(hot_list)
data_tab['date']=pd.to_datetime(data_tab['date'],format='%Y-%m-%d %H:%M:%S')
print(data_tab.dtypes)
print(data_tab.notnull())
# 用算术中位数填充缺失值
data_tab['recommend']=data_tab['recommend'].fillna(data_tab['recommend'].median())
data_tab['star']=data_tab['star'].fillna(data_tab['star'].median())
data_tab['watcher']=data_tab['watcher'].fillna(data_tab['watcher'].median())
# 丢掉列date为空的组
data_tab=data_tab.dropna(subset=['date'])
# 计算帖子第一次上热榜距今的时间差,结果为时间戳对象
data_tab['update'] = data_tab['date'] - pd.to_datetime(datetime.datetime.now(), format='%Y-%m-%d %H:%M:%S')
data_tab.to_csv('hot_list2.csv', encoding='utf_8_sig') # utf-8
print(data_tab.notnull())
data_tab=pd.read_csv('hot_list2.csv')
print(data_tab)



if __name__=="__main__":
# save_section(get_url())
tab_clean()

存储的生数据(json)

处理前的数据(csv)

处理后的数据(csv)

数据分析

仅使用pandas对数据进行简单的分析

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#%%

import pandas as pd
%matplotlib inline
%pylab inline

#%%

data_tab=pd.read_csv('hot_list2.csv')
data_tab=data_tab.drop('Unnamed: 0',axis=1)
data_tab.describe()

#%% md

![](https://pic2.zhimg.com/v2-837f86cb4b41d8be67428db73afa2bd5_r.jpg)

#%%

data_tab.describe()
# 问题关注人数大于1000的问题
star_over_100=data_tab['star']>1000
# 问题推荐人数大于100的问题
recommend_over_100=data_tab['recommend']>100
# 问题上热榜时间在一天之内,表示新话题
new_topic=data_tab['update']<='-1day'
# 筛选出满足以上所有条件的结果
filter_tab=data_tab[(data_tab['star']>1000) & (data_tab['recommend']>100) & (data_tab['update']<='-1day')]
filter_tab

#%%

# 对以上三个对象单独分组
grouped=filter_tab[['star','recommend','update']]
# 以时间为标准进行索引分组
grouped.groupby('update').sum()

#%%

# 以收藏人数和推荐数为分组索引查看时间
grouped.groupby([filter_tab['star'],filter_tab['recommend']])['update'].sum()

#%% md

**对表格的每一项数据分别作图,同时画出平局值或算术中位数线**

#%%

# 将发布时间作为x轴,top,recommend,star,watcher作为y轴
time_x=data_tab[['top','recommend','star','watcher']].groupby(data_tab['date']).sum()
# 按照top值重排列
# time_x=time_x.sort_values('top',ascending=True)
time_x

#%%

# 将top作为x轴,recommend,star,watcher作为y轴
top_x=data_tab[['recommend','star','watcher']].groupby(data_tab['top']).sum()
top_x

#%% md

绘制折线图

#%%

time_x['recommend_avg']=time_x['recommend'].mean()
time_x[['recommend','recommend_avg']].plot(kind='line',figsize=[10,5],title='recommend-time',grid=True,xlabel='time',ylabel='recommend')

#%% md

绘制多重折线图

#%%

time_x[['recommend','star']].plot(kind='line',figsize=[10,5],title='recommend-time',grid=True,xlabel='time',ylabel='recommend')

#%% md

话题推荐度和收藏度的相关性分析

- 0.8-1.0 极强相关
- 0.6-0.8强相关
- 0.4-0.6中等相关
- 0.2-0.4弱相关
- 0.0-0.2极弱相关或无相关

#%%

# 推荐度-收藏
print('推荐度-收藏',time_x['recommend'].corr(time_x['star']))
# 浏览量-收藏
print('浏览量-收藏',time_x['watcher'].corr(time_x['star']))
# 浏览量-推荐
print('浏览量-推荐',time_x['watcher'].corr(time_x['recommend']))

#%% md

绘制直方图

#%%

time_x['star_med']=time_x['recommend'].median()
time_x[['star','star_med']].plot(kind='hist',figsize=[10,5],title='star-time',grid=True)

#%% md

绘制柱状图

#%%

time_x['watcher'].plot(kind='bar',figsize=[10,5],title='watcher-time',xlabel='watcher',ylabel='time',grid=True)

#%% md

绘制饼图

#%%

# watcher大于平均且star也大于平均的话题的推荐度
over_avg=top_x[(top_x['watcher']>top_x['watcher'].mean())&(top_x['star']>top_x['star'].mean())]
over_avg['recommend'].plot(kind='pie',figsize=[5,5],counterclock=True,startangle=90)

pd2csv

csv文件介绍在前面numpy库入门提到

DataFrame对象转csv格式保存

1
2
data_tab.to_csv('hot_list.csv', encoding='utf_8_sig')  # encoding='utf-8'也行
data_tab=pd.read_csv('hot_list.csv')

时间对象直接求差

1
data_tab['update'] = data_tab['date'] - pd.to_datetime(datetime.datetime.now(), format='%Y-%m-%d %H:%M:%S')

总结与完善

其实数据里只爬到问题部分,回答部分爬不到。通过链接爬取的页面其实是这个:

只能爬取到页面的部分问题(前两到三个),因为回答部分是ajax异步加载的…不过在XHR数据里找找也许能找到,看图:

以此问题为例:

1
2
answers?include=data%5B%2A%5D.is_normal%2Cadmin_cl…limit=5&offset=5&platform=desktop&sort_by=default
answers?include=data%5B%2A%5D.is_normal%2Cadmin_cl…imit=5&offset=10&platform=desktop&sort_by=default

这是页面外的第0-5个问题和5-10个问题的api链接,唯一的区别在于offset=5和offset=10,根据这个参数可以爬取到所有的回答。

感兴趣的话可以完善一下代码,这此的作业关键任务在于数据清理,上面提到的方案就不补完了。