成为数据科学家的第一个project

通过对太平洋电脑网二手手机交易论坛的爬虫数据收集与分析,揭示了用户交易偏好及市场趋势,苹果手机占据主导地位,国产手机紧随其后。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 项目要求:

内容完整程度
可用性(可操作、易操作、美观)

  • 确定项目目标:

分析某二手手机交易论坛上的帖子,从中得出其用户行为的描述,为用户进行分类,输出洞察报告。

  • 从互联网获取数据:

使用python构建一个定向爬虫,爬取太平洋电脑网的二手手机交易论坛http://itbbs.pconline.com.cn/es/f240027.html的数据。
首先观察网页,
网页央视
可以看到网页中有很多有价值的信息,包括分类信息,标题,用户,时间等,因此我们确信通过对该网页的爬取可以获得我们想要的数据。
下面开始分析网页源码,经过分析我们可以发现每个帖子的信息都在如下的结构中:网页源码

每个帖子的信息都在class=’cf’的<li>标签中,下面关注我们需要爬取的信息:用户uid在class=’area-author’的<div>标签的data-uid属性中,分类信息在class=’tit-sub’的<a>标签的string中,标题在class=’tit-toe’的<a>标签的string中,时间在class=’time’的<span>标签的string中。
经过分析,我们已经可以构建爬虫了,就像平常一样使用request库和beautifulsoup4库即可,下面是爬虫部分源码的截图,完整源码见附录1。
爬虫
目标网页论坛像大部分论坛一样是分页的,某一页只显示特定数量的帖子,因此需要爬取多页数据,但是由于目标网页内部逻辑,当爬取页数过多会出现很多重复数据,经过试验发现爬取小于100页时数据没有重复,因此我们爬取100页的数据,爬取结果有1032条数据。数据的形态如下:
爬取的数据

  • 数据清洗/整理:

首先观察发现,该网站中大于95%的帖子是用来出售手机的,因此放弃对买/卖/换的分类,全部认为是出售二手手机。
由于数据是以自然语言的形式存在的,需要对数据进行清洗和整理,判断出所交易手机的品牌。
首先我们使用到了python的第三方库——jieba库,来进行中文的分词。再对分出的词进行词频统计,统计的部分结果如下:
词频
在这里我们采用了‘符合关键词——打标签’的方法,把所有词中能够反映出手机品牌的,按词频从高到低在python字典中对应到手机品牌,整理出的关键词结果如下:
关键词
然后我们根据人们发帖写标题的规律——重要的信息写在前面,把分类与标题拼成一个字符串,分词后对每个词寻找对应的品牌信息,若有对应则直接打上标签。
与此同时,整个帖子中都不存在关键词的,最后的标签结果会是None,这类数据直接删除,这样很多人成交后会把帖子改成《已出》等标题的无效信息就被清洗掉了。
清洗整理过后的数据再加上用户uid和时间信息,即完成了数据的清洗和整理,整理后的数据形态如下所示:
清洗整理后的数据
数据清洗和整理的源代码见附录2.

  • 描述统计&洞察结论:

有了上面的数据,我们已经可以各厂商在该论坛占比的基本情况进行统计了,统计结果如下:
初步统计

可见苹果手机在二手市场中占有绝对优势的份额达到了2/3;另人欣喜的是,国产手机华为、小米分列二、三位,而如魅族,vivo等国产手机也占有一定的份额,国产手机总共占有约1/4的份额,而当年的手机巨头三星、诺基亚等正逐步没落。

  • 选择变量&算法:

因为要研究的是这些用户与二手交易相关的行为,因此选择变量为各品牌手机拥有台数。
我们的目标是将用户分群,因此选择聚类,方法选择最简单的 K-means 算法。

  • 设定参数 & 加载算法:

我们对每个用户的手机台数进行统计,得到的部分结果如下:
用户统计
统计的源码见附录3.
然后就可以用k-means算法进行分类了,经过多次测试,发现当聚类数k=3时,分类结果的可解释性良好,分类结果如下(聚类源码见附录4)。
聚类结果
我们可以根据这张表对不同类别的人进行解读了,测试通过!

  • 报告撰写:

