您的位置:首页 >聚焦 >

爬虫实战!Python采集二手车数据并进行数据分析

2022-02-28 12:04:36    来源:程序员客栈
前言

前段时间给朋友采集某子二手车平台的数据,虽然最后搞定了js加密,也通过价格对比短时间内算是搞定了字体反爬,但总是感觉很不完美,网站字体一共有5种对应关系,每次请求后都用价格对比,严重拖慢了系统运行速度不说,还容易出错。这次换一个平台,采集数据后进行简单的数据分析。

1.数据采集1.1车辆列表页面分析

通过对网页的url分析发现,URL地址p后面的数字是用来的控制翻页,plog_id可以不用添加。继续分析网页源代码:

发现车辆的信息就在网页源代码里,同时也发现网页有字体反爬,继续在网页源代码里搜索网页字体的URL。

根据每辆车详情页面的分析,还要拿到每辆车的data-car-id。

1.2车辆详情页面分析

可以看到网页的URL地址拼接了车辆的data-car-id,同样后面的plog_id也可以省略。

同样所有的数据也都在网页源代码里,同样也有字体文件的URL。

1.3实施步骤请求字体URL,手动建立对应关系这里请求过程很简单,用requests的get方法就可以拿到文件,打开后观察字体的对应关系。

手动建立对应关系字典(根据观察,这个网站每天会更换一次字体对应关系,还没有瓜子那么BT)

#字体对应关系relation_table={"zero":"0","one":"2","two":"1","four":"3","three":"4","five":"8","seven":"7","nine":"9","six":"6","eight":"5"}请求列表页面,拿到车辆的data-car-id

defget_carid(index):"""获取车辆id"""response=session.get(f"https://www.renrenche.com/bj/ershouche/p{index}/",headers=headers)html=etree.HTML(response.text)car_id=html.xpath("//*[@id="search_list_wrapper"]/div/div/div[1]/ul/li/a/@data-car-id")#获取车辆id列表returncar_id

需要注意的是,返回的car_id是一个列表。

处理字体,获取真实的对应关系,方便后续替换

defwoff_font(font_url):"""获取字体真实对应关系"""newmap={}resp=session.get(font_url)#请求字体链接woff_data=BytesIO(resp.content)font=TTFont(woff_data)#读取woff数据cmap=font.getBestCmap()#获取xml文件字体对应关系font.close()fork,vincmap.items():value=vkey=str(k-48)#获取真实的keytry:get_real_data=relation_table[value]except:get_real_data=""ifget_real_data!="":newmap[key]=get_real_data#将字体真实结果对应returnnewmap请求每辆车的详情页面,拿到所有数据,并写入csv文件

defget_data(url):"""提取数据"""resp=session.get(url,headers=headers)html=etree.HTML(resp.text)font_text="".join(html.xpath("/html/head/style[2]/text()")).strip()#获取字体链接font_url=re.search(r"https.*?woff",font_text).group()font=woff_font(font_url)title="".join(html.xpath("//*[@id="zhimaicar-detail-header-right"]/div[1]/h1/text()")).strip()#获取原始标题trans_title="".join([iifnoti.isdigit()elsefont[i]foriintitle])#替换错误字体,获取真实标题mil="".join(html.xpath("//*[@id="zhimaicar-detail-header-right"]/div[4]/ul/li[1]/div/p[1]/strong/text()"))#获取原始行驶里程trans_mil="".join([iifnoti.isdigit()elsefont[i]foriinmil])#获取真实行驶里程lice_time="".join(html.xpath("//*[@id="report"]/div/div[3]/div[2]/ul/li[9]/span[2]/text()")).strip()#获取上牌时间emi_level="".join(html.xpath("//*[@id="zhimaicar-detail-header-right"]/div[4]/ul/li[4]/div/p[1]/strong/text()")).strip()#获取排放等级trans="".join(html.xpath("//*[@id="basic-parms"]//tr[3]/td[2]/div[2]/text()")).strip()#获取变速箱形式c_name="".join(html.xpath("//*[@id="zhimaicar-detail-header-right"]/div[4]/ul/li[6]/p[1]/strong/text()")).strip()#获取过户次数colo="".join(html.xpath("//*[@id="report"]/div/div[3]/div[2]/ul/li[6]/span[2]/text()")).strip()#获取车身颜色fu_type="".join(html.xpath("//*[@id="report"]/div/div[3]/div[2]/ul/li[13]/span[2]/text()")).strip()#获取燃料类型out_time="".join(html.xpath("//*[@id="report"]/div/div[3]/div[2]/ul/li[11]/span[2]/text()")).strip()#获取出厂日期model="".join(html.xpath("//*[@id="basic-parms"]//tr[2]/td[1]/div[2]/text()")).strip()#获取车型engine="".join(html.xpath("//*[@id="engine"]//tr[2]/td[1]/div[2]/text()")).strip()#获取发动机struc="".join(html.xpath("//*[@id="basic-parms"]//tr[4]/td[2]/div[2]/text()")).strip()#获取车身结构guid_price="".join(html.xpath("//*[@id="zhimaicar-no-discount"]/div[1]/span/text()")).strip()#获取指导价price="".join(html.xpath("//*[@id="zhimaicar-no-discount"]/p/text()")).strip()#获取售价iflen(trans_title)!=0andlen(lice_time)==10andlen(out_time)==10:print(trans_title,trans_mil,lice_time,emi_level,trans,c_name,colo,fu_type,out_time,model,engine,struc,guid_price,price)csv_writer.writerow([trans_title,trans_mil,lice_time,emi_level,trans,c_name,colo,fu_type,out_time,model,engine,struc,guid_price,price])#写入csv文件

