美文网首页数据蛙强化课程第一期
爬取简书专栏文章,分析用户提交作业的情况

爬取简书专栏文章,分析用户提交作业的情况

作者: bf3780a4db09 | 来源:发表于2019-03-04 14:49 被阅读20次

参考网址:https://www.jianshu.com/p/17e264a08555

背景及目的

一帮对数据分析感兴趣的人组成了学习小组,计划每周总结一篇文章,提交到简书专栏。截止到2月24号,本专栏已成立11周,现在通过爬取专栏数据,来总结小组成员提交作业情况。

1、数据爬取

字段:
name:作者
title:文章标题
word_age:文章字数(防止有同学敷衍了事)
publish_time: 文章发布时间(带*的表示文章后来编辑过)
comments_count:评论数量
likes_count:喜欢数
工具:Python中的requests+Beautifulsoup+re库
思路:从首页爬取文章标题、地址、评论数和点赞数,从文章详情页爬取作者、发布时间和文章字数
结果:保存到aa库中的exercises表中

import requests
from bs4 import BeautifulSoup
import pymysql
from requests.exceptions import RequestException
import re
# import json


headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 ' +
                  '(KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36'
    }


def get_page_index(number):
    url = 'https://www.jianshu.com/c/af12635a5aa3?order_by=added_at&page=%s' % number
    try:
        responses = requests.get(url, headers=headers)
        if responses.status_code == 200:
            return responses.text
        return None
    except RequestException:
        print('请求有错误')
        return None


def get_page_detail(url):
    try:
        responses = requests.get(url, headers=headers)
        if responses.status_code == 200:
            return responses.text
        return None
    except RequestException:
        print('请求详情页有错误')
        return None


def parse_detail_page(title, html):  # 获得标题和链接后,分析每一页的内容,获得发布时间和文章字数
    title = title
    soup = BeautifulSoup(html, 'html.parser')
    name = soup.find_all('div', class_='info')[0].find_all('span', class_='name')[0].find_all('a')[0].contents[0]
    content_detail = soup.find_all('div', class_='info')[0].find_all('div', class_='meta')[0]
    # print(content_detail)
    # content_detail = [info.contents[0] for info in content_detail]  # 时间、字数、评论、喜欢
    publish_time = content_detail.find_all('span', 'publish-time')[0].contents[0]
    word_age = content_detail.find_all('span', 'wordage')[0].contents[0]
    return name, title, word_age, publish_time


def parse_index_page(html):
    soup = BeautifulSoup(html, 'html.parser')
    note_list = soup.find_all(name='ul', class_='note-list')[0]
    content_li = note_list.find_all(name='li')  
    dir = {}
    for link in content_li:
        # url = link.find_all('a', class_='title')[0]
        # title = url.contents[0]  # 获取每篇文章的标题,并去除标签格式
        # dir[link] = title
        url = link.find_all('a', class_='title')[0]
        linknew = 'https://www.jianshu.com/' + url.get('href')  # 获取每篇文章的链接
        title = url.contents[0]  # 获取每篇文章的标题,并去除标签格式
        pattern = re.compile(
            '<li.*?class="meta">.*?<i.*?ic-list-comments"></i>(.*?)\n*</a>.*?ic-list-like"></i>(.*?)</span>.*?</li>', re.S)
        result = re.findall(pattern, str(link))
        comments_count = result[0][0]
        likes_count = result[0][1]
        dir[linknew] = [title, comments_count, likes_count]
    # print(title, comments_count, likes_count)
    return dir


def save_to_mysql(name, title, word_age, publish_time, comments_count, likes_count):
    conn = pymysql.connect(host='localhost', user='root', password='123', db='aa', port=3306, charset='utf8')
    cur = conn.cursor()
    insert_data = 'insert into exercises(name, title, word_age, publish_time,comments_count,' \
                  'likes_count)''values(%s, %s, %s, %s, %s, %s)'
    val = [name, title, word_age, publish_time, comments_count, likes_count]

    cur.execute(insert_data, val)
    conn.commit()
    conn.close()


def write_to_file(name, title, word_age, publish_time, comments_count, likes_count):
    content = name + ' ' + title + ' ' + word_age + ' ' + publish_time + ' ' + comments_count + ' ' +likes_count
    with open('datafrog.txt', 'a', encoding='utf-8') as f:
        f.write(content + '\n')
        f.close()


def main():
    for number in range(1, 53):
        html = get_page_index(number)  # 网页源码
        dir = parse_index_page(html)  # 解析网页源码,得到文章标题和链接
        for link, values in dir.items():
            # print(link)
            html = get_page_detail(link)
            # print(html)
            comments_count, likes_count = values[1], values[2]
            name, title, word_age, publish_time = parse_detail_page(values[0], html)
            save_to_mysql(name, title, word_age, publish_time, comments_count, likes_count)
            write_to_file(name, title, word_age, publish_time,  comments_count, likes_count)


