复工复产,利用Python爬虫爬取火车票信息

Python 爬虫操作基本操作

python 标准库 urllib

获取信息

import urllib.request

response = urllib.request.urlopen('http://www.baidu.com/')

print(response.read().decode('utf-8'))
<!DOCTYPE html><!--STATUS OK-->
此处省略
        <script>
                if(navigator.cookieEnabled){
            document.cookie="NOJS=;expires=Sat, 01 Jan 2000 00:00:00 GMT";
        }
                </script>
    

            <script src="http://ss.bdimg.com/static/superman/js/components/hotsearch-8598bcf712.js"></script>
    </body>
        
	</html>

上传信息

import urllib.request
import urllib.parse

data = bytes(urllib.parse.urlencode({'word':'hello'}), encoding='utf-8')

response = urllib.request.urlopen('http://httpbin.org/post', data=data)
response
html = response.read().decode('utf-8')
print(html)
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "word": "hello"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "10", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Python-urllib/3.6", 
    "X-Amzn-Trace-Id": "Root=1-5ec0a4a4-4da16208bf73bce8b0088a14"
  }, 
  "json": null, 
  "origin": "182.137.240.188", 
  "url": "http://httpbin.org/post"
}

python 标准库 urllib3

获取信息

import urllib3

# 处理与线程的连接以及线程安全
http = urllib3.PoolManager()
response = http.request('GET', 'http://www.baidu.com')

print(response.data.decode())
<!DOCTYPE html><!--STATUS OK-->
<html>
<head>
	<meta http-equiv="content-type" content="text/html;charset=utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=Edge">
	<link rel="dns-prefetch" href="//s1.bdstatic.com"/>
	<link rel="dns-prefetch" href="//t1.baidu.com"/>
	<link rel="dns-prefetch" href="//t2.baidu.com"/>
	<link rel="dns-prefetch" href="//t3.baidu.com"/>
	<link rel="dns-prefetch" href="//t10.baidu.com"/>
	<link rel="dns-prefetch" href="//t11.baidu.com"/>
	<link rel="dns-prefetch" href="//t12.baidu.com"/>
	<link rel="dns-prefetch" href="//b1.bdstatic.com"/>
	<title>百度一下,你就知道</title>
	<link href="http://s1.bdstatic.com/r/www/cache/static/home/css/index.css" rel="stylesheet" type="text/css" />
    此处省略
</body></html>

上传信息

import urllib3

# 处理与线程的连接以及线程安全
http = urllib3.PoolManager()
response = http.request('POST', 'http://httpbin.org/post', fields={'word':'hello'})

print(response.data.decode())
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "word": "hello"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "128", 
    "Content-Type": "multipart/form-data; boundary=14c9abc7c138e04cfb65714db9055b19", 
    "Host": "httpbin.org", 
    "X-Amzn-Trace-Id": "Root=1-5ec0a5af-6146cbc0386719987392e77c"
  }, 
  "json": null, 
  "origin": "182.137.240.188", 
  "url": "http://httpbin.org/post"
}

第三方库 requests

获取特征信息

import requests

response = requests.get('http://www.baidu.com/')

# 防止中文乱码
response.encoding='utf-8'

print('状态码\n', response.status_code)
print('请求地址\n', response.url)
print('头部信息\n', response.headers)
print('Cookie\n', response.cookies)
print('文本源码\n', response.text)
print('字节源码\n', response.content)
状态码
 200
请求地址
 http://www.baidu.com/
