Python高级应用程序设计任务要求
用Python实现一个面向主题的网络爬虫程序,并完成以下内容:
(注:每人一题,主题内容自选,所有设计内容与源代码需提交到博客园平台)
一、主题式网络爬虫设计方案(15分)
1.主题式网络爬虫名称
去哪儿网攻略信息爬取
2.主题式网络爬虫爬取的内容与数据特征分析
爬取内容:文章链接、标题、简要秒速信息、发布者、发布者个人标签、出发日期、天数、拍照数量、出行的类型、旅行的标签、途经、行程路线、人均消费、观看数、点赞数、评论数
数据特征分析:
- 旅游出行类别分析
- 旅游出行各类别所占比例分析
- 对出行类别、出行描述与人均消费可视化图分析
- 各月份旅游量分析
- 出行天数、人均消费、观看数散点图分析
3.主题式网络爬虫设计方案概述(包括实现思路与技术难点)
实现思路:编写 HandleQuNar 类,HandleQuNar 类中 __init__ () 初始化方法,初始化爬虫过程中的 User-Agent;handle_travel()方法对url迭代,采用requests库的get()获取对应的html网页,利用BeautifulSoup的find_all方法获取每一页的10个li进行遍历,利用find等方法获取标签内相应的文本并对获取的数据进行处理。每爬取完一个li(攻略信息),将其设置成字典的格式,调用save()方法进行保存;save()方法将数据存储到csv文件中;process_num()方法对爬取下来的部分数据进行处理;handle_request()方法用于返回url对应的html文本;—main—方法用于执行程序。
技术难点:对数据的处理有一定的难点
二、主题页面的结构特征分析(15分)
1.主题页面的结构特征
共200页,每页有10条 li 对应着每一个攻略信息,通过查看网页源代码,发现每页都是静态页面,无ajax、js产生的动态数据

2.Htmls页面解析
图示中黑色框内为需爬取的数据。

3.节点(标签)查找方法与遍历方法
(必要时画出节点树结构)
爬取采用request库和BeautifulSoup库,利用BeautifulSoup的find_all方法获取每一页的10个li进行遍历,利用find等方法获取标签内相应的文本并对获取的数据进行处理。

