简书之旅
这是自己爬取自己的文章,然后进行数据分析的!哈哈哈
声明:本文中获取的数据仅供学习使用,未用作任何商业用途;如有转载,请注明作者和原文出处
周末美食
这是本周的美食,很赞👍制作特点总结一下:
-
蒜多点
-
放点红椒或者胡萝卜丁,颜色更好些
-
最后焖的时候,水少些😃
image
简书APP之旅
最近爬取简书上面的数据,进行了数据分析和可视化处理,同时也制作了最终的词云图,主要是想知道最近2
年都干了什么
这次爬取和以往很大的不同的是:网页是ajax
动态加载的,对网页构造的分析和花费了很长的时间,还去B站上看了崔庆才大佬的视频,所以整体上还是加大了难度。
爬虫真的不简单呀😄东西越学越多,也越难咯!哈哈哈😃
image爬取内容
-
题目name
-
简介abstract
-
砖石数Masonry(后来省略)
-
观看Watch
-
评论comment
-
点赞like(praise)
-
时间time
以简书上面的一篇文章为例来进行解释。本来也想把砖石数爬取下来进行分析,但不是每篇都有文章都有砖石数,所以没有获取这个数据Masonry
数据
总共有530
篇文章,但是不知道为什么爬出来总是只有527=58*9+5
篇。在最后一页的爬取中总是只有5
条
因为网页的代码结构是一样的,爬不出来,很是无解😭暂且就这样子咯
image实际爬到的数据只有527
条:
网页结构
ajax网页
这是第一次爬取ajax
动态加载的网页数据,知乎上看到一篇关于ajax
的讲解:
AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
举一个例子,打开这个页面,先不要动,观察右边滚动条的长度,然后当你把滚动条下拉到底之后,滚动条就变短了,即页面变长了,也就是说有一部分数据是这个时候才加载出来的。这个过程就是动态加载,基于ajax技术。我们可以看到在拉动滚动条的时候,页面上的数据变多了,但是URL始终没有变化,它不是像翻页那样将数据存到了另一个网页。
https://zhuanlan.zhihu.com/p/35682031
网页更新
比如在这个网页的源码中,当右侧栏中的滚动条静止不动的时候,只有9篇文章即9个<li></li>
标签对
现在当滚动条向下滑动的时候,li
标签会自动更新
网页规律
针对ajax
加载的网页,在右键—检查—Networks—XHR
中查看:
通过headers
获取一条URL
地址:
这样我们就找到了整个爬取的URL地址,可以实现全网数据的爬取
爬取数据
导入库
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库获取网页内容
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)
image
爬取单个字段
以name
为例:
其他字段的正则表达式为:
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个:
image时间的爬取到的只是具体的时间,不要年月日
image全网爬取
URL地址和请求头需要进行更换
# 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)
数据处理
全网数据
通过上面的代码得到了全网的数据:
image字段信息
imagetime字段
排序
首先根据time
字段进行排序
df_sort = df.sort_values("time")
print(df_sort.dtypes)
image
属性改变
解决的是将time
字段的object
数据类型改成和时间相关的。
后面发现:不用处理好像也可以正常处理time字段😭
image散点图
横坐标是文章的名称name
,纵坐标是发表的时间time
,颜色是点赞次数like
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()
image
time标记处理
处理规则
人为地将发表文章的时间分为4个阶段,并且用不同的数值表示:
-
零点-早上8点:1
-
早上8点-下午2点:2
-
下午2点-晚上8点:3
-
晚上8点-晚上12点:4
增加flag字段
增加一个新的字段flag
来标记上述信息:
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
image
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()
绘图
image image结论
通过上述图形可以看出来,在第4个阶段里面发表文章的概率是比较大的,说明大部分文章是晚上发表的:和实际情况也是符合的😃
read字段
数据信息
image绘图
# 绘图
# 颜色的随机生成:#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()
image
comment字段
绘图
主要是对每篇文章的评论数量进行统计分析和制图
image image结论
从上面的饼图看出来:评论数为0的文章占据了绝大多数,接近90%;评论数为2的文章其次
还是很少人评论呀😭😭
like字段
数据
image绘图
# 绘图
# 颜色的随机生成:#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()
image
结论
-
点赞1次的文章最多,2次的文章其次
-
最多的点赞次数是38次
-
居然还有一篇文章一个赞👍都没有😭
词性分析与词云图
-
处理的是文章标题字段
name
,分析标题中哪些出现的频率高,则作为重点学习的对象 -
主要是使用
jieba
分词与wordcloud
制作词云图
Jieba词性分析
生成列表
image实现分词
# 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方法展开
image
分词结果放入列表
# 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
image
-
将待处理的句子放入列表中
-
对列表中的每个句子分词
-
将上面步骤中的分词结果放入到另一个列表中,方便后续处理
jieba使用总结
-
将待处理的句子放入列表中
-
对列表中的每个句子分词
-
将上面步骤中的分词结果放入到另一个列表中,方便后续处理
Wordcloud词云图
绘图
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') # 把词云保存下来
初步结果
image
图中的札记实在是扎眼呀😃
这是因为自己Python札记写了很多的原因;另外3个比较突出的词语:利用、进行、数据分析是因为自己看了《利用Python进行数据分析》这本书
进一步处理
删除几个无价值的信息之后再进行绘图,选择了一个背景图:
noUse = ["札记","利用","进行","打卡","笔记","学习"]
for col in noUse:
while col in jieba_name:
jieba_name.remove(col)
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') # 把词云保存下来
image
结论
从上面优化后的图形中可以看出来:
-
Python
是最突出的。的确如此,写的文章很多是关于Python
的 -
数据分析
是一个亮点:因为《利用Python进行数据分析》这本书,还有就是很多pandas的文章 -
MySQL
也是另一个亮点:学习了很多数据库的知识,包含:MySQL、SQL、Sqlzoo、数据库等 -
机器一词是出自于机器学习中的,吴恩达老师带我入门的,学了很多的入门知识:吴恩达老师的视频、算法、数据结构等
-
深圳
二字主要是因为到了深圳之后写了很多关于深圳的文章
数据还是非常准确的,很有参考价值👍数据不会说谎