头部信息
 {'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Content-Encoding': 'gzip', 'Content-Type': 'text/html', 'Date': 'Sun, 17 May 2020 02:55:45 GMT', 'Last-Modified': 'Mon, 23 Jan 2017 13:27:56 GMT', 'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18', 'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 'Connection': 'close', 'Transfer-Encoding': 'chunked'}
Cookie
 <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
文本源码
 <!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html
......
<img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

字节源码
 b'<!DOCTYPE html>\r\n<!--STATUS OK--><html> 
 ......
 </div> </div> </body> </html>\r\n'

模拟浏览器访问

直接访问被 403 拒绝
import requests

url = 'https://www.whatismyip.com/'

response = requests.get(url)
print(response.status_code)
403
添加 headers

headers 是浏览器访问服务器使用的,有的网页具有反爬机制,我们需要设置 headers 来模拟浏览器访问,以 Google Chrome 浏览器为例,按下 F12 进入控制台,在顶部选择 network,按 F5 刷新,在获取的信息中选择到需要的网页信息,例如本例的 /www.whatismyip.com,点击 Headers 下拉获取 user-agent 信息
在这里插入图片描述

import requests

url = 'https://www.whatismyip.com/'
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'}

response = requests.get(url, headers=headers)
print(response.content.decode('utf-8'))
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
   
</script>
</body>
</html>
<script>
jQuery(function () {
  jQuery('[data-toggle="tooltip"]').tooltip();
});
</script> 

报错信息

设置读取的 timeout 迫使出现网络超时错误

import requests
try:
    response = requests.get('http://www.baidu.com', timeout=0.1)
    print(response.status_code)
except Exception as error:
    print('Timeout:', str(error))
Timeout: HTTPConnectionPool(host='127.0.0.1', port=1080): Read timed out. (read timeout=0.1)

设置多个请求错误处理

import requests
from requests.exceptions import ReadTimeout, HTTPError, RequestException
try:
    response = requests.get('https://www.whatismyip.com/', timeout=0.5)
    print(response.status_code)
except ReadTimeout:
    print('Timeout')
except HTTPError:
    print('HttpError')
except RequestException:
    print('RequestError')

try:
    headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'}
    response = requests.get('https://www.whatismyip.com/', timeout=1, headers=headers)
    print(response.status_code)
except ReadTimeout:
    print('Timeout')
except HTTPError:
    print('HttpError')
except RequestException:
    print('RequestError')

RequestError
Timeout

设置代理

代理可以在西刺免费代理IP上获取
在这里插入图片描述

import requests

proxy = {'https':'119.84.112.139:80', 'http':'101.132.190.101:80'}

response_https = requests.get('https://www.baidu.com', proxies=proxy)
print('response_https:', response_https.status_code)

response_http = requests.get('http://www.baidu.com', proxies=proxy)
print('response_http:', response_http.status_code)
response_https: 200
response_http: 200

BeautifulSoup 应用

安装

可在此处查询官网文档
在这里插入图片描述

使用 Win10 系统直接使用

pip install bs4

如果你用的是新版的Debain或ubuntu,那么可以通过系统的软件包管理来安装:

$ apt-get install Python-bs4

Beautiful Soup 4 通过PyPi发布,所以如果你无法使用系统包管理安装,那么也可以通过 easy_install 或 pip 来安装.包的名字是 beautifulsoup4 ,这个包兼容Python2和Python3.

$ easy_install beautifulsoup4
$ pip install beautifulsoup4

(在PyPi中还有一个名字是 BeautifulSoup 的包,但那可能不是你想要的,那是 Beautiful Soup3 的发布版本,因为很多项目还在使用BS3, 所以 BeautifulSoup 包依然有效.但是如果你在编写新项目,那么你应该安装的 beautifulsoup4 )

如果你没有安装 easy_install 或 pip ,那你也可以 下载BS4的源码 ,然后通过setup.py来安装.

$ Python setup.py install

如果上述安装方法都行不通,Beautiful Soup的发布协议允许你将BS4的代码打包在你的项目中,这样无须安装即可使用.

作者在Python2.7和Python3.2的版本下开发Beautiful Soup, 理论上Beautiful Soup应该在所有当前的Python版本中正常工作

html 文件解析

from bs4 import BeautifulSoup

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

soup = BeautifulSoup(html_doc, features='lxml')
soup.title
<title>The Dormouse's story</title>
soup.title.name
'title'
soup.title.string
"The Dormouse's story"
soup.title.parent.name
'head'
soup.p
<p class="title"><b>The Dormouse's story</b></p>
soup.p['class']
['title']
soup.a
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
soup.find_all('a')
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
soup.find(id="link3")
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>

文档解析

soup = BeautifulSoup(open('index.html'), features='lxml')
print(soup.prettify)
<bound method Tag.prettify of <html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p></body></html>>

网页解析

response = requests.get('http://news.baidu.com')

soup = BeautifulSoup(response.text, features='lxml')
print(soup.find('title').text)
百度新闻——海量中文资讯平台

12306火车票爬取

特别说明

本次案例根据 《零基础学 Python》修改而来,在原案例中,由于书本原案例是 2018 年编写的代码,随着 12306 的迭代更新,旧的爬取方式不在适用,本次主要修改的是 query函数,修改了爬取方式,界面布局等

Pycharm 配置 Qt

Pycharm 下载

Pycharm 点击此处进入下载官网
在这里插入图片描述
Pycharm 提供专业版(Professional)与社区版(Community),社区版免费且开源,专业版具有 30 天试用期,需要付费购买,可以使用支付宝支付,我使用的是专业版 pycharm-professional-2020.1.1
在这里插入图片描述

Qt 安装与配置

通过如下命令安装

!pip install PyQt5

注:我是用的是 Anaconda 环境配置,下面的路径选择根据自己电脑路径修改

配置三个扩展工具 External Tools,依次点击 File→Settings→Tools→External Tools,点击+号,三个工具的配置输入如下:
Qt Designer:用于绘制界面

属性参数
NameQt Designer(自己取名字)
DescriptionCreate Qt UI(描述信息,可以不写)
ProgramE:\Anaconda3\Library\bin\designer.exe(根据自己Python环境填写路径)
Arguments
Working directoryE:\Anaconda3\Library\bin
在这里插入图片描述
PyUIC:将 UI 界面转换为 Python 可识别的代码
属性参数
NamePyUIC(自己取名字)
DescriptionUI to py file(描述信息,可以不写)
ProgramE:\Anaconda3\envs\tensorflow1.x\python.exe(根据自己Python环境填写路径)
Arguments-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
Working directory$FileDir$
在这里插入图片描述
qrc2py:将需要用到的资源文件转换成 Python 可识别文件(在设置 UI 时可以在 UI 转换成 py 文件后手动添加资源文件,但过程相较于繁琐,此处采取在编辑UI时直接添加资源文件)
属性参数
Nameqrc2py(自己取名字)
Description无(描述信息,可以不写)
ProgramE:\Anaconda3\envs\tensorflow1.x\Scripts\pyrcc5.exe(根据自己Python环境填写路径)
Arguments$FileName$ -o $FileNameWithoutExtension$_rc.py
Working directory$FileDir$
在这里插入图片描述
配置完成后如下
在这里插入图片描述

界面绘制

打开配置的扩展工具 Qt Designer
在这里插入图片描述
绘制 UI 界面部分需要具备基本的 Qt 操作,左边是界面布局的一些按钮,控件等,右边是调节控件的参数例如命名,大小等,界面使用了添加资源文件,所以后面需要将生成的 qrc 文件转换,界面如何绘制不再赘述,界面如图所示:
在这里插入图片描述
绘制完界面后,点击如下,将界面转换成 py 文件,UI 文件名与 py 文件名相同,都为 MianWindow
在这里插入图片描述
转换 qrc 文件类似上面转换 UI 操作,转换后需要在 MianWindow.py 中加入:

import source_rc

代码文件

主要的程序文件如下

MianWindow.py

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'MianWindow.ui'
#
# Created by: PyQt5 UI code generator 5.14.2
#
# WARNING! All changes made in this file will be lost!


from PyQt5 import QtCore, QtGui, QtWidgets
import sys
import time
from get_stations import *
import source_rc

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import *
from query_request import *
from PyQt5.QtGui import *

class Ui_mainWindow(object):
    # setupUi由Qt Designer设计
    def setupUi(self, mainWindow):
        mainWindow.setObjectName("mainWindow")
        mainWindow.resize(960, 850)
        mainWindow.setMinimumSize(QtCore.QSize(480, 360))
        mainWindow.setMaximumSize(QtCore.QSize(960, 1080))
        self.centralwidget = QtWidgets.QWidget(mainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label_title_img = QtWidgets.QLabel(self.centralwidget)
        self.label_title_img.setGeometry(QtCore.QRect(0, 0, 960, 225))
        self.label_title_img.setStyleSheet("background-image: url(:/png/src/bg1.png);")
        self.label_title_img.setText("")
        self.label_title_img.setObjectName("label_title_img")
        self.widget_input = QtWidgets.QWidget(self.centralwidget)
        self.widget_input.setGeometry(QtCore.QRect(0, 225, 640, 80))
        self.widget_input.setStyleSheet("background-image: url(:/png/src/bg2.png);")
        self.widget_input.setObjectName("widget_input")
        self.label_departure = QtWidgets.QLabel(self.widget_input)
        self.label_departure.setGeometry(QtCore.QRect(0, 32, 60, 15))
        self.label_departure.setObjectName("label_departure")
        self.lineEdit_departure = QtWidgets.QLineEdit(self.widget_input)
        self.lineEdit_departure.setGeometry(QtCore.QRect(55, 30, 113, 21))
        self.lineEdit_departure.setText("")
        self.lineEdit_departure.setObjectName("lineEdit_departure")
        self.lineEdit_destination = QtWidgets.QLineEdit(self.widget_input)
        self.lineEdit_destination.setGeometry(QtCore.QRect(235, 30, 113, 21))
        self.lineEdit_destination.setText("")
        self.lineEdit_destination.setObjectName("lineEdit_destination")
        self.label_destination = QtWidgets.QLabel(self.widget_input)
        self.label_destination.setGeometry(QtCore.QRect(175, 32, 60, 15))
        self.label_destination.setObjectName("label_destination")
        self.lineEdit_date = QtWidgets.QLineEdit(self.widget_input)
        self.lineEdit_date.setGeometry(QtCore.QRect(430, 30, 113, 21))
        self.lineEdit_date.setObjectName("lineEdit_date")
        self.label_date = QtWidgets.QLabel(self.widget_input)
        self.label_date.setGeometry(QtCore.QRect(355, 32, 72, 15))
        self.label_date.setObjectName("label_date")
        self.pushButton_inquire = QtWidgets.QPushButton(self.widget_input)
        self.pushButton_inquire.setGeometry(QtCore.QRect(548, 26, 93, 28))
        self.pushButton_inquire.setObjectName("pushButton_inquire")
        self.label_departure.raise_()
        self.lineEdit_departure.raise_()
        self.lineEdit_destination.raise_()
        self.label_destination.raise_()
        self.label_date.raise_()
        self.pushButton_inquire.raise_()
        self.lineEdit_date.raise_()
        self.label_logo = QtWidgets.QLabel(self.centralwidget)
        self.label_logo.setGeometry(QtCore.QRect(640, 225, 320, 80))
        self.label_logo.setStyleSheet("background-image: url(:/png/src/logo.png);")
        self.label_logo.setText("")
        self.label_logo.setObjectName("label_logo")
        self.widget_train_class = QtWidgets.QWidget(self.centralwidget)
        self.widget_train_class.setGeometry(QtCore.QRect(0, 305, 960, 35))
        self.widget_train_class.setStyleSheet("background-image: url(:/png/src/bg3.png);")
        self.widget_train_class.setObjectName("widget_train_class")
        self.label_train_class = QtWidgets.QLabel(self.widget_train_class)
        self.label_train_class.setGeometry(QtCore.QRect(20, 10, 72, 15))
        self.label_train_class.setObjectName("label_train_class")
        self.checkBox_G = QtWidgets.QCheckBox(self.widget_train_class)
        self.checkBox_G.setGeometry(QtCore.QRect(120, 9, 91, 19))
        self.checkBox_G.setObjectName("checkBox_G")
        self.checkBox_D = QtWidgets.QCheckBox(self.widget_train_class)
        self.checkBox_D.setGeometry(QtCore.QRect(280, 9, 91, 19))
        self.checkBox_D.setObjectName("checkBox_D")
        self.checkBox_Z = QtWidgets.QCheckBox(self.widget_train_class)
        self.checkBox_Z.setGeometry(QtCore.QRect(440, 9, 91, 19))
        self.checkBox_Z.setObjectName("checkBox_Z")
        self.checkBox_T = QtWidgets.QCheckBox(self.widget_train_class)
        self.checkBox_T.setGeometry(QtCore.QRect(600, 9, 91, 19))
        self.checkBox_T.setObjectName("checkBox_T")
        self.checkBox_K = QtWidgets.QCheckBox(self.widget_train_class)
        self.checkBox_K.setGeometry(QtCore.QRect(760, 9, 91, 19))
        self.checkBox_K.setObjectName("checkBox_K")
        self.label_information = QtWidgets.QLabel(self.centralwidget)
        self.label_information.setGeometry(QtCore.QRect(0, 340, 960, 62))
        self.label_information.setStyleSheet("background-image: url(:/png/src/bg4.png);")
        self.label_information.setText("")
        self.label_information.setObjectName("label_information")
        self.tableView_information = QtWidgets.QTableView(self.centralwidget)
        self.tableView_information.setGeometry(QtCore.QRect(0, 402, 960, 448))
        self.tableView_information.setObjectName("tableView_information")

        self.model = QStandardItemModel()  # 创建存储数据的模式
        # 根据空间自动改变列宽度并且不可修改列宽度
        self.tableView_information.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        # 设置表头不可见
        self.tableView_information.horizontalHeader().setVisible(False)
        # 纵向表头不可见
        self.tableView_information.verticalHeader().setVisible(False)
        # 设置表格内容文字大小
        font = QtGui.QFont()
        font.setPointSize(10)
        self.tableView_information.setFont(font)
        # 设置表格内容不可编辑
        self.tableView_information.setEditTriggers(QAbstractItemView.NoEditTriggers)
        # 垂直滚动条始终开启
        self.tableView_information.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

        self.widget_input.raise_()
        self.label_title_img.raise_()
        self.label_logo.raise_()
        self.widget_train_class.raise_()
        self.label_information.raise_()
        self.tableView_information.raise_()
        mainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(mainWindow)
        QtCore.QMetaObject.connectSlotsByName(mainWindow)

    def retranslateUi(self, mainWindow):
        _translate = QtCore.QCoreApplication.translate
        mainWindow.setWindowTitle(_translate("mainWindow", "12306官网查询"))
        self.label_departure.setText(_translate("mainWindow", "出发地:"))
        self.label_destination.setText(_translate("mainWindow", "目的地:"))
        self.label_date.setText(_translate("mainWindow", "出发日期:"))
        self.pushButton_inquire.setText(_translate("mainWindow", "查询"))
        self.label_train_class.setText(_translate("mainWindow", "车次类型:"))
        self.checkBox_G.setText(_translate("mainWindow", "G-高铁"))
        self.checkBox_D.setText(_translate("mainWindow", "D-动车"))
        self.checkBox_Z.setText(_translate("mainWindow", "Z-直达"))
        self.checkBox_T.setText(_translate("mainWindow", "T-特快"))
        self.checkBox_K.setText(_translate("mainWindow", "K-快车"))

        self.lineEdit_date.setText(get_time())  # 出发日显示当天日期
        self.pushButton_inquire.clicked.connect(self.on_click)  # 查询按钮指定单击事件的方法
        self.checkBox_G.stateChanged.connect(self.change_G)  # 高铁选中与取消事件
        self.checkBox_D.stateChanged.connect(self.change_D)  # 动车选中与取消事件
        self.checkBox_Z.stateChanged.connect(self.change_Z)  # 直达车选中与取消事件
        self.checkBox_T.stateChanged.connect(self.change_T)  # 特快车选中与取消事件
        self.checkBox_K.stateChanged.connect(self.change_K)  # 快车选中与取消事件

    # 将所有车次分类复选框取消勾选
    def checkBox_default(self):
        self.checkBox_G.setChecked(False)
        self.checkBox_D.setChecked(False)
        self.checkBox_Z.setChecked(False)
        self.checkBox_T.setChecked(False)
        self.checkBox_K.setChecked(False)

    # 查询按钮的单击事件
    def on_click(self):
        get_from = self.lineEdit_departure.text() # 获取出发地
        get_to = self.lineEdit_destination.text()  # 获取到达地
        get_date = self.lineEdit_date.text()  # 获取出发时间
        # 判断车站文件是否存在
        if isStations() == True:
            stations = eval(read())  # 读取所有车站并转换为dic类型
            # 判断所有参数是否为空,出发地、目的地、出发日期
            if get_from != "" and get_to != "" and get_date != "":
                # 判断输入的车站名称是否存在,以及时间格式是否正确
                if get_from in stations and get_to in stations and is_valid_date(get_date):
                    # 获取输入的日期是当前年初到现在一共过了多少天
                    inputYearDay = time.strptime(get_date, "%Y-%m-%d").tm_yday
                    # 获取系统当前日期是当前年初到现在一共过了多少天
                    yearToday = time.localtime(time.time()).tm_yday
                    # 计算时间差,也就是输入的日期减掉系统当前的日期
                    timeDifference = inputYearDay - yearToday
                    # 判断时间差为0时证明是查询当前的查票,
                    # 以及29天以后的车票。12306官方要求只能查询30天以内的车票
                    if timeDifference >= 0 and timeDifference <= 28:
                        from_station = stations[get_from]  # 在所有车站文件中找到对应的参数,出发地
                        to_station = stations[get_to]  # 目的地
                        data = query(get_date, from_station, to_station)  # 发送查询请求,并获取返回的信息
                        self.checkBox_default()
                        if len(data) != 0:  # 判断返回的数据是否为空
                            # 如果不是空的数据就将车票信息显示在表格中
                            self.displayTable(len(data), 16, data)
                        else:
                            self.messageDialog('警告', '没有返回的网络数据!')
                    else:
                        self.messageDialog('警告', '超出查询日期的范围内,'
                                                 '不可查询昨天的车票信息,以及29天以后的车票信息!')
                else:
                    self.messageDialog('警告', '输入的站名不存在,或日期格式不正确!')
            else:
                self.messageDialog('警告', '请填写车站名称!')
        else:
            self.messageDialog('警告', '未下载车站查询文件!')

    # 高铁复选框事件处理
    def change_G(self, state):
        # 选中将高铁信息添加到最后要显示的数据当中
        if state == QtCore.Qt.Checked:
            # 获取高铁信息
            g_vehicle()
            # 通过表格显示该车型数据
            self.displayTable(len(type_data), 16, type_data)
        else:
            # 取消选中状态将移除该数据
            r_g_vehicle()
            self.displayTable(len(type_data), 16, type_data)

    # 动车复选框事件处理
    def change_D(self, state):
        # 选中将动车信息添加到最后要显示的数据当中
        if state == QtCore.Qt.Checked:
            # 获取动车信息
            d_vehicle()
            # 通过表格显示该车型数据
            self.displayTable(len(type_data), 16, type_data)

        else:
            # 取消选中状态将移除该数据
            r_d_vehicle()
            self.displayTable(len(type_data), 16, type_data)

    # 直达复选框事件处理
    def change_Z(self, state):
        # 选中将直达车信息添加到最后要显示的数据当中
        if state == QtCore.Qt.Checked:
            # 获取直达车信息
            z_vehicle()
            self.displayTable(len(type_data), 16, type_data)
        else:
            # 取消选中状态将移除该数据
            r_z_vehicle()
            self.displayTable(len(type_data), 16, type_data)

    # 特快复选框事件处理
    def change_T(self, state):
        # 选中将特快车信息添加到最后要显示的数据当中
        if state == QtCore.Qt.Checked:
            # 获取特快车信息
            t_vehicle()
            self.displayTable(len(type_data), 16, type_data)
        else:
            # 取消选中状态将移除该数据
            r_t_vehicle()
            self.displayTable(len(type_data), 16, type_data)

    # 快速复选框事件处理
    def change_K(self, state):
        # 选中将快车信息添加到最后要显示的数据当中
        if state == QtCore.Qt.Checked:
            # 获取快速车信息
            k_vehicle()
            self.displayTable(len(type_data), 16, type_data)

        else:
            # 取消选中状态将移除该数据
            r_k_vehicle()
            self.displayTable(len(type_data), 16, type_data)

    # 显示消息提示框,参数title为提示框标题文字,message为提示信息
    def messageDialog(self, title, message):
        msg_box = QMessageBox(QMessageBox.Warning, title, message)
        msg_box.exec_()

    # 显示车次信息的表格
    # train参数为共有多少趟列车,该参数作为表格的行。
    # info参数为每趟列车的具体信息,例如有座、无座卧铺等。该参数作为表格的列
    def displayTable(self, train, info, data):
        self.model.clear()
        for row in range(train):
            for column in range(info):
                # 添加表格内容
                item = QStandardItem(data[row][column])
                # 向表格存储模式中添加表格具体信息
                self.model.setItem(row, column, item)
        # 设置表格存储数据的模式
        self.tableView_information.setModel(self.model)

# 获取系统当前时间并转换请求数据所需要的格式
def get_time():
    # 获得当前时间时间戳
    now = int(time.time())
    # 转换为其它日期格式,如:"%Y-%m-%d %H:%M:%S"
    timeStruct = time.localtime(now)
    strTime = time.strftime("%Y-%m-%d", timeStruct)
    return strTime


def is_valid_date(str):
    '''判断是否是一个有效的日期字符串'''
    try:
        time.strptime(str, "%Y-%m-%d")
        return True
    except:
        return False

# 定义显示函数
def show_MainWindow():
    app = QtWidgets.QApplication(sys.argv)  # 实例化QApplication类,作为GUI主程序入口
    MainWindow = QtWidgets.QMainWindow()  # 实例化QtWidgets.QMainWindow类,创建自带menu的窗体类型QMainWindow
    ui = Ui_mainWindow()  # 实例化UI类
    ui.setupUi(MainWindow)  # 设置窗体UI
    MainWindow.show()  # 显示窗体
    sys.exit(app.exec_())
    # 当来自操作系统的分发事件指派调用窗口时,
    # 应用程序开启主循环(mainloop)过程,
    # 当窗口创建完成,需要结束主循环过程,
    # 这时候呼叫sys.exit()方法来,结束主循环过程退出,
    # 并且释放内存。为什么用app.exec_()而不是app.exec()?
    # 因为exec是python系统默认关键字,为了以示区别,所以写成exec_


# 主程序入口
if __name__ == '__main__':
    if isStations() == False:
        getStation()
    show_MainWindow()

query_request.py

from get_stations import *


'''5-7目的地,3车次,6出发地,8出发时间,9到达时间,10历时,26无坐,29硬座,
   24软座,28硬卧,33动卧,23软卧,21高级软卧,30二等座,31一等座,32商务座特等座
'''

data = []  # 用于保存整理好的所有车次信息
type_data = []  # 保存车次分类后最后的数据


def query(date, from_station, to_station):
    print(date, from_station, to_station)
    data.clear()  # 清空数据
    type_data.clear()  # 清空车次分类保存的数据
    # 设置cookie
    cookie = 'JSESSIONID=245782306A8F72B197AE2ADA05F463A8; BIGipServerotn=1708720394.50210.0000; RAIL_EXPIRATION=1589844843366; RAIL_DEVICEID=R0B-jSpnTZ4NSWa2MVZNuBUvmwAoHG22Rqb8eQm0qu7ZUWdpbKElaHY3oqEUR8AG2ooarmYVW3kNP98Lkhn5YqoPa5KUUB8IMjRdPEZ-iZbqgyh-gOFgMNRRpieZq3GBI36yzGkOErVsDyR9NWWrDJY_EThOSJ5f; BIGipServerpassport=921174282.50215.0000; route=9036359bb8a8a461c164a04f8f50b252; _jc_save_fromStation=%u5317%u4EAC%2C{}; _jc_save_toStation=%u4E0A%u6D77%2C{}; _jc_save_fromDate={}; _jc_save_toDate={}; _jc_save_wfdc_flag=dc'.format(
        from_station, to_station, date, date)
    # 设置标头
    headers = {
        'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36',
        'Cookie': cookie}
    # 查询请求地址
    url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(
        date, from_station, to_station)
    # 发送查询请求
    response = requests.get(url, headers=headers)
    # 修改编码格式
    response.encoding = 'utf-8'
    # 将json数据转换为字典类型,通过键值对取数据
    result = response.json()
    result = result['data']['result']
    # 判断车站文件是否存在
    if isStations() == True:
        # 读取所有车站并转换为dic类型,eval()获取字符串值
        stations = eval(read())
        if len(result) != 0:  # 判断返回数据是否为空
            for i in result:
                # 分割数据并添加到列表中
                tmp_list = i.split('|')
                # 因为查询结果中出发站和到达站为站名的缩写字母,所以需要在车站库中找到对应的车站名称
                from_station = list(stations.keys())[list(stations.values()).index(tmp_list[6])]
                to_station = list(stations.keys())[list(stations.values()).index(tmp_list[7])]
                # 创建座位数组,由于返回的座位数据中含有空既“”,所以将空改成--这样好识别
                seat = [tmp_list[3], from_station, to_station, tmp_list[8], tmp_list[9], tmp_list[10]
                    , tmp_list[32], tmp_list[31], tmp_list[30], tmp_list[21]
                    , tmp_list[23], tmp_list[33], tmp_list[28], tmp_list[24], tmp_list[29], tmp_list[26]]
                newSeat = []
                # 循环将座位信息中的空改成--
                for s in seat:
                    if s == "":
                        s = "--"
                    else:
                        s = s
                    newSeat.append(s)  # 保存新的座位信息
                data.append(newSeat)
        return data   # 返回整理好的车次信息


# 获取高铁信息的方法
def g_vehicle():
    if len(data) != 0:
        for g in data:  # 循环所有火车数据
            i = g[0].startswith('G')  # 判断车次首字母是不是高铁
            if i:  # 如果是将该条信息添加到高铁数据中
                type_data.append(g)


# 移除高铁信息的方法
def r_g_vehicle():
    if len(data) != 0 and len(type_data) != 0:
        for g in data:
            i = g[0].startswith('G')
            if i:  # 移除高铁信息
                type_data.remove(g)


# 获取动车信息的方法
def d_vehicle():
    if len(data) != 0:
        for d in data:  # 循环所有火车数据
            i = d[0].startswith('D')  # 判断车次首字母是不是动车
            if i == True:  # 如果是将该条信息添加到动车数据中
                type_data.append(d)


# 移除动车信息的方法
def r_d_vehicle():
    if len(data) != 0 and len(type_data) != 0:
        for d in data:
            i = d[0].startswith('D')
            if i == True:  # 移除动车信息
                type_data.remove(d)


# 获取直达车信息的方法
def z_vehicle():
    if len(data) != 0:
        for z in data:  # 循环所有火车数据
            i = z[0].startswith('Z')  # 判断车次首字母是不是直达
            if i == True:  # 如果是将该条信息添加到直达数据中
                type_data.append(z)


# 移除直达车信息的方法
def r_z_vehicle():
    if len(data) != 0 and len(type_data) != 0:
        for z in data:
            i = z[0].startswith('Z')
            if i == True:  # 移除直达车信息
                type_data.remove(z)


# 获取特快车信息的方法
def t_vehicle():
    if len(data) != 0:
        for t in data:  # 循环所有火车数据
            i = t[0].startswith('T')  # 判断车次首字母是不是特快
            if i == True:  # 如果是将该条信息添加到特快车数据中
                type_data.append(t)


# 移除特快车信息的方法
def r_t_vehicle():
    if len(data) != 0 and len(type_data) != 0:
        for t in data:
            i = t[0].startswith('T')
            if i == True:  # 移除特快车信息
                type_data.remove(t)


# 获取快速车数据的方法
def k_vehicle():
    if len(data) != 0:
        for k in data:  # 循环所有火车数据
            i = k[0].startswith('K')  # 判断车次首字母是不是快车
            if i == True:  # 如果是将该条信息添加到快车数据中
                type_data.append(k)


# 移除快速车数据的方法
def r_k_vehicle():
    if len(data) != 0 and len(type_data) != 0:
        for k in data:
            i = k[0].startswith('K')
            if i == True:  # 移除快车信息
                type_data.remove(k)

get_stations.py

import requests
import re
import os


# 获取地名信息
def getStation():
    url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9142'
    response = requests.get(url, verify=True)
    # 返回中文与大写字母
    stations = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text)
    stations = dict(stations)
    stations = str(stations)
    write(stations)


# 站点文件写入
def write(item):
    with open('stations.txt', 'w', encoding='utf-8') as f:
        f.write(item)


# 站点文件读取
def read():
    with open('stations.txt', 'r', encoding='utf-8') as f:
        data = f.readline()

    return data


# 判断站点文件是否存在
def isStations():
    isStations = os.path.exists('stations.txt')

    return isStations

Pyinstaller 程序打包

直接运行

运行主程序如下
在这里插入图片描述

打包运行

程序打包后运行出错参见参见pygame 实现 flappybird 并打包成 exe 运行文件使用 Pygame 创建五子棋游戏解决方案
在命令行直接输入:

pyinstaller -F -w -i logo.ico main.py

打包运行效果如下:
在这里插入图片描述

(附)简单爬取操作

进入 12306 车票查询官网,输入北京到上海如下图所示
在这里插入图片描述
按 F12,再按 F5 刷新(刷新后可能需要重新点击查询),最终界面应如下,其中包含了大量信息,除了车次信息还有网页的图片文件信息等:
在这里插入图片描述
找到代表车次信息的信息条,名称大致为 query?leftTicketDTO.train_date=2020-05-17&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=SHH&purpose_codes=ADULT(不同的时间可能不同)
在这里插入图片描述
Headers 与 Response 选项卡就是我们需要的信息,Headers 里面包含了 Cookie 文件,消息头,User-Agent 等,Response 则是包含了车次信息,里面包含站点信息,时间,座位信息以及一些反爬的混淆信息,我们需要适用正则化,字符串处理方法等提取信息,具体操作参见 query_request.py

Git 地址

所有文件均上传至 GitHub
欢迎 star

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值