三、网络爬虫程序设计(60分)
爬虫程序主体要包括以下各部分,要附源代码及较详细注释,并在每部分程序后面提供输出结果的截图。
import requests
import re
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
from datetime import datetime
from urllib import parse
import os
from pandas import DataFrame
class HnadleQuNar(object):
'''定义标头'''
def __init__(self):
self.headers = {
"User-Agent": UserAgent().random
}
'''定义qunar_data.csv的路径'''
file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'qunar_data.csv')
def handle_travel(self):
'''
解析去哪儿网的攻略信息
对于大部分字段用try-except,如果无相关字段,将以''保存
'''
for i in range(1, 201):
'''对200页的数据进行爬取'''
travelbook_url = "https://travel.qunar.com/travelbook/list.htm?page=%d&order=hot_heat"%(i)
travelbook_result = self.handle_request(method="GET", url=travelbook_url)
soup = BeautifulSoup(travelbook_result , "html.parser" , from_encoding="utf-8")
print("正在爬取:{}".format(travelbook_url))
lis = soup.find_all("li", class_="list_item")#获取所有相关的li节点
for li in lis:#遍历每一页的li节点
'''获取url信息'''
url = li.find("h2").find('a')["href"]
url = parse.urljoin('https://travel.qunar.com', url)
'''获取title信息'''
try:
title = li.find("h2").find('a').get_text()
except:
title = ''
try:
describle = li.find("h2").find('p')['class'][1]
except:
describle = ''
spans = li.find_all("span", class_="intro")
for span in spans:
'''获取发布者的用户名信息'''
try:
username = span.find("span").find("a",href=re.compile(r"space")).get_text()
except:
username = ''
'''获取发布者的个人标签'''
try:
label = span.find("span").find_all("a")
label = label[1].find("span")["title"]
except:
label = ''
'''获取出发日期'''
try:
startdate = span.find("span", class_="date").get_text()
startdate = re.findall(r'(\d+-\d+-\d+)' , startdate)[0]
startdate = datetime.strptime(startdate, "%Y-%m-%d")
except:
startdate = ''
'''获取天数'''
days = span.find("span",class_="days").get_text()
days = re.findall(r"\d+",days)[0]
days = int(days)
'''获取照片数量'''
photo_nums = span.find("span", class_="photo_nums").get_text()
photo_nums = int(re.findall(r"\d+", photo_nums)[0])
'''获取人均消费'''
try:
price = span.find("span", class_="fee").get_text()
price = int(re.findall(r"\d+",price)[0])
except:
price = 0
'''获取出行人员的类型'''
try:
people = span.find("span", class_="people").get_text()
except:
people = ''
'''获取旅行的标签'''
try:
trip = span.find("span", class_="trip").get_text()
except:
trip = ''
'''
获取旅行的途经
获取旅行的行程路线
'''
try:
places = li.find_all("p", class_="places") # 因途经或行程路线有为空的可能,所以在此做一个逻辑判断
if(len(places)!=0):
for i in range(len(places)):
if(len(places) == 2):
if(i==0):
via_places =places[i].get_text()
else:
distance = places[i].get_text()
elif(len(places)==1):
if("途经" in places[i].get_text()):
via_places = places[i].get_text()
distance = ''
else:
distance = places[i].get_text()
via_places = ''
else:
via_places = ''
distance = ''
except:
via_places = ''
distance = ''
'''获取该行程的观看数'''
view_nums = li.find("p", class_="user_info").find("span",class_="nums").find("span").find("span").get_text()
view_nums = self.process_num(view_nums)
'''获取该行程的点赞数'''
praise_nums = li.find("p", class_="user_info").find("span", class_="nums").find("span",class_="icon_love").find("span").get_text()
praise_nums = self.process_num(praise_nums)
'''获取该行程的评论数'''
comment_nums = li.find("p", class_="user_info").find("span", class_="nums").find("span",class_="icon_comment").find("span").get_text()
comment_nums = self.process_num(comment_nums)
# 写入到csv文件
df = DataFrame({
"url": [url],
"title": [title],
"describle": [describle],
"username": [username],
"label": [label],
"date": [startdate],
"days": [days],
"photo_nums": [photo_nums],
"people": [people],
"trip": [trip],
"via_places": [via_places],
"distance": [distance],
"price": [price],
"view_nums": [view_nums],
"praise_nums": [praise_nums],
"comment_nums": [comment_nums]
})
'''利用save()将攻略信息保存'''
self.save(df)
def save(self,df):
'''
将数据写入csv
'''
# 若文件已存在,将以追加的形势存入csv文件中
if (os.path.exists(self.file_path)):
df.to_csv(self.file_path, header=False, index=False, mode="a+", encoding="utf_8_sig")
else:
df.to_csv(self.file_path, index=False, mode="w+", encoding="utf_8_sig")
def process_num(self,value):
'''
将例如点赞数,观看数的一些数据格式化
'''
if("万" in value):
value = float(re.search(r"\d+\.{0,1}\d{0,}",value)[0])
value = int(value*10000)
else:
value = int(re.search(r"\d+\.{0,1}\d{0,}",value)[0])
return value
def handle_request(self,method,url,data=None,info=None):
if method == "GET":
response = requests.get(url=url,headers = self.headers)
try:
response.raise_for_status() #状态码
response.encoding = 'UTF-8' #定义编码格式
return response.text #获取对于的html文本
except Exception as e:
raise e
else:
pass
'''主函数'''
if __name__ == "__main__":
qunar = HnadleQuNar()
qunar.handle_travel()
1.数据爬取与采集
2.对数据进行清洗和处理
2.1进行导包
import pandas as pd import numpy as np import matplotlib.pyplot as plt from datetime import datetimefrom mpl_toolkits. mplot3d import Axes3D
2.2获取数据所保存的csv文件
qunar = pd.read_csv(r"D:\Python\qunar_data.csv")
2.3查看数据前5行
qunar.head()

2.4查看数据分布情况
qunar.shape

2.5查看是否存在重复值
qunar.duplicated()

2.6去除"username"与"title"的重复值
# "title"、"username"不可存在重复值qunar['username'].drop_duplicates qunar['title'].drop_duplicatesqunar.head()

2.7使用describe查看统计信息
qunar.describe()

2.8查看"describle"数据的合计,并将"describle"数据内的拼音转换为中文
qunar['describle'].value_counts()

