查看原文
其他

爬虫俱乐部助你回家—12306余票监测(一)

爬虫俱乐部 Stata and Python数据分析 2022-03-15

本文作者:杨长青

本文编辑:胡   婧

技术总编:张学人

爬虫俱乐部郑重推出中国高校上市公司高管排行榜,受到社会各界的广泛关注。我们所采用的数据全部来源于百度词条、新浪财经、国泰安等,客观公正!如有需要,可联系爬虫俱乐部!

爬虫俱乐部将于2019年1月19日至25日在武汉举行两期Stata编程技术定制培训,此次采取初级班和高级班分批次培训模式,采用理论与案例相结合的方式,旨在帮助大家熟悉Stata核心的爬虫技术,以及Stata与其他软件交互的高端技术。目前还有少量名额大家抓紧时间报名啦!详细培训大纲及报名方式,请见往期推文《2019寒假Stata编程技术定制培训班》。报名表下载请点击文末阅读原文呦~

一年一度的春运即将来临,各位看官回家的票有没有买好呢?反正小编已经按捺不住激动的心情,开始蠢蠢欲动了。但是作为技术控,就应该有技术控的抢票姿态,鉴于12306逆天的验证码,小编放弃了控制12306自动抢票的骚操作,开始走向自动余票提醒:有余票=>微信推送余票信息的道路。

