第一次写爬虫,咱从入门级——12306车票爬取 开始
我们要爬取的信息是https://www.12306.cn/index/上的车票信息
当我们选择出发地和目的地,点击查询可以获得相关的车票信息,我们现在要将这些信息使用Python爬取
假如我输入出发地为武汉,目的地为广州,则查询结果如下
程序效果如下:
找到车票数据的传输链接
按下F12打开Google浏览器的开发者模式,找到Network–>XHR,里面可以找到带有车票数据的链接,如下图所示(如果没有可以刷新一下页面)
我们选定这个query?leftTicket,展开data-resul,可以看到下图所示的内容
也可以看到这个链接为
https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2020-10-13&leftTicketDTO.from_station=WHN&leftTicketDTO.to_station=GZQ&purpose_codes=ADULT
又换了几个出发地和到达地尝试一下,发现变化的参数只有train_date、from_station、to_station,即日期,出发地,到达地
但我们发现出发地和到达地都是简称,那么怎样获得站名简称呢
刷新一下网页,在all中发现了这个文件station_name,语义上就能知道了,嘿嘿
获取站点名与简称的字典
下面就开始写代码了,首先有几个库是需要装的,要是没装,可以先安装一下
python -m pip install prettytable,selenium,requests
stations.py
import re import requests url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9161' requests.packages.urllib3.disable_warnings() response = requests.get(url, verify=False) stations = re.findall(u'([u4e00-u9fa5]+)|([A-Z]+)', response.text) stations=dict(stations) print(stations)#此处print出字典检查一下看看有没有问题
通过stations文件,我们就可以获取站名简称
运行结果如下
构造请求链接获取车票信息
在获取到简称之后,就可以构造链接了,观察他的请求结果,发现是类似这样的结果
我们要获取到result中的结果,同时将含有列车停运的结果去除
get_tickets()函数
def get_tickets(froms,tos,date): ''' froms:出发地 tos:到达地 date:日期 ''' froms = stations[froms]#转换名称为简称 tos = stations[tos] #这里是做了一个动态获取cookie,不懂的可以看看我的其他文章,要是我还没写就百度找一下 chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--headless') #此处做了一个后台打开浏览器的设置 browser = webdriver.Chrome(chrome_options=chrome_options) browser.get('https://www.12306.cn/index/index.html') time.sleep(3)#这个等待3秒是为了防止网页还没加载完就下一步了,保证后续获取到cookie #以下是获取cookie并组成完整的cookie Cookie = browser.get_cookies() strr = '' for c in Cookie: strr += c['name'] strr += '=' strr += c['value'] strr += ';' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36', 'Cookie':strr } print(headers)#检查是否成功获取cookie,也可以写一个循环来检查,我懒就没写了 browser.quit()#退出后台的浏览器,不退出会占内存的 #构建链接 request_url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(date,froms,tos) #发送请求 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
解析获得的信息
获取的信息如下
4GLHFQ1iKtT3Vp45aqKC%2FVUQBEU3j%2FzFH9kyv5dCX4llmfRhfLGohfEoVO1gjw8WlJ5tEO0dOPei%0AAGI4tWQqXNh5xXY36JbwJX6RcD%2Bv5NVZCazQrnb19tpLjHVzMvjr7wsOKh2%2FdWnro4EemxrbvzgX%0ACwPsdRj6hDo11vd4sntVbyDbtcIo1fnLznDNxhvpPWYuDOPASjrdGJxhFNH4HdvU5wRXM80sW2W8%0AEfjkh%2BVn%2Fi1d%2F397mYXxQyW6ehJqrvi6enFnZZ6bM4IsjeRxbXnUx0rJGu68uPYciccN%2F%2BCdZIjz%0ADFs7tA%3D%3D|预订|87000K129500|K1295|YIJ|GZQ|WCN|GZQ|00:45|13:28|12:43|Y|SOotAvHbzmywXdrxTntAfWPwToY356wSaQMbGaZVww4OpCRF3M1IwMThCX4%3D|20201011|3|J1|26|33|1|0||||1|||无||1|20|||||403010W0|4311|1|0||4042550001302385000110138500201013853000||||||1|0
可以看得出来是由 | 符号进行分割的,数了一下后有46个内容,我们用正则表达式来提取有用的信息
decrypt()函数
def decrypt(string): string = ''.join(string)#传过来的是list类型,我们把它转成str reg = re.compile('.*?|预订|.*?|(.*?)|.*?|.*?|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|.*?|.*?|.*?|.*?|.*?|.*?|.*?|.*?|.*?|.*?|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|.*?|.*?|.*?|.*') result = re.findall(reg,string)[0] return result
这个正则表达式只取了信息里前36个里的部分数据,因为后10个没啥作用,“(.*?)”这种即为取用,不带括号的为不取用,自己多看,对照着一个一个看,看多了就懂了。
获取用户输入的信息
get_info()函数
def get_info(): 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]
这个函数很简单,没啥说的
运行
header = ["车次","出发站","到达站","出发时间","到达时间","历时","商务座","一等座","二等座","高级软卧","软卧","动卧","硬卧","软座","硬座","无座"] if __name__ == '__main__': pt = PrettyTable() pt.field_names = header [fw,tw,st]=get_info() trainlist=get_tickets(fw,tw,st) new_dict = {v: k for k, v in stations.items()} for item in trainlist: result=list(decrypt(item)) result[1] = new_dict[result[1]] result[2] = new_dict[result[2]] results = [result[0],result[1],result[2],result[3],result[4],result[5],result[-1],result[-2],result[-3],result[-12],result[-10],result[-6],result[-5],result[-8],result[-4],result[-7]] pt.add_row(results) print(pt)
这里使用了prettytable优化输出的内容,其中result就是解析后的车票信息了
以下为整个项目的内容,一共分两个文件stations.py 和tickets.py,stations.py在前面已经贴出来了,这里就不再重复。
tickets.py
from stations import stations from prettytable import PrettyTable from selenium import webdriver import datetime import requests import json import re import time header = ["车次","出发站","到达站","出发时间","到达时间","历时","商务座","一等座","二等座","高级软卧","软卧","动卧","硬卧","软座","硬座","无座"] def get_tickets(froms,tos,date): froms = stations[froms] tos = stations[tos] chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--headless') browser = webdriver.Chrome(chrome_options=chrome_options) browser.get('https://www.12306.cn/index/index.html') time.sleep(3) Cookie = browser.get_cookies() strr = '' for c in Cookie: strr += c['name'] strr += '=' strr += c['value'] strr += ';' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36', 'Cookie':strr } print(headers) browser.quit() request_url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(date,froms,tos) 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 def get_info(): 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] def decrypt(string): string = ''.join(string) reg = re.compile('.*?|预订|.*?|(.*?)|.*?|.*?|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|.*?|.*?|.*?|.*?|.*?|.*?|.*?|.*?|.*?|.*?|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|.*?|.*?|.*?|.*') result = re.findall(reg,string)[0] return result if __name__ == '__main__': pt = PrettyTable() pt.field_names = header [fw,tw,st]=get_info() trainlist=get_tickets(fw,tw,st) new_dict = {v: k for k, v in stations.items()} for item in trainlist: result=list(decrypt(item)) result[1] = new_dict[result[1]] result[2] = new_dict[result[2]] results = [result[0],result[1],result[2],result[3],result[4],result[5],result[-1],result[-2],result[-3],result[-12],result[-10],result[-6],result[-5],result[-8],result[-4],result[-7]] pt.add_row(results) print(pt)
文章结束!!!
欢迎关注公众号:Python爬虫数据分析挖掘
记录学习python的点点滴滴;
回复【开源源码】免费获取更多开源项目源码;
公众号每日更新python知识和【免费】工具;
本文已同步到【开源中国】、【腾讯云社区】、【CSDN】;