Fork me on GitHub

简书之旅

最近爬取了简书上面自己文章的数据,进行了数据分析处理和可视化呈现,同时也制作了最终的词云图,主要是想知道最近2年都干了什么

这次爬取和以往很大的不同的是:网页是ajax动态加载的,对网页构造的分析和花费了很长的时间,还去B站上看了崔庆才大佬的视频,所以整体上还是加大了难度。

爬虫真的不简单呀😄东西越学越多,也越难,但是当爬取到数据之后也更开心咯!哈哈哈😃

本文中获取到的数据仅供学习使用,未用作任何商业用途;如有转载,请注明出处及作者

周末美食

这是本周的美食,很赞👍制作特点总结一下:

  1. 蒜多点
  2. 放点红椒或者胡萝卜丁,颜色更好些
  3. 最后焖的时候,水少些😃

爬取内容

  • 题目name
  • 简介abstract
  • 砖石数Masonry(后来省略)
  • 观看Watch
  • 评论comment
  • 点赞like(praise)
  • 时间time

以简书上面的一篇文章为例来进行解释。本来也想把砖石数爬取下来进行分析,但不是每篇都有文章都有砖石数,所以没有获取这个数据Masonry

数据

总共有530篇文章,但是不知道为什么爬出来总是只有527=58*9+5篇。在最后一页的爬取中总是只有5

因为网页的代码结构是一样的,爬不出来,很是无解😭暂且就这样子咯

实际爬到的数据只有527条:

网页结构

ajax网页

这是第一次爬取ajax动态加载的网页数据,知乎上看到一篇关于ajax的讲解:

AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。

举一个例子,打开这个页面,先不要动,观察右边滚动条的长度,然后当你把滚动条下拉到底之后,滚动条就变短了,即页面变长了,也就是说有一部分数据是这个时候才加载出来的。这个过程就是动态加载,基于ajax技术。我们可以看到在拉动滚动条的时候,页面上的数据变多了,但是URL始终没有变化,它不是像翻页那样将数据存到了另一个网页。

https://zhuanlan.zhihu.com/p/35682031

网页更新

比如在这个网页的源码中,当右侧栏中的滚动条静止不动的时候,只有9篇文章即9个<li></li>标签对

现在当滚动条向下滑动的时候,li标签会自动更新

网页规律

针对ajax加载的网页,在右键—检查—Networks—XHR中查看:

通过headers获取一条URL地址:

这样我们就找到了整个爬取的URL地址,可以实现全网数据的爬取

爬取数据

导入库

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
import re
import requests
import pandas as pd
import csv
import jieba
import numpy as np
import matplotlib.pyplot as plt

# 绘图
import random

import plotly as py
import plotly_express as px
import plotly.graph_objects as go
import dash
import dash_core_components as dcc
import dash_html_components as html

# 显示所有列
# pd.set_option('display.max_columns', None)

# 显示所有行
# pd.set_option('display.max_rows', None)

# 设置value的显示长度为100,默认为50
# pd.set_option('max_colwidth',100)

获取网页内容

使用requests库获取网页内容

1
2
3
4
5
6
url = "https://www.js.com/u/635acc145?order_by=shared_at&page=1"
headers = {"User-Agent": "实际请求头"}

response = requests.get(url=url,headers=headers) # 得到响应
res = response.content.decode('utf-8', 'ignore') # 获取源码
result.append(res)

爬取单个字段

name为例:

其他字段的正则表达式为:

1
2
3
4
5
abstract = re.findall('<p class="abstract">\s*(.*?)\n\s*</p>', result, re.S)
watch = re.findall('class="iconfont ic-list-read"></i>(.*?)\s</a>',result, re.S) # \s匹配空白字符;\S匹配任意非空白字符
comment = re.findall('class="iconfont ic-list-comments"></i>(.*?)\s</a>',result, re.S)
like = re.findall('class="iconfont ic-list-like"></i>(.*?)</span>',result, re.S)
time = re.findall('lass="time" data-shared-at=(.*?)></span>',result, re.S)