以徐州到滕州为例,我们想爬取1月26号的余票信息,登录12306官网查询余票页面(https://kyfw.12306.cn/otn/leftTicket/init),在下图所示的红框内输入出发地目的地以及出发日期

由于余票信息是异步加载的,我们需要通过谷歌浏览器开发者工具找到中间请求的URL,可以发现我们需要的信息就在

https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date=2019-01-14&leftTicketDTO.from_station=XCH&leftTicketDTO.to_station=TXK&purpose_codes=ADULT 下的result里面。

下面我们运用selenium库驱动PhantomJS来获取网页源码信息,其中selenium是一个自动化测试工具,大家可以通过pip install下载安装。利用它可以驱动浏览器执行特定动作,同时还可以获取浏览器当前呈现的源码,做到“可见可爬”,回避了各种反爬措施。最近小编在学习这个库,所以暂且用它牛刀小试一下。由于selenium不自带浏览器,所以我们这里选择小巧的无界面浏览器PhantomJS。PhantomJS浏览器文件我们将会为大家上传到云里。

首先介绍一下,所需要加载的模块。

Selenium库:模拟浏览器运行的库。

Pandas库:Python科学计算库。

Json库:解析JSON后将其转为Python字典或者列表。

Wxpy库:可用来实现各种个人微信号的自动化操作,我们这里用来实现微信自动发送余票信息。

time模块: Python标准库中的模块,用来实现时间间隔控制,循环获得余票信息。


加载程序如下:

from selenium import webdriver
import pandas as pd
import json
import numpy as np
from wxpy import *
import time

下面开始进入正题,第一步爬取余票信息,还是以徐州到滕州为例。首先获取网页源码,程序如下:

driver = webdriver.PhantomJS('D:\\webdriver\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe')
#phantomjs所在位置
url='https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date='+'2019-01-14' \                   +'&leftTicketDTO.from_station='+'XCH' \                   +'&leftTicketDTO.to_station='+'TXK' \                   +'&purpose_codes=ADULT'
driver.get(url) html=driver.page_source driver.quit() #关闭phantomjs

最终获取的文本信息如下:

可以发现,我们需要的信息以json数据的形式存在,把网页的标签删除,就可以直接将json数据转换成字典的形式。程序如下:

html = html.replace('''<html><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">''', '') \.replace('</pre></body></html>', '')#删除网页标签信息
html=json.loads(html) #剩下的网页源码是json数据,json.loads将json格式数据转换为字典

这样就把网页的信息,存储到了名为html的字典中,而余票信息(即上面原网页的表格内容)就被已列表的形式,存在了result中。

自然,我们接下来需要做的就是将余票信息从result中提取出来。通过观察上图能发现,车次信息清晰地用“|”分隔开,这里就可以通过split函数将字符串分割,再根据信息位置,提取我们需要的信息。

程序如下:

data={
          '车次': '',
         '始发站': '',
         '终点站': '',
         '出发时间': '',
         '到达时间': '',
         '全程时间': '',
         '商务座': '',
         '一等座': '',
         '二等座': '',
         '高级软卧': '',
         '软卧': '',
         '动卧': '',
         '硬卧': '',
         '软座': '',
         '硬座': '',
         '无座': ''          }  #生成一个空的字典,将我们需要的数据信息定义为键名
result=pd.DataFrame(data,index=[0]) #将字典转成一个dataframe,方便数据处理
for i in html['data']['result']:   #我们需要的数据在html字典键名为result中
    item = i.split('|')#用"|"进行分割    data['车次'] = item[3]#车次在3号位置    data['始发站'] = item[6]#始发站信息在6号位置    data['终点站'] = item[7]#终点站信息在7号位置    data['出发时间'] = item[8]#出发时间信息在8号位置    data['到达时间'] = item[9]#抵达时间在9号位置    data['全程时间'] = item[10]#经历时间在10号位置    data['商务座'] = item[32] or item[25]# 特别注意:商务座在32或25位置    data['一等座'] = item[31]#一等座信息在31号位置    data['二等座'] = item[30]#二等座信息在30号位置    data['高级软卧'] = item[21]#高级软卧信息在31号位置    data['软卧'] = item[23]#软卧信息在23号位置    data['动卧'] = item[27]#动卧信息在27号位置    data['硬卧'] = item[28]#硬卧信息在28号位置    data['软座'] = item[24]#软座信息在24号位置    data['硬座'] = item[29]#硬座信息在29号位置    data['无座'] = item[26]#无座信息在26号位置    df1=pd.DataFrame(data,index=[0]) #将赋值后的字典转换成datafram,命名为df1    frames=[result,df1] #result,df1构成一个列表,再使用concat    result = pd.concat(frames,axis=0, ignore_index=True) #将两个列表按行合并,然后冲着索引
columns=['车次','始发站','终点站','出发时间','到达时间','商务座','一等座','二等座','高级软卧','软卧','动卧','硬卧','软座','硬座','无座'] result=result.reindex(columns=columns) #将result的列名按columns排列,我们想先看到车次和时间嘛.

通过数据清洗,原网页表格信息就规整的放到了名为result的dataframe中。如下图所示:

这样就完成了1月26号滕州到徐州的余票信息的爬取,但是大家需要查询的出发地、目的地各不相同,所以为了方便使用和程序移植,我们可以把上述过程,封装到一个函数中。

我们重新看上面的URL。如下图表示的那样,URL中trian_data=后接出发时间from_station=后接出发城市城市代码(XCH是徐州的城市代码),to_station=后接目的城市代码(TXK是滕州的城市代码)。我们只要把这三个信息改成函数参数,就可以方便地查询不同出发地、目的地以及出发时间的余票信息了。

爬虫俱乐部是您身边的科研助手,能够为您在数据处理实证研究中提供帮助。承蒙近四万粉丝的支持与厚爱,我们在腾讯课堂推出了网络视频课程,专注于数据整理、网络爬虫、循环命令编制和结果输出…李老师及团队精彩地讲解,深入浅出,注重案例与实战,让您更加快速高效地掌握Stata技巧及数据处理的精髓,而且可以无限次重复观看,在原有课程基础上已上传了三节全新的内容!百分百好评,简单易学,一个月让您从入门到精通。绝对物超所值!观看学习网址:

https://ke.qq.com/course/286526?tuin=1b60b462,

敬请关注!

关于城市和代码的对照表,将其存储在了一个名为地名代码对照表.txt中,并将其上传到云中,可以将其读取到python,转换成字典形式,程序如下:

f = open('地名代码对照表.txt','r')#打开txt
dict1 = eval(f.read()) #将txt载入成字典形式
f.close() #关闭txt

最终字典dict1中的文件形式如下,键名为城市名,键值为城市代码。

这样,我们就可以直接通过输入城市的名字,来提取对应的代码信息,比如要提取滕州的城市代码,直接输入:

dict1['滕州']

就能提取相应的城市代码:

以上就解决了封装函数的所有障碍,直接将出发城市、目的城市、出发日期设成参数即可。我们将函数名命名为get_news(start,end,day)。Start,end,day对应上述参数,返回值为一各包含所有列车余票信息的dataframe 。

程序如下:

def get_news(start,end,day):    driver = webdriver.PhantomJS('D:\\webdriver\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe')    url='https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date='+day.title() \                   +'&leftTicketDTO.from_station='+dict1[start.title()] \                   +'&leftTicketDTO.to_station='+dict1[end.title()] \                   +'&purpose_codes=ADULT'    driver.get(url)    html=driver.page_source    html = html.replace('''<html><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">''', '').replace('</pre></body></html>', '')    html=json.loads(html)    data={
          '车次': '',
         '始发站': '',
         '终点站': '',
         '出发时间': '',
         '到达时间': '',
         '全程时间': '',
         '商务座': '',
         '一等座': '',
         '二等座': '',
         '高级软卧': '',
         '软卧': '',
         '动卧': '',
         '硬卧': '',
         '软座': '',
         '硬座': '',
         '无座': ''          }     result=pd.DataFrame(data,index=[0])
    for i in html['data']['result']:        item = i.split('|')#用"|"进行分割        data['车次'] = item[3]#车次在3号位置        data['始发站'] = item[6]#始发站信息在6号位置        data['终点站'] = item[7]#终点站信息在7号位置        data['出发时间'] = item[8]#出发时间信息在8号位置        data['到达时间'] = item[9]#抵达时间在9号位置        data['全程时间'] = item[10]#经历时间在10号位置        data['商务座'] = item[32] or item[25]# 特别注意:商务座在32或25位置        data['一等座'] = item[31]#一等座信息在31号位置        data['二等座'] = item[30]#二等座信息在30号位置        data['高级软卧'] = item[21]#高级软卧信息在31号位置        data['软卧'] = item[23]#软卧信息在23号位置        data['动卧'] = item[27]#动卧信息在27号位置        data['硬卧'] = item[28]#硬卧信息在28号位置        data['软座'] = item[24]#软座信息在24号位置        data['硬座'] = item[29]#硬座信息在29号位置        data['无座'] = item[26]#无座信息在26号位置        df1=pd.DataFrame(data,index=[0])        frames=[result,df1]        result = pd.concat(frames,axis=0, ignore_index=True)    columns=['车次','始发站','终点站','出发时间','到达时间','商务座','一等座','二等座','高级软卧','软卧','动卧','硬卧','软座','硬座','无座']    result=result.reindex(columns=columns)
   return result    driver.quit()

下面,调用函数查询武汉到滕州的1月26号火车票的情况:

a=get_news('武汉','滕州','2019-01-26') #将函数返回结果赋给a

最终输出结果如下,1月26日从武汉到滕州有一班高铁和一班火车,并已全部告罄。想哭~~~

注:

本文所需资源下载链接为: https://stata-club-1257787903.cos.ap-chengdu.myqcloud.com/ResidualTicketOn12306.zip

有问题,不要怕!访问 

http://www.wuhanstring.com/uploads/5_aboutus/爬虫俱乐部-用户问题登记表.docx (复制到浏览器中)下载爬虫俱乐部用户问题登记表并按要求填写后发送至邮箱statatraining@163.com,我们会及时为您解答哟~

爬虫俱乐部的github主站正式上线了!我们的网站地址是:https://stata-club.github.io,粉丝们可以通过该网站访问过去的推文哟~

爬虫俱乐部隆重推出数据定制及处理业务,您有任何网页数据获取及处理方面的难题,请发邮件至我们邮箱statatraining@163.com,届时会有俱乐部高级会员为您排忧解难!

对爬虫俱乐部的推文累计打赏超过1000元我们即可给您开具发票,发票类别为“咨询费”。用心做事,只为做您更贴心的小爬虫!


往期推文推荐

关于我们

微信公众号“爬虫俱乐部”分享实用的stata命令,欢迎转载、打赏。爬虫俱乐部是由李春涛教授领导下的研究生及本科生组成的大数据分析和数据挖掘团队。


此外,欢迎大家踊跃投稿,介绍一些关于stata的数据处理和分析技巧。

投稿邮箱:statatraining@163.com

投稿要求:
1)必须原创,禁止抄袭;
2)必须准确,详细,有例子,有截图;
注意事项:
1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。
2)邮件请注明投稿,邮件名称为“投稿+推文名称”。
3)应广大读者要求,现开通有偿问答服务,如果大家遇到关于stata分析数据的问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存