6

我用Python爬虫爬取并分析了C站前100用户最高访问的2000篇文章

 2 years ago
source link: https://blog.csdn.net/FRIGIDWINTER/article/details/121094823
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近系统地学习了正则表达式,发现正则表达式最大的应用之一——网络爬虫之前一直没有涉猎,遂借此契机顺带写一个爬虫的demo备用。选择对象是CSDN排行榜前100用户,各自按访问量从高到低排序的前20篇文章,使用一些简单的数据分析手段看看技术热点,方便今后拓宽技术栈。

主要爬取的数据是文章标题和访问量,先总体可视化总体文章的技术关键词;然后按访问量分组,可视化每个访问段的技术热点。

获得服务器API

首先我们要知道通过什么接口可以获得网站数据:首先进入博客总榜,按F12进入控制台,选中Network选项卡监视网络请求,然后刷新网页。从下图可以看到在API"https://blog.csdn.net/phoenix/web/blog/all-rank?page=1&pageSize=20"中我们可以拿到我们想要的用户信息——主要是用户名
在这里插入图片描述
现在到用户博客首页,同样地,按F12进入控制台,选中Network选项卡监视网络请求,然后点击按访问量排序,则可以发现另一个关键APIhttps://blog.csdn.net/community/home-api/v1/get-business-list?page=1&size=20&businessType=blog&orderby=ViewCount&noMore=false&username={},如下图所示。
在这里插入图片描述
我们与服务器的交互就依靠这两个API进行。

程序总体设计

思考一下,我们总共有如下的公共变量:

# 请求头
headers = {
            'User-Agent':
            'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
        }
# 排行榜url
rankUrl = "https://blog.csdn.net/phoenix/web/blog/all-rank?page={}&pageSize=20"
# 按访问量排行的文章列表
mostViewArtical = "https://blog.csdn.net/community/home-api/v1/get-business-list?page=1&size=20&businessType=blog&orderby=ViewCount&noMore=false&username={}"

userNames =[] # 用户名列表
titleList = []	# 文章标题列表
viewCntList = [] # 访问量列表

为便于管理,引入一个类进行爬虫,专门负责与服务器进行数据交互

class GetInfo:
    def __init__(self) -> None:
        # 请求头
        self.headers = {
            'User-Agent':
            'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
        }
        # 排行榜url
        self.rankUrl = "https://blog.csdn.net/phoenix/web/blog/all-rank?page={}&pageSize=20"
        # 按访问量排行的文章列表
        self.mostViewArtical = "https://blog.csdn.net/community/home-api/v1/get-business-list?page=1&size=20&businessType=blog&orderby=ViewCount&noMore=false&username={}"

        self.userNames = []
        self.titleList, self.viewCntList = [], []

交互完成后,再使用别的库进行数据分析,将两个过程分离开

用户名爬取

定义一个私有的初始化函数

def __initRankUsrName(self):
    usrNameList = []
    for i in range(5):
        response = requests.get(url=self.rankUrl.format(i),
                                headers=self.headers)
        response.encoding = 'utf-8'
        response.raise_for_status()
        soup = BeautifulSoup(response.text, 'html.parser')
        information = json.loads(str(soup))
        for item in information['data']['allRankListItem']:
            usrNameList.append(item['userName'])
    return usrNameList

这里获取用户名主要是为了动态生成第二个API

再定义一个私有函数,输入参数是用户名列表:

def __initArticalInfo(self, usrList):
    titleList = []
    viewCntList = []
    for name in usrList:
        url = self.mostViewArtical.format(name)
        # print(url)
        response = requests.get(url=url, headers=self.headers)
        response.encoding = 'utf-8'
        response.raise_for_status()
        titleList.extend(re.findall(r"\"title\":\"(.*?)\"", response.text))
        viewCntList.extend(re.findall(r"\"viewCount\":(.*?),", response.text))
    return titleList, viewCntList

这里我使用正则表达式直接处理字符串,并返回文章标题列表、访问量列表。可以随便访问一个API做实验,这里以我的用户名为例,可以看到要获取文章标题就是以\"title\":\"(.*?)\"去匹配,其中\用于转义;要获取访问量就是以\"viewCount\":(.*?),去匹配,访问数字没有加引号。

事实上,用正则匹配不需要将返回的字符串加载为Json字典,可能有更快的处理效率(但不如json灵活)

在这里插入图片描述
这个爬虫类就设计好了,完整代码如下:

class GetInfo:
    def __init__(self) -> None:
        # 请求头
        self.headers = {
            'User-Agent':
            'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
        }
        # 排行榜url
        self.rankUrl = "https://blog.csdn.net/phoenix/web/blog/all-rank?page={}&pageSize=20"
        # 按访问量排行的文章列表
        self.mostViewArtical = "https://blog.csdn.net/community/home-api/v1/get-business-list?page=1&size=20&businessType=blog&orderby=ViewCount&noMore=false&username={}"

        self.userNames = self.__initRankUsrName()
        self.titleList, self.viewCntList = self.__initArticalInfo(
            self.userNames)

    def __initArticalInfo(self, usrList):
        titleList = []
        viewCntList = []
        for name in usrList:
            url = self.mostViewArtical.format(name)
            # print(url)
            response = requests.get(url=url, headers=self.headers)
            response.encoding = 'utf-8'
            response.raise_for_status()
            titleList.extend(re.findall(r"\"title\":\"(.*?)\"", response.text))
            viewCntList.extend(
                re.findall(r"\"viewCount\":(.*?),", response.text))
        return titleList, viewCntList

    def __initRankUsrName(self):
        usrNameList = []
        for i in range(5):
            response = requests.get(url=self.rankUrl.format(i),
                                    headers=self.headers)
            response.encoding = 'utf-8'
            response.raise_for_status()
            soup = BeautifulSoup(response.text, 'html.parser')
            information = json.loads(str(soup))
            for item in information['data']['allRankListItem']:
                usrNameList.append(item['userName'])
        return usrNameList


info = GetInfo()

使用也很方便,只需要实例化调用其中的列表属性即可。

将文本数据存成csv格式,先设计表头:

if not os.path.exists("articalInfo.csv"):
    #创建存储csv文件存储数据
    with open('articalInfo.csv', "w", encoding="utf-8-sig", newline='') as f:
        csv_head = csv.writer(f)
        csv_head.writerow(['title', 'viewCnt'])

注意编码格式为utf-8-sig,否则会乱码

接下来存数据:

length = len(info.titleList)
for i in range(length):
    if info.titleList[i]:
        with open('articalInfo.csv', 'a+', encoding='utf-8-sig') as f:
            f.write(info.titleList[i] + ',' + info.viewCntList[i] + '\n')

总体数据可视化

新建一个模块专门用于可视化数据,与爬虫分离开,因为爬虫是慢IO过程,会影响调试效率,后面可以试试用协程来处理爬虫。

首先,把爬虫的信息读取到txt文件去

df = pd.read_csv('articalInfoNor.csv', encoding='utf-8-sig',usecols=['title', 'viewCnt'])
titleList = ','.join(df['title'].values)
with open('text.txt','a+', encoding='utf-8-sig') as f:
    f.writelines(titleList)

如何返回分词结果:

def getKeyWordText():
    # 读取文件信息
    file = open(path.join(path.dirname(__file__), 'text.txt'), encoding='utf-8-sig').read()
    return ' '.join(jieba.cut(file))

借助词云库可视化一下:

bg_pic = imread('2.jpg')

#生成词云
wordcloud = WordCloud(font_path=r'C:\Windows\Fonts\simsun.ttc',mask=bg_pic,background_color='white',scale=1.5).generate(text)
image_colors = ImageColorGenerator(bg_pic)

#显示词云图片
plt.imshow(wordcloud)
plt.axis('off')
plt.show()

#保存图片
wordcloud.to_file('test.jpg')

在这里插入图片描述

这个大大的“的”是什么鬼?显然高频关键词里有太多语气助词、连接词,我们最好设置一个停用词列表把这些明显不需要的词屏蔽掉。我这里采用修饰器的方法让代码更简洁,关于修饰器的内容可以参考Python修饰器

def splitText(mode):
    stopWords = ["的","与","和","建议","收藏","使用","了","实现","我","中","你","在","之","年","月","日"]
    def warpper(func):
        def warp():
            textSplit = func()
            if mode:
                temp = [word for word in textSplit if word not in stopWords]
                return ' '.join(temp)
            else:
                return ' '.join(textSplit)
        return warp
    return warpper

当mode=True时启用屏蔽,否则关闭屏蔽,那么之前的函数应该修改为:

# 返回关键词文本
@splitText(False)
def getKeyWordText():
    # 读取文件信息
    file = open(path.join(path.dirname(__file__), 'text.txt'), encoding='utf-8-sig').read()
    return jieba.cut(file)

再来一次:

在这里插入图片描述