if __name__ == '__main__':
    main()

读取数据

import pymysql
import pandas as pd
import numpy as np
import matplotlib as plt
%matplotlib inline
conn = pymysql.connect(host='localhost', user='root', password='123', db='aa', port=3306, charset='utf8')
sql = 'select * from exercises'
df =  pd.read_sql(sql, conn)
del df['id']
df.head()

结果


image.png

2、数据处理

查看数据总体信息

df.info()

返回


image.png
  • 将字数字段中的'字数'清除,修改为int类型
df['word_age'] = df['word_age'].apply(lambda x:x[3:])
df['word_age'] = df['word_age'].astype(int)
  • 删除publish_time字段中的*号,修改为datetime类型
df['publish_time'] = df['publish_time'].str.strip('*')
df['publish_time'] = pd.to_datetime(df['publish_time'])
  • 修改字段comments_count、likes_count为int类型
df['comments_count'] = df['comments_count'].astype('int')
df['likes_count'] = df['likes_count'].astype('int')
  • 将发布时间定位在2019年2月24号之前,正好一周
df = df.loc[~(df['publish_time']>='2019-02-25')]
df.index = list(range(0,df.shape[0]))

3、数据分析

  • 文章篇数


    image.png
grouped_name = df.groupby('name')
grouped_name.count().sort_values(by='title',ascending=False).head()

返回


image.png
grouped_name.count().min()

返回


image.png
grouped_name.count().sort_values(by='title',ascending=False).apply(lambda x:x.cumsum()/x.sum()).reset_index()['title'].plot()

返回


image.png
grouped_name.count().reset_index()['title'].plot.hist()

返回

image.png
小结:
一共有74为简书作者向专栏投稿,共计516篇;
平均每人投稿7篇,截止到第11周,说明有部分同学少交了几次作业;
提交文章数最多为25篇,最少为1篇;
提交文章数排名前五的用户为Spareribs、1点点De小任性丶、夜希辰、凡人求索、蜗牛上高速c;
排名前1/3的用户贡献了60%的文章数,有点二八原则的趋势;
约1/3的用户的投稿量少于三篇。
  • 文章字数


    image.png

    竟然有写了十多个字,甚至还有0个的,选了几个点进去看了一下,这些文章大都是以图片形式呈现的,所以不算在字数里面,删除这些字数为0的计算平均值。


    image.png
grouped_name.sum().sort_values(by='word_age',ascending=False).head()

返回


image.png
grouped_name.sum().sort_values(by='word_age',ascending=False).head()

返回


image.png
grouped_name.sum().sort_values(by='word_age',ascending=False).apply(lambda x:x.cumsum()/x.sum()).head(15)['word_age']

返回

image.png
小结:
小组成员总计写了411305个字,平均每人写了5539个字,最多的写了28803字;
部分文章以图片形式呈现,字数比较少,实际上小组成员的总结并不止这41万字;
字数排名前五的用户为夜希辰、王阿根、凡人求索、怀柔小龙虾、Spareribs,文章最多的并不代表字数最多,这些用户的每篇总结都很详细;
字数排名前十五的用户贡献了55%的字数,大家都很努力啊!
  • 评论数


    image.png
comment = df.pivot_table(index='name',values=['comments_count','title'],aggfunc={'comments_count':'sum','title':'count'})
head_comment = comment.sort_values(by='comments_count',ascending=False).head()
head_comment

返回


image.png
df.sort_values(by='comments_count',ascending=False).head(1)

返回


image.png
image.png

小结:
总计获得225条评论,平均每篇文章获得3条评论,这里面还包括本人回复的,评论不是很多;
评论数排在前五的用户为夜希辰、凡人求索、Spareribs、小佳数据分析、Lykit01,前两位的平均评论可以达到每篇两条;
评论数最高的文章是专栏的第一篇文章:关于0基础入门数据分析的,大家果然都是奔着转行去的;
约75%的用户总评论数小于等于3条,其中包括大量0评论用户。
小组成员之间的互动还是比较低的。

  • 喜欢数


    image.png
df.sort_values(by='likes_count',ascending=False).head(1)

返回


image.png
grouped_name.sum().sort_values(by='likes_count',ascending=False).head()

返回


image.png

小结:
总计获得1501个赞,平均每人获赞20个;
点赞数最多的文章是可视化神器--Plotly,共获得152个赞,贡献了约10%的点赞数;
点赞数排名前五的用户分别为Spareribs、凡人求索、estate47、夜希辰、1点点De小任性丶,用户estate47写的字不是很多,但是凭借可视化神器--Plotly一文拔得头筹。

  • 发布时间