qunar['describle'] = qunar['describle'].replace("meitu", "美图")
qunar['describle'] = qunar['describle'].replace("ganhuo", "干货")
qunar['describle'] = qunar['describle'].replace("duantupai", "短途派")
qunar['describle'] = qunar['describle'].replace("jinghua", "臻华")
qunar['describle'] = qunar['describle'].replace("wenyifan", "文艺范")
qunar['describle'].value_counts()

3.文本分析(可选):jieba分词、wordcloud可视化
4.数据分析与可视化
显示正常中文和负号
plt.rcParams['font.family'] = ['sans-serif'] plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
4.1查看出行人员类别的直方图
plt.bar(peoples.index,
peoples.values,
label="people各类人数直方图")
plt.legend()
plt.show()

4.2查看各旅行人员类别所占比例
peoples = qunar['people'].value_counts()Type = peoples.index
Data = peoples.values
plt.pie(Data ,labels=Type, autopct='%1.1f%%')
plt.axis('equal')
plt.title('各旅行人员类别所占比例')
plt.show()

4.3查看"describle"与"people"所对应的人均消费信息并可视化
df_pivot = qunar.pivot_table(index="people", columns="describle", values="price") df_pivot.shape df_pivot

df_pivot.plot(kind='bar', title='people and price')
plt.xlabel('people')
plt.ylabel('price')

4.4查看去哪儿各月份攻略信息的旅游量
for i in range(len(qunar['date'])):
qunar['date'][i] = qunar['date'][i].replace(qunar['date'][i],qunar['date'][i][5:7])
dates = qunar['date'].value_counts()
plt.bar(dates.index,
dates.values,
label="各月份攻略信息的旅游量")
plt.legend()
plt.show()

4.5查看出行天数、人均消费、观看人数的3D散点图
fig = plt.figure()
ax = Axes3D(fig)
x = np.array(qunar['days'])
y = np.array(qunar['price'])
z = np.array(qunar['view_nums'])
ax. scatter(x, y, z)
ax.set_xlabel('出行天数')
ax.set_ylabel( '人均消费' )
ax.set_zlabel('观看人数' )
plt.show()

(例如:数据柱形图、直方图、散点图、盒图、分布图、数据回归分析等)
5.数据持久化
将爬下来的数据存入csv文件中,以实现数据持久化

