一、需求分析
1.根据本次任务找到数据源,从能够获取全国旅游景点信息的三个网站获取数据源:
URL=携程旅行(ctrip.com)
URL=同城(qunar.com)
URL=穷游网(qyer.com)
URL=去哪儿(qunar.com)
- 由于不同的网址中的数据字段大体上会有不一致,所以对应的爬取字段也有所不同,因此,对于字段的选取有如下选择;
1)URL=携程旅行(ctrip.com),所对应的字段:景区等级,景点名称,景点所在省市,景点热度,景区评论(用户名,用户评论内容,用户评论时间)。
Jqlevel name place province rd time usercontent username
2)URL=去哪儿(qunar.com),所对应的字段:景点所在城市,景点名称,景点详细地址,景点排行榜,景点门票,景点简介,景区评论。
address comment_content comment_count comment_time mentioned_times name open_time parent parent_rank username wander_time
3)URL=穷游网(qyer.com),所对应的字段:景区等级,景点名称,景点所在省市,景点热度,景区评论(用户名,用户评论内容,用户评论时间)。
Jqlevel name place province rd time usercontent username
4)URL=同城旅行(ly.com),所对应的字段:景区等级,景点名称,景点所在省市,景点热度,景区评论(用户名,用户评论内容,用户评论时间)。
Jqlevel name place province rd time usercontent username
3.每个网站采集数据2000条以上;
(1)携程旅行(ctrip.com),28819条数据
(2)去哪儿(qunar.com),1213条数据
(3)穷游网(qyer.com),890条数据
(4)同城旅行(ly.com),1714条数据
4.对脏数据进行清洗和并实现数据库存储;
(1).数据异常值
存在景区异常,景区观赏人数为负值。
(2).数据重复,爬取数据时出现相同数据。
爬取数据中,景区评论用户id和评论重复,在清洗数据时,需要注意是一个人评论了多个景区,还是该景区的该评论重复出现多次。
(3).通过对所爬取的数据初步观察可以得到,存在数据的缺失,针对数据的缺失,我们可以根据数据的特征利用处理缺失值策略进行数据的处理,例如对于重要性低,缺失率低的数据可以不做处理或简单填充;对于重要性低,缺失率高的数据可以去除该字段;对于重要性高,缺失率低的数据,通过计算进行填充或通过经验和业务知识估计;对于重要性高,缺失率高的数据,则可以选择尝试从其他渠道取数补全或使用其他字段通过计算获取。
对清洗完的数据再存入数据库中。
存入数据库时,首先需要我们在数据库中建立对应的mysql表。
由于我爬取的网站都是相同字段,所以建表时,除了表名不一样,其他代码都基本相同。
- 确定热门旅游景点分析目标,完成三个以上可视化图表;
(1)携程旅行(ctrip.com),旅游景点的可视化分析图。
(2)去哪儿(qunar.com),旅游景点的可视化分析图。
(3)穷游网(qyer.com),旅游景点的可视化分析图。
(4)同城旅行(ly.com),旅游景点的可视化分析图。
二、爬虫设计与实现
1.本次实验采用Scrapy框架,完成此次数据的爬取,Scrapy框架是一个高效率的开发工具,可以让开发者专注解析环节,减少代码编写。在编写Scrapy框架之前,需要先解决发送请求、解析数据、存储数据、突破网络反爬虫限制、去重等一系列问题。采取可读性更强的 xpath 代替正则,速度快,通过xpath爬取所需要的数据字段。
四个网站采取的都是scrapy框架
采集过程中的优势:
1)scrapy 是异步的,可以灵活调节并发量
2)采取可读性更强的 xpath 代替正则,速度快。
通过xpath爬取所需要的数据字段
3)写 middleware,方便写一些统一的过滤器
4)同时在不同的 url 上爬行
爬取去哪儿时,景点详情页面和景点列表页面以及景点列表页面的翻页
- 支持 shell 方式,方便独立调试
在爬取过程中,出现了数据爬取为空,此时通过shell方式。
今天在使用scrapy框架写爬虫代码时,运行爬虫文件后既不输出内容也不报错,然后就试着调试一下:
Scrapy.poispyder的同级目录下创建main.py,里面写如下代码
#调试
from scrapy import cmdline
cmdline.execute('scrapy crawl poi'.split())
#poi代表爬虫文件名
在爬虫文件你想调试的地方打上断点,回到main.py文件,右键点击调试,则可以了.
- 通过管道的方式存入数据库,灵活,可保存为多种形式
由于我们存储的mysql,在使用管道存入时,需在mysql中建立对应的表和爬取数据字段。
难题:
1).多个不同网址的跳转和景点列表下一页url的跳转的拼接。
2).以及在存入数据库时,数据量过大,超出了范围。
数据库建表时,插入字段超过了我们所设置的值,就会报dtaframe的异常错误。
- .插入数据库时,经常会出现list out of range的错误,经过发现,产生这个错误的原因是因为在scrapy爬虫爬取数据过程中,爬取到了空值。
- .存取数据过大时,数据库的表可以分为多个进行存储。
2.E-R图:(旅游景点和景区评论分别作为两个实体进行E-R图)
- 设计数据存储的结构,选择数据存储的物理方式(文本或mysql数据库),将文件存储为csv文件。
在爬取最开始的数据是存储csv文件。命令 scrapy crawl poi -o poi.csv
清洗后存入数据库。
三、数据预处理
利用Scrapy框架爬取出的数据,对数据进行清洗,将清洗后无误的数据存入数据库,并做好三个可视化图。(以携程网为例子)
总体思路(如下思维导图):
(1)读取通过scrapy框架爬取的文件,观察数据的大体情况;
df = pd.read_csv(r'C:\Users\yyqx\PycharmProjects\pythonProject1\xiecheng\xc.csv', encoding='gb18030')
scoresDF=pd.DataFrame(df,columns=['jqlevel' 'name' 'place' 'province' 'rd' 'time' 'usercontent' 'username'])
scoresmodDF=scoresDF.copy()
print(df.duplicated().value_counts())#判断数据有无重复
print(df.isnull())#缺失值
print(df.isnull().any())#isnull函数看一下是否有空值,结果是有空值的地方显示为True,没有的显示为False
print(df[df.isnull().values==True])#想具体看哪几行有空值,可以再用data.isnull().values==True来定位
df.dropna(how='all', axis=0, inplace=True)
print(df.isnull().all())
data1 = df[df.isnull().values == True]
df2 = data1['place'].str.replace("重庆市", " ")
data2 = data1.drop_duplicates()
print(data1.fillna({"jqlevel": "未评级","place":"未知"}))
结果图:
从结果图大致可以看出一共有28820条数据,对于每一个字段的非空数量都有一个大致的展示,他们的数据类型都是int64t类型;
- 查看数据的重复情况;
print(data.duplicated()) #查看有无重复数据,True表示有重复数据,False表示没有重复数据;
由结果图可以看出,数据中,存在重复数据,因此我们需删除重复数据;
data.drop_duplicates(inplace=True)
print(data.info())
(3)查看数据中每一列的缺失情况;
print(data.isnull().sum()) #查看每一列中的缺失值数量
由于再爬取大量景区数据过程中,评级的等级较少,所以在进行数据清洗时,对于景区等级(jqlevel)的值处理时,我们采取用暂未评级代替空值;
其他几个旅游网站数据清洗图:(同理)
清洗后的数据(cx1.csv)存入数据库;
创建数据库代码:
use yyqx;
CEATE TABLE If not exists tc(
name varchar(255) NOT NULL,
province varchar(255)NOT NULL,
jqlevel varchar(255) DEFAULT NULL,
place varchar(255) NOT NULL,
rd varchar(255) NOT NULL,
username varchar(255) NOT NULL,
commentcontent varchar(3200) NOT NULL,
time varchar(255) NOT NULL
)character set gbk;
数据库表
列名 | 数据类型 | 是否为空 | 备注 |
name | varchar(200) | no | 景点名称 |
parent_rank | varchar(100) | no | 排行榜 |
mentioned_times | varchar(50) | no | 提到次数 |
address | varchar(50) | no | 景点地址 |
Open_time | varchar(200) | no | 景点开放时间 |
username | varchar(50) | no | 用户名 |
usercontent | varchar(50) | no | 评论内容 |
tuume | varchar(50) | no | 评论时间 |
四、数据可视化
相关代码以及注释:(引用的外部库pyecharts)
#xy轴翻转图
bar2=(
Bar()
# tmp = number['jqlevel'].iloc[-5:].tolist()
.add_xaxis(list(number_tmp['lable']))
.add_yaxis('热门景点数据',list(number_tmp['num']))
.reversal_axis()
.set_global_opts(title_opts=opts.TitleOpts(title='热门景点数据'),
yaxis_opts=opts.AxisOpts(name='景点等级'),
xaxis_opts=opts.AxisOpts(name='数量'))
.set_series_opts(label_opts=opts.LabelOpts(position='right'),
itemstyle_opts={
"normal": {
"color": JsCode(
"""newecharts.graphic.LinearGradient(1, 0, 0, 0,
[{offset: 0,color: 'rgba(15, 47, 84, 1)'},
{offset: 1,color: 'rgba(343, 152, 212, 1)'}],
false)"""
),
}
}
)
)
bar2.render()
bar2.render_notebook()
图2通过景区的热度为纵坐标,景区名称为横坐标计算出人们在携程网上常去浏览的排行前十景区
主要代码及注释如下:(主要依赖于matplotlib)
result=data2["rd"].groupby(data2["name"]).count().\
reset_index(name='count').sort_values("count",ascending=False)
index=list(result["name"][:10])
values=list(result["count"][:10])
data3=pd.DataFrame(values,index)
rects=data3.plot(kind='bar',legend=None,title="携程旅游景区热度前10名")
plt.xticks(rotation=60)
plt.ylabel("热度")
mpl.rcParams['font.sans-serif']=['Microsoft YaHei']
plt.show()
result = data2["rd"].groupby(data2["name"]).count(). \
reset_index(name='count').sort_values("count", ascending=False)
index =list(number_tmp['lable'])
values = list(number_tmp['num'])
explode = (0, 0.08, 0, 0, 0, 0)
rects = plt.pie(values, explode=None, labeldistance=1.1, autopct='%1.1f%%', startangle=90, pctdistance=0.6)
plt.axis('equal')
plt.legend(loc='best', labels=index)
mpl.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.title('携程网前景区等级排名')
plt.show()
其他几个网站数据清洗和可视化原理也差不多:
- 总结
此次旅游景点爬取的项目可以让我们直观的看见排行榜靠前的热门城市,对于等级较高的景点也可以有个大致的了解,对我们想去哪儿旅游,有了一个可靠的借鉴。
通过此次课程设计,让我更加熟悉了Scrapy框架的运用,对于xpath的路径查询也更加熟练,在网页分析中,我们可以根据定位分析自己编写所要爬取数据的xpath路径;在处理分页的过程中,遇到过页面跳转网址不变的情况,面对这种情况,我们要找出翻页命令所在的位置,然后操控这条命令即可,具体操作就是先打开开发者模式
,在打开开发者模式的情况下点击翻页,找到翻页后返回的内容表单 (一般是XHR格式),查看其headers (注意pages,start,p等字眼),提取相应的部分,在python中编写语句实现控制。在此次清洗数据过程中,主要就是填充缺失值,去除不必要的字段,将数据的单位统一,方便可视化的进行。
1.在爬虫方面最主要的问题就是xpath的选取和不同网址url的拼接。
name = response.xpath('//*[@id="js_mainleft"]/div[3]/h1/text()').extract_first()#名字
parent_str = response.xpath('//div[@class="ranking"]/text()').extract_first()#排行榜
parent = parent_str.split('景点排名')[0]
parent_rank = response.xpath('//div[@class="ranking"]/span[@class="sum"]/text()').extract_first()
wander_time = response.xpath('//div[@class="time"]/text()').extract_first()##建议游览出游时间
mentioned_times = response.xpath('//div[@class="countbox"]//div[@class="sum"]/text()').extract_first()
summery_tag = response.xpath('//div[@class="e_summary_list clrfix"]')
address = summery_tag.xpath('//td[@class="td_l"]//span/text()').extract_first()
open_time = summery_tag.xpath('//td[@class="td_r"]//p/text()').extract_first()
comment_count = response.xpath('//span[@class="e_nav_comet_num"]//text()').extract_first()
comment_content=response.xpath('//p[@class="first"]//text()')[0].get()
comment_time=response.xpath('//*[@class="e_comment_add_info"]/ul/li/text()')[0].get()
username=response.xpath('//*[@class="e_comment_usr_name"]/a/text()')[0].get()
解决思路:一,xpath路径不对,网页内容有隐藏的陷阱
- 网页结构混乱,缺少一些标签,导致xpath无法识别。
在面对以上错误时,我采取的方法是:
第一步:先用正则表达式把这一段网页内容提取出来。
第二步:看要不要对这个网页内容做处理(比如有的网页会在冒号、斜杠等处加入\,这样的话需要去掉)
第三步:利用lxml模块的xpath对这个页面进行信息提取。
- 翻页,爬取不同网页。
我查看我爬取的网址是静态,通过一个range()循环
如果是动态,爬取下一页按钮的xpath路径进行函数调用
(动态如下):
# 景点详情页
if (re.match(r"http://travel.qunar.com/p-oi", url)):
yield parse_item(response)
# 景点列表页
elif (re.match(r"http://travel.qunar.com/p-cs", url)):
url_list = response.xpath('//a[@class="titlink" and @data-beacon="poi"]/@href').extract()
for url in url_list:
yield Request(url, callback=self.parse)
next_page_url = response.xpath('//a[@class="page next"]/@href').extract_first()
if (next_page_url != None):
yield Request(next_page_url, callback=self.parse)
(静态如下);
def start_requests(self):
for i in range(1, 201):
url = 'https://www.ly.com/scenery/NewSearchList.aspx?&action=getlist&page=' + str(i)
yield Request(url, callback=self.parse)
- 存入csv中,数据报错。
通过写shell文件进行对mysql的调试
发现报错产生原因是因为数据量大于所设的存储值。
- 数据可视化:
在数据可视化过程中,只完成了简单的柱形图,饼图进行表示。
在过程中,原来思路是借用外部库绘制中国地图在上面显示各个地区对应景点个数,绘制一张旅游图谱。
# from pyecharts.charts import Map
# geo = Geo()
# province_distribution = {'河南': 45.23, '北京': 37.56, '河北': 21, '辽宁': 12, '江西': 6, '上海': 20, '安徽': 10, '江苏': 16, '湖南': 9, '浙江': 13, '海南': 2, '广东': 22, '湖北': 8, '黑龙江': 11, '澳门': 1, '陕西': 11, '四川': 7, '内蒙古': 3, '重庆': 3, '云南': 6, '贵州': 2, '吉林': 3, '山西': 12, '山东': 11, '福建': 4, '青海': 1, '天津': 1, '其他': 1}
# provice = list(province_distribution.keys())
# values = list(province_distribution.values())
# map=Map("dtu",width=1200, height=600)
# map.add("", provice, values, maptype='china', is_visualmap=True, visual_text_color='#000')
# map.show_config()
# map.render()
# map.render(path="中国地图.html")
# geo.add_schema(maptype="china")
# geo.render()
# Geo(is_ignore_nonexistent_coord=True)
# ls_xc = df2[['jqlevel', 'place']].values.tolist()
# geo = Geo()
# geo.add_schema(maptype="china")
# geo.add('图表名称:携程网旅游图谱', ls_xc)
# geo.render()
目前虽然该代码部分已完成,但是地图并未完全绘制。
总结:
基于该旅游数据建立的柱形图,饼图,对热门旅游景点排行榜是一个指导。通过这些图,游客们可以发现那些地方排名前10的景点,以及游客对这些旅游景点的热度。