爬取12306一是一个非常有乐趣的事情,同时也是爬虫入门程序。
我在爬虫刚入门时,这个程序写了许久才搞明白
今天重新拉出来发个博客
本文写于小白时期,亲测有效,就懒得改了
这个程序就不做GUI窗口了,毕竟我计较懒(其实是不会)
首先,我们要爬取的网站是https://www.12306.cn/index/的车票信息
当我们选择出发地和目的地,点下查询就可以获得相关的车票信息,而我们要将这些信息用Python爬取
假如我输入出发地为北京,目的地为上海,则查询结果如下
先看一下程序效果(丑的惨不忍睹)
接下来正式开始爬虫
像这样的信息,一般都是ajax异步加载的,至于什么是ajax异步加载
其实就是通过js将页面动态渲染,减小了网页负担
那么怎么爬取这种数据呢?
抓包
按下F12打开Google浏览器的开发者模式,找到Network–>XHR,里面可以抓取ajax异步请求
当打开Google Chrome的XHR,按下查询发现了这样的请求
点开这个请求,查看他的链接
https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2020-04-21&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=SHH&purpose_codes=ADULT
又换了几个地方尝试,发现变化的参数只有train_date、from_station、to_station,即出发日,出发地,目的地
但是发现出发地和目的地是简称,那么怎样获得站名简称呢
刷新一下网页,在all中发现了这个文件
https://kyfw.12306.cn/otn/resources/js/framework/favorite_name.js(可能发生变化,以自己看到的为准)
看看他的响应体Preview,发现了我们想要的站名即简称
这样我们要先把这玩意爬下来
get_station.py
import requests
import re
def get_station():
url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9142'
response = requests.get(url).text
name = re.findall(r'.*?\|(.*?)\|.*?\|.*?\|.*?\|.*?',response)
referred = re.findall(r'.*?\|.*?\|(.*?)\|.*?\|.*?\|.*?',response)
station = dict(zip(name,referred))
file = open('station.txt','w',encoding='utf-8')
file.write(str(station))
file.close()
return station
通过这样一个程序,我们就可以获取站名简称
运行结果如下
{‘北京北’: ‘VAP’, ‘北京东’: ‘BOP’, ‘北京’: ‘BJP’, ‘北京南’: ‘VNP’, ‘北京西’: ‘BXP’, ‘广州南’: ‘IZQ’, ‘重庆北’: ‘CUW’, ‘重庆’: ‘CQW’, ‘重庆南’: ‘CRW’, ‘重庆西’: ‘CXW’, ‘广州东’: ‘GGQ’, ‘上海’: ‘SHH’, ‘上海南’: ‘SNH’,…
此处省略10000字
我们首先访问了这个链接,通过正则表达式获取站名列表和简称列表,并将他们转为字典,写入文件
在获取到简称之后,就可以构造链接了,观察他的请求结果,发现是类似这样的结果
我们要获取到result中的结果,同时将含有列车停运的结果去除
get_tickets.py
import requests
import json
from urllib.parse import urlencode
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36',
'Cookie': 'JSESSIONID=85ABACBDDEC5EF0D3F4390E49C235DCD; BIGipServerotn=569377290.24610.0000; BIGipServerpool_passport=183304714.50215.0000; RAIL_EXPIRATION=1584746738988; RAIL_DEVICEID=c_HPEh5qqB0-onW7FlqB5a2T-w9tiHZ95ePILEBaXLQ3Nj84j7a4PV1ezmRs7O57oEVHFp3JcbAEi_s3qJb_bqey5sGYiQ-RmKrzrZ0wzbndDLKGidKjF1l5UZ4FjwqSTdhbaSx8ds-5RgV-KxQrm0mINenavAb3; route=c5c62a339e7744272a54643b3be5bf64; _jc_save_fromStation=%u5317%u4EAC%2CBJP; _jc_save_fromDate=2020-03-17; _jc_save_toDate=2020-03-17; _jc_save_wfdc_flag=dc; _jc_save_toStation=%u5929%u6D25%2CTJP'
}
def get_tickets(fromwhere,towhere,startime):
data = {
'leftTicketDTO.train_date': startime,
'leftTicketDTO.from_station': fromwhere,
'leftTicketDTO.to_station': towhere,
'purpose_codes': 'ADULT',
}
request_url = 'https://kyfw.12306.cn/otn/leftTicket/query?' + urlencode(data)
response = json.loads(requests.get(request_url,headers=headers).text)
result = response['data']['result']
new_list = []
for item in result:
if not '列车停运' in item:
new_list.append(item)
else:
pass
return new_list
代码简单,不做详细介绍
a1M3%2FUzhyJTMAZT71W6ZfN68662hsWaBMlmBsbvx%2BxVWGWhLebrLW7DzbdUuls20VR%2BVbiySPM6T%0AFmoziK649b%2FqUXE2OU3po2eL3xPDK476PSrF7q%2BfUAW%2F%2BIAEc6VJ%2Fu3rzWLB6rCNYQCw78qbSHsW%0AX3ByQhue1INsKq3bPSSe5DM9S8IlyE2%2FL5RFdu6CEnY37uMZjrjROosZx7OJtQx2i%2FEV9eKb6iWO%0A9Zy9u9b%2BGmSJYXfNR8qTC%2F%2BR9Ub3RcgknRLOr45A5vcNqx5bCIpMNe1k61u%2FARFCIFwW%2FJo%3D|预订|240000D70901|D709|VNP|SHH|VNP|SHH|19:46|07:46|12:00|Y|JmRqJPg0lxl8mWDLziQaL3KoPp3cYuDom7d71dq1TO9zFrfw|20200421|3|P4|01|04|1|0||||有|||||有||有||||O0J0I0|OJI|1|0|||||||||"
这些信息,我们怎样处理出我们想要的信息呢?
我们发现,在预定之后,比对我们看到的信息,发现可以进行处理,将我们想要的信息提取出来
Decrypt.py
import re
def decrypt(string):
reg = re.compile('.*?\|预订\|.*?\|(.*?)\|(.*?)\|(.*?)\|.*?\|.*?\|(.*?)\|(.*?)\|(.*?)\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|.*?\|.*?\|.*?\|.*')
result = re.findall(reg,string)[0]
return result
main.py
from get_station import get_station
from get_tickets import get_tickets
from Decrypt import decrypt
import datetime
def get_message():
fw = input("请输入你的出发地>>>")
tw = input("请输入你的目的地>>>")
st = input("请输出出发时间;格式:(年-月-日)(默认为今日日期)>>>>")
if st == '':
st = datetime.date.today()
return fw,tw,st
else:
today = datetime.date.today()
date = str(today).split('-')
list = st.split('-')
if int(list[0]) < int(date[0]) or int(list[0]) > int(date[0]):
exit("输入的年份不在我的查询范围之内")
else:
if int(list[1]) < int(date[1]) or int(list[1]) > int(date[1])+1:
exit("你输入的月份不在我的查询范围之内")
else:
if int(list[2]) < int(date[2]):
exit("你输入的日期不在我的查询范围之内")
else:
if int(list[1]) < 10 and int(list[1][0]) != 0:
list[1] = '0' + list[1]
if int(list[2]) < 10 and int(list[2][0]) != 0:
list[2] = '0' + list[2]
return fw,tw,list[0] + '-' + list[1] + '-' + list[2]
message = get_message()
def run():
station_name = get_station()
try:
fromwhere = get_station()[message[0]]
towhere = get_station()[message[1]]
startime = message[2]
tickets = get_tickets(fromwhere,towhere,startime)
print("\n车次 出发站 到达站 出发时间 到达时间 历时 商务座/特等座 一等座 二等座/二等包座 高级软卧 软卧一等卧 动卧 硬卧/二等卧 软座 硬座 无座 其他")
for item in tickets:
result = list(decrypt(item))
new_dict = {
v: k for k, v in station_name.items()}
result[1] = new_dict[result[1]]
result[2] = new_dict[result[2]]
print("%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s"%(result[0],result[1],result[2],result[3],result[4],result[5],result[-1],result[-2],result[-3],result[-4],result[-5],result[-6],result[-7],result[-8],result[-9],result[-10],result[-11]))
except KeyError as k:
print("I can't find the city %s"%k)
if __name__ == '__main__':
run()
这是我之前写的程序,可能有瑕疵,请谅解