聚类模型结果可归为下图:
聚类模型
可见该论坛中大部分还是普通用户,人均交易约为1次,大致就是把不用的手机放到论坛上换点小钱的那种。
平台中的苹果商人数量很多,有110人,占比达到16.9%,由于人数较多导致人均交易次数被分流而很有限,只有3.4次。于此同时我们可以发现二手苹果商人对苹果手机执着程度很高,其他品牌的手机人均拥有数量均不到0.1台,应该是钟情于苹果手机的高利润了。
安卓商人这一类人数很少,仅有6人,占比仅仅1%,但是人均交易次数高达8.2次,可见由于人数较少,交易的机会也较大。其中二手三星手机交易比重最大,达3.5次,或许是因为三星作为外国企业在国内的利润较高。于此同时我们可以发现安卓商人可谓是爱好广泛,苹果、华为、小米手机人均拥有1台以上,不像二手苹果商人那样钟情于苹果市场。
以上为对用户的聚类结果,可见由于二手安卓商人人数较少,交易次数较多,在该论坛中倒卖二手安卓机的商机还是很大的。

最后附上词云图片(制作图片的源码见附录5):
词云

  • 附录:

附录1

#爬取数据
import requests
from bs4 import BeautifulSoup
allUniv=[]
def getHTML(url):
    try:
        r=requests.get(url)
        r.raise_for_status()
        r.encoding='gb2312'
        return r.text
    except:
        return ''
def getInfoList(soup):
    lils=soup.find_all('li',{'class':'cf'})
    ils=[]
    for li in lils:
        title=li.find('a',{'class':'tit-sub'}).string.replace('[','').replace(']','')
        content=li.find('a',{'class':'tit toe'}).string
        time=li.find('span',{'class':'time'}).string
        uid=li.find('div',{'class':'area-author'}).attrs['data-uid']
        ils.append([title,content,time,uid])
    return ils
def getOnePageInfo(url):
    html=getHTML(url)
    soup=BeautifulSoup(html,'html.parser')
    ils=getInfoList(soup)
    return ils
def rowFmt(row):
    f_row=[]
    for e in row:
        t=e.replace(',',',')
        f_row.append(t)
    return f_row
def main(num):
    info=getOnePageInfo('http://itbbs.pconline.com.cn/es/f240027.html')
    for i in range(2,num):
        info.extend(getOnePageInfo('http://itbbs.pconline.com.cn/es/f240027_'+str(i)+'.html'))
    fw=open('info__'+str(num)+'.csv','w')
    for row in info:
        f_row=rowFmt(row)
        try:
            fw.write(','.join(f_row)+'\n')
        except:
            print(row)
    fw.close()
    print('pages:',num,len(info),'finished')
main(100)

附录2:

