Jonjs' Blog

如少年般, 迎风而立

0%

【PHP】 抓取并运用 12306 车站数据

环境参数

PHP 7.4 + Nginx/Apache

车站数据

12306(客站): https://kyfw.12306.cn/otn/resources/js/framework/station_name.js
moerail.ml(所有站点, ~270KB): http://moerail.ml/station.js

代码

数据转换 - data-transfer.php 代码:

<?php
  $s = file_get_contents("station_12306.csv");
  //12306数据下载后请保存为 station_12306.csv
  $s = ltrim($s, "var station_names ='@");
  $s = rtrim($s, "';");
  $array = explode("@", $s);
  $s = implode("\n", $array);
  $s = strtr($s, "|", ",");
  $file = fopen("station_12306.csv", "wb");
  fwrite($file, $s);
  fclose($file);
  echo "station_12306.csv 转换完毕.<br>";

  $s = file_get_contents("station.js");
  //12306数据下载后请保存为 station.js
  $s = ltrim($s, "var stationNames = '@");
  $s = rtrim($s, "';");
  $array = explode("@", $s);
  $s = implode("\n", $array);
  $s = strtr($s, "|", ",");
  $s = str_replace(" ", "", $s);
  $file = fopen("station_moerail.csv", "wb");
  //自动转换为 station_moerail.csv
  fwrite($file, $s);
  fclose($file);
  echo "station_moerail.csv 转换完毕.<br>";

查询页面(以moerail数据为准) - query.php 代码:

注: 转换后的车站数据文件为station_moerail.csv, 表单HTML文件下以html/form_query.html为准, 此html文件中不包含任何php代码

<?php
if(!isset($_GET["station"]) || empty($_GET["station"])) {
  include "html/form_query.html"; //仅展示输入框
} else {
  $station = $_GET["station"];
  include "html/form_query.html";

  $file = fopen("station_moerail.csv", "rb");
  $feedback = "";
  while (!feof($file)) { //逐行读取数据
    $line = fgets($file, 9999);
    if(substr_count($line, $station) > 0) { //寻找符合的车站行
      $feedback = $feedback.$line."|"."";
    }
  }

  $result = rtrim($feedback, "|");
  //移除$feedback字符串中最后一个无用的 "|" 字符
  $resultArray = explode("|", $result);
  //把String $feedback分割为数组存入$resultArray
  //$resultArray中储存了查询到的所有数据
  
  //数据HTML展示
  echo '<br><br>
<table frame="box" rules="all">
  <tr>
    <th>站名</th>
    <th>拼音码</th>
    <th>电报码</th>
    <th>TMIS</th>
  </tr>
  ';
  foreach ($resultArray as $value) {
  //$value中储存的是单个车站的各种数据, 继续向下分割, 获得各项信息输出
    $array = explode(",", $value);
    echo "<tr><td>$array[1]</td><td>$array[0]</td><td>$array[2]</td><td>$array[3]</td></tr>";
  }
  echo "</table>";
  echo "已展示所有检索到的内容.";
  fclose($file);
}

查询表单 - form_query.html 部分代码:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>查询 | JonRail</title>
</head>
<body>

<style>
  * {
    font-family: 'Bahnschrift';
  }
  input{
    font-size: 14pt;
    padding: 5px;
  }
  input::-webkit-input-placeholder {
    color: #DDDDDD;
    font-size: 12pt;
  }
  input:focus{
       outline: 1px solid #000; 
    }
    table, th, td {
      padding: 8px;
      font-size: 12pt;
    }
</style>

<h1>查询</h1>
<form action="../query.php" method="GET"> <!-- ../ 是上级目录 -->
  <input type="text" name="station" id="station" size="32" placeholder="漯河东/lhd/UDN/22355" autocomplete="off" />
  <br>
  <!--<input type="button" class="btn" value="查询" />-->
</form>

</body>
</html>

过程解析 (moerail数据为准)

  1. data_transfer.php 数据转换:

JS数据, 以下数据显示了北京北、北京东两个车站的数据, 我们以此为例:

var stationNames = '@bjb|北京北|VAP|12713|京2@bjd|北京东|BOP|12158|京2';

移除左右多余的字符, 数据按X|X|X|X|X@X|X|X|X|X保留, 结果为:

bjb|北京北|VAP|12713|京2@bjd|北京东|BOP|12158|京2

按@分割字符串, 将每个车站的数据逐行排列, 结果为:

bjb|北京北|VAP|12713|京2
bjd|北京东|BOP|12158|京2

将”|”替换为”,”并储存为csv文件, 最终结果为:

bjb,北京北,VAP,12713,京2
bjd,北京东,BOP,12158,京2

那么, 该csv文件的结构就是:

拼音码,车站名,电报码,TMIS码,车站所属行政区及限制代码

注: 车站限制代码具体内容请见 http://moerail.ml/bureau.js 和 客里表

  1. query.php 接收数据、处理数据

表单采用 GET 方式提交数据, 那么我们后端就采用 $_GET["station"] 来接收用户所输入的文本, 例如用户输入为北京, 接下来我们需要遍历csv数据文件每行, 逐行查找北京这个字段, 如果搜寻到有关的行, 那么就把这一行的数据储存进string $feedback中, 不同行之间用|字符隔开;

$file = fopen("api/station_moerail.csv", "rb");
$feedback = "";
while (!feof($file)) {
  $line = fgets($file, 9999); //逐行扫描
  if(substr_count($line, $station) > 0) { //如果能够在该行中找到$station文本
    $feedback = $feedback.$line."|".""; //该行数据存入变量
  }
}

接下来, 移除最右侧一个无效的|字符, 将string $result|字符分割, 存入array $resultArray中,

$result = rtrim($feedback, "|");
$resultArray = explode("|", $result);

此时该数组中的数据结构大致为:

0 -> 'bjb,北京北,VAP,12713,京2'
1 -> 'bjd,北京东,BOP,12158,京2'

输出表格头, 接下来开始处理上述数组中的内容, 进行最终展示

echo '<br><br>
<table frame="box" rules="all">
<tr>
  <th>站名</th>
  <th>拼音码</th>
  <th>电报码</th>
  <th>TMIS</th>
</tr>
';

使用foreach()遍历上述数组, 每一次遍历, 就可以输出一行表格内容, 下面的代码就将array $resultArray中的每一项文本数据存入string $value, 在遍历过程中, 只需要关注string $value即可.

我们先把string $value,分割, 存入数组array $array, 接下来直接获取数组中的具体文本, 进行HTML输出展示.

期间, array $array结构大致是:

0 -> 'bjb'
1 -> '北京北'
2 -> 'VAP'
3 -> '12713'
4 -> '京2'

最后输出剩余HTML内容、关闭文件流即可.

foreach ($resultArray as $value) {
  $array = explode(",", $value);
  echo "<tr><td>$array[1]</td><td>$array[0]</td><td>$array[2]</td><td>$array[3]</td></tr>";
}

echo "</table>";
echo "已展示所有检索到的内容.";

fclose($file);

效果动图

show.gif