最好是检查每个字段每页的个数是否为9个:

时间的爬取到的只是具体的时间,不要年月日

全网爬取

URL地址和请求头需要进行更换

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
# from multiprocessing import Pool

import re
import requests
import pandas as pd
import csv
import jieba
import numpy as np
import matplotlib.pyplot as plt

# !!!注意with语句的位置,放在开头:防止属性字段的重复写入到数据中

# 写入文件
with open("jianshu.csv", "a", encoding="utf-8") as f: # 将写入的模式改成"a":表示追加模式
writer =csv.DictWriter(f,fieldnames=["name","abstract","read","comment","like","time"])
writer.writeheader()

# url地址和请求头需要更换成实际内容
for i in range(1,60): # 爬取59页数据
url = "https://www.js.com/u/6?shared_at&page={}".format(i)
headers = {"User-Agent": "实际请求头"}

response = requests.get(url=url,headers=headers) # 得到响应
res = response.content.decode('utf-8', 'ignore')

name_list = re.findall('class="title" target="_blank".*?>(.*?)</a>',res, re.S)
# \n:换行符;\s*:任意次数的空白;\.\.\.:匹配3个...
abstract_list = re.findall('<p class="abstract">\s*(.*?)\n\s*</p>', res, re.S)
# 单独写出匹配数字:{1}表示匹配1次;{1,}表示至少匹配1次
# masonry_list = re.findall(r'class="iconfont ic-paid1"></i>\s(\d{1,}.\d{1,}).*?</span>',res, re.S)
read_list = re.findall('class="iconfont ic-list-read"></i>(.*?)\s</a>',res, re.S) # \s匹配空白字符;\S匹配任意非空白字符
comment_list = re.findall('class="iconfont ic-list-comments"></i>(.*?)\s</a>',res, re.S)
like_list = re.findall('class="iconfont ic-list-like"></i>(.*?)</span>',res, re.S)
time_list = re.findall('lass="time" data-shared-at=".*?T(.*?)\+.*?></span>',res, re.S)

result_list = []
for j in range(len(name_list)):
result = {
"name": name_list[j],
"abstract": abstract_list[j],
# "masonry": masonry_list[j],
"read": read_list[j],
"comment": comment_list[j],
"like": like_list[j],
"time": time_list[j]
}

result_list.append(result)
writer.writerows(result_list)

数据处理

全网数据

通过上面的代码得到了全网的数据:

字段信息

time字段

排序

首先根据time字段进行排序

1
2
df_sort = df.sort_values("time")
print(df_sort.dtypes)

属性改变

解决的是将time字段的object数据类型改成和时间相关的。

后面发现:不用处理好像也可以正常处理time字段😭

散点图

横坐标是文章的名称name,纵坐标是发表的时间time,颜色是点赞次数like

1
2
3
4
5
6
7
8
fig = px.scatter(df_sort,x="name",y="time",color="like",height=900,width=1350)

app = dash.Dash()
app.layout = html.Div([
dcc.Graph(figure=fig)
])

app.run_server()

time标记处理

处理规则

人为地将发表文章的时间分为4个阶段,并且用不同的数值表示:

  • 零点-早上8点:1
  • 早上8点-下午2点:2
  • 下午2点-晚上8点:3
  • 晚上8点-晚上12点:4

增加flag字段

增加一个新的字段flag来标记上述信息:

1
2
3
4
5
6
7
8
9
for i in range(len(df_sort)):
if "00:00:00" <= df_sort.loc[i,"time"] < "08:00:00":
df_sort.loc[i,"flag"] = 1
elif "08:00:00" <= df_sort.loc[i,"time"] < "14:00:00":
df_sort.loc[i,"flag"]= 2
elif "14:00:00" <= df_sort.loc[i,"time"] < "20:00:00":
df_sort.loc[i,"flag"] = 3
else:
df_sort.loc[i,"flag"] = 4

1
2
3
4
5
6
7
8
fig = px.scatter(df_sort,x="flag",y="time",color="flag",height=800,width=1350)