#清洗,整理数据
import jieba
#[用户,品牌]
rep={'苹果':'Apple','国行':'Apple','6':'Apple','x':'Apple',
     'iphone':'Apple','7p':'Apple',
     '三星':'SAMSUNG','小米':'小米',
     '8':'Apple','8p':'Apple','7':'Apple','港版':'Apple',
     '华为':'华为',
     '6sp':'Apple',
     '荣耀':'华为','s8':'SAMSUNG','note8':'SAMSUNG',
     'iphone6':'Apple','6s':'Apple','iphone7':'Apple','xs':'Apple'
     ,'魅族':'魅族','vivo':'vivo',
     '5s':'Apple','5':'Apple',
     '7plus':'Apple','iphone6s':'Apple','iphonex':'Apple',
     '魅蓝':'魅族','note10':'SAMSUNG','note9':'SAMSUNG',
     'x23':'vivo','s9':'SAMSUNG',
     '8plus':'Apple',
     'mix2s':'小米',
     'iphone7p':'Apple',
     'oppo':'oppo',
     '6p':'Apple',
     '红米':'小米',
     'iphone4':'Apple',
     'mate9':'华为',
     'iphone7plus':'Apple',
     '诺基亚':'Nokia',
     '粪':'Apple',
     'note6':'SAMSUNG','x20a':'vivo','p20':'华为','mate10pro':'华为',
     'mate10':'华为','p9':'华为','s7edge':'SAMSUNG',
     'mix2':'小米',
     'xsm':'Apple','xsmax':'Apple',
     'mx5':'魅族',
     '7p128':'Apple',
     'note3':'SAMSUNG','r15':'oppo',
     'i7p':'Apple','i7':'Apple','6sp128g':'Apple','pius':'Apple',
     'pro6s':'魅族','mate8':'华为','r9':'oppo','6x':'小米','4x':'小米',
     'p10plus':'华为',
     'r17':'oppo','oppor11':'oppo','oppor11s':'oppo','oppaa73':'oppo',
     '8p64g':'Apple','iphone5s':'Apple',
     '10pro':'华为','mate9pro':'华为',
     'iphone8p':'Apple','ip6':'Apple','i6':'Apple',
     'iphone6sp':'Apple','iphone5':'Apple','iphone8':'Apple',
     'vivonex':'vivo',
     '6s128g':'Apple','6sp5.5':'Apple','nexs':'vivo'}
def listWords(filename):
    fo=open(filename,'r')
    words=[]
    counts={}
    for line in fo:
        txt=line.split(',')[1]
        t=txt.lower()
        word=jieba.lcut(t)
        words.extend(word)
    for word in words:
        counts[word]=counts.get(word,0)+1
    lst=sorted(counts.items(),key=lambda x:x[1],reverse=True)
    count=0
    for l in lst:
        count+=1
        print(l)
    print(count)
def getBrand(txt):
    words=jieba.lcut(txt)
    for word in words:
        if rep.get(word,0)!=0:
            return rep[word]
    return 0
    
def getInfo(filename):
    ils=[]
    fo=open(filename,'r')
    count=0
    for line in fo:
        line=line.replace('\n','')
        content=line.split(',')
        txt=content[0]+content[1].lower()
        time=content[2]
        uid=content[3]
        brand=getBrand(txt)
        if brand!=0:
            ils.append([uid,brand,time])
            count+=1
    print(count)
    return ils
def main(filename):
    ils=getInfo(filename)
    fw=open('clr_'+filename,'w')
    for row in ils:
        fw.write(','.join(row)+'\n')
    fw.close()
main('info__100.csv')

附录3:

#统计用户信息
#[apple,sunsung,huawei,oppo,vivo,meizu,mi,nokia]
def getBrandCount(filename):
    brandCount={}
    fo=open(filename,'r')
    for line in fo:
        line=line.replace('\n','')
        content=line.split(',')
        brandCount[content[1]]=brandCount.get(content[1],0)+1
    print(brandCount)
    return brandCount
def getUserCount(filename):
    userCount={}
    fo=open(filename,'r')
    for line in fo:
        line=line.replace('\n','')
        content=line.split(',')
        uid=content[0]
        brand=content[1]
        value=userCount.get(uid,
                            {'Apple':0,'SAMSUNG':0,'华为':0,'oppo':0,
                             'vivo':0,'魅族':0,'小米':0,'Nokia':0})
        value[brand]+=1
        userCount[uid]=value
    return userCount
def main(filename):
    uc=getUserCount(filename)
    fw=open('uc_'+filename,'w')
    count=0
    for item in uc.items():
        ls=[item[0]]
        values=list(item[1].values())
        val=[]
        for v in values:
            val.append(str(v))
        ls.extend(val)
        fw.write(','.join(ls)+'\n')
        count+=1
    fw.close()
    print(count,'finished')
main('clr_info__100.csv')

附录4:

#用户聚类
from numpy import *
def loadData(filename):
    data=[]
    fo=open(filename)
    for line in fo:
        line=line.replace('\n','')
        l1=line.split(',')
        ls=[]
        for i in range(1,9):
            ls.append(float(l1[i]))
        #print(ls)
        data.append(ls)
    fo.close()
    return data