通过对请求后的数据分析,由于网页结构差异,有些返回的数据是空,容易导致写入错误数据,为了方便后期处理,加了判断。同时这里需要注意,Chrome浏览器tbody的坑。

1.4爬虫部分全部代码

fromlxmlimportetreeimportrequestsimportrefromfontTools.ttLibimportTTFontfromioimportBytesIOfrommultiprocessingimportQueuefromconcurrent.futuresimportThreadPoolExecutorimportcsvqueue_list=Queue()session=requests.session()headers={"User-Agent":"Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/98.0.4758.102Safari/537.36","Referer":"https://www.renrenche.com/bj/ershouche/?plog_id=3978670bd754fae06b75b0bc33540592",}#字体对应关系relation_table={"zero":"0","one":"2","two":"1","four":"3","three":"4","five":"8","seven":"7","nine":"9","six":"6","eight":"5"}defget_carid(index):"""获取车辆id"""response=session.get(f"https://www.renrenche.com/bj/ershouche/p{index}/",headers=headers)html=etree.HTML(response.text)car_id=html.xpath("//*[@id="search_list_wrapper"]/div/div/div[1]/ul/li/a/@data-car-id")#获取车辆id列表returncar_iddefwoff_font(font_url):"""获取字体真实对应关系"""newmap={}resp=session.get(font_url)#请求字体链接woff_data=BytesIO(resp.content)font=TTFont(woff_data)#读取woff数据cmap=font.getBestCmap()#获取xml文件字体对应关系font.close()fork,vincmap.items():value=vkey=str(k-48)#获取真实的keytry:get_real_data=relation_table[value]except:get_real_data=""ifget_real_data!="":newmap[key]=get_real_data#将字体真实结果对应returnnewmapdefget_data(url):"""提取数据"""resp=session.get(url,headers=headers)html=etree.HTML(resp.text)font_text="".join(html.xpath("/html/head/style[2]/text()")).strip()#获取字体链接font_url=re.search(r"https.*?woff",font_text).group()font=woff_font(font_url)title="".join(html.xpath("//*[@id="zhimaicar-detail-header-right"]/div[1]/h1/text()")).strip()#获取原始标题trans_title="".join([iifnoti.isdigit()elsefont[i]foriintitle])#替换错误字体,获取真实标题mil="".join(html.xpath("//*[@id="zhimaicar-detail-header-right"]/div[4]/ul/li[1]/div/p[1]/strong/text()"))#获取原始行驶里程trans_mil="".join([iifnoti.isdigit()elsefont[i]foriinmil])#获取真实行驶里程lice_time="".join(html.xpath("//*[@id="report"]/div/div[3]/div[2]/ul/li[9]/span[2]/text()")).strip()#获取上牌时间emi_level="".join(html.xpath("//*[@id="zhimaicar-detail-header-right"]/div[4]/ul/li[4]/div/p[1]/strong/text()")).strip()#获取排放等级trans="".join(html.xpath("//*[@id="basic-parms"]//tr[3]/td[2]/div[2]/text()")).strip()#获取变速箱形式c_name="".join(html.xpath("//*[@id="zhimaicar-detail-header-right"]/div[4]/ul/li[6]/p[1]/strong/text()")).strip()#获取过户次数colo="".join(html.xpath("//*[@id="report"]/div/div[3]/div[2]/ul/li[6]/span[2]/text()")).strip()#获取车身颜色fu_type="".join(html.xpath("//*[@id="report"]/div/div[3]/div[2]/ul/li[13]/span[2]/text()")).strip()#获取燃料类型out_time="".join(html.xpath("//*[@id="report"]/div/div[3]/div[2]/ul/li[11]/span[2]/text()")).strip()#获取出厂日期model="".join(html.xpath("//*[@id="basic-parms"]//tr[2]/td[1]/div[2]/text()")).strip()#获取车型engine="".join(html.xpath("//*[@id="engine"]//tr[2]/td[1]/div[2]/text()")).strip()#获取发动机struc="".join(html.xpath("//*[@id="basic-parms"]//tr[4]/td[2]/div[2]/text()")).strip()#获取车身结构guid_price="".join(html.xpath("//*[@id="zhimaicar-no-discount"]/div[1]/span/text()")).strip()#获取指导价price="".join(html.xpath("//*[@id="zhimaicar-no-discount"]/p/text()")).strip()#获取售价iflen(trans_title)!=0andlen(lice_time)==10andlen(out_time)==10:print(trans_title,trans_mil,lice_time,emi_level,trans,c_name,colo,fu_type,out_time,model,engine,struc,guid_price,price)csv_writer.writerow([trans_title,trans_mil,lice_time,emi_level,trans,c_name,colo,fu_type,out_time,model,engine,struc,guid_price,price])#写入csv文件if__name__=="__main__":f=open("renrenche1.csv","w",encoding="utf-8",newline="")csv_writer=csv.writer(f)csv_writer.writerow(["车型","行驶里程","上牌时间","排放等级","变速箱形式","过户次数","车身颜色","燃料类型","出厂日期","车型","发动机型号","车身结构","新车含税价","到手价"])foriinrange(1,51):car_id=get_carid(i)foridincar_id:car_url=f"https://www.*********.com/bj/car/{id}"queue_list.put(car_url)#get_data(car_url,font)pool=ThreadPoolExecutor(max_workers=30)whilequeue_list.qsize()>0:pool.submit(get_data,queue_list.get())1.5获取的文件2.简单的数据分析2.1数据清洗

