基于Python的网络爬虫可视化分析

148 阅读10分钟

1 背景分析

在互联网技术迅速发展的背景下,网络数据呈现出爆炸式增长,对数据的应用需要在大量数据中记性挖掘搜索,搜索引擎结合这一需求就应运而生,不只是搜索数据信息,还要帮助人们找到需要的结果被人们所应用。信息数据的处理就需要爬虫技术加以应用来收集网络信息。作为搜索引擎的重要组成部分,网络爬虫的设计直接影响着搜索引擎的质量。网络爬虫是一个专门从万维网上下载网页并分析网页的程序。它将下载的网页和采集到的网页信息存储在本地数据库中以供搜索引擎使用。网络爬虫的工作原理是从一个或若干初始网页的链接开始进而得到一个链接队列。伴随着网页的抓取又不断从抓取到的网页里抽取新的链接放入到链接队列中,直到爬虫程序满足系统的某一条件时停止。 Python语言简单易用,现成的爬虫框架和工具包降低了使用门槛,具体使用时配合正则表达式的运用,使得数据抓取工作变得生动有趣。在数据搜索方面,现在的搜索引擎虽然比刚开始有了很大的进步,但对于一些特殊数据搜索或复杂搜索,还不能很好的完成,利用搜索引擎的数据不能满足需求,网络安全,产品调研,都需要数据支持,而网络上没有现成的数据,需要自己手动去搜索、分析、提炼,格式化为满足需求的数据,而利用网络爬虫能自动完成数据获取,汇总的工作,大大提升了工作效率。 网络在我们的生活中越来越重要,网络的信息量也越来越大,研究该课题可以更好的理解网络爬虫的原理以及可视化分析的作用。

2 需求分析

现在的社会已经进入了信息时代,尤其是网络购物成为一种很普遍的购物方式,大数据的获取和分析对于促进经济发展有着重要的意义。掌握消费者的爱好和习惯,有助于商家及时的调整商品的类型和定价。 手机在我们的日常生活中使用的越来越频繁,为了更好的掌握消费者对于手机品牌、价格以及店铺的喜好程度,我们选取京东网站的手机产品作为我们研究的目标,通过网络爬虫技术获取网站的数据,利用数据库技术存储数据,最后用可视化分析的形式给出我们最终的研究结果。

3 网络爬虫技术简介

网络爬虫技术,别名“网络蜘蛛”,指的就是一种通过依照既定程序自动获取网页信息或脚本的技术。其可以在互联网当中帮助搜索引擎下载各类信息资料,并通过依次进行数据的采集和分析处理。最后完成数据的统一存储。当程序处于正常运行阶段时,爬虫会从一个或多个初始URL开始下载网页内容,随后依托搜索方式或内容匹配法将网页中其所需内容进行精准“抓取”,与此同时爬虫也会不间断地从网页中获取新URL。当爬虫检索到的信息满足停止条件时将自动停止检索。此时其将自动进入到抓取数据的处理环节,通过构建索引并妥善存储数据,使得用户可以依照自身的实际需求随时提取、查阅数据库中的数据资料。基于Python的网络爬虫技术,因使用了Python编写程序,可以抛弃传统笨重的IDE,仅使用一个文本编辑器便可以基本完成开发网络爬虫技术功能,为技术人员的编程工作提供巨大便利。加之Python本身拥有相对比较完善的爬虫框架,可支持迅速挖掘、提取和处理信息数据等程序任务。在充分发挥Python强大网络能力下,即便面对海量的信息数据检索要求,只通过编写少数代码便可以有效完成网页下载并利用其网页解析库,准确解读和表达各网页标签,以有效提升抓取数据的质量水平。

4 详细设计与功能实现

项目设计主要分为几个步骤:根据需求,确定我们需要爬取的网站和数据类型;通过Python爬虫技术对网页进行解析;将数据持久化,存储到数据库中,以便于随时提取、查询、添加数据;通过获取的数据进行可视化分析,得到我们的结论。本项目以手机为例,对京东商城中50多个手机品牌(华为、Apple、小米、OPPO、VIVO……)进行了数据的爬取,获得了超过5万条的数据,包括商品品牌、商品名称、售价、店铺信息、评价量等信息,并将数据存储到MySQL数据库中。在数据分析阶段,我们对获取到的数据从多个角度进行了可视化分析,并给出了我们的结论。

1、爬虫部分

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium import webdriver
from bs4 import BeautifulSoup
from urllib import parse
import time
import pymysql
​
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
browser = webdriver.Chrome(chrome_options=chrome_options)
wait = WebDriverWait(browser, 10)
​
def get_url(n, word,pinpai):
    print('正在爬取第' + str(n) + '页')
    # 确定搜索商品的内容
    keyword = {'keyword':word}
    # 页面n与参数page的关系
    page = '&page=%s' % (2 * n - 1)
    pinpai='&ev=exbrand_%s'%(pinpai)
    url = 'https://search.jd.com/Search?' +parse.urlencode(keyword) +pinpai+'&enc=utf-8' + page
    print(url)
    return url
