以探索东北旅游业现状为例:阐述入门级城市大数据基础技能分享–Python爬虫,地理信息数据处理与可视化 (上篇)
本文多数内容基于Python讲述,默认本文读者拥有基本的Python基础。
近年来,城市规划,商业分析,数字营销等行业都大量需要大数据技术进行支撑。数据不再仅限计算机从业者和码农的专属技能,基本上所有需要做量化分析的职业都再积极地尝试利用数据来支撑业务或研究,甚至原来的定性分析的很多职业也都大量的使用大数据来进行工作。基本的数据分析,挖掘,可视化的能力已成为置业发展的必备技能。
本文从城市学者的角度,以探索东北旅游业现状为例,阐述基本的城市大数据应用所需技能,本文主要包括三部分内容,分为上中下三篇来进行阐述。希望通过这篇文章可以帮助和我一样刚刚入门的城市学者掌握基础的数据技能
- 上篇:爬虫,以获取百度旅游景点信息,评论信息为例
- 中篇:地理信息数据处理,将景点名称通过百度API匹配空间坐标,然后转化为WGS84坐标系
- 下篇:可视化,基于Python进行基础数据分析,并进行可视化。基于R进行空间数据可视化。
1.网络爬虫简介(小白请阅读)
对于和作者一样的非CS背景的同学来说,对于数据挖掘的框架理解是很弱的,根据我的理解,(对于小规模项目可先不涉及scrapy这样的多线程爬虫框架),常见的数据抓取包括以下四个过程:
- 源代码获取 ,通过requests或者selenium这样的工具我们可以拿到网站的源代码,可以理解为拿到我们看到的所有网页内容,这其中包含了我们要的网页各项元素,但是它们基本都是以HTML或者JavaScript的形式存在的,很多内容是我们不需要而,所以我们需要解析;
- 源代码解析 ,可以理解为将所需的内容从源代码中找出来,源代码的解析方式有很多这里推荐三个主流的语法,①正则表达式re:简单粗暴,可以解决大多数问题,但是较为低效率;②xpath,适用于能完全解析的情况,最为便捷,但是获取动态数据容易出现bug; ③beautisoup,类似于xpath的一种方法,功能更加完善;建议大家入门先学习基本的正则表达式,应用范围很广;
- 数据处理 ,通过解析的数据是非结构化的,多数以字典或者列表的形式存在,我们一般用pandas这样的package来进行处理,将源数据整理成我们需要的结构化数据,同时也需要根据具体需要进行数据清理;
- 数据存储 处理好的整齐数据,可以选择存放在本地,也可以存入数据库,主要的存储形式Python都提供了很好的接口,本文介绍的代码都是将数据存储在本计算机上,下一篇文章将详细讲解如何存储在基于MySQL的数据库上;
动态网页与静态网页
本文是基于动态网页进行爬虫的案例,有HTML和JavaScript基础的同学比较好理解,静态网页就是提前将网页写好“装修好”放在服务器上供用户访问,动态网页则是将网页框架“装修好”放在服务器上,框架中的内容通过JavaScript从数据库中传输到网页上,比如实时的 房价,微博内容。目前多数网页都是动态网页,所以建议初学者直接从动态网页入手,本文的技术路线为使用无头浏览器phantomjs+selenium结合的方式来获取动态网页。
所需环境
本文作者的操作环境为Mac os 10.x, 所有操作均基于Python3.6编写,关于在R中出图以及空间操作将在后续给出,所需要的Python package 有以下几个,建议大家都使用Anaconda进行安装,其一站式的安装,可以跳过99%Python安装的坑:
- pandas ,Python数据处理必备包;
- selenium ,获取动态页面的工具包,之前作者也是使用requests进行抓取,但是现在绝大多数网页都是动态页面,基本信息都是在js代码中,所以建议大家入门就学习动态网页爬取;
- phantomjs ,顾名思义,无头浏览器,安装简便;
- re ,正则表达式调用;
- time ,计时器调用;
2.爬虫内容
接下来介绍代码:
第一步,先踩点:了解所爬的网站框架
爬虫程序没有一个标准的代码结构,因为每一个网站的结构是不一样的,了解网站结构就像是踩点一样,提前摸清网站框架,才能设计爬虫的程序,才能以不变应万变。以本次要爬的百度旅游为例。 我们的任务是获取所有景点的评论内容,评论时间以及每条评论的评分。
百度旅游大致框架如下:每个景点比如中央大街的域名为https://lvyou.baidu.com/zhongyangdajie/remark/?rn=15&pn=0&style=hot#remark-container,并且可以根据需要改变域名进行翻页。其中"zhongyangdajie"字段就代表了景点的名称。如果想要获取所有的景点评论,就需要先获取所有景点名称,而景点信息可以从主页:https://lvyou.baidu.com/haerbin/jingdian/来进行获取。接下来工作主要是获取所有景区名称与爬取评论。
第二步:获取景点名称
#获取域名,输入是源代码,
新技术路径
import selenium
from selenium import webdriver
import pandas as pd
import requests
import numpy as np
import re
import matplotlib.pyplot as plt
import time
def geturl(shu):
pattern=r"a href=\"(.*?)条点评"
a=re.findall(pattern,shu)
for i in range(0,18,1):
print(i)
pattern=r"/(.*?)/remark"
b=re.findall(pattern,a[i])[0]
a[i]=b
a=a[0:18]
return(a)
#mid=geturl(source)
#先获取所有的名字
url="http://lvyou.baidu.com/haerbin/jingdian/"
browser = webdriver.PhantomJS()
browser.get(url)
time.sleep(20)
source=browser.page_source
if len(source)>140000:
#获取第一页的名字
jingdian=geturl(source)
for i in range(0,10,1):
#先翻页每页尝试10次
y=1
while y<10:
y=y+1
print(y)
browser.find_elements_by_xpath("//a[@title=\"Go to next page\"]")[0].click()
time.sleep(20)
source=browser.page_source
if len(source)>140000:
jingdian1=geturl(source)
print(jingdian1)
jingdian=jingdian+jingdian1
y=10
else:
print("network_is_boken")
data=pd.DataFrame(data={"jingdian":jingdian})
输出的data就包含了所有的哈尔滨的景点名称,可以理解为每个景点在百度旅游这个网站上钥匙。
第三步:获取评论信息
#新技术路径
import selenium
from selenium import webdriver
import pandas as pd
import requests
import numpy as np
import re
import matplotlib.pyplot as plt
import time
获取域名,输入是源代码,
def geturl(shu):
pattern=r"a href=\"(.*?)条点评"
a=re.findall(pattern,shu)
for i in range(0,18,1):
print(i)
pattern=r"/(.*?)/remark"
b=re.findall(pattern,a[i])[0]
a[i]=b
a=a[0:18]
return(a)
#mid=geturl(source)
#获取评论时间,输入是源代码
def getct(source):
patternc=r"ri-time\">(.*?)<"
c=re.findall(patternc,source)
return(c)
#c=getct(source)
#c
#获取评论内容输入是源代码
def getcc(source):
patternb=r"class=\"ri-remarktxt\">(.*?)
"
b=re.findall(patternb,source)
return(b)
#获取评分输入是源代码
def getcs(source):
patterna=r"ri-star ri-star-(.*?)\""
a=re.findall(patterna,source)
return(a)
#组装一下获取单个景点结果的,输入经典名称,输出是dataframe 前五页吧,一个评价还是个bug
def getjingdian(name):
us="https://lvyou.baidu.com/"
ud="/remark/?rn=15&pn=0&style=hot#remark-container"
url=us+name+ud
source=getsource(url,60000)
if len(source)>60000:
pattern1=r"itemprop=\"name\">(.*?)"#获取名字
namej=re.findall(pattern1,source)[0]
pattern2=r"all-counts\">(.*?)条" #获取数字
number=re.findall(pattern2,source)
number=int(number[0])
#计算长度
fori=(number-number%15)/15
fori=int(fori) #浮点数转化为整数
#先获取第一页
timeall=getct(source)
commentall=getcc(source)
scoreall=getcs(source)
if fori>1:
for i in range(1,fori,1):
i=i*15
url=us+name+"/remark/?rn=15&pn="+str(i)+"&style=hot#remark-container"
source=getsource(url,60000)
if len(source)>60000:
time=getct(source)
timeall=timeall+time
comment=getcc(source)
commentall=commentall+comment
score=getcs(source)
scoreall=scoreall+score
print(timeall)
print(commentall)
print(scoreall)
print(len(timeall))
print(len(commentall))
print(len(scoreall))
t=len(timeall)
c=len(commentall)
s=len(scoreall)
try:
if t!=c or t!=s:
print("ok")
m=min([t,c,s])
print(m)
timeall=timeall[0:m]
print("ok1")
commentall=commentall[0:m]
print("ok2")
scoreall=scoreall[0:m]
print("ok3")
except:
commentall=timeall
scoreall=timeall
data=pd.DataFrame(data={"time": timeall,"comment":commentall,"score":scoreall})
data["地点"]=namej
return(data)
else:
data=pd.DataFrame(data={"time": ["2015-1-21 13:27","2015-1-21 13:27"],"comment":["1","2"],"score":["2","5"],"地点":["哈尔滨","哈尔滨"]})
return(data)
#getjingdian("hengtoushan")#可以了
#6:强行攻击页面,输入为url 和要求的长度 返回源代码 q强行攻击11遍 并且容错
def getsource(url,length):
x=0
url=url
while x<5:
x=x+1
print(x)
try:
browser = webdriver.PhantomJS()
browser.get(url)
time.sleep(30)
source=browser.page_source
except:
source="cuole"+url
print(source)
if len(source)>length: #到了要求就返回source
x=11
print(11)
return(source)
if x==5:
#到了五还不性就返回no
source="bushiduide"
return(source)
print("url"+"buok")
#获取评论
#先给弄个表头,分段开始获取,防止服务器崩溃
data1=pd.DataFrame(data={"time": ["2015-1-21 13:27","2015-1-21 13:27"],"comment":["1","2"],"score":["2","5"],"地点":["哈尔滨","哈尔滨"]})
for i in range(0,3,1):
jingname=jingdian[i]
data=getjingdian(jingname)
data1=(data1,data)
data1=pd.concat(data1)
data1.to_csv("0dao3.csv")
data1=pd.DataFrame(data={"time": ["2015-1-21 13:27","2015-1-21 13:27"],"comment":["1","2"],"score":["2","5"],"地点":["哈尔滨","哈尔滨"]})
for i in range(3,7,1):
jingname=jingdian[i]
data=getjingdian(jingname)
data1=(data1,data)
data1=pd.concat(data1)
data1.to_csv("3dao7.csv")
data1=pd.DataFrame(data={"time": ["2015-1-21 13:27","2015-1-21 13:27"],"comment":["1","2"],"score":["2","5"],"地点":["哈尔滨","哈尔滨"]})
for i in range(7,20,1):
jingname=jingdian[i]
data=getjingdian(jingname)
data1=(data1,data)
data1=pd.concat(data1)
#
data1.to_csv("7dao20.csv")
data1=pd.DataFrame(data={"time": ["2015-1-21 13:27","2015-1-21 13:27"],"comment":["1","2"],"score":["2","5"],"地点":["哈尔滨","哈尔滨"]})
for i in range(20,50,1):
jingname=jingdian[i]
data=getjingdian(jingname)
data1=(data1,data)
data1=pd.concat(data1)
#
data1.to_csv("20dao50.csv")
data1=pd.DataFrame(data={"time": ["2015-1-21 13:27","2015-1-21 13:27"],"comment":["1","2"],"score":["2","5"],"地点":["哈尔滨","哈尔滨"]})
for i in range(50,len(jingdian),1):
jingname=jingdian[i]
data=getjingdian(jingname)
data1=(data1,data)
data1=pd.concat(data1)
#
data1.to_csv("50daolast.csv")
小结
本文对于抓取数据的流程有一个简单的介绍,为了简单易懂,代码写的比较冗余。但是容错性比较强,bug基本已经都排除了。因为细节内容还是比较多,新入手的小伙伴可能还有一些不太清楚地细节,可以结合之前的内容和代码中的备注进行参考,也可以下载下来自己运行一下。
接下来两篇文章将阐述如何对地理数据进行清洗与解析、数据可视化的内容。