df['hour'] = df['publish_time'].apply(lambda x:x.hour)
plt.rcParams['figure.figsize'] = (9,5)
df['hour'].value_counts().plot.bar()

返回


image.png

小结:
大部分同学都在下午16点之后或者中午11点提交作业,其中晚上19点是提交的高峰期;
凌晨1点到早上7点,偶尔会有同学提交作业。

  • 每周提交情况
df['year'] = df['publish_time'].apply(lambda x:x.isocalendar()[0])
df['week'] = df['publish_time'].apply(lambda x:x.isocalendar()[1])
df.groupby(['year','week']).count()['name']

返回


image.png

第一篇投稿时间是2018年的第49周,但是有两篇2018年39周和48周的文章,猜测原因在于这些作者将之前的文章投稿到了专栏,此处将这两篇的投稿时间设为前一篇文章的投稿时间

df.loc[df['week']==39,['year','week']] = np.NaN
df.loc[df['week']==48,['year','week']] = np.NaN
df = df.fillna(method='bfill')
df.groupby(['year','week']).count()['name'].plot()

返回


image.png

图中的第一周应该不是正式开始的第一周吧?群主发布学习计划,算个预热,投稿文章不多。

  • 看一下小组成员提交文章的时间间隔
df.groupby('name').apply(lambda x: x['week']-x['week'].shift(-1)).replace({-51.0:1.0,-50.0:2.0,-49.0:3.0,-1.0:0.0}).value_counts().plot.bar()

返回


image.png

小结:
刚开始投稿比较积极,后面有些疲软,特别是春节期间,投稿量大幅下降;
投稿最多的时候能达到80篇,现在的投稿量只能达到当时的一半,部分同学未能坚持投稿;
大部分同学的投稿是1周,投稿周期最长可达4周,部分同学一周会上传多篇文章。

  • 看看哪些用户有在11周内提交至少11文章呢?
name_gp = df.groupby('name').count()
name_gp.loc[name_gp['title']>=11].reset_index()

返回


image.png
everyweek_name = name_gp.loc[name_gp['title']>=11].reset_index()['name']
df.loc[df['name'].isin(everyweek_name)].groupby('name')['week'].nunique()

返回


image.png

小结:
用户1点点De小任性丶、Greatsmile、Lykit01、Spareribs、cynthia猫、estate47、yimengtianya1、凡人求索、夜希辰、小佳数据分析、张叁疯、王阿根、蜗牛上高速c在前11周内都提交了至少11篇文章,其中大部分用户均有一周提交多篇文章的行为

  • 每周最受欢迎的作者和文章,受欢迎度=0.2 * 文章字数+0.2 * 评论数+0.6 * 点赞数
def get_popularity(x):
    score = 0.2*x['word_age'] + 0.2*x['comments_count'] + 0.6*x['likes_count']
    return score
df['score'] = df.apply(get_popularity,axis=1)
df.groupby(['year','week']).apply(lambda x: x.loc[x['score'].idxmax()])['title']

返回


image.png

每周最受欢迎文章涵盖了数据分析师所要具备的多种技能

grouped_week = df.groupby(['year','week','name'])
score_week = grouped_week.sum().reset_index()
best_score_week = score_week.groupby(['year','week']).apply(lambda x: x.loc[x['score'].idxmax()])
best_score_week['name'].value_counts()

返回


image.png

小结:
凡人求索、王阿根和夜希辰多次获得周最受欢迎作者。
学习小组正式成立之后,投稿量先上升再下降,刚开始大家还是很积极的,后面有点坚持不下去;
过年那段时间投稿量大幅下降,最近渐渐有在回升,过完年,大家开始慢慢回到学习上了;
用户凡人求索、王阿根和夜希辰多两次获得周最受欢迎作者;
每周最受欢迎的文章包括数据分析常用库的使用、业务知识、统计学、机器学习、爬虫等。

总结

1.截至2019.2.24日,,一共有74位小组成员提交了共516篇文章。
2.一天中提交作业的时段在16点之后或者中午11点,19点是高峰期。
3.平均每位小组成员发布了7篇文章。最多的已发布25篇。
4.投稿量最多的时候一周能达到80篇,现在投稿量仅为一半,部分同学未能坚持投稿。
5.大部分同学的投稿周期是1周,投稿周期最长可达4周,部分同学一周会上传多篇文章。
6.平均写作字数5539个。最多的累积写作字数达到28803个。
7.小组成员之间的互动较低,成员更愿意点赞,较少发表评论。
8.用户夜希辰、王阿根和凡人求索多次获得周最受欢迎作者。
9.比较受欢迎的文章涵盖了数据分析师所要具备的多种技能。

相关文章

网友评论

    本文标题:爬取简书专栏文章,分析用户提交作业的情况

    本文链接:https://www.haomeiwen.com/subject/yjzbuqtx.html