​
def parse_page(url,pinpai):
    print('爬取信息并保存中...')
    browser.get(url)
    # 把滑轮慢慢下拉至底部,触发ajax
    for y in range(100):
        js = 'window.scrollBy(0,100)'
        browser.execute_script(js)
        time.sleep(0.1)
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#J_goodsList .gl-item')))
    html = browser.page_source
    soup = BeautifulSoup(html, 'lxml')
    # 找到所有商品标签
    goods = soup.find_all('li', class_="gl-item")
    # 遍历每个商品,得到每个商品的信息
    for good in goods:
        num = good['data-sku']
        tag = good.find('div', class_="p-price").strong.em.string
        money = good.find('div', class_="p-price").strong.i.string
        #就是京东有些商品竟然没有店铺名,导检索store时找不到对应的节点导致报错
        store = good.find('div', class_="p-shop").span
        commit = good.find('div', class_="p-commit").strong.a.string
        name = good.find('div', class_="p-name p-name-type-2").a.em
        image = good.find('div', class_="p-img").a.img.get('src')  
        detail_addr = good.find('div', class_="p-img").find('a')['href']
        
        if store is not None:
            new_store = store.a.string
        else:
            new_store = '没有找到店铺 - -!'
        new_name = ''
        for item in name.strings:  
            new_name = new_name + item
        product = (num,pinpai,new_name,money,new_store,commit,image,detail_addr)
        save_to_mysql(product)
        print(product)
​
def save_to_mysql(result):
    db = pymysql.connect("localhost", "root", "", "jd")
    cursor = db.cursor()  # 使用cursor()方法获取操作游标
    sql = "INSERT INTO information(info_num,info_brand,info_name,info_money,info_store,info_commit,info_image,info_detail) \
            VALUES ('%s','%s', '%s','%s', '%s','%s', '%s', '%s')" % \
            (result[0], result[1],result[2],result[3],result[4],result[5],result[6],result[7])  
    try:
        cursor.execute(sql)  # 执行sql语句
        db.commit()  # 提交到数据库执行
        print('保存成功!')
    except:
        db.rollback()  # 发生错误时回滚
        print('保存失败!')
    db.close()  # 关闭数据库连接
   
def main():
    try:
        word = input('请输出你想要爬取的商品:')
        pinpai = input('请输出你想要爬取的品牌:')
        pages = int(input('请输入你想要抓取的页数(范围是1-100):'))
       
        # 京东最大页面数为100
        if 1 <= pages <= 100:
            page = pages + 1
            for n in range(1, page):
                url = get_url(n, word,pinpai)
                parse_page(url,pinpai)
            print('爬取完毕!')
            browser.close()
        else:
            print('请重新输入!')
            main()
    except Exception as error:
        print('出现异常!', error)
        return None
​
if __name__ == '__main__':
main()
​

2、可视化分析部分

import numpy as np
import matplotlib
import re
import matplotlib.pyplot as plt
#原始数据
data = pd.read_csv("information.csv",header=0,encoding="gbk",usecols = [1,2,3,4,5])#读取csv数据文件
data = pd.DataFrame(data)
print("Number of samples: %d" % len(data))
data.fillna(0)
​
#数据预处理
def type(x):
    words1=['移动电源','充电宝','数据线','音箱','麦克风','耳机','手机壳','钢化膜','保护','支架']
    for element in words1:
        if x.find(element)!=-1: 
            return(str('配件-')+element)
        elif x.find('二手')!=-1:
            return('二手手机')
    else:
        return('新手机')        
​
def type2(x):
    words1=['老人','学生','商务','5G','智能']
    for element in words1:
        if x.find(element)!=-1: 
            return(element+str('手机'))
    else:
        return('智能手机') 
    
def trans(c):
    if c.find('+')!=-1:
        c=c.replace('+','')
    if c.find('万')!=-1:
        c=c.replace('万','')
        c=float(c)*10000
    c=str(int(c))
    return c
​
def check_contain_eng(check_str):
    if check_str.find('(')!=-1 or check_str.find(')')!=-1 or check_str.find('-')!=-1:
        check_str=check_str.replace('(','')
        check_str=check_str.replace(')','')
        check_str=check_str.replace('-','')
    if u'\u4e00' <=check_str<= u'\u9fff':
        check_str = re.sub('[a-zA-Z]','',check_str)
    return str(check_str)
           
