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

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

作者: 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