def dist(va,vb):#calc distance
    return sqrt(sum(power((va-vb),2)))

def randCents(dataSet,k):#random centers
    n=shape(dataSet)[1]
    cents=mat(zeros((k,n)))
    for i in range(n):
        mini=min(dataSet[:,i])
        maxi=max(dataSet[:,i])
        rangei=float(maxi-mini)
        cents[:,i]=mini+rangei*random.rand(k,1)
    return cents

def kMeans(dataSet,k,getDist=dist,getCents=randCents):
    m=shape(dataSet)[0]
    cluster=mat(zeros((m,2)))
    cents=getCents(dataSet,k)
    flag=True
    while flag:
        flag=False
        for i in range(m):
            minDist=inf
            minIdx=-1
            for j in range(k):
                dist=getDist(cents[j,:],dataSet[i,:])
                if dist<minDist:
                    minDist=dist
                    minIdx=j
            if cluster[i,0]!=minIdx:
                flag=True
            cluster[i,:]=minIdx,minDist**2
        #print(cents)
        for cent in range(k):
            pts=[]
            for data in range(m):
                if cluster[data,0]==cent:
                    pts.append(dataSet[data,:])
            #pts=mat(pts)
            cents[cent,:]=mean(pts,axis=0)
    return cents,cluster
def writeMat(m,filename):
    fw=open(filename,'w')
    for i in range(shape(m)[0]):
        row=[]
        for j in range(shape(m)[1]):
            row.append(str(m[i,j]))
        fw.write(','.join(row)+'\n')
    fw.close()
def main(filename,k):
    data=loadData(filename)
    dataSet=mat(data)
    cents0,cluster0=kMeans(dataSet,k)
    print(cents0)
    writeMat(cents0,str(k)+'cents__'+filename)
    writeMat(cluster0,str(k)+'cluster__'+filename)
main('uc_clr_info__100.csv',3)

附录5:

#作图
import numpy as np
import matplotlib.pyplot as plt
from wordcloud import WordCloud
import PIL
import jieba

def drawPie(filename):
    fo=open(filename)
    fracs={}
    explode=[0.03,0.1,0.1,0.1,0.1,0.1,0.1,0.1]
    plt.axes(aspect=1)
    count=0
    for line in fo:
        line=line.replace('\n','')
        ls=line.split(',')
        fracs[ls[1]]=fracs.get(ls[1],0)+1
        count+=1
    print(fracs)
    val=fracs.values()
    key=list(fracs.keys())
    for i in range(len(key)):
        if key[i]=='华为':
            key[i]='HUAWEI'
            continue
        elif key[i]=='魅族':
            key[i]='MEIZU'
            continue
        elif key[i]=='小米':
            key[i]='MI'
            continue
    plt.pie(x=val,labels=key,explode=explode,autopct='%3.1f %%',shadow=False,labeldistance=1.1)
    plt.show()
    print(count)
    for i in fracs.items():
        print(i[0],i[1]/1032*100)

def wordcloudplot(txt):
    path = r'C:\Windows\Fonts\FZSTK.TTF'
    alice_mask = np.array(PIL.Image.open('bg.jpg'))
    wordcloud = WordCloud(font_path=path,
                          background_color="white",
                          margin=5, width=1800, height=800, mask=alice_mask, max_words=80, max_font_size=60,
                          random_state=42).generate(txt)
    wordcloud.to_file('dora.jpg')
    plt.imshow(wordcloud)
    plt.axis("off")
    plt.show()

def main():
    fo=open('info__100.csv')
    ls=[]
    count=0
    for line in fo:
        txt=line.split(',')[1]
        t=txt.lower()
        word=jieba.lcut(t)
        for w in word:
            if len(w)>1:
                ls.append(w)
    print(len(ls))
    words=' '.join(ls)
    #print(words)
    wordcloudplot(words)
main()
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值