data["商品类型"] = data["info_name"].apply(type)    
data["info_commit"] = data["info_commit"].apply(trans)
data["info_commit"] = data["info_commit"].apply(pd.to_numeric)
data["info_brand"] = data["info_brand"].apply(check_contain_eng)
data["手机类型"] = data["info_name"].apply(type2) 
data.fillna(0)
​
#对数据进行数据类型的转换以及数据筛选
data['info_money'] = data['info_money'].astype(int)
data['info_commit'] = data['info_commit'].astype(int)
data1=data[(data['商品类型']=='新手机')]
data2=data[(data['商品类型']=='二手手机')]
​
#不同品牌的评论量占比
font = {
    'family' : 'SimHei',
    'size'   : 25
}
matplotlib.rc('font', **font);
​
plt.rcParams['figure.figsize'] = (20.0, 20.0)
gb1 = data1.groupby(
    by=['info_store'], 
    as_index=False
)['info_commit'].agg({
    'info_commit':np.sum
});
g1=gb1[gb1['info_commit']>2000000]
plt.pie(g1['info_commit'], labels=g1['info_store'], autopct='%0.1f%%');
plt.title('不同店铺销量分析')
plt.legend(loc='lower right')
plt.show()
​
#不同店铺平均售价分析
gb1 = data1.groupby(
    by=['info_store'], 
    as_index=False
)['info_money'].agg({
    'info_money':np.average
});
g1=gb1[(gb1['info_money']>8000)]
index = np.arange(g1['info_store'].size);
plt.barh(index, g1['info_money'], height = 0.5,color='R');
​
plt.yticks(index,g1['info_store'])
plt.xlabel('售价8000元以上店铺平均售价分析')
plt.ylabel('商品售价')
#不同价格区间购买人数
data1['info_money'] = data1['info_money'].astype(int)
bins = [min(data1['info_money'])-1,500,1000,3000,5000, max(data1['info_money'])+1];
labels = ['500及以下','500到1000', '1000到3000','3000到5000', '5000以上'];
​
价格分层 = pd.cut(data1['info_money'], bins, labels=labels)
data1['info_money'] = 价格分层
gb1 = data1.groupby(
    by=['info_money'], 
    as_index=False
)['info_commit'].agg({
    'info_commit':np.sum
});
plt.pie(gb1['info_commit'], labels=gb1['info_money'], autopct='%.2f%%');
plt.title('不同价格区间购买人数百分比')
plt.show()
​
​
#均价3000元以上手机品牌平均售价
data1=data[(data['商品类型']=='新手机')]
gb1 = data1.groupby(
    by=['info_brand'], 
    as_index=False
)['info_money'].agg({
    'info_money':np.average
});
​
​
g1=gb1[gb1['info_money']>3000]
index = np.arange(g1['info_brand'].size);
plt.bar(index, g1['info_money'], width = 0.35,color='R');
plt.xticks(index,g1['info_brand'])
plt.xlabel('均价3000元以上手机品牌')
plt.ylabel('商品售价')
​
#均价1000-3000元以上手机品牌平均售价
g2=gb1[(gb1['info_money']>1000)&(gb1['info_money']<3000)]
index = np.arange(g2['info_brand'].size);
plt.bar(index, g2['info_money'], width = 0.35,color='R');
plt.xticks(index,g2['info_brand'])
plt.xlabel('均价1000-3000元手机品牌')
plt.ylabel('商品售价')
​
#散点图———————商品价格和购买人数关系
data3=data[(data['商品类型']=='新手机')&(data['info_money']<15000)]
plt.plot(data3['info_money'],data3['info_commit'],'.', color='blue')
plt.xlabel('商品售价')
plt.ylabel('购买人数')
plt.title('商品价格和购买人数关系')
plt.grid(True)
plt.show()
​
#不同手机类型平均价格分析
data1=data[(data['商品类型']=='新手机')]
gb1 = data1.groupby(
    by=['手机类型'], 
    as_index=False
)['info_money'].agg({
    'info_money':np.average
});
​
index = np.arange(gb1['手机类型'].size);
plt.bar(index, gb1['info_money'], width = 0.35,color='R');
plt.xticks(index,gb1['手机类型'])
plt.xlabel('手机类型')
plt.ylabel('商品平均售价')
# plt.legend(gb1)
​
​
#不同手机类型购买人数占比分析
gb1 = data1.groupby(
    by=['手机类型'], 
    as_index=False
)['info_commit'].agg({
    'info_commit':np.sum
});
​
plt.pie(gb1['info_commit'], labels=gb1['手机类型'], autopct='%0.1f%%);
plt.title('不同手机类型购买人数占比')
plt.legend(loc='lower right')
plt.show()
​