6.完整代码
1 import requests
2 import re
3 from bs4 import BeautifulSoup
4 from fake_useragent import UserAgent
5 from datetime import datetime
6 from urllib import parse
7 import os
8 from pandas import DataFrame
9 import pandas as pd
10 import numpy as np
11 import matplotlib.pyplot as plt
12 from datetime import datetime
13 from mpl_toolkits. mplot3d import Axes3D
14
15 class HnadleQuNar(object):
16 '''定义标头'''
17 def __init__(self):
18 self.headers = {
19 "User-Agent": UserAgent().random
20 }
21 '''定义qunar_data.csv的路径'''
22 file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'qunar_data.csv')
23 def handle_travel(self):
24 '''
25 解析去哪儿网的攻略信息
26 对于大部分字段用try-except,如果无相关字段,将以''保存
27 '''
28 for i in range(1, 201):
29 '''对200页的数据进行爬取'''
30 travelbook_url = "https://travel.qunar.com/travelbook/list.htm?page=%d&order=hot_heat"%(i)
31 travelbook_result = self.handle_request(method="GET", url=travelbook_url)
32 soup = BeautifulSoup(travelbook_result , "html.parser" , from_encoding="utf-8")
33 print("正在爬取:{}".format(travelbook_url))
34 lis = soup.find_all("li", class_="list_item")#获取所有相关的li节点
35 for li in lis:#遍历每一页的li节点
36 '''获取url信息'''
37 url = li.find("h2").find('a')["href"]
38 url = parse.urljoin('https://travel.qunar.com', url)
39 '''获取title信息'''
40 try:
41 title = li.find("h2").find('a').get_text()
42 except:
43 title = ''
44
45 try:
46 describle = li.find("h2").find('p')['class'][1]
47 except:
48 describle = ''
49 spans = li.find_all("span", class_="intro")
50 for span in spans:
51 '''获取发布者的用户名信息'''
52 try:
53 username = span.find("span").find("a",href=re.compile(r"space")).get_text()
54 except:
55 username = ''
56 '''获取发布者的个人标签'''
57 try:
58 label = span.find("span").find_all("a")
59 label = label[1].find("span")["title"]
60 except:
61 label = ''
62 '''获取出发日期'''
63 try:
64 startdate = span.find("span", class_="date").get_text()
65 startdate = re.findall(r'(\d+-\d+-\d+)' , startdate)[0]
66 startdate = datetime.strptime(startdate, "%Y-%m-%d")
67 except:
68 startdate = ''
69 '''获取天数'''
70 days = span.find("span",class_="days").get_text()
71 days = re.findall(r"\d+",days)[0]
72 days = int(days)
73 '''获取照片数量'''
74 photo_nums = span.find("span", class_="photo_nums").get_text()
75 photo_nums = int(re.findall(r"\d+", photo_nums)[0])
76 '''获取人均消费'''
77 try:
78 price = span.find("span", class_="fee").get_text()
79 price = int(re.findall(r"\d+",price)[0])
80 except:
81 price = 0
82 '''获取出行人员的类型'''
83 try:
84 people = span.find("span", class_="people").get_text()
85 except:
86 people = ''
87 '''获取旅行的标签'''
88 try:
89 trip = span.find("span", class_="trip").get_text()
90 except:
91 trip = ''
92 '''
93 获取旅行的途经
94 获取旅行的行程路线
95 '''
96 try:
97 places = li.find_all("p", class_="places")
98 if(len(places)!=0):
99 for i in range(len(places)):
100 if(len(places) == 2):
101 if(i==0):
102 via_places =places[i].get_text()
103 else:
104 distance = places[i].get_text()
105 elif(len(places)==1):
106 if("途经" in places[i].get_text()):
107 via_places = places[i].get_text()
108 distance = ''
109 else:
110 distance = places[i].get_text()
111 via_places = ''
112 else:
113 via_places = ''
114 distance = ''
115 except:
116 via_places = ''
117 distance = ''
118 '''获取该行程的观看数'''
119 view_nums = li.find("p", class_="user_info").find("span",class_="nums").find("span").find("span").get_text()
120 view_nums = self.process_num(view_nums)
121 '''获取该行程的点赞数'''
122 praise_nums = li.find("p", class_="user_info").find("span", class_="nums").find("span",class_="icon_love").find("span").get_text()
123 praise_nums = self.process_num(praise_nums)
124 '''获取该行程的评论数'''
125 comment_nums = li.find("p", class_="user_info").find("span", class_="nums").find("span",class_="icon_comment").find("span").get_text()
126 comment_nums = self.process_num(comment_nums)
127 # 写入到csv文件
128 df = DataFrame({
129 "url": [url],
130 "title": [title],
131 "describle": [describle],
132 "username": [username],
133 "label": [label],
134 "date": [startdate],
135 "days": [days],
136 "photo_nums": [photo_nums],
137 "people": [people],
138 "trip": [trip],
139 "via_places": [via_places],
140 "distance": [distance],
141 "price": [price],
142 "view_nums": [view_nums],
143 "praise_nums": [praise_nums],
144 "comment_nums": [comment_nums]
145 })
146 '''利用save()将攻略信息保存'''
147 print(df)
148 self.save(df)
149
150 def save(self,df):
151 '''
152 将数据写入csv
153 '''
154 # 如果文件已经存在了,追加的形式写入文件,且不设置columns
155 if (os.path.exists(self.file_path)):
156 # 字符编码采用utf-8-sig,因为存在表情包
157 df.to_csv(self.file_path, header=False, index=False, mode="a+", encoding="utf_8_sig")
158 else:
159 df.to_csv(self.file_path, index=False, mode="w+", encoding="utf_8_sig")
160
161 def process_num(self,value):
162 '''
163 将例如点赞数,观看数的一些数据格式化
164 '''
165 if("万" in value):
166 value = float(re.search(r"\d+\.{0,1}\d{0,5}",value)[0])
167 value = int(value*10000)
168 else:
169 value = int(re.search(r"\d+\.{0,1}\d{0,5}",value)[0])
170 return value
171
172 def handle_request(self,method,url,data=None,info=None):
173 if method == "GET":
174 response = requests.get(url=url,headers = self.headers)
175 try:
176 response.raise_for_status() #状态码
177 response.encoding = 'UTF-8' #定义编码格式
178 return response.text #获取对于的html文本
179 except Exception as e:
180 raise e
181 else:
182 pass
183 '''主函数'''
184 if __name__ == "__main__":
185 qunar = HnadleQuNar()
186 qunar.handle_travel()
187
188
189 qunar = pd.read_csv(r"D:\Python\qunar_data.csv")
190 qunar.head()#查看文件前5行
191 qunar.shape#查看文件行列数
192 qunar.duplicated()
193
194 # "title"、"username"不可存在重复值
195 qunar['username'].drop_duplicates
196 qunar['title'].drop_duplicates
197 qunar.head()
198 qunar.describe()
199
200 #将"describle"字段中的拼音改为中文
201 qunar['describle'] = qunar['describle'].replace("meitu", "美图")
202 qunar['describle'] = qunar['describle'].replace("ganhuo", "干货")
203 qunar['describle'] = qunar['describle'].replace("duantupai", "短途派")
204 qunar['describle'] = qunar['describle'].replace("jinghua", "臻华")
205 qunar['describle'] = qunar['describle'].replace("wenyifan", "文艺范")
206 qunar['describle'].value_counts()#修改后的"describle"统计
207
208 #用于正常显示中文和负号
209 plt.rcParams['font.family'] = ['sans-serif']
210 plt.rcParams['font.sans-serif'] = ['SimHei']
211 plt.rcParams['axes.unicode_minus']=False
212
213 #查看出行人员类别的直方图
214 peoples = qunar['people'].value_counts()
215 plt.bar(peoples.index,
216 peoples.values,
217 label="people各类人数直方图")
218 plt.legend()
219 plt.show()
220
221 #查看各旅行人员类别所占比例(扇形图)
222 Type = peoples.index
223 Data = peoples.values
224 plt.pie(Data ,labels=Type, autopct='%1.1f%%')
225 plt.axis('equal')
226 plt.title('各旅行人员类别所占比例')
227 plt.show()
228
229 #查看"describle"与"people"所对应的人均消费透视表并可视化
230 df_pivot = qunar.pivot_table(index="people", columns="describle", values="price")
231 df_pivot.shape
232 df_pivot.plot(kind='bar', title='people and price')
233 plt.xlabel('people')
234 plt.ylabel('price')
235
236 #查看去哪儿各月份攻略信息的旅游量
237 for i in range(len(qunar['date'])):
238 qunar['date'][i] = qunar['date'][i].replace(qunar['date'][i],qunar['date'][i][5:7])
239 dates = qunar['date'].value_counts()
240 plt.bar(dates.index,
241 dates.values,
242 label="各月份攻略信息的旅游量")
243 plt.legend()
244 plt.show()
245
246 #查看出行天数、人均消费、观看人数的3D散点图
247 fig = plt.figure()
248 ax = Axes3D(fig)
249 x = np.array(qunar['days'])
250 y = np.array(qunar['price'])
251 z = np.array(qunar['view_nums'])
252 ax. scatter(x, y, z)
253 ax.set_xlabel('出行天数')
254 ax.set_ylabel( '人均消费' )
255 ax.set_zlabel('观看人数' )
256 plt.show()
四、结论(10分)
1.经过对主题数据的分析与可视化,可以得到哪些结论?
1.1通过对出行人员类别的可视图分析,可以看出,去哪儿攻略中大多数的出行人员类别都是“”三五好友“”,攻略中所占比例为36.5%。而出行人员类别中的"学生"在攻略中是最少的。攻略中所占比例为1.3%.
1.2通过对"describle"与"people"所对应的人均消费信息并可视图分析,在人均消费中,属出行类别为"家庭",描述类别为"精品"的人均消费最高。而类别为"学生"的人均消费都较低。同时也可以看出:出行描述中,"精品"的人均消费最高,"短途派"的人均消费最低。出行类别中,"家庭"、"亲子"人均消费较其他出行类别人均消费更高。
1.3通过对去哪儿各月份攻略信息的旅游量可视图分析,可以看出,旅游攻略中,大多数的出行时间为4-10月份,其中6月的旅游量最高,12月份的旅游量最低。
1.4通过对出行天数、人均消费、观看人数的3D散点图分析,可以看出出行天数和人均消费存在正相关,其他之间存在较弱的相关性。
2.对本次程序设计任务完成的情况做一个简单的小结。
通过这次的爬虫任务以及数据分析,了解了更多关于爬虫的技巧和知识,也发现了对这方面还需要补充很多的知识,’需要查缺补漏,更深层的掌握爬虫技术。
来源:https://www.cnblogs.com/wuzezong/p/11992040.html