上一篇我们爬取瓜子二手车一级页面
汽车的车辆概况 、车龄、全款价、汽车原价
和二级页面
汽车排量、过户情况、变速箱、表显里程、最低首付
等信息,最终结果如下:
效果成功实现,但是效率低下
所以今天我们使用多线程生产者消费者模式来重新获取瓜子二手车信息
可以看得出,同样是为了防止被反爬,
我加上了3s的休眠
但是速度还是相当可观的......
还没有看过的小伙伴们点击这里
python实战| 二手车难买?手把手带你爬取瓜子二手车-二级页面
先来简单了解一下概念
什么是 生产者-消费者 模型?
生产者-消费者模式是一个十分经典的多线程并发协作的模式,弄懂生产者-消费者问题能够让我们对并发编程的理解加深。
所谓生产者-消费者问题,实际上主要是包含了两类线程,一种是生产者线程用于生产数据,另一种是消费者线程用于消费数据,为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库。
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为;而消费者只需要从共享数据区中去获取数据,就不再需要关心生产者的行为。
爬虫当中为什么要使用 生产者-消费者 模型?
传统爬虫流程:
requests 请求所要爬取的页面
等待响应回来接收数据
解析页面数据
持久化存储
重复以上操作
流程有两个问题
请求页面之后,数据接收回来之前,程序处在阻塞状态
接收数据之后,到下一次请求之前,网络处在闲置当中
生产者-消费者爬虫流程:
生产者:
requests 请求所要爬取的页面
等待响应回来接收数据
将数据保存到队列当中
进行下一次请求
消费者:
判断队列当中是否有数据,如果有则往后进行
解析页面数据
持久化存储
重复以上步骤
因为具体的页面分析我在上一篇文章中已经做了详尽的描述,
所以在此不在赘述
直接看代码
# 引入包
importrequests
fromqueueimportQueue
importthreading
fromfake_useragentimportUserAgent
fromlxmlimportetree
importopenpyxlasop
生产者
# 生产者 访问页面,获取下载地址,将数据存入缓冲区
classProcuder(threading.Thread):
headers = {
'Referer':'https://www.guazi.com/xa/buy/o1/',
'Cookie':'uuid=aecbdf7d-8648-4f28-dbf9-902293bbf045; clueSourceCode=%2A%2300; user_city_id=176; ganji_uuid=6311203881588826872627; guazitrackersessioncadata=%7B%22ca_kw%22%3A%22%25e7%2593%259c%25e5%25ad%2590%25e4%25ba%258c%25e6%2589%258b%25e8%25bd%25a6%22%7D; sessionid=d173d24e-e570-4019-cc19-47bcd5e33348; lg=1; Hm_lvt_bf3ee5b290ce731c7a4ce7a617256354=1623491103; close_finance_popup=2021-06-12; cainfo=%7B%22ca_a%22%3A%22-%22%2C%22ca_b%22%3A%22-%22%2C%22ca_s%22%3A%22pz_baidu%22%2C%22ca_n%22%3A%22pcbiaoti%22%2C%22ca_medium%22%3A%22-%22%2C%22ca_term%22%3A%22-%22%2C%22ca_content%22%3A%22-%22%2C%22ca_campaign%22%3A%22-%22%2C%22ca_kw%22%3A%22%25e7%2593%259c%25e5%25ad%2590%25e4%25ba%258c%25e6%2589%258b%25e8%25bd%25a6%22%2C%22ca_i%22%3A%22-%22%2C%22scode%22%3A%22-%22%2C%22keyword%22%3A%22-%22%2C%22ca_keywordid%22%3A%22-%22%2C%22display_finance_flag%22%3A%22-%22%2C%22platform%22%3A%221%22%2C%22version%22%3A1%2C%22client_ab%22%3A%22-%22%2C%22guid%22%3A%22aecbdf7d-8648-4f28-dbf9-902293bbf045%22%2C%22ca_city%22%3A%22xa%22%2C%22sessionid%22%3A%22d173d24e-e570-4019-cc19-47bcd5e33348%22%7D; _gl_tracker=%7B%22ca_source%22%3A%22-%22%2C%22ca_name%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_id%22%3A%22-%22%2C%22ca_s%22%3A%22self%22%2C%22ca_n%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22sid%22%3A64163631266%7D; cityDomain=xa; preTime=%7B%22last%22%3A1623491941%2C%22this%22%3A1623491102%2C%22pre%22%3A1623491102%7D; Hm_lpvt_bf3ee5b290ce731c7a4ce7a617256354=1623491941',
'User-Agent': str(UserAgent().random)
}
def__init__(self, page_queue, img_queue, *args, **kwargs):
super(Procuder, self).__init__(*args, **kwargs)
self.page_queue = page_queue
self.img_queue = img_queue
defrun(self)->None:
whileTrue:
ifself.page_queue.empty():
break
url = self.page_queue.get()
self.parse_page(url)
# 解析页面
defparse_page(self, url):
resp = requests.get(url, headers=self.headers).text
html = etree.HTML(resp)
lis = html.xpath("//ul[@class='carlist clearfix js-top']/li")
forliinlis:
car_info = li.xpath('./a/h2/text()')
car_info =''.join(car_info)# 汽车概况
car_age = li.xpath('./a/div[1]/text()[1]')# 车龄
car_age =''.join(car_age)
car_money = li.xpath('./a/div[2]/p/text()')
car_money = (''.join(car_money)).strip() +'万'# 全款价
car_money_before = li.xpath('./a/div[2]/em/text()')
car_money_before =''.join(car_money_before)# 汽车原价
# 下一页数据连接
links = li.xpath('./a/@href')
x ='https://www.guazi.com'
page_down = [x + iforiinlinks]
# 获取汽车详情
forpageinpage_down:
response = requests.get(page, headers=self.headers)
ifresponse.status_code ==200:
html = etree.HTML(response.text)
car_displace = html.xpath("//li[@class='three']/span/text()")
car_displace =''.join(car_displace)# 汽车排量
car_transf = html.xpath("//ul[@class='basic-eleven clearfix']/li[@class='seven']/div/text()")
car_transf =''.join(car_transf).strip()# 过户情况
car_change = html.xpath("//ul[@class='assort clearfix']/li[@class='last']/span/text()")
car_change =''.join(car_change)# 变速箱
car_mile = html.xpath("//li[@class='two']/span/text()")
car_mile =''.join(car_mile)# 表显里程
car_pay = html.xpath("//a[@class='loanbox js-loan']/span[@class='f24']/text()")
car_pay =''.join(car_pay)
ifcar_pay =='':
car_pay ='该车仅支持全款'# 最低首付
self.img_queue.put((car_info, car_age, car_money, car_money_before, car_displace, car_transf, car_change, car_mile, car_pay))
消费者
# 消费者,缓冲区获取数据并保存到excel
classConsumer(threading.Thread):
def__init__(self, page_queue, img_queue, *args, **kwargs):
super(Consumer, self).__init__(*args, **kwargs)
self.page_queue = page_queue
self.img_queue = img_queue
defrun(self)->None:
ws = op.Workbook()
wb = ws.create_sheet(index=0)
wb.cell(row=1, column=1, value='车辆概况')
wb.cell(row=1, column=2, value='车龄')
wb.cell(row=1, column=3, value='全款价')
wb.cell(row=1, column=4, value='汽车原价')
wb.cell(row=1, column=5, value='汽车排量')
wb.cell(row=1, column=6, value='过户情况')
wb.cell(row=1, column=7, value='变速箱')
wb.cell(row=1, column=8, value='表显里程')
wb.cell(row=1, column=9, value='最低首付')
count =2
whileTrue:
ifself.img_queue.empty()andself.page_queue.empty():
break
car_info, car_age, car_money, car_money_before, car_displace, car_transf, car_change, car_mile, car_pay = self.img_queue.get()
wb.cell(row=count, column=1, value=car_info)
wb.cell(row=count, column=2, value=car_age)
wb.cell(row=count, column=3, value=car_money)
wb.cell(row=count, column=4, value=car_money_before)
wb.cell(row=count, column=5, value=car_displace)
wb.cell(row=count, column=6, value=car_transf)
wb.cell(row=count, column=7, value=car_change)
wb.cell(row=count, column=8, value=car_mile)
wb.cell(row=count, column=9, value=car_pay)
print(car_info, car_age, car_money, car_money_before, car_displace, car_transf, car_change, car_mile,
car_pay)
count +=1
ws.save('多线程瓜子二手车.xlsx')
主函数
defmain():
page_queue = Queue(100)
img_queue = Queue(1000)
# 获取50页数据
forpageinrange(1,50+1):
url =f'https://www.guazi.com/cd/buy/o{page}/#bread'
page_queue.put(url)
# 开启5个生产者线程
forxinrange(5):
t = Procuder(page_queue, img_queue)
t.start()
# 开启10个消费者线程
forxinrange(10):
t = Consumer(page_queue, img_queue)
t.start()
t.join()
运行程序
可视化分析
对于车辆概况这一列,我计划是提取空格前的字符串,如下标出来的那样。
因为数据太多(2000+条数据),为了更加直观的展示,我选择数量最多的前20种车型作为展示。
# 提取汽车名称
pd_data['汽车名称'] = pd_data['车辆概况'].map(lambdax: x.split(" ")[0])
name = pd_data['汽车名称'].value_counts()
# 统计汽车名称(前20)
name1 = name.index.tolist()[:20]
# 车龄汽车名称对应数量(前20)
name2 = name.tolist()[:20]
print(name1)
print(name2)
'''
结果展示:
['大众', '丰田', '日产', '福特', '别克', '现代', '本田', '雪佛兰', '大众POLO', '斯柯达', '宝马3系', '奔驰C级', '哈弗H6', '宝马5系', '宝马X1', '马自达', '奥迪A4L', '吉利', 'Jeep', '起亚']
[210, 144, 139, 122, 96, 85, 65, 62, 30, 28, 27, 26, 26, 24, 24, 24, 20, 17, 16, 15]
'''
数据分析完成之后我们用图标做可视化分析,这里我们使用的还是pyecharts做分析。
分别使用柱状图、饼图、漏斗图、折线图来展示
# 1、柱状图
defbarPage()-> Bar:
bar = (
Bar()
.add_xaxis(x_data)
.add_yaxis("柱状图", y_data)
.set_global_opts(
title_opts=opts.TitleOpts(title="柱状图分析"),
legend_opts=opts.LegendOpts(is_show=False), )
)
returnbar
# 2、饼图
defpiePage()-> Pie:
pie = (
Pie()
.add("", [list(z)forzinzip(x_data, y_data)])
.set_global_opts(title_opts=opts.TitleOpts(title="饼图分析"))
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
)
returnpie
# 3、折线图
deflinePage()-> Line:
line = (
Line()
.add_xaxis(x_data)
.add_yaxis("折线图", y_data)
.set_global_opts(title_opts=opts.TitleOpts(title="折线图分析"))
)
returnline
# 4、漏斗图
deffunnelPage()-> Funnel:
funnel = (
Funnel()
.add("漏斗图", [list(z)forzinzip(x_data, y_data)])
.set_global_opts(title_opts=opts.TitleOpts(title="漏斗图分析"))
)
returnfunnel
可视化展示
汽车品牌柱状图
汽车品饼图
汽车品折线图
汽车品漏斗图
可以很直观的展示出前20名展示车辆的名称和各自对应的数量分布。
这就是和看Excel的最直接区别。
接下来我们再对 '表显里程' 这一列进行分析。
第一列是我们爬虫获取到的数据,
第二列是我们处理之后用来做数据分析的数据。
第三列是我根据里程将数据划分为不同的区间段。
有什么用?您接着往下瞅~~~
pd_data.loc[:,'表显里程1'] = pd_data['表显里程'].str.replace('万公里','').astype('float32')# 去除 30 ’万公里‘
pd_data['里程区间'] = pd.cut(pd_data['表显里程1'], [0,2,4,6,8,10,20],
labels=['0-2','2-4','4-6','6-8','8-10','>10'])
mile = pd_data['里程区间'].value_counts()
mile1 = mile.index.tolist()# 里程种类
mile2 = mile.tolist()# 里程种类对应数量'''
print(mile1)
print(mile2)
'''
['4-6', '6-8', '2-4', '8-10', '>10', '0-2']
[415, 374, 351, 261, 243, 176]
'''
接下来调用刚才的画图方法,然后展示图表如下:
行驶里程柱状图
行驶里程饼图
行驶里程折线图
行驶里程漏斗图
将全部数据表显里程划分为6个区间,通过图标可以很直观的看出4-6万公里之间的汽车将近50%,0-2万公里之间的新车占比是最少的。
接下来我们再对 '最低首付' 这一列进行分析。左边是原始数据,右边是我们经过整理后用于数据分析的数据
我将其划分为['0-3', '3-5', '5-8', '8-10', '>10']五个区间,分别统计在这个价格分为内的车辆数量分布情况。
pd_data = pd_data.dropna(subset=['最低首付'])
# 划分价格区间
pd_data['价格区间'] = pd.cut(pd_data['最低首付'], [0,3,5,8,10,15], labels=['0-3','3-5','5-8','8-10','>10'])
# 统计数量
price = pd_data['价格区间'].value_counts()
price1 = price.index.tolist()# 价格区间段
price2 = price.tolist()#价格区间段对应数量'''
print(price1)
print(price2)
'''
['0-3', '3-5', '5-8', '8-10', '>10']
[859, 491, 199, 62, 33]
'''
绘图展示如下:
价格分布柱状图
价格分布饼图
价格分折线图
价格分布漏斗图
tras = pd_data['过户情况'].value_counts()
tras1 = tras.index.tolist()# 过户情况分类
tras2 = tras.tolist()# 过户情况分类对应数量
print(tras1)
print(tras2)
'''
['0次过户', '1次过户', '2次过户', '3次过户', '4次过户', '5次过户', '6次过户', '14次过户', '10次过户', '8次过户']
[791, 761, 148, 73, 28, 12, 4, 1, 1, 1]
'''
汽车过户柱状图
汽车过户饼图
汽车过户折线图
汽车过户漏斗图
defdisp_cars():
disp = pd_data['汽车排量'].value_counts()
disp1 = disp.index.tolist()# 汽车排量种类
disp2 = disp.tolist()# 汽车排量种类对应数量
print(disp1)
print(disp2)
'''
['1.6L', '2.0T', '1.5L', '2.0L', '1.5T', '1.4T', '1.6T', '1.8L', '1.8T', '1.4L', '2.4L', '2.5L', '1.2T', '3.0T', '1.3T', '1.3L', '2.7L', '1.2L', '3.0L', '1.0L', '1.0T', '2.3T', '2.3L', '3.6T', '3.6L', '0.9T', '3.5L', '2.5T', '15.0T', '2.7T', '2.8L', '2.4T']
[330, 294, 266, 199, 133, 116, 82, 80, 68, 48, 40, 33, 30, 16, 15, 12, 8, 8, 7, 5, 4, 4, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1]
'''
汽车排量柱状图
汽车排量饼图
汽车排量折线图
汽车排量漏斗图
chg = pd_data['变速箱'].value_counts()
chg1 = chg.index.tolist()# 变速箱种类
chg2 = chg.tolist()# 变速箱种类对应数量
print(chg1)
print(chg2)
'''
['自动', '手动']
[1611, 203]
'''
变速箱柱状图
变速箱饼图
变速箱折线图
变速箱漏斗图
age = pd_data['车龄'].value_counts()
age1 = age.index.tolist()# 车龄年代分布
age2 = age.tolist()# 车龄年代分布数量
print(age1)
print(age2)
'''
['2016年', '2017年', '2018年', '2015年', '2014年', '2019年', '2013年', '2012年', '2020年', '2011年', '2010年', '2021年', '2009年', '2007年']
[317
车龄柱状图
车龄饼图
车龄折线图
车龄漏斗图
有什么问题的小伙伴可以私我或者留言咨询哈,
欢迎大家提出宝贵意见!
网友评论