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

一、主题式网络爬虫设计方案(15分)
1.主题式网络爬虫名称
爬取微博博主的信息和博文
2.主题式网络爬虫爬取的内容与数据特征分析
对指定的微博博主信息与博文分类爬取
3.主题式网络爬虫设计方案概述(包括实现思路与技术难点)
  先爬取页面的HTML,其中城市对应简称是需要另外的接口查询得出,然后使用正则表达式,取出城市名和简称的值
二、主题页面的结构特征分析(15分)
1.主题页面的结构特征
https://kyfw.12306.cn/otn/login/init
2.Htmls页面解析

3.节点(标签)查找方法与遍历方法
(必要时画出节点树结构)

 
三、网络爬虫程序设计(60分)
爬虫程序主体要包括以下各部分,要附源代码及较详细注释,并在每部分程序后面提供输出结果的截图。
1.数据爬取与采集
爬虫程序主体要包括以下各部分,要附源代码及较详细注释,并在每部分程序后面提供输出结果的截图。

总代码

  1 from login import Login
  2 import os
  3 import json
  4 import time
  5 from collections import deque, OrderedDict
  6
  7 class Station:
  8     """ 查询车票信息 """
  9
 10     def __init__(self):
 12         self.session = Login.session
 13         self.headers = Login.headers
 14
 15
 16     def station_name_code(self):
 17         """
 18         功能:获取每个站点的名字和对应的代码,并保存到本地
 19         :return: 无
 20         """
 21         filename = 'station_name.txt'
 22
 23         url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js'
 24         resp = self.session.get(url, headers=self.headers)
 25         if resp.status_code == 200:
 26             print('station_name_code():获取站点信息成功!')
 27             with open(filename, 'w') as f:
 28                 for each in resp.text.split('=')[1].split('@'):
 29                     if each != "'":
 30                         f.write(each)
 31                         f.write('\n')
 32         else:
 33             print('station_name_code() error! status_code:{}, url: {}'
 34                   .format(resp.status_code, resp.url))
 35
 36     def save_station_code(self, filename):
 37         """
 38         功能:从站点文件中提取站点与其对应的代码,并保存到文件中
 39         :return:
 40         """
 41
 42         if not os.path.exists(filename):
 43             print('save_station_code():',filename,'不存在,正在下载!')
 44             self.station_name_code()
 45
 46         file = 'name_code.json'
 47         name_code_dict = {}
 48         with open(filename, 'r') as f:
 49             for line in f:
 50                 # 对读取的行都进行split操作,然后提取站点名和其代码
 51                 name = line.split('|')[1] # 站点名字
 52                 code = line.split('|')[2] # 每个站点对应的代码
 53                 # 每个站点肯定都是唯一的
 54                 name_code_dict[name] = code
 55
 56         # 把name,code保存到本地文件中,方便以后使用
 57         with open(file, 'w') as f:
 58             # 不以ascii码编码的方式保存
 59             json.dump(name_code_dict, f, ensure_ascii=False)
 60
 61
 62     def query_ticket(self):
 63         """
 64         功能:查票操作
 65         :return: 返回查询到的所有车次信息
 66         """
 67
 68         data = self._query_prompt()
 69         if not data:
 70             print('query_ticket() error: {}'.format(data))
 71         _, from_station, to_station = data.keys()
 72         train_date = data.get('train_date')
 73         from_station_code = data.get(from_station)
 74         to_station_code = data.get(to_station)
 75
 76         query_param = 'leftTicketDTO.train_date={}&' \
 77                       'leftTicketDTO.from_station={}&' \
 78                       'leftTicketDTO.to_station={}&' \
 79                       'purpose_codes=ADULT'\
 80             .format(train_date, from_station_code, to_station_code)
 81
 82         url = 'https://kyfw.12306.cn/otn/leftTicket/queryZ?'
 83
 84         full_url = url + query_param
 85         resp = self.session.get(full_url, headers=self.headers)
 86         if resp.status_code == 200 and resp.url == full_url:
 87             print('query_ticket() 成功!然后进行车票清理工作!')
 88             self._get_train_info(resp.json(), from_station, to_station)
 89
 90         else:
 91             print('query_ticket() error! status_code:{}, url:{}\norigin_url:{}'
 92                   .format(resp.status_code, resp.url, full_url))
 93
 94     def _get_train_info(self, text, from_station, to_station):
 95         """
 96         功能:提取出查询到的列车信息
 97         :param text: 包含所有从起点站到终点站的车次信息
 98         :return: 返回所有车次信息
 99         """
