本文主要介绍了使用Python抓取去哪儿网站的景点信息并使用BeautifulSoup解析内容获取景点名称、票销售量、景点星级、热度等数据,然后使用xlrd、xlwt、xlutils等库来处理Excel,写入到Excel中,最后使用matplotlib可视化数据,并用百度的heatmap.js来生成热力图。
首先,上张效果图:
如果想了解更多Python的伙伴或者小白中有任何困难不懂的可以加入我们python交流学习QQ群:250933691,多多交流问题,互帮互助,群里有不错的学习教程和开发工具。资源分享
下面就详细来介绍如何一步步实现。
准备省份名单
访问是按照省份来进行搜索的,所以我们需要准备一份全国各省份的名单,这里,我已经准备好了这份名单
北京市,天津市,上海市,重庆市,河北省,山西省,辽宁省,吉林省,黑龙江省,江苏省,浙江省,安徽省,福建省,江西省,山东省,河南省,湖北省,湖南省,广东省,海南省,四川省,贵州省,云南省,陕西省,甘肃省,青海省,台湾省,内蒙古自治区,广西壮族自治区,西藏自治区,宁夏回族自治区,新疆维吾尔自治区,香港,澳门
将这些数据保存为TXT,一行
然后使用Python加载:
def ProvinceInfo(province_path):
tlist = []
with open(province_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
tlist = line.split(',')
return tlist
构建URL
这里URL是根据城市名称信息来生成的
site_name = quote(province_name) # 处理汉字问题
url1 = 'http://piao.qunar.com/ticket/list.htm?keyword='
url2 = '®ion=&from=mps_search_suggest&page='
url = url1 + site_name + url2
当然上面这个URL还不是最终的URL,因为一个城市搜索后有很多页面,我们需要定位到具体页面才行,这涉及到了如何判断页面数的问题,放在下文。
抓取页面信息函数:
# 获得页面景点信息
def GetPageSite(url):
try:
page = urlopen(url)
except AttributeError:
logging.info('抓取失败!')
return 'ERROR'
try:
bs_obj = BeautifulSoup(page.read(), 'lxml')
# 不存在页面
if len(bs_obj.find('div', {'class': 'result_list'}).contents) <= 0:
logging.info('当前页面没有信息!')
return 'NoPage'
else:
page_site_info = bs_obj.find('div', {'class': 'result_list'}).children
except AttributeError:
logging.info('访问被禁止!')
return None
return page_site_info
# 获取页面数目
def GetPageNumber(url):
try:
page = urlopen(url)
except AttributeError:
logging.info('抓取失败!')
return 'ERROR'
try:
bs_obj = BeautifulSoup(page.read(), 'lxml')
# 不存在页面
if len(bs_obj.find('div', {'class': 'result_list'}).contents) <= 0:
logging.info('当前页面没有信息!')
return 'NoPage'
else:
page_site_info = bs_obj.find('div', {'class': 'pager'}).get_text()
except AttributeError:
logging.info('访问被禁止!')
return None
# 提取页面数
page_num = re.findall(r'\d+\.?\d*', page_site_info.split('...')[-1])
return int(page_num[0])
# 格式化获取信息
def GetItem(site_info):
site_items = {} # 储存景点信息
site_info1 = site_info.attrs
site_items['name'] = site_info1['data-sight-name'] # 名称
site_items['position'] = site_info1['data-point'] # 经纬度
site_items['address'] = site_info1['data-districts'] + ' ' + site_info1['data-address'] # 地理位置
site_items['sale number'] = site_info1['data-sale-count'] # 销售量
site_level = site_info.find('span', {'class': 'level'})
if site_level:
site_level = site_level.get_text()
site_hot = site_info.find('span', {'class': 'product_star_level'})
if site_hot:
site_hot = site_info.find('span', {'class': 'product_star_level'}).em.get_text()
site_hot = site_hot.split(' ')[1]
site_price = site_info.find('span', {'class': 'sight_item_price'})
if site_price:
site_price = site_info.find('span', {'class': 'sight_item_price'}).em.get_text()
site_items['level'] = site_level
site_items['site_hot'] = site_hot
site_items['site_price'] = site_price
return site_items
# 获取一个省的所有景点
def GetProvinceSite(province_name):
site_name = quote(province_name) # 处理汉字问题
url1 = 'http://piao.qunar.com/ticket/list.htm?keyword='
url2 = '®ion=&from=mps_search_suggest&page='
url = url1 + site_name + url2
NAME = [] # 景点名称
POSITION = [] # 坐标
ADDRESS = [] # 地址
SALE_NUM = [] # 票销量
SALE_PRI = [] # 售价
STAR = [] # 景点星级
SITE_LEVEL = [] # 景点热度
i = 0 # 页面
page_num = GetPageNumber(url + str(i + 1)) # 页面数
logging.info('当前城市 %s 存在 %s 个页面' % (province_name, page_num))
flag = True # 访问非正常退出标志
while i < page_num: # 遍历页面
i = i + 1
# 随机暂停1--5秒,防止访问过频繁被服务器禁止访问
time.sleep(1 + 4 * random.random())
# 获取网页信息
url_full = url + str(i)
site_info = GetPageSite(url_full)
# 当访问被禁止的时候等待一段时间再进行访问
while site_info is None:
wait_time = 60 + 540 * random.random()
while wait_time >= 0:
time.sleep(1)
logging.info('访问被禁止,等待 %s 秒钟后继续访问' % wait_time)
wait_time = wait_time - 1
# 继续访问
site_info = GetPageSite(url_full)
if site_info == 'NoPage': # 访问完成
logging.info('当前城市 %s 访问完成,退出访问!' % province_name)
break
elif site_info == 'ERROR': # 访问出错
logging.info('当前城市 %s 访问出错,退出访问' % province_name)
flag = False
break
else:
# 返回对象是否正常
if not isinstance(site_info, Iterable):
logging.info('当前页面对象不可迭代 ,跳过 %s' % i)
continue
else:
# 循环获取页面信息
for site in site_info:
info = GetItem(site)
NAME.append(info['name'])
POSITION.append(info['position'])
ADDRESS.append(info['address'])
SALE_NUM.append(info['sale number'])
SITE_LEVEL.append(info['site_hot'])
SALE_PRI.append(info['site_price'])
STAR.append(info['level'])
logging.info('当前访问城市 %s,取到第 %s 组数据: %s' % (province_name, i, info['name']))
return flag, NAME, POSITION, ADDRESS, SALE_NUM, SALE_PRI, STAR, SITE_LEVEL
最后就是把数据写入到Excel中,这里因为数据量很大,而且是获得了一个城市的数据后再写入一次,而在爬取过程中很可能由于各种原因中断,因而每次读取Excel都会判断当前省份是否已经读取过。
# 创建Excel
def CreateExcel(path, sheets, title):
try:
logging.info('创建Excel: %s' % path)
book = xlwt.Workbook()
for sheet_name in sheets:
sheet = book.add_sheet(sheet_name, cell_overwrite_ok=True)
for index, item in enumerate(title):
sheet.write(0, index, item, set_style('Times New Roman', 220, True))
book.save(path)
except IOError:
return '创建Excel出错!'
# 设置Excel样式
def set_style(name, height, bold=False):
style = xlwt.XFStyle() # 初始化样式
font = xlwt.Font() # 为样式创建字体
font.name = name # 'Times New Roman'
font.bold = bold
font.color_index = 4
font.height = height
# borders= xlwt.Borders()
# borders.left= 6
# borders.right= 6
# borders.top= 6
# borders.bottom= 6
style.font = font
# style.borders = borders
return style
# 加载Excel获得副本
def LoadExcel(path):
logging.info('加载Excel:%s' % path)
book = xlrd.open_workbook(path)
copy_book = copy(book)
return copy_book
# 判断内容是否存在
def ExistContent(book, sheet_name):
sheet = book.get_sheet(sheet_name)
if len(sheet.get_rows()) >= 2:
return True
else:
return False
# 写入Excel并保存
def WriteToTxcel(book, sheet_name, content, path):
logging.info('%s 数据写入到 (%s-%s)' % (sheet_name, os.path.basename(path), sheet_name))
sheet = book.get_sheet(sheet_name)
for index, item in enumerate(content):
for sub_index, sub_item in enumerate(item):
sheet.write(sub_index + 1, index, sub_item)
book.save(path)
完成了前面几个步骤之后,我们就已经做好了爬取数据的工作了,现在就是需要可视化数据了,这里,设计的主要内容有:读取Excel数据,然后对每一个sheet(一个省份)读取数据,并去处重复数据,最后按照自己的要求可视化,当然,这里地图可视化部分使用了百度的heatmap.js工具,首先需要把景点的经纬度等信息生成json格式。
def GenerateJson(ExcelPath, JsonPath, SalePath, TransPos=False):
try:
if os.path.exists(JsonPath):
os.remove(JsonPath)
if os.path.exists(SalePath):
os.remove(SalePath)
sale_file = open(SalePath, 'a', encoding='utf-8')
json_file = open(JsonPath, 'a', encoding='utf-8')
book = xlrd.open_workbook(ExcelPath)
except IOError as e:
return e
sheets = book.sheet_names()
sumSale = {} # 总销售量
for sheet_name in sheets:
sheet = book.sheet_by_name(sheet_name)
row_0 = sheet.row_values(0, 0, sheet.ncols - 1) # 标题栏数据
# 获得热度栏数据
for indx, head in enumerate(row_0):
if head == '销售量':
index = indx
break
level = sheet.col_values(index, 1, sheet.nrows - 1)
# 获得景点名称数据
for indx, head in enumerate(row_0):
if head == '名称':
index = indx
break
site_name = sheet.col_values(index, 1, sheet.nrows - 1)
if not TransPos:
for indx, head in enumerate(row_0):
if head == '经纬度':
index = indx
break
pos = sheet.col_values(index, 1, sheet.nrows - 1)
temp_sale = 0 # 临时保存销售量
for i, p in enumerate(pos):
if int(level[i]) > 0:
lng = p.split(',')[0]
lat = p.split(',')[1]
lev = level[i]
temp_sale += int(lev)
sale_temp = sheet_name + site_name[i] + ',' + lev
json_temp = '{"lng":' + str(lng) + ',"lat":' + str(lat) + ', "count":' + str(lev) + '}, '
json_file.write(json_temp + '\n')
sale_file.write(sale_temp + '\n')
sumSale[sheet_name] = temp_sale
else:
pass
json_file.close()
sale_file.close()
return sumSale
当然,上面这个函数同时还绘制了景点销量信息的图。不过这里先讨论生成json文本后接下来处理。运行上面的程序会在你指定的路径下生成一个名为LngLat.json的文件,使用文本编辑器打开,然后把内容复制到heatmap.html这个文件的数据部分,这里为了代码不至于太长我删除了大部分数据信息,只保留了一部分,你只需要把下面的代码复制保存为html格式然后在 var points =[]中添加生成的json内容就可以了。最后使用浏览器打开,即可看到下面这样的效果:
热力图功能示例
如果想了解更多Python的伙伴或者小白中有任何困难不懂的可以加入我们python交流学习QQ群:250933691,多多交流问题,互帮互助,群里有不错的学习教程和开发工具。资源分享