#新增品牌列df["品牌"]=df["车型"].apply(lambdax:x.split("-")[0])df.duplicated().value_counts()#查找重复数据#False1330#True18#dtype:int64df.drop_duplicates(inplace=True)#删除重复数据df.duplicated().value_counts()#检查重复数据#False1330#dtype:int64df.dropna(axis=0,how="any",inplace=True)#删除重复值2.2选择数据,并进行数据处理

df_new=df[["品牌","出厂日期","上牌时间","行驶里程","过户次数","新车含税价","到手价"]]#清理出厂日期和上牌时间列的异常值time1=df_new["出厂日期"].map(lambdax:len(x)==10)time2=df_new["上牌时间"].map(lambdax:len(x)==10)df_1=df_new[time1&time2]#转换行驶里程等列的数据格式df_1.loc[:,"行驶里程"]=df_1["行驶里程"].map(lambdax:float(x.replace("万公里","")))df_1.loc[:,"过户次数"]=df_1["过户次数"].map(lambdax:int(x.replace("次","")))df_1.loc[:,"新车含税价"]=df_1["新车含税价"].map(lambdax:float(x.replace("万","")))df_1["出厂日期"]=pd.to_datetime(df_1["出厂日期"])df_1["上牌时间"]=pd.to_datetime(df_1["上牌时间"])2.3数据分析Describe

df_1.describe()协方差矩阵和相关系数矩阵

df_1.cov()#协方差矩阵df_1.corr()#相关系数矩阵品牌占比分析

p_num=df_1["品牌"].value_counts()p_num=p_num[p_num.values>10]#选择数量大于10的品牌frompyechartsimportoptionsasoptsfrompyecharts.chartsimportPiedatas=list(zip(p_num.index.to_list(),p_num.to_list()))pie=Pie()pie.add("",datas,radius=[0,200],)pie.set_global_opts(title_opts=opts.TitleOpts(title="二手车品牌占比"),legend_opts=opts.LegendOpts(pos_right="right"))pie.set_series_opts(label_opts=opts.LabelOpts(formatter="{b},{c},{d}%"))pie.render_notebook()价格分析

bins=[0,5,15,30,50,130]s_bar=pd.cut(df_1.到手价,bins).value_counts()frompyecharts.chartsimportBarx_num=[str(i)foriins_bar.index]bar=(Bar().add_xaxis(x_num).add_yaxis("",s_bar.values.tolist(),bar_width="20%",).set_global_opts(title_opts=opts.TitleOpts(title="车型售价区间"),datazoom_opts=opts.DataZoomOpts(type_="inside")))bar.render_notebook()半成品漏斗图

frompyecharts.chartsimportFunnelfunnel=Funnel()funnel.add("",datas)funnel.set_global_opts(title_opts=opts.TitleOpts(title="二手车保有量占比"),legend_opts=opts.LegendOpts(pos_right="right"))funnel.render_notebook()

推荐蚂蚁老师的全套Python课程

先保存图片,然后抖音打开扫码购买,提供代码、课件、答疑服务

关键词: 对应关系 行驶里程 出厂日期

相关阅读