现在就正常多了。可以看到Python和Java是绝对的领先,之后是各位总结的方法论等等,算法的词频反而不高?

我把数据进一步分层为

1、访问量>10W
2、访问量5W~10W
3、访问量1W~5W
4、访问量5K~1W
5、访问量5K以下

先来看看数据分布情况:

在这里插入图片描述
我猜如果分段分得再细一点可能趋于正态分布~

分组可视化看看:

在这里插入图片描述

>10W的词云

在这里插入图片描述

5~10W的词云

在这里插入图片描述

1~5W的词云

在这里插入图片描述

5k~1W的词云

感觉从这里开始更百花齐放一些,似乎也更关注具体问题的解决

在这里插入图片描述

5k以下的词云

不得不感叹python在每个阶段都是牌面

import requests
from bs4 import BeautifulSoup
import os, json, re, csv

class GetInfo:
    def __init__(self) -> None:
        # 请求头
        self.headers = {
            'User-Agent':
            'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
        }
        # 排行榜url
        self.rankUrl = "https://blog.csdn.net/phoenix/web/blog/all-rank?page={}&pageSize=20"
        # 按访问量排行的文章列表
        self.mostViewArtical = "https://blog.csdn.net/community/home-api/v1/get-business-list?page=1&size=20&businessType=blog&orderby=ViewCount&noMore=false&username={}"

        self.userNames = self.__initRankUsrName()
        self.titleList, self.viewCntList = self.__initArticalInfo(
            self.userNames)

    def __initArticalInfo(self, usrList):
        titleList = []
        viewCntList = []
        for name in usrList:
            url = self.mostViewArtical.format(name)
            # print(url)
            response = requests.get(url=url, headers=self.headers)
            response.encoding = 'utf-8'
            response.raise_for_status()
            titleList.extend(re.findall(r"\"title\":\"(.*?)\"", response.text))
            viewCntList.extend(
                re.findall(r"\"viewCount\":(.*?),", response.text))
        return titleList, viewCntList

    def __initRankUsrName(self):
        usrNameList = []
        for i in range(5):
            response = requests.get(url=self.rankUrl.format(i),
                                    headers=self.headers)
            response.encoding = 'utf-8'
            response.raise_for_status()
            soup = BeautifulSoup(response.text, 'html.parser')
            information = json.loads(str(soup))
            for item in information['data']['allRankListItem']:
                usrNameList.append(item['userName'])
        return usrNameList
        
info = GetInfo()

if not os.path.exists("articalInfo.csv"):
    #创建存储csv文件存储数据
    with open('articalInfo.csv', "w", encoding="utf-8-sig", newline='') as f:
        csv_head = csv.writer(f)
        csv_head.writerow(['title', 'viewCnt'])

length = len(info.titleList)
for i in range(length):
    if info.titleList[i]:
        with open('articalInfo.csv', 'a+', encoding='utf-8-sig') as f:
            f.write(info.titleList[i] + ',' + info.viewCntList[i] + '\n')
from wordcloud import WordCloud,ImageColorGenerator
import matplotlib.pyplot as plt
from imageio import imread
import jieba
import pandas as pd
from os import path

df = pd.read_csv('articalInfoCom.csv', encoding='utf-8-sig',usecols=['title', 'viewCnt'])
titleList = ','.join(df['title'].values)
with open('text.txt','a+', encoding='utf-8-sig') as f:
    f.writelines(titleList)

def splitText(mode):
    stopWords = ["的","与","和","建议","收藏","使用","了","实现","我","中","你","在","之","年","月","日"]
    def warpper(func):
        def warp():
            textSplit = func()
            if mode:
                temp = [word for word in textSplit if word not in stopWords]
                return ' '.join(temp)
            else:
                return ' '.join(textSplit)
        return warp
    return warpper

# 返回关键词文本
@splitText(True)
def getKeyWordText():
    # 读取文件信息
    file = open(path.join(path.dirname(__file__), 'text.txt'), encoding='utf-8-sig').read()
    return jieba.cut(file)


text = getKeyWordText()
#读取txt文件、背景图片
bg_pic = imread('2.jpg')

#生成词云
wordcloud = WordCloud(font_path=r'C:\Windows\Fonts\simsun.ttc',mask=bg_pic,background_color='white',scale=1.5).generate(text)
image_colors = ImageColorGenerator(bg_pic)

#显示词云图片
plt.imshow(wordcloud)
plt.axis('off')
plt.show()

#保存图片
wordcloud.to_file('test.jpg')

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK