Python实战| 9383字手把手教你使用多线程爬取瓜子二手车并且可视化展示!
还没有看过的小伙伴们点击这里
python实战| 二手车难买?手把手带你爬取瓜子二手车-二级页面
先来简单了解一下概念
什么是 生产者-消费者 模型?
生产者-消费者模式是一个十分经典的多线程并发协作的模式,弄懂生产者-消费者问题能够让我们对并发编程的理解加深。
所谓生产者-消费者问题,实际上主要是包含了两类线程,一种是生产者线程用于生产数据,另一种是消费者线程用于消费数据,为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库。
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为;而消费者只需要从共享数据区中去获取数据,就不再需要关心生产者的行为。
爬虫当中为什么要使用 生产者-消费者 模型?
传统爬虫流程:
流程有两个问题
请求页面之后,数据接收回来之前,程序处在阻塞状态
接收数据之后,到下一次请求之前,网络处在闲置当中
生产者-消费者爬虫流程:
消费者:判断队列当中是否有数据,如果有则往后进行解析页面数据持久化存储重复以上步骤
因为具体的页面分析我在上一篇文章中已经做了详尽的描述,
所以在此不在赘述
直接看代码
# 引入包
import requests
from queue import Queue
import threading
from fake_useragent import UserAgent
from lxml import etree
import openpyxl as op
生产者
# 生产者 访问页面,获取下载地址,将数据存入缓冲区
class Procuder(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
def run(self) -> None:
while True:
if self.page_queue.empty():
break
url = self.page_queue.get()
self.parse_page(url)
# 解析页面
def parse_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")
for li in lis:
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 + i for i in links]
# 获取汽车详情
for page in page_down:
response = requests.get(page, headers=self.headers)
if response.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)
if car_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
class Consumer(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
def run(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
while True:
if self.img_queue.empty() and self.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')
主函数
def main():
page_queue = Queue(100)
img_queue = Queue(1000)
# 获取50页数据
for page in range(1, 50+1):
url = f'https://www.guazi.com/cd/buy/o{page}/#bread'
page_queue.put(url)
# 开启5个生产者线程
for x in range(5):
t = Procuder(page_queue, img_queue)
t.start()
# 开启10个消费者线程
for x in range(10):
t = Consumer(page_queue, img_queue)
t.start()
t.join()
运行程序
可视化分析
对于车辆概况这一列,我计划是提取空格前的字符串,如下标出来的那样。
因为数据太多(2000+条数据),为了更加直观的展示,我选择数量最多的前20种车型作为展示。
# 提取汽车名称
pd_data['汽车名称'] = pd_data['车辆概况'].map(lambda x: 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、柱状图
def barPage() -> 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), )
)
return bar
# 2、饼图
def piePage() -> Pie:
pie = (
Pie()
.add("", [list(z) for z in zip(x_data, y_data)])
.set_global_opts(title_opts=opts.TitleOpts(title="饼图分析"))
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
)
return pie
# 3、折线图
def linePage() -> Line:
line = (
Line()
.add_xaxis(x_data)
.add_yaxis("折线图", y_data)
.set_global_opts(title_opts=opts.TitleOpts(title="折线图分析"))
)
return line
# 4、漏斗图
def funnelPage() -> Funnel:
funnel = (
Funnel()
.add("漏斗图", [list(z) for z in zip(x_data, y_data)])
.set_global_opts(title_opts=opts.TitleOpts(title="漏斗图分析"))
)
return funnel
可视化展示
汽车品牌柱状图
汽车品饼图
汽车品折线图
汽车品漏斗图
可以很直观的展示出前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万公里之间的新车占比是最少的。
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]
'''
汽车过户柱状图
汽车过户饼图
汽车过户折线图
汽车过户漏斗图
def disp_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
车龄柱状图
车龄饼图
车龄折线图
车龄漏斗图
精彩推荐
python实战| 二手车难买?手把手带你爬取瓜子二手车-二级页面?
我是如何使用Python读取Excel并将其展示到web页面上?
Python构建代理池,突破IP的封锁爬取海量数据(送项目源码)