100         if not text:
101             print('_query_train_info() error: text为:', text)
102         # 把json文件转变成字典形式
103         result = dict(text)
104         # 判断有无车次的标志
105         if result.get('data').get('map'):
106             train_info = result.get('data').get('result')
107             train_list = deque()
108             for item in train_info:
109                 split_item = item.split('|')
110                 item_dict= {}
111                 for index, item in enumerate(split_item,0):
112                     print('{}:\t{}'.format(index, item))
113                 if split_item[11] == 'Y': # 已经开始卖票了
114                     item_dict['train_name'] = split_item[3] # 车次名
115                     item_dict['depart_time'] = split_item[8] # 出发时间
116                     item_dict['arrive_time'] = split_item[9] # 到站时间
117                     item_dict['spend_time'] = split_item[10] # 经历时长
118                     item_dict['wz'] = split_item[29] # 无座
119                     item_dict['yz'] = split_item[28] # 硬座
120                     item_dict['yw'] = split_item[26] # 硬卧
121                     item_dict['rw'] = split_item[23] # 软卧
122                     item_dict['td'] = split_item[32] # 特等座
123                     item_dict['yd'] = split_item[31] # 一等座
124                     item_dict['ed'] = split_item[30] # 二等座
125                     item_dict['dw'] = split_item[33] # 动卧
126                     train_list.append(item_dict)
127                 # 无法买票的车次,有可能是已卖光,也有可能是还不开卖
128                 elif split_item[0] == '':
129                     print('_query_train_info():车次{}的票暂时不能购买!'
130                           .format(split_item[3]))
131                 else:
132                     print('_query_train_info():车次{}还未开始卖票,起售时间为:{}'
133                           .format(split_item[3], split_item[1]))
134             # 调用方法来打印列车结果
135             self._print_train(train_list, from_station, to_station)
136         else:
137             print('_get_train_info() error: 从{}站到{}站有没列车!'
138                   .format(from_station, to_station))
139
140     def _print_train(self, train_info, from_station, to_station):
141         """
142         功能:打印查询到的车次信息
143         :param train_info: 提取出来的车次信息
144         :return:
145         """
146
147         if not train_info:
148             print('_print_train() error: train_info是None!')
149             return
150
151         print('从{}到{}还有余票的列车有:'.format(from_station, to_station))
152         for item in train_info:
153             if 'G' in item['train_name']: # 高铁
154                 self._print_high_train_info(item)
155             elif 'D' in item['train_name']: # 动车
156                 self._print_dong_train_info(item)
157             else:
158                 self._print_train_info(item)
159
160     def _print_high_train_info(self, item):
161         """
162         功能:打印高铁车次信息
163         :param item: 所有高铁车次
164         :return:
165         """
166         print('车次:{:4s}\t起始时间:{:4s}\t到站时间:{:4s}\t'
167               '经历时长:{:4s}\t特等座:{:4s}\t一等座:{:4s}\t二等座:{:4s}'
168               .format(item['train_name'], item['depart_time'],item['arrive_time'],
169                       item['spend_time'],item['td'], item['yd'], item['ed']))
170
171     def _print_dong_train_info(self, item):
172         """
173         功能:打印动车的车票信息
174         :param item: 所有动车车次
175         :return:
176         """
177         print('车次:{:4s}\t起始时间:{:4s}\t到站时间:{:4s}\t'
178               '经历时长:{:4s}\t一等座:{:4s}\t二等座:{:4s}\t软卧:{:4s}\t动卧:{:4s}'
179               .format(item['train_name'], item['depart_time'], item['arrive_time'],
180                       item['spend_time'],item['yd'],item['ed'], item['rw'], item['dw']))
181     def _print_train_info(self,item):
182         """
183         功能:打印普通列出的车次信息
184         :param item: 所有普通车次
185         :return:
186         """
187         print('车次:{:4s}\t起始时间:{:4s}\t到站时间:{:4s}\t经历时长:{:4s}\t'
188               '软卧:{:4s}\t硬卧:{:4s}\t硬座:{:4s}\t无座:{:4s}'
189               .format(item['train_name'], item['depart_time'], item['arrive_time'],
190                       item['spend_time'],item['rw'], item['yw'], item['yz'], item['wz']))
191     def _query_prompt(self):
192         """
193         功能: 与用户交互,让用户输入:出发日期,起始站和终点站并判断其正确性
194         :return: 返回正确的日期,起始站和终点站
195         """
196
197         time_flag, train_date = self._check_date()
198         if not time_flag:
199             print('_query_prompt() error:', '乘车日期不合理,请检查!!')
200             return
201         # 创建有序字典,方便取值
202         query_data = OrderedDict()
203         from_station = input('请输入起始站:')
204         to_station = input('请输入终点站:')
205
206         station_flag = True
207         filename = 'name_code.json'
208         with open(filename, 'r') as f:
209             data = dict(json.load(f))
210             stations = data.keys()
211             if from_station not in stations or to_station not in stations:
212                 station_flag = False
213                 print('query_prompt() error: {}或{}不在站点列表中!!'
214                     .format(from_station, to_station))
215             # 获取起始站和终点站的代码
216             from_station_code = data.get(from_station)
217             to_station_code = data.get(to_station)
218         query_data['train_date'] = train_date
219         query_data[from_station] = from_station_code
220         query_data[to_station] = to_station_code
221
222         if time_flag and  station_flag:
223             return query_data
224         else:
225             print('query_prompt() error! time_flag:{}, station_flag:{}'
226                   .format(time_flag, station_flag))
227
228
229
230     def _check_date(self):
231         """
232         功能:检测乘车日期的正确性
233         :return: 返回时间是否为标准的形式的标志
234         """
235
236         # 获取当前时间的时间戳
237         local_time = time.localtime()
238         local_date = '{}-{}-{}'.\
239             format(local_time.tm_year, local_time.tm_mon, local_time.tm_mday)
240         curr_time_array = time.strptime(local_date, '%Y-%m-%d')
241         curr_time_stamp = time.mktime(curr_time_array)
242         # 获取当前时间
243         curr_time = time.strftime('%Y-%m-%d', time.localtime(curr_time_stamp))
244
245         # 计算出预售时长的时间戳
246         delta_time_stamp = '2505600'
247         # 算出预售票的截止日期时间戳
248         dead_time_stamp = int(curr_time_stamp) + int(delta_time_stamp)
249         dead_time = time.strftime('%Y-%m-%d', time.localtime(dead_time_stamp))
250         print('合理的乘车日期范围是:({})~({})'.format(curr_time, dead_time))
251
252         train_date = input('请输入乘坐日期(year-month-day):')
253         # 把乘车日期转换成时间戳来比较
254         # 先生成一个时间数组
255         time_array = time.strptime(train_date, '%Y-%m-%d')
256         # 把时间数组转化成时间戳
257         train_date_stamp = time.mktime(time_array)
258         # 获取标准的乘车日期
259         train_date_time = time.strftime('%Y-%m-%d', time.localtime(train_date_stamp))
260         # 做上面几步主要是把用户输入的时间格式转变成标准的格式
261         # 如用户输入:2018-2-22,那么形成的查票URL就不是正确的
262         # 只有是:    2018-02-22,组合的URL才是正确的!
263         # 通过时间戳来比较时间的正确性
264         if int(train_date_stamp) >= int(curr_time_stamp) and \
265             int(train_date_stamp) <= dead_time_stamp:
266             return True, train_date_time
267         else:
268             print('_check_date() error: 乘车日期:{}, 当前系统时间:{}, 预售时长为:{}'
269                   .format(train_date_time, curr_time, dead_time))
270             return False, None
271
272
273
274 def main():
275     filename = 'station_name.txt'
276     station = Station()
277     station.station_name_code()
278     station.save_station_code(filename)
279     station.query_ticket()
280
281 if __name__ == '__main__':
282     main()

我们填好所有必要信息时,点击查询按钮,得到的结果如下:

  

  在所有结果中我们只看到了3条信息,最主要的还是第一条,爬取了深圳到北京的车票

  

2.对数据进行清洗和处理
3.文本分析(可选):jieba分词、wordcloud可视化
4.数据分析与可视化
(例如:数据柱形图、直方图、散点图、盒图、分布图、数据回归分析等)
 5.数据持久化
 6.附完整程序代码
四、结论(10分)
1.经过对主题数据的分析与可视化,可以得到哪些结论?
2.对本次程序设计任务完成的情况做一个简单的小结。
12-13 20:03
查看更多