app = dash.Dash()
app.layout = html.Div([
dcc.Graph(figure=fig)
])

app.run_server()
绘图

结论

通过上述图形可以看出来,在第4个阶段里面发表文章的概率是比较大的,说明大部分文章是晚上发表的:和实际情况也是符合的😃

read字段

数据信息

绘图
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
# 绘图

# 颜色的随机生成:#123456 # 加上6位数字构成
def random_color_generator(number_of_colors):
color = ["#"+''.join([random.choice('0123456789ABCDEF') for j in range(6)])
for i in range(number_of_colors)]
return color

text = read.values

trace = go.Bar(
x = read.index,
y = read.values,
text = text,
marker = dict(
color = random_color_generator(232),
line = dict(color='rgb(8, 48, 107)', # 柱子的外围线条颜色和宽度
width = 1.5)
),
opacity = 0.5 # 透明度设置
)

# 数据部分:一定是列表的形式
data = [trace]

# 布局设置
layout = go.Layout(
title = 'Information of read', # 整个图的标题
margin = dict(
l = 100 # 左边距离
),
xaxis = dict(
title = 'Number of reading' # 2个轴的标题
),
yaxis = dict(
title = 'Count of the number of reading'
),
width = 70000, # figure的宽高
height = 800
)

fig = go.Figure(data=data, layout=layout)

fig.update_traces(textposition="outside") # 将每个占比显示出来,也就是y轴的值

fig.show()
结果

结果的部分截图,可向右侧滑动

另一种绘图方式:

1
px.bar(x=read.index,y=read.values,color=read.index,range_color=[10,100000],height=800,width=30000)

comment字段

绘图

主要是对每篇文章的评论数量进行统计分析和制图

结论

从上面的饼图看出来:评论数为0的文章占据了绝大多数,接近90%;评论数为2的文章其次

还是很少人评论呀😭😭

like字段

数据

绘图
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
# 绘图

# 颜色的随机生成:#123456 # 加上6位数字构成
def random_color_generator(number_of_colors):
color = ["#"+''.join([random.choice('0123456789ABCDEF') for j in range(6)])
for i in range(number_of_colors)]
return color

trace = go.Bar(
x = like_number.index,
y = like_number.values,
text = text,
marker = dict(
color = random_color_generator(100),
line = dict(color='rgb(8, 48, 107)', # 柱子的外围线条颜色和宽度
width = 1.5)
),
opacity = 0.7 # 透明度设置
)

# 数据部分:一定是列表的形式
data = [trace]

# 布局设置
layout = go.Layout(
title = 'Information of like', # 整个图的标题
margin = dict(
l = 100 # 左边距离
),
xaxis = dict(
title = 'Number of like' # 2个轴的标题
),
yaxis = dict(
title = 'Count of like'
),
width = 900, # figure的宽高
height = 500
)

fig = go.Figure(data=data, layout=layout)

fig.update_traces(textposition="outside") # 将每个占比显示出来,也就是y轴的值

fig.show()

结论
  • 点赞1次的文章最多,2次的文章其次
  • 最多的点赞次数是38次
  • 居然还有一篇文章一个赞👍都没有😭

词性分析与词云图

  • 处理的是文章标题字段name,分析标题中哪些出现的频率高,则作为重点学习的对象

  • 主要是使用jieba分词与wordcloud制作词云图

Jieba词性分析

生成列表

实现分词
1
2
3
4
5
# 2-实现分词

for i in range(len(name_list)):
seg_list = jieba.cut(name_list[i].strip(), cut_all=False) # seg_list只是一个generator生成器:<class 'generator'>
print(("Default Mode: " + "/ ".join(seg_list))) # 用list方法展开

分词结果放入列表
1
2
3
4
5
6
7
8
9
10
# 3-将分词的结果全部放入一个列表中,方便后续处理

jieba_name = []

for i in range(len(name_list)):
seg_list = jieba.cut(name_list[i].strip(), cut_all=False) # seg_list只是一个generator生成器:<class 'generator'>
for str in list(seg_list): # 对list(seg_list)中的每个元素进行追加
jieba_name.append(str)

