Python高级应用程序设计任务

冷暖自知 提交于 2020-02-25 12:43:48

Python高级应用程序设计任务要求

用Python实现一个面向主题的网络爬虫程序,并完成以下内容:
(注:每人一题,主题内容自选,所有设计内容与源代码需提交到博客园平台)

一、主题式网络爬虫设计方案(15分)
1.主题式网络爬虫名称

  去哪儿网攻略信息爬取

2.主题式网络爬虫爬取的内容与数据特征分析

  爬取内容:文章链接、标题、简要秒速信息、发布者、发布者个人标签、出发日期、天数、拍照数量、出行的类型、旅行的标签、途经、行程路线、人均消费、观看数、点赞数、评论数

  数据特征分析:

  1. 旅游出行类别分析
  2. 旅游出行各类别所占比例分析
  3. 对出行类别、出行描述与人均消费可视化图分析
  4. 各月份旅游量分析
  5. 出行天数、人均消费、观看数散点图分析

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.对本次程序设计任务完成的情况做一个简单的小结。

  通过这次的爬虫任务以及数据分析,了解了更多关于爬虫的技巧和知识,也发现了对这方面还需要补充很多的知识,’需要查缺补漏,更深层的掌握爬虫技术。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!