jieba_name

jieba使用总结

  1. 将待处理的句子放入列表中
  2. 对列表中的每个句子分词
  3. 将上面步骤中的分词结果放入到另一个列表中,方便后续处理

Wordcloud词云图

绘图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from wordcloud import WordCloud
import matplotlib.pyplot as plt

text = " ".join(i for i in jieba_name) # 待处理的字符串

# 先下载SimHei.ttf字体,放置到自己的某个目录下,然后将font换成自己的路径即可
font = r'/Users/piqianchao/Desktop/spider/SimHei.ttf'

wc = WordCloud(collocations=False, font_path=font, # 路径
max_words=2000,width=4000,
height=4000, margin=2).generate(text.lower())

plt.imshow(wc)
plt.axis("off")
plt.show()

wc.to_file('jianshu.png') # 把词云保存下来
初步结果

图中的札记实在是眨眼呀

这是因为自己Python札记写了很多的原因;另外3个比较突出的词语:利用、进行、数据分析是因为自己看了《利用Python进行数据分析》这本书

进一步处理

删除几个无价值的信息之后再进行绘图,选择了一个背景图:

1
2
3
4
5
noUse = ["札记","利用","进行","打卡","笔记","学习"]

for col in noUse:
while col in jieba_name:
jieba_name.remove(col)
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
from os import path
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator

d = path.dirname('.') # 在ide中使用这段代码
# d = path.dirname(__file__)

# 待处理的文件(去掉无效信息之后的)
text = " ".join(i for i in jieba_name)

# read the mask / color image taken from
# http://jirkavinse.deviantart.com/art/quot-Real-Life-quot-Alice-282261010
alice_coloring = np.array(Image.open(path.join(d, "wordcloud.png")))

# 设置停用词
stopwords = set(STOPWORDS)
stopwords.add("said")

# 路径改成自己的
font = r'/Users/piqianchao/Desktop/spider/SimHei.ttf'

# 你可以通过 mask 参数 来设置词云形状
wc = WordCloud(background_color="white", font_path=font,
max_words=2000, mask=alice_coloring,
height=6000,width=6000,
stopwords=stopwords, max_font_size=40, random_state=42)

# generate word cloud
wc.generate(text)

# create coloring from image
image_colors = ImageColorGenerator(alice_coloring)

# show
# 在只设置mask的情况下,你将会得到一个拥有图片形状的词云
plt.imshow(wc, interpolation="bilinear")
plt.axis("off")
plt.show()
wc.to_file('jianshu_one.png') # 把词云保存下来

# recolor wordcloud and show
# we could also give color_func=image_colors directly in the constructor
# 直接在构造函数中直接给颜色:通过这种方式词云将会按照给定的图片颜色布局生成字体颜色策略
plt.imshow(wc.recolor(color_func=image_colors), interpolation="bilinear")
plt.axis("off")
plt.show()
wc.to_file('jianshu_two.png') # 把词云保存下来

结论

从上面优化后的图形中可以看出来:

  1. Python是最突出的。的确如此,写的文章很多是关于Python
  2. 数据分析是一个亮点:因为《利用Python进行数据分析》这本书,还有就是很多pandas的文章
  3. MySQL也是另一个亮点:学习了很多数据库的知识,包含:MySQL、SQL、Sqlzoo、数据库等
  4. 机器一词是出自于机器学习中的,吴恩达老师带我入门的,学了很多的入门知识:吴恩达老师的视频、算法、数据结构等
  5. 深圳二字主要是因为到了深圳之后,在深圳写了很多文章,甚至有些文章也是关于深圳的

数据还是非常准确的,很有参考价值👍数据不会说谎

本文标题:简书之旅

发布时间:2020年07月18日 - 20:07

原始链接:http://www.renpeter.cn/2020/07/18/%E7%AE%80%E4%B9%A6%E4%B9%8B%E6